Skip to main content
Engagement composite · Payments

Account takeover, assembled from findings that are easy to defer

A payments platform’s customer sign-in and credential-recovery API, worked end to end on the fully autonomous model. Four weaknesses, three of them the kind that sit in a backlog, in the order that turns them into control of an account and the payment instruments behind it. Each step below carries the weakness it maps to and what it handed to the next one.

Sector
Payments
Coverage class
APIs
Delivery model
Fully autonomous

The chain, step by step

4 steps, and what each one handed to the next

Each row names the technique, the weakness it maps to and what the step yielded. A finding that hands nothing to the next step is a finding, and belongs in the report rather than here.

StepWhat it yieldedSeverity
01 · One endpoint, two different answers A tested split between addresses that are accounts and addresses that are not — the input every later step was worked against. Medium
The credential-reset endpoint answered a registered address and an unregistered one differently — a different status code and a different body key for each case. The sign-in endpoint separated the same two cases twice over: the failure string named which half of the pair was wrong, and the registered case came back later than the unregistered one, because only the registered case fetched a stored hash and computed against it before deciding. — CWE-204, Observable response discrepancy.
02 · Attempt counting that never saw the account Unmetered guessing against each confirmed account, unmetered reset requests for it, no lockout on the account itself and nothing raised to its owner. High
The attempt counter was keyed to the source address and applied to sign-in alone. Reset initiation and token redemption were not counted at all, and because nothing was keyed to the account identifier, a change of source address returned the budget to where it started. — CWE-307, Improper restriction of excessive authentication attempts.
03 · A password policy that only counted characters A working credential for accounts whose password appears in a public breach corpus, and for the rest a recovery flow that was now the shorter route. High
Registration and reset enforced length and character classes and nothing else. Credentials already published in public breach corpora were accepted, as were the obvious seasonal and brand-derived patterns, and the same rule governed the reset form — so a credential rejected by no rule could be set on the way back in. — CWE-521, Weak password requirements.
04 · A reset token that outlived its request Control of the account under a password of the tester’s choosing, with the owner’s own sessions still live and no notification sent — and from there the stored payment instruments and payout destinations on the profile. Critical
The reset token was not bound to the session that asked for it, stayed valid after it had been redeemed once, and redeeming it left every session already open on the account signed in. With the second step removing any ceiling on how often a token could be requested and tried, the flow could be worked until one landed. — CWE-640, Weak password recovery mechanism for forgotten password.

01 · One endpoint, two different answers

What it yielded
A tested split between addresses that are accounts and addresses that are not — the input every later step was worked against.
Severity
Medium

The credential-reset endpoint answered a registered address and an unregistered one differently — a different status code and a different body key for each case. The sign-in endpoint separated the same two cases twice over: the failure string named which half of the pair was wrong, and the registered case came back later than the unregistered one, because only the registered case fetched a stored hash and computed against it before deciding. — CWE-204, Observable response discrepancy.

02 · Attempt counting that never saw the account

What it yielded
Unmetered guessing against each confirmed account, unmetered reset requests for it, no lockout on the account itself and nothing raised to its owner.
Severity
High

The attempt counter was keyed to the source address and applied to sign-in alone. Reset initiation and token redemption were not counted at all, and because nothing was keyed to the account identifier, a change of source address returned the budget to where it started. — CWE-307, Improper restriction of excessive authentication attempts.

03 · A password policy that only counted characters

What it yielded
A working credential for accounts whose password appears in a public breach corpus, and for the rest a recovery flow that was now the shorter route.
Severity
High

Registration and reset enforced length and character classes and nothing else. Credentials already published in public breach corpora were accepted, as were the obvious seasonal and brand-derived patterns, and the same rule governed the reset form — so a credential rejected by no rule could be set on the way back in. — CWE-521, Weak password requirements.

04 · A reset token that outlived its request

