I was working on my own site and went to look at the password change endpoint, and I caught something that made me sit up: it never actually confirmed that the person sending the request owned the account they were changing. Found it in my own code, which is a humbling place to find it. Writing it up because it's a clean example of a bug that is really easy to ship and really easy to miss.
The bug
A password change is supposed to answer one question first: are you actually the person whose password this is? That proof normally comes from something the server can check, like a valid session for a logged in user, or a one time reset token that got sent to the real account holder.
Mine skipped that. The endpoint took an identifier, something like a phone number, and set a new password without ever requiring a login token to prove ownership. So knowing the identifier was treated as being the account holder. That's the whole bug.
Stripped down to an illustrative shape:
POST /api/account/set-password
{
"phone": "<an account's phone>",
"new_password": "anything"
}
No session. No reset token tied to that phone. No verification code. It just did it.
Why it matters
This isn't a small leak, it's full account takeover. Phone numbers are not secret, so "you need to know the phone number" is a speed bump, not a lock. Anyone who can find one gets the account. In OWASP terms this is Broken Access Control, which sits at number one on their list, and now I get why.
The fix
The fixes are not exotic, which is the annoying part:
- Never treat a client supplied identifier like a phone number as proof of identity. Knowing whose account it is is not the same as owning it.
- Require real proof on every state changing request. A valid server side session, or a one time token that was delivered to the account holder and bound to that specific account.
- Add a fresh verification step for password changes, like a code sent to the registered email or phone.
- Rate limit and log the endpoint so abuse is slow and visible.
One missing check did all the damage. That's usually how it goes.
What I took from it
The thing that got me is how quiet this bug is. Nothing crashes, no error, and the request that breaks the account looks almost identical to the one that's supposed to work. The only difference is a check that isn't there. Made me a lot more careful about the gap between "you told me who you are" and "I confirmed who you are," because that gap is where a lot of real security actually lives.
Go check your own password reset. Make sure it's asking the question it's supposed to ask.