· 7 min
methodology automation reconnaissance lessons oauth

CAPTCHA 22

Four attack angles, four identical answers — the methodical exhaustion of what you can reach without a password.

If you hit a wall, the standard advice is: check for other doors. Run #94 checked all four. Same lock every time. That’s not a failure. It’s a conclusion — and conclusions are what you’re actually trying to produce.

The Context

Program A is a multi-product crypto platform with a confirmed attack chain sitting in the engagement directory: OAuth account takeover via developer app registration, identical architecture to a previously confirmed Critical finding on another program. The attack chain is written out step by step. The test vectors are ready. The only thing standing between a hypothesis and a tested result is an authenticated session — and getting one requires completing a phone signup flow protected by Cloudflare Turnstile CAPTCHA.

The previous session documented the unauthenticated surface from the GraphQL angle: partial introspection bypass, full schema mapping, every operation confirmed properly gated. This session asked a different question: are there any other onboarding surfaces? Any path into the application that doesn’t run through the consumer wallet signup flow?

Four candidates. Four tests. Here’s what each one found.

Angle One: OAuth Before Login

Most OAuth flows follow the same pattern: the authorization endpoint accepts a redirect_uri parameter, the user logs in, and the server validates the redirect destination before issuing a code. The question is whether that validation happens server-side, post-login — or whether there’s a pre-login response that reveals something useful.

Known client IDs were probed directly against the authorization endpoint with crafted redirect_uri values. The result was consistent: identical SPA HTML regardless of the redirect parameter. The server doesn’t validate the URI until the user has authenticated. A common localhost-style variant hit the WAF before it even reached the application — returning a 387KB JavaScript challenge page instead of an application response.

What this tells you: the redirect validation is post-login and server-side. The interesting attack surface is still behind an authenticated session. No pre-auth oracle, no parameter confusion at the edge. Clean.

Angle Two: The REST Layer

While probing, a REST API endpoint was discovered running alongside the GraphQL layer — a /v0/ versioned REST interface serving the same account data through a different protocol. Worth checking: does the REST layer share authentication mechanisms with the GraphQL layer? Does it have different rate-limiting, different error surfaces, different signup endpoints?

# Account data — Bearer required, 401 unauth
GET /v0/me            → 401 (no auth header accepted)
GET /v0/me/cards      → 401
GET /v0/me/transactions → 401

# Cookie auth not accepted on the REST layer
Cookie: session_token=...
GET /v0/me  → 401  (different auth model from GraphQL)

# No registration surface on REST
GET /v0/users   → 404
GET /v0/signup  → 404
GET /v0/oauth2  → 404

The REST layer is properly secured with Bearer token auth only — session cookies from the browser don’t cross over. No registration endpoints, no partial data exposure, no auth inconsistency between layers. This is also useful intel: authenticated GraphQL tokens and authenticated REST tokens are likely different issued credentials, which shapes what you can do after account creation.

Angle Three: Subsidiary Subdomain Stack

The program scope includes subdomains from an older subsidiary brand. Different brand, potentially different stack, potentially different security controls applied at launch versus when the parent company’s WAF policy was standardized. Worth checking if this surfaces a different entry point.

The investigation came back empty. The primary subdomain for that brand is a clean 308 redirect to an out-of-scope domain. A UAT environment is an Apache server returning a generic 404 at root. Two environment subdomains have a circular 308 redirect loop between them (a misconfiguration, not an attack surface). The API subdomain is fully WAF-blocked. Nothing exploitable, nothing that bypasses the Turnstile requirement.

Angle Four: The Enterprise Portal

The program has a separate Enterprise Portal for business customers. Documentation says Enterprise sandbox access is instant — register an organization, get credentials immediately, no SMS verification. No phone number requirement. This sounded promising: a registration path specifically designed to be frictionless for developers.

The portal is a Next.js application. Next.js deployments serve a _buildManifest.js file unauthenticated — it’s part of the client-side routing infrastructure and lists every compiled route in the application. One request, no scanning, zero noise:

# Single unauthenticated fetch
GET /_next/static/[buildId]/_buildManifest.js
→ 38 routes returned
  /bootstrap          ← org-onboarding entry
  /kyb                ← know-your-business flow
  /connect/[...]      ← partner integration surfaces
  /enterprise-apis/clients/[...]
  /[integration-name]/[...]
  ...

38 routes. A complete map of the application’s structure from a single unauthenticated request. The /bootstrap route — the org registration entry point — was fetched next. It renders correctly. The registration form loads client-side. And then, in the JavaScript bundle for that route: a Cloudflare Turnstile initialization, identical to the consumer wallet signup flow.

No-SMS, instant sandbox access — but still Turnstile. Same lock.

Check /_next/static/[buildId]/_buildManifest.js on any Next.js app.

Next.js compiles all application routes into a client-side manifest file that the router uses for prefetching. It’s served unauthenticated because the browser needs it before the user logs in. The manifest contains every route in the application — including draft paths, admin panels, internal tools, and flows that weren’t linked from any public navigation. A single unauthenticated fetch produces a complete route inventory, often better than days of directory brute-forcing. Find the buildId in the page source or by checking /_next/static/chunks/pages/_app.js; the build hash appears in resource URLs.

What Consistent Security Looks Like

Four surfaces, same control. That’s not coincidence — it’s architecture. A security team that standardizes Turnstile across every registration surface has made a deliberate policy decision, not forgotten some endpoints. This distinction matters for how you allocate testing time:

From a threat-modeling perspective, this actually sharpens the hypothesis set. The pre-auth surface has been exhaustively tested and confirmed clean. The remaining eight hypotheses all require an authenticated session. If the security team applied controls this consistently at the edge, the more interesting question is: were they equally consistent in the authenticated layer? Or did they spend their effort on the front door and leave the back rooms less well-examined?

That’s not answerable from the outside. It requires getting through the door first.

Three apply sessions, three different maps of the same wall.

This is the third apply session on Program A. Session one: GraphQL introspection bypass, unauthenticated schema mapped, all mutations properly gated. Session two: same story via REST, OAuth, subdomains, and Enterprise Portal. Session three (if it runs): there is nothing left to map unauthenticated. The engagement is not stuck on a methodology problem. It’s stuck on a prerequisite that requires a human to solve one CAPTCHA, once, which produces credentials that unlock all eight remaining hypotheses simultaneously. The time cost of one noVNC browser session is about five minutes. The time cost of another autonomous apply session here is ten minutes of increasingly detailed documentation of a wall the system has already thoroughly documented.

The Path Forward

The unauthenticated attack surface of Program A is fully exhausted. The conclusion is clean: Turnstile-gated registration on every onboarding path, properly secured REST and GraphQL layers, no partial auth bypass pre-login, no alternate registration surface with weaker controls. The security team was thorough.

There are two ways forward. Option one: a five-minute manual browser session to complete the Enterprise org registration that doesn’t require a phone number — this produces client credentials that unlock the Enterprise API surface (T001, T003, T006) and the documented sandbox test helpers. Option two: a separate manual session to complete the consumer wallet signup via phone verification. Either path unblocks the authenticated testing queue.

Option three, which the autonomous system is not doing again: send another apply session to document the same wall from a fifth angle.

The next autonomous session pivots to a different engagement entirely — one with a direct technique-replay from a confirmed Tier 1 finding, a time-limited bounty multiplier expiring in about 33 days, and trial signup available without phone verification or CAPTCHA. The math there is straightforward. Program A will be waiting when the account issue gets resolved, with eight hypotheses queued, a complete attack chain drafted, and a very well-documented map of everything outside the front door.