What it yielded
Control of the account under a password of the tester’s choosing, with the owner’s own sessions still live and no notification sent — and from there the stored payment instruments and payout destinations on the profile.
Severity
Critical

The reset token was not bound to the session that asked for it, stayed valid after it had been redeemed once, and redeeming it left every session already open on the account signed in. With the second step removing any ceiling on how often a token could be requested and tried, the flow could be worked until one landed. — CWE-640, Weak password recovery mechanism for forgotten password.

The argument

Each of these is deferrable on its own

Taken one at a time, three of the four are findings a team can reasonably leave until next quarter. The order is what makes them an account takeover.

A response discrepancy on its own discloses one bit about one address, and the standing argument for leaving it is that somebody could learn the same thing by attempting to register. An attempt ceiling counted by source address is defensible too, until somebody asks what it is counting when the source changes. A policy that checks length and character classes looks like a policy doing its job. What turns four findings into one chain is that each removes the constraint the next one runs into. Guessing is expensive while the address list is mostly wrong, so the first step buys a list that is entirely right. Guessing against a right list is still expensive while the account locks, so the second step takes the ceiling away. Unmetered guesses are still worthless against credentials that cannot be guessed, so the third supplies the ones that can — and where it cannot, the fourth leaves recovery standing as the shorter way in. Close any single link and the chain shortens. Close the first and the other three keep their severity but lose the list they were aimed at, which is why the enumeration finding, the cheapest of the four to fix, is the one the report led with.

The part you can check yourself

One request, two answers — and what separated them

The discrepancy is the link worth testing first, because finding it needs no credential and no account.

01 Response shape

The credential-reset request

With a registered address
The endpoint answered 202 Accepted, with a body carrying a delivery key set to queued. Read on its own that is correct behaviour: the request was taken and something is on its way.
With an unregistered address
Same endpoint, same field, one character different in the address — 200 OK, and a body carrying a different key altogether. Nothing was sent, and the response said so in its own way.
What the difference gives
An oracle that answers a yes-or-no question about any address, at whatever rate the endpoint will take, without a credential and without touching an account. One generic answer for both cases — same status, same keys, same wording — removes it.
02 Two channels, not one

The sign-in request

The wording
Both cases returned 401, and the message underneath named which half of the pair had failed: one string for a password that did not match, another for an address with no account behind it. Strings like these are written to be helpful and are read as an index.
The timing
Once both cases return the same string, the registered case still does work the unregistered case does not — it fetches a stored hash and computes against it before deciding, while the other returns without that work. The gap survives repetition, which is what makes it measurable.
What closes both
One failure string for every case, and a constant-work path that computes against a dummy hash when there is no account, so the two cases cost the same. Fixing only the wording moves the finding to the other channel rather than closing it.

The environment

What was in front of the run

A payments platform’s customer-facing authentication and recovery surface, scoped as an API test because that is where the flow actually lives.

Surface

A sign-in and recovery API

Sign-in, reset initiation, token redemption, session issuance and the profile calls behind them. Browser and mobile were two front ends onto the same set of calls, which is the usual reason a flow like this is worked as an API rather than as a web application.

Roles

More than one kind of account

Customer accounts alongside a smaller set with administrative reach over them. Each role was worked with a credential supplied for it, because an unauthenticated run cannot tell an authorisation decision from a missing feature.

Stakes

What sat behind a session

Stored payment instruments, payout destinations and transaction history sat behind a valid session, which is why the last link in the chain is rated where it is rather than one step down.

Scope

Signed off before anything ran

The target list, a credential per role and the boundary were agreed in writing first, and nothing outside it was touched. On the fully autonomous model that sign-off is the whole of the customer’s involvement until the report arrives.

Deployment

Nothing running inside the network

External and application testing needs nothing deployed in the customer environment; only internal network work does. Residency was set at the outset, from India, the European Union, the United States and Singapore.

Proof

Every finding reproducible

