Skip to content

Build1 publisher3 min readPublished

A leftover localhost callback lets Cognito hand OAuth tokens to whatever binds port 3000

Cognito validates the redirect target by matching it against the app client's allowed callback list, so a development entry nobody removed is a valid destination for an authorization code, or for the tokens themselves where implicit grant is still on.

The Engineer · Build desk

Illustration accompanying A leftover localhost callback lets Cognito hand OAuth tokens to whatever binds port 3000

What happened

  • A dev.to analysis argues that the http://localhost:3000/callback entry teams add during development stays in the Cognito app client's allowed list and becomes a valid destination for OAuth tokens.
  • Where implicit grant is still enabled, the access and ID tokens arrive in the URL fragment at whatever is listening on port 3000, so the attacker never has to exchange a code.
  • Cloud security scanners confirm that a callback URL is configured, and none evaluate whether one of the entries would let an attacker receive the redirect.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Telling a production endpoint from a staging one takes someone who knows the estate, so this part of the review stays manual, per app client, in every account.
  • exposure One interception on a client with revocation disabled and a 365-day refresh token gives an attacker a session that outlasts the incident response which would normally end it.
  • decision Teams still running implicit grant have to decide whether the fragment response is worth keeping, since it removes the attacker's need to hold anything but a listener.

Collecting a redirect on port 3000 of someone else's machine costs the attacker a foothold first. The post's own list of exploitable environments says so: compromised workstations, shared development servers, CI runners with exposed ports [8]. Teams that also added 3001 and 8080 gave the attacker more listeners to pick from [9]. Two of the other patterns it flags need nothing at all on the victim's device. A dangling Route 53 CNAME or a decommissioned S3 bucket lets an attacker stand up attacker.example.com and receive the redirect under a wildcard match [10]. An http:// entry sitting beside the https:// one lets someone on the same coffee shop wifi take the redirect in transit [11]. Cleaning an allowed list in order of who can reach it puts those two ahead of the laptop entry.

Cognito's part in this is a comparison. At step 4 of the five-step flow the post lays out [3], it checks the supplied redirect_uri against the app client's allowed callback URLs, and if http://localhost:3000/callback is on the list, the redirect is valid [4]. The authorize link is otherwise ordinary: client_id, response_type=code, scope=openid+email+profile [5]. The user authenticates on the real login page of the real identity provider [6].

For the code-flow version to end in a hijacked session, the attacker has to complete the token exchange. On a public client that needs no secret, and the post notes that SPAs and mobile apps do not use client secrets [18]. Implicit grant removes the step. The access token and ID token land in the fragment of the localhost URL, where the listener reads them [7].

What happens after the theft depends on two app client settings the post supplies as an example. With EnableTokenRevocation = false, the legitimate user cannot invalidate the stolen tokens, and the attacker's session runs until they expire [15]. With AccessTokenValidity at 24 hours and RefreshTokenValidity at 365 days [16], one interception yields 365 successive access tokens before the refresh token runs out [17].

The post argues that no scanner asks whether the callback list contains an attacker-reachable entry, because the question is not a boolean [14]. Three of the four dangerous patterns are properties of the string, and a rule that reads the entries can flag a loopback host, an http scheme or a wildcard by pattern, which is more than confirming a URL is present [13]. The staging case is the one that needs someone who knows the estate, since nothing in an HTTPS URL says which environment has the thinner logging and the softer database [12].

This is one dev.to analysis, labelled human-authored with AI used for formatting and proofreading, and it argues from configuration without citing a breach [20]. Its author wrote that the leftover entry "is now a token exfiltration endpoint" [2], and puts the remediation at thirty seconds [19].

What to watch

  • Whether PKCE is enabled on the app client changes the code-flow half of this; the post's five-step walkthrough stops at the app exchanging the code for tokens.
  • Whether any cloud posture scanner ships a rule that reads the allowed callback entries and flags loopback hosts, http schemes and wildcards by pattern.
  • Whether the 24-hour access token and 365-day refresh token in the post match what teams run, since it presents them as an example configuration.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories