- bug-bounty
- web-security
- otp
- business-logic
Two easy ways to break phone verification
Same registration flow, same final impact, two different OTP bypasses.
November 2025 log. Same target, same registration flow, two different ways to bypass the phone verification step.
Context
This target used SMS OTP during registration. The expected model was simple:
- enter a phone number
- receive a code
- validate the code
- finish account creation with a phone number the platform now considers trusted
That sounds straightforward, but the trust was not kept in one place. Once the flow opened up, there were at least two different ways to get to the end with a number that had never actually been verified.

Registration model
The registration flow mixed three different states:
- the phone number currently visible in the form
- the number that had received an OTP
- the internal "verified" state that unlocked the rest of the signup
Those three things should have stayed tightly bound to each other on the server side. They did not.
Bug 1: classic TOCTOU
The first issue was a TOCTOU bug.
TOCTOU means Time Of Check to Time Of Use. In practice, the application checks one thing, then later uses a related value without confirming that it is still the same one it checked before.
Here the sequence was:
- verify a real phone number I control
- let the application unlock the rest of the registration form
- change the phone number field to a fake or uncontrolled one
- submit the final registration anyway
So the check happened on phone number A, but the account was created with phone number B.
That means the OTP step was real, but the number stored at the end of the flow was no longer the verified one.

This one was closed as a duplicate, but it was still useful because it showed that the verification state was not strongly attached to the final account creation step.
Bug 2: response manipulation during OTP verification
The second issue hit the same feature from a different angle.
Instead of changing the phone number after the check, I targeted the OTP verification response itself.
The flow was:
- start once with a real phone number I control
- request the OTP and submit the correct code
- capture the successful verification response
- restart with a fake phone number
- request an OTP for that fake number
- enter any invalid code
- intercept the server error and replace it with the previously saved success response
The frontend accepted the manipulated response and moved on as if the OTP had been validated.
In other words, the browser was not continuing because the server had a strong, current verification state for this transaction. It was continuing because the response looked like a successful one.

This was the accepted report.
Why these bugs are technically interesting
Both bugs have the same visible result, but the root cause is different:
- Bug 1 is state drift between verification and final submission
- Bug 2 is over-trust in client-visible verification output
That distinction matters because it changes what you should test.
If you only look at the final impact, "phone verification bypass", the two reports sound almost identical. But one is about how the app reuses a stale checked value, and the other is about how the app trusts a success shape coming back to the client.
Impact
The immediate impact is not glamorous, but it is real:
- accounts can be created with fake or uncontrolled phone numbers
- anti-abuse controls tied to phone verification become weaker
- one-account-per-number assumptions stop meaning much
- the platform gets polluted with bad identity data
Most of the time I would rate this medium, sometimes even low. It becomes more serious if the phone number is later reused as a recovery factor, a trust signal, or a uniqueness control for promotions or business onboarding.
What I test in OTP flows
When I reach a phone verification step, I usually check a few boring things first:
- After a successful OTP, what fields become editable?
- If I change the phone number after verification, does the verified state survive?
- Does the final submission use a trusted server-side value or whatever is still sitting in the form?
- If I save one success response, can I replay or transplant it into another attempt?
The app already verified my Google email, so it "assumes" the next step is safe. Never trust an app that assumes you're done with verification.
I prefer doing this via DevTools because it's faster than switching to my proxy for every single request. If it works there, it'll work everywhere. Whenever I see a disabled field that contains my PII, my first reflex is always to see if the backend actually cares about that "disabled" attribute.
The main point is this: do not stop testing once the code is accepted. The interesting bugs are often in what happens immediately after.
Disclosure
I reported both issues separately because they had different causes even though they ended in the same type of bypass.
The TOCTOU issue was closed as a duplicate. The response-manipulation issue was accepted and paid €188.
Closing
This was a good example of why one weak registration flow can contain multiple real bugs at once.
The OTP itself may be fine. The weak part can be the field that gets reused later, or the response the frontend decides to trust.