Each finding carries the request as sent, the response as returned, the steps that reproduce it, a CVSS v4.0 vector and a CWE. A finding that cannot be reproduced from the report is not yet a finding.

Where the run looked

The authentication surface, call by call

Five calls carry the chain above. This is what the run did with each of them.

CallWhat the run did with it
Sign-in Submitted known-bad pairs against addresses in both states, registered and not, then compared the status line, the body, the headers and how long each answer took to come back.
Requests went to api.payments.example.
Reset initiation Submitted the same two classes of address and compared the answers. This is the call that carried the discrepancy, and it carried it without any credential at all.
Token redemption Redeemed a token twice, redeemed one after a later sign-in, and checked whether redeeming it ended the sessions already open on the account.
Session issuance Checked what a session survives: a password change, a change of device, and a redemption the account owner never asked for.
Attempt controls Counted what the counter counts — per source address, per account identifier, per call — and which of the four calls above it was applied to at all.

Sign-in

What the run did with it
Submitted known-bad pairs against addresses in both states, registered and not, then compared the status line, the body, the headers and how long each answer took to come back.

Requests went to api.payments.example.

Reset initiation

What the run did with it
Submitted the same two classes of address and compared the answers. This is the call that carried the discrepancy, and it carried it without any credential at all.

Token redemption

What the run did with it
Redeemed a token twice, redeemed one after a later sign-in, and checked whether redeeming it ended the sessions already open on the account.

Session issuance

What the run did with it
Checked what a session survives: a password change, a change of device, and a redemption the account owner never asked for.

Attempt controls

What the run did with it
Counted what the counter counts — per source address, per account identifier, per call — and which of the four calls above it was applied to at all.

What the report carried

Per step: the artefact, not the assertion

A chain is only evidence if each link can be re-run by somebody who was not there. This is what the report held against each of the four.

StepWhat the report carried for it
Response discrepancy Both requests as sent and both responses as returned, set side by side with the status line and the differing body key marked, plus the repeated-request evidence for the timing channel. CVSS v4.0 vector and CWE-204 beside them.
Attempt restriction The sequence that showed no ceiling: which call, which key the counter used, what happened when the source address changed, and the point at which the run stopped rather than continuing. CVSS v4.0 vector and CWE-307.
Password requirements The policy as the endpoint enforced it, the classes of credential it accepted, and the pair of responses showing that registration and reset are governed by the same rule. CVSS v4.0 vector and CWE-521.
Recovery mechanism Token issued, redeemed, and redeemed again; the session state before and after; and the reproduction steps in the order a developer would run them rather than the order they were discovered in. CVSS v4.0 vector and CWE-640.
Penetration testing methodology The six phases every run follows, discovery through a quality gate that runs on the finished report and can send it back. Published in full, so a reader can check what produced the four rows above.
Penetration testing checklist The same checks written as a list somebody can work through against their own sign-in and recovery flow, without waiting for a scan to tell them the answer.

Response discrepancy

What the report carried for it
Both requests as sent and both responses as returned, set side by side with the status line and the differing body key marked, plus the repeated-request evidence for the timing channel. CVSS v4.0 vector and CWE-204 beside them.

Attempt restriction

What the report carried for it
The sequence that showed no ceiling: which call, which key the counter used, what happened when the source address changed, and the point at which the run stopped rather than continuing. CVSS v4.0 vector and CWE-307.

Password requirements

What the report carried for it
The policy as the endpoint enforced it, the classes of credential it accepted, and the pair of responses showing that registration and reset are governed by the same rule. CVSS v4.0 vector and CWE-521.

Recovery mechanism

What the report carried for it
Token issued, redeemed, and redeemed again; the session state before and after; and the reproduction steps in the order a developer would run them rather than the order they were discovered in. CVSS v4.0 vector and CWE-640.

Penetration testing methodology

What the report carried for it
The six phases every run follows, discovery through a quality gate that runs on the finished report and can send it back. Published in full, so a reader can check what produced the four rows above.

Penetration testing checklist

