Chaining · Case Study · 7 min read
Three low findings and an account takeover
A previous report had listed all three of these findings as low severity and the client had deferred all three. Each rating was defensible. Together they were an account takeover, which is the problem with rating findings one at a time.
The client was a customer portal for a services business, holding identity documents, payment methods and service history. They had been tested the previous year and had a remediation backlog they were working through in severity order, which is the sensible thing to do and which is why the three findings in this writeup were still open.
We were engaged for the annual retest. The first thing we did was read the previous report, which is not something every firm does, and it is where this engagement started.
The three findings, as previously rated
A self-XSS in the profile display name field. Rated low, correctly on its own terms, because a user can only inject into their own session and nobody attacks themselves.
A missing CSRF token on the profile update endpoint. Rated low because the endpoint only changes cosmetic profile fields, and the interesting endpoints all had tokens.
A CORS policy that reflected the Origin header with credentials allowed. Rated low because the only endpoints under that policy returned public reference data.
Three reasonable ratings from a competent tester. We have written ratings like these ourselves.
What we did with them
Self-XSS stops being self-XSS the moment you can write to someone else’s profile. The missing CSRF token on the profile endpoint let us do exactly that: a page on our own domain, a form posting to their profile update route, submitted automatically when a logged-in user visited it.
So we set the victim’s display name to our payload. The payload now executed in the victim’s session, on the victim’s origin, every time they loaded their own dashboard.
The third finding turned that into a takeover. With credentialed CORS reflecting any origin, our payload could call the account API from the victim’s browser and read the response back to us. We took the session token, the registered email address and the partially masked payment method, and used the email change flow to move the account to an address we controlled.
What we demonstrated to the client
We ran the full chain against a second account that we created and controlled, from a fresh browser profile, and recorded every request and response. The client watched the victim account move to our email address in about eleven seconds from the moment the page was loaded.
We did not run it against a real customer, and we did not need to. One demonstrated path is as convincing as a hundred, and using real customer data to prove a point is a choice we do not make.
Why severity-sorted backlogs create this
Remediation queues are worked in severity order because that is rational. The consequence is that low findings accumulate, and low findings are exactly the components a chain is built from. A chain does not need a high-severity link. It needs three low ones that compose.
This is why we escalate findings to their maximum realistic impact before rating them, and why the report writes the chain out end to end rather than listing its parts separately. The same three findings in a severity-sorted list get deferred again. Written as one path from first request to demonstrated takeover, they were fixed in nine days.
What to ask of your own reports
Look at your open low findings and ask whether any two of them touch the same session. A self-XSS plus any write primitive is a stored XSS. Any XSS plus a permissive CORS policy is data exfiltration. Any of those plus a weak account recovery flow is a takeover.
If your last report did not attempt those combinations, it did not tell you your risk. It told you your inventory.
In short
- Point 1
- Self-XSS is a low finding until something else can write to another user’s profile.
- Point 2
- Reflecting the Origin header with credentials allowed turns any XSS into exfiltration.
- Point 3
- Rate findings after escalating them, not before. The chain is the finding.
- Point 4
- Read the previous report. The open low-severity backlog is where the next incident is assembled.