I bypassed AWS API Gateway auth with a trailing slash. Got $12K bounty

GeorgeWoff251 pts0 comments

GuptaLog: I bypassed AWS API Gateway auth with a trailing slash. Got $12K bounty.

Friday, 10 April 2026

I bypassed AWS API Gateway auth with a trailing slash. Got $12K bounty.

I was poking at a fintech’s mobile API and noticed something that made no sense. GET /v1/accounts returned 401. GET /v1/accounts/ returned 200 with full account data. One character. Completely different security posture.<br>What I was looking at<br>The API ran on AWS HTTP API — the newer, cheaper alternative to REST API. Lambda authorizer checked a JWT against Cognito, returned an IAM policy. Standard.<br>Routes in OpenAPI:<br>YAML

/v1/accounts:<br>get:<br>x-amazon-apigateway-integration:<br>uri: arn:aws:apigateway:...

/v1/accounts/{accountId}:<br>get:<br>x-amazon-apigateway-integration:<br>uri: arn:aws:apigateway:...The authorizer ran on every request. But HTTP API makes two decisions: does this route exist, and does the authorizer allow it? Those two layers didn’t agree on what a "match" meant.<br>The weird results<br>I ran ffuf on the path. The results were… inconsistent.<br>RequestResponseGET /v1/accounts401 UnauthorizedGET /v1/accounts/200 OK + full dataGET /v1/accounts//200 OKGET /v1/accounts?foo=bar401 UnauthorizedGET /v1/accounts%2f404 Not FoundThe pattern: any path that sort-of matched a route prefix triggered the authorizer, then fell through to the integration without re-checking auth.<br>HTTP API does greedy path matching by default. /v1/accounts/ matched /v1/accounts as a prefix. The authorizer ran and returned Allow. Then the integration executed — but the integration mapping was fuzzy. The path got rewritten, the auth context got dropped, and suddenly I was inside without a valid JWT.<br>How the bypass actually worked<br>I traced it carefully. The $default route in HTTP API is a catch-all. The fintech had set it to return 404. But they’d also attached a mock integration for health checks at some point. That mock didn’t check auth — just returned {"status": "ok"}.<br>But /v1/accounts/ wasn’t hitting the mock. It was hitting the real backend. API Gateway’s greedy match rewrote the trailing-slash path, stripped the slash, and forwarded to the /v1/accounts integration. The auth check happened on the original path. The integration ran on the rewritten path. The rewrite dropped the auth context.<br>I confirmed it with a custom header. The authorizer sets context.authorizer.userId. The integration reads it. When I hit /v1/accounts/, the integration received userId: undefined. The integration didn’t validate userId. It just returned all accounts for the API key — which wasn’t even required here because auth was supposed to be the JWT.<br>The real damage<br>Same bypass worked on POST /v1/transfers/. I could initiate wire transfers without a valid JWT.<br>The backend checked that fromAccount belonged to the user. But userId was undefined, so it defaulted to a system account. I stopped after one $0.01 test transfer. It went through.<br>Telling them<br>I wrote it up. Screenshots of the 401 vs 200. The ffuf output. The exact path rewrite behavior. They fixed it the next day.<br>Switched from HTTP API to REST API (stricter path matching)<br>Added userId validation in every Lambda, not just the authorizer.

I got $12,000 bounty for it. Planning to go to Dubai :)

at

April 10, 2026

Email ThisBlogThis!Share to XShare to FacebookShare to Pinterest

No comments:

Post a Comment

Newer Post

Home

Subscribe to:<br>Post Comments (Atom)

I poisoned a Hugging Face dataset and it stayed up for 6 months

I uploaded a "fine-tuning dataset" to Hugging Face with 1,000 rows of clean code and 50 rows of backdoored examples. The backdoor: any funct...

I reproduced a Claude Code RCE. The bug is everywhere.

Last week, security researcher Joernchen published a clever RCE in Claude Code 2.1.118 . I spent Saturday reproducing it from the advis...

I bypassed AWS API Gateway auth with a trailing slash. Got $12K bounty.

I was poking at a fintech’s mobile API and noticed something that made no sense. GET /v1/accounts returned 401. GET /v1/accounts/ returned...

I poisoned a Hugging Face dataset and it stayed up for 6 months

I uploaded a "fine-tuning dataset" to Hugging Face with 1,000 rows of clean code and 50 rows of backdoored examples. The backdoor: any funct...

Search This Blog

Home

Contributors

Piyush Gupta

Verdaily

Report Abuse

Blog Archive

May 2026 (2)

April 2026 (1)

accounts integration auth path returned authorizer

Related Articles