Back to Cheat Sheets

🔑 Broken Authentication

OWASP Serverless Top 10 - SAS-2

CRITICAL RISK

📋 What Is It?

Broken Authentication in serverless is the failure to consistently verify who (or what) is invoking a function, across every way it can be reached. A serverless app is a fleet of independently deployable functions, and each can be triggered by many event sources - API Gateway, a public Function URL, an S3 upload, SNS/SQS messages, EventBridge rules, DynamoDB streams, schedules, or a direct SDK Invoke. Authentication enforced at the front-door API Gateway does nothing for a function reached through any other door. The hard part is not having authentication - it is enforcing it consistently at every one of many entry points.

SAS-2 OWASP Rank
Every Entry Point
High Impact

⚠️ Common Exploits

  • Open Function URL: AuthType: NONE exposes privileged logic to the internet
  • "Internal" Trigger Trust: S3/SNS/SQS events acted on without checking the actor
  • Decode-Only JWT: claims read without verifying signature, expiry, audience, issuer
  • alg:none Forgery: attacker-crafted {"role":"admin"} accepted
  • Token Replay: non-expiring or leaked tokens still valid weeks later
  • Inconsistent Coverage: one route ships without an authorizer
  • Shared Static Keys: one leaked key authenticates every service call

🔴 Attack Flow

1. Enumerate functions, Function URLs, ARNs, and triggers
↓
2. Probe each entry point directly; note which respond without auth
↓
3. Bypass the gateway via Function URL / event source / direct invoke
↓
4. Forge or replay tokens where validation is weak or missing
↓
5. IMPACT: Run privileged logic and exfiltrate - no login required!

❌ Vulnerable Code

# Bad: public Function URL bypasses API Gateway (serverless.yml) functions: adminReport: handler: handler.adminReport url: authorizer: none ← AuthType NONE, anyone with the URL invokes it exportData: handler: handler.exportData events: - httpApi: { path: /export, method: get } ← no authorizer, silently public // Bad: JWT decoded but never verified - claims are attacker-controlled exports.verify = async (event) => { const token = (event.headers.authorization || '').replace(/^Bearer /, ''); const claims = JSON.parse(Buffer.from(token.split('.')[1], 'base64')); if (claims.role === 'admin') return { isAuthorized: true }; ← no signature check }; # Bad: S3 event trusted as "internal" - uploader may be untrusted def handler(event, context): key = event['Records'][0]['s3']['object']['key'] process_import(key) # parses file, writes to prod DB grant_entitlements(key) ← no authenticated actor behind it // Bad: shared static key for service-to-service auth if (event.headers['x-api-key'] === process.env.SHARED_KEY) { /* allow */ }

✅ Secure Code

# Good: require IAM auth on the URL; every route carries an authorizer functions: adminReport: handler: handler.adminReport url: authorizer: aws_iam # callers must sign requests with SigV4 exportData: handler: handler.exportData events: - httpApi: path: /export method: get authorizer: { name: cognitoAuth } # no route ships open // Good: full JWT verification against the provider's JWKS const { createRemoteJWKSet, jwtVerify } = require('jose'); const JWKS = createRemoteJWKSet(new URL(process.env.JWKS_URI)); exports.verify = async (event) => { const token = (event.headers.authorization || '').replace(/^Bearer /, ''); const { payload } = await jwtVerify(token, JWKS, { issuer: process.env.EXPECTED_ISSUER, // iss audience: process.env.EXPECTED_AUDIENCE, // aud; signature + exp enforced, alg:none rejected }); return { isAuthorized: true, context: { sub: payload.sub } }; }; # Good: confirm the source and validate before privileged work def handler(event, context): rec = event['Records'][0] if rec['s3']['bucket']['name'] != os.environ['TRUSTED_BUCKET']: raise Exception('event from unexpected bucket') validate_object(rec['s3']['object']['key']) # scan BEFORE processing

✓ Prevention Checklist

  • Enumerate every function and every trigger it exposes
  • Enforce authentication at each entry point, not just the gateway
  • Never leave a Function URL at AuthType: NONE
  • Centralise identity (Cognito/OIDC) with a reused authorizer
  • Verify token signature, expiry, audience, and issuer every time
  • Reject alg: none and unverified claims
  • Keep token lifetimes short; rotate and never bake in secrets
  • Validate event sources; do not infer identity from the trigger
  • Sign service-to-service calls with IAM/SigV4
  • Scope every execution role to least privilege
  • Never treat an ARN or URL as a secret
  • Gate CI to fail on open URLs / missing authorizers

🔍 Detection & Tools

Identity & Verification:

Amazon Cognito jose (JWKS) IAM / SigV4 Secrets Manager SSM Parameter Store

Scanning & Monitoring:

Checkov cfn-lint gitleaks CloudTrail CloudWatch

How to Test:

  • Enumerate all triggers and invoke each without credentials
  • Look for Function URLs with AuthType: NONE
  • Present alg:none and forged-claim tokens to authorizers
  • Replay old/expired tokens; check they are rejected
  • Attempt direct lambda:Invoke as an unexpected principal
  • Scan IaC for routes missing an authorizer

🌍 Real-World

These are documented incident classes - recurring patterns, not specific CVEs.

  • Public Function URLs reachable without auth: a URL created with auth type none "for testing" and left in place; anyone who discovers it invokes privileged logic with no credentials, bypassing every gateway control.
  • Trusting "internal" event triggers: functions act on S3/SNS/SQS/EventBridge payloads because they "came from AWS," while the underlying source is influenced by untrusted uploaders or publishers.
  • Broken or absent token validation: a custom authorizer decodes a JWT without verifying signature/expiry/audience/issuer, letting attackers forge any user or role.
  • Leaked long-lived credentials: non-expiring tokens or static service secrets pulled from client bundles, logs, or git history and replayed indefinitely.
  • Inconsistent enforcement: one route or one differently-deployed function ships without its authorizer, undoing the protection on all the others.

📌 Quick Tips

  • DO NOT leave a Function URL at AuthType: NONE
  • DO NOT trust a token because it decodes
  • DO NOT treat an event trigger as proof of identity
  • DO authenticate every entry point consistently
  • DO verify signature, exp, aud, and iss every time
  • DO centralise identity with a reused authorizer
  • DO back every function with a least-privilege role

📜 Compliance

Related Standards:

  • OWASP Serverless Top 10 - SAS-2
  • CWE-287 Improper Authentication
  • CWE-345 Insufficient Verification of Data Authenticity
  • CWE-347 Improper Verification of Cryptographic Signature
  • CWE-613 Insufficient Session Expiration