Skip to content

Build1 publisher2 min readPublished

The bare Todo API's list route calls table.scan() and returns every user's rows to an anonymous caller

Yvan Saf deployed the same serverless Todo API twice on one AWS account, once bare and once hardened, then attacked both from his own terminal. The bare list route reads the whole DynamoDB table because nothing in the request path knows who is calling.

The Engineer · Build desk

Photograph accompanying The bare Todo API's list route calls table.scan() and returns every user's rows to an anonymous caller
Photo: dev.to

What happened

  • Yvan Saf deployed the same small Todo API twice on one AWS account, once with no security controls at all and once hardened the way he says he would build it for a real client.
  • After creating tasks for two fictional users, Saf called the endpoint anonymously and got the entire dataset back in a single request.
  • On the hardened version a Lambda Authorizer checks a signed token first, and the same anonymous request comes back 401 before it reaches the table.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision Anyone copying the hardened pattern has to ship both halves. The token check keeps strangers out; the per-user index query is what stops a valid token from reading a neighbour's rows.
  • exposure In the bare deployment the URL is the entire access control, so a link pasted into a chat log or left in a browser history is enough to read both users' records.
  • constraint The defect lives in a handler function, not in an account setting, so a review of IAM policies or edge rules will not surface an unscoped scan sitting behind an open route.
  • capability With the Terraform and the attack scripts published, a team can stand up the same pair on a scratch account and see for itself which control produced which response code.

Call the vulnerable list route and the Lambda runs `table.scan()`, then returns a 200 with every item it read and a count [4]. A scan reads the whole table [5]. The route does not filter by user, because it has no idea who is calling, and there is no authentication in front of it, so anyone holding the URL can call it [6]. Saf created tasks for two fictional users, then called the endpoint anonymously [7]. "One anonymous request, the whole dataset back. Nothing about this required skill on my part," he wrote [8]. The tooling was a terminal [2].

The hardened deployment fixes that in two places, and the two are not interchangeable. A Lambda Authorizer checks a signed token before the request goes anywhere near the handler, so the anonymous call comes back 401 without reaching the table [9]. The handler then queries only the calling user's tasks through a DynamoDB index, so an authenticated request never returns someone else's data [10]. One control decides who gets in. The other decides what the query is allowed to read. An API that gains the authorizer and keeps the unscoped scan still hands both users' rows to anyone who can obtain a token.

Both versions run Client to API Gateway to Lambda to DynamoDB [3], on the same AWS account [1]. "Same boxes. What changes is what sits between them, and what each one is allowed to do," Saf wrote [11]. The AWS-managed parts are constant across the two runs, so every difference the attacks found sits in customer-configured code and policy [19].

For the anonymous dump to reproduce on someone else's stack, two things have to hold: the list path fetches rows without a predicate scoped to the caller, and the edge admits unauthenticated requests to that path. The demonstration app is small on purpose: create a task, list tasks, read one, update it, delete it [12]. "I kept the business logic this simple on purpose, because the interesting part of this project has nothing to do with to-do lists," Saf wrote [13].

Saf says the tests ran against infrastructure he owns, in line with AWS's penetration testing policy, which he describes as permitting testing of resources you control without prior authorization [15]. The code, Terraform files and attack scripts are published at github.com/YvanSaf/aws-serverless-todo-api [14]. His title counts four attacks; enumeration is the one set out in code, and cross-site scripting follows it [18].

What to watch

  • Whether the published Terraform reproduces the 401 path on a fresh account, since the hardened result rests on the authorizer configuration as shipped.
  • Whether Saf reports latency or cost for the authorizer hop, which is the figure a team weighing the hardened pattern in production would need.
  • The rest of the series, including whether the hardened deployment blocks a stored cross-site scripting payload at write time or at read time.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories