Broken Access Control: The Complete Practical Guide

Multistep Process Access Control Flaws

Bypassing workflows that only check your role on step one: jumping to later steps, replaying confirmation with tampered data, and skipping approval stages.

Published

Access control does not only break on single requests. It breaks on the shape of a workflow. A multistep flow, a checkout, a funds transfer, an account change, an admin approval, often checks your permission at the start and then trusts that check through the remaining steps. Send a later step directly, or tamper the state carried between steps, and you do something the flow never authorized.

Flows that only check the role on step one

The classic pattern: step one confirms you are allowed to begin, and steps two and three assume that confirmation still holds. The later steps are where the action actually happens, and they frequently do not re-verify the user or the state. The whole bug is that authorization was a gate at the entrance, not a check at the point of action.

Jumping straight to a later step

If you know the later-step request, send it without doing the earlier ones:

# Intended flow
POST /transfer/start      (checks you may transfer)
POST /transfer/review
POST /transfer/confirm     (actually moves the money)
 
# Attack: send the confirm directly
POST /transfer/confirm    {"amount":5000,"to":"attacker"}

If /transfer/confirm performs the action without re-checking that you passed the earlier gate, the gate is decorative.

Replaying a confirmation step with tampered data

When a flow carries state forward into a final confirmation, test whether that state is trusted blindly. Change what the earlier steps "agreed" to:

# Step two returned a review with an amount and recipient
POST /transfer/confirm
{"amount":5000,"to":"attacker","token":"<from step two>"}   ->  change amount/recipient and resend

If the confirmation commits whatever you send rather than what the earlier steps approved, you have rewritten the transaction after it was authorized.

State carried in hidden fields the client controls

Flows often pass state between steps in hidden form fields, query parameters, or client-held tokens, a price, an account id, a role, an approval flag. Anything the client carries, the client can edit:

<hidden> accountId=52      ->   accountId=1
<hidden> approved=false    ->   approved=true

If the server reads these on the next step instead of re-deriving them from the session and server state, you set them yourself.

Skipping the approval stage of a workflow

Approval workflows (a request that a manager or admin must sign off) are a high-value target. Test whether the "approved" state can be set directly, whether the action endpoint checks for a real approval record, and whether you can reach the post-approval step without the approval ever happening. If the final action trusts an approved flag you control, you approve your own request.

The fix and the mindset

The defense is to treat every step as its own authorization boundary: re-verify the user, and re-derive the state from server-side records, at the exact point the action is performed, never trusting that an earlier step did it. As an attacker, map the whole flow, then attack the last step first, because that is where the action lives and the check is most often missing. When a flow trusts the Referer or a redirect target to prove where you came from, see Referer and Location-based bypass. Practice jumping steps on a free live target.

← Back to Broken Access Control: The Complete Practical Guide