Currently accepting new security engagementsRequest a scope
Redacted engagementHighAuthentication and session management

Password reset and session handling gaps found by testing the whole account lifecycle

Reviewing authentication as a lifecycle rather than a login page surfaced several issues that individually looked minor and together produced a reliable way to hold access to an account after the owner had tried to lock it down. Most of these do not appear in scanner output at all, because every response involved is a legitimate one.

A multi-tenant business application with invitations, role changes and self-service password reset

Redacted. This describes work that was carried out. The client, the product, the affected endpoints and the exact requests are deliberately absent, and no detail here would help anyone locate or reproduce the issue on the original system. Published with the class and the reasoning intact because that is the part you can act on.

The situation

The client had passed an automated scan and wanted to know what that scan could not see. Scope covered registration, invitation, login, password reset, email change, role change and account deactivation, across three roles.

How it was approached

  1. Modelled the account lifecycle as a state machine and listed every transition, including the ones that are rarely exercised such as deactivation and role downgrade.
  2. Held sessions open in parallel across two browsers and two roles, then performed each transition and checked what the other session could still do.
  3. Examined reset tokens for predictability, reuse after use, expiry, and whether they remained valid after the address they were issued to had been changed.
  4. Checked whether privilege changes took effect on existing sessions or only at next login.

What was found

Reset tokens stayed valid longer than the interface implied and were not invalidated when a newer one was issued. Existing sessions survived a password change, so a session obtained earlier kept working after the owner had reset their password specifically to end it. A role downgrade did not take effect until the affected user signed out and back in, leaving the higher permission live in the meantime.

Why it mattered

An attacker with any earlier foothold, a shared machine, a stolen session or an old reset link, retains access through exactly the actions a user takes to remove them. From the owner's side the account looks secured. It is not.

How it was fixed

  • Invalidate every other session on password change, and say so in the confirmation message so the user knows it happened.
  • Make reset tokens single use, short lived, and invalidated as soon as a newer token is issued for the same account.
  • Re-evaluate permissions on each request from stored state rather than from a claim that was written into the session at login.
  • Invalidate outstanding reset tokens when the account's email address changes.
  • Show the user their active sessions and give them a way to end one.
Check this on your own system

Three things worth doing this week

None of these needs a purchase order. They are the checks that most often turn up something on a system nobody has looked at from the outside.

Test the transitions, not the screens

Login and reset both work correctly in isolation in almost every application. The defects sit in what happens to state that already existed when the transition occurred.

A password change should be an eviction

Most users reset a password because they think somebody else has access. If other sessions survive it, the action did not do the one thing it was taken for.

Permissions cached at login are permissions frozen at login

If you write a role into a token, decide explicitly how quickly a revocation must take effect, and build for that. Silence here means the answer is whenever they happen to sign out.

More in this area

Penetration testing

All case studies
Redacted engagement

OAuth misconfiguration that allowed one account to be linked to another

Single sign-on was configured so that the identity returned by the provider was trusted more than the account it was being attached to. The result was an account linking path that let one identity end up in control of a different user's account. This is the class of issue that reads as a configuration detail and behaves as a full account takeover.

Redacted engagement

SQL injection and stored cross-site scripting behind an authenticated interface

Two classic injection classes, both reachable only after login, which is why neither had ever been reported by an unauthenticated scan. The database defect exposed data across the whole application. The scripting defect ran in the browser of whoever reviewed the affected record, which in this product was always an administrator.

Want to know whether this applies to you?

Tell us what you are running and which of these worries you. We will tell you plainly whether it is worth testing, what it would cost, and when the answer is that you should fix something yourself first.