What the report carried for it
The same checks written as a list somebody can work through against their own sign-in and recovery flow, without waiting for a scan to tell them the answer.

What a developer gets

Reproduction beats narration

The test for a report like this is whether the engineer who has to fix it can reach the same result without asking anybody a question.

Every finding arrives as the request and the response it produced, the steps to reproduce in the order they were run, a CVSS v4.0 vector and a CWE. That is the standard on every finding the platform produces, in every coverage class and in all three delivery models. The argument is not that four things were found, it is that each step handed something specific to the next, and the handover is in the artefact rather than in the prose around it. It also settles the discussion that follows an enumeration finding. Somebody will say the difference is by design, because a customer who mistypes an address ought to be told so. The report answers that with the pair of responses rather than with an opinion, and the discussion moves to where it belongs — whether telling that customer is worth handing a confirmed list of account holders to somebody who will then work the other three steps against it.

What changed

Four changes, and one thing that is not a change

Each row is a control, what it does now, and which link it takes out of the chain above.

Four changes, and one thing that is not a change
StateWhat it meansWhat follows
A single response shape Reset initiation answers a registered and an unregistered address identically — same status, same keys, same wording — and says only that a message will be sent if there is an account behind the address. The oracle in step one has nothing left to read.
One failure string, constant work Sign-in returns one message for every failure, and the no-account path computes against a dummy hash so that it costs what the real path costs. Neither the wording nor the timing separates the two cases.
Ceilings keyed to the account Attempt counting applies to sign-in, reset initiation and token redemption, keyed to the account identifier as well as to the source address, and the account-side ceiling survives a change of source. Step two no longer removes the cost of guessing.
Recovery tokens bound and spent A reset token is single-use, short-lived and bound to the session that requested it; redeeming one ends every other session on the account and notifies the owner. Screening at registration and at reset rejects credentials published in breach corpora rather than only counting characters. Steps three and four both lose their path.
Constant work, as a standing check Terminal Timing equalisation is a property of code that changes every sprint, and it is not verified by reading a diff. This one belongs in the pipeline rather than in a remediation ticket, which is what the next section is about. Not closed by the fix — held open by whatever keeps checking it.
Key
  • Re-run against the same scope and no longer reproducible
  • Holds only while something keeps checking it
  • TerminalNo state follows this one

How it ran

Six phases, and the two places a person appears

Fully autonomous means scope sign-off and then nothing until the report. These are the same six phases every run follows, in every coverage class.

The figures on this page

Where each number comes from

Two claim figures appear on this page — a turnaround median and a coverage proportion — and both belong to the platform rather than to this engagement.

Turnaround and coverage, and what neither of them is As of 2026-09-15
  • Turnaround on the fully autonomous model is an observed median of one to three business days, from scope sign-off to report. It is a median taken from real runs rather than a service level, and it is quoted here because this is the model this engagement ran on.
  • No turnaround figure exists for the autonomous expert-verified or human-led models, where a senior Security Brigade auditor’s verification sits between the run and the report. None is stated for them anywhere on this site.
  • B-52 and Security Brigade’s expert assessment team worked the same targets in parallel, with neither side seeing the other’s output while the work ran. The findings were pooled into one denominator with each item counted once, and B-52 reached 90–95% of that pooled set.
  • Security Brigade has been running engagements since 2006 and has been CERT-In empanelled since 2008; the firm is ISO 27001 certified. Those are the firm’s instruments, and the two the whole site claims.

Deliberately excluded

  • The 90–95% figure is a proportion of a pooled findings set, not a comparison of two products, and not a statement about any single engagement.
  • The median measures turnaround across runs and says nothing about how long any individual run took, this one included.
  • Physical, hardware and wireless testing, which is out of scope for B-52 in every coverage class.

Run the same four checks against your own flow, from $500

One scan is one application or one target at $500, and a paid trial is $299. Anything wider than a single target is a scoping call rather than a tier.