This weakness occurs when software fails to enforce the correct sequence or state transitions in a multi-step process. An attacker can skip steps, repeat…
This weakness occurs when software fails to enforce the correct sequence or state transitions in a multi-step process. An attacker can skip steps, repeat steps, or jump directly to later stages without completing required prerequisites — bypassing validation, payment, approval, or authentication checks that should occur in order.
02How It Happens
Workflows typically require users to progress through defined states: for example, a checkout process might require cart review → shipping address → payment → confirmation. When the application doesn't validate the current state before allowing the next action, or doesn't prevent direct access to later steps, an attacker can manipulate the sequence. This often happens when state is tracked only on the client side (in a hidden form field or URL parameter), or when the server accepts requests without verifying that all prerequisite steps have been completed.
03Real-World Impact
Workflow bypass can lead to serious business logic flaws: completing a purchase without payment, accessing restricted features without proper authorization, skipping mandatory security steps like email verification, or approving transactions without required review. The impact depends on what the workflow protects — it could result in financial loss, unauthorized access, data exposure, or compliance violations.
04Vulnerable & Fixed Patterns
Vulnerable pattern
# Flask example: multi-step form without state enforcement
@app.route('/checkout/payment', methods=['POST'])
def process_payment():
user_id = session.get('user_id')
amount = request.form.get('amount')
# No check that shipping address was provided first
charge_card(user_id, amount)
return redirect('/checkout/confirmation')
Why it's vulnerable: The application processes payment without verifying that the user completed the shipping step. An attacker can POST directly to /checkout/payment and skip address entry entirely.
Fixed pattern
@app.route('/checkout/payment', methods=['POST'])
def process_payment():
user_id = session.get('user_id')
# Verify all prior steps are complete
if not session.get('shipping_address_confirmed'):
return redirect('/checkout/shipping')
amount = request.form.get('amount')
charge_card(user_id, amount)
session['payment_confirmed'] = True
return redirect('/checkout/confirmation')
Vulnerable pattern
<?php
// No state validation before processing approval
if ($_POST['action'] === 'approve_request') {
$request_id = $_POST['request_id'];
$approver_id = $_SESSION['user_id'];
// Directly approves without checking if review step was completed
update_request_status($request_id, 'approved', $approver_id);
echo "Request approved.";
}
?>
Why it's vulnerable: The script approves a request based solely on a POST parameter, without verifying that the user reviewed the request details or that the request is in a state that allows approval.
Fixed pattern
<?php
if ($_POST['action'] === 'approve_request') {
$request_id = $_POST['request_id'];
$approver_id = $_SESSION['user_id'];
// Verify request exists and is in the correct state
$request = get_request($request_id);
if ($request['status'] !== 'pending_review') {
wp_die('Request cannot be approved from its current state.');
}
// Verify user has reviewed it (e.g., via a session flag or timestamp)
if (!isset($_SESSION['reviewed_request_' . $request_id])) {
wp_die('You must review the request before approving.');
}
update_request_status($request_id, 'approved', $approver_id);
echo "Request approved.";
}
?>
05Prevention Checklist
Track workflow state server-side. Store the current step or state in a server-controlled session or database record, never in client-side form fields or URL parameters alone.
Validate state transitions. Before processing any action, verify that the current state allows that action. Reject requests that attempt invalid transitions.
Require completion of prerequisites. Check that all mandatory prior steps have been completed (e.g., address provided, terms accepted, payment confirmed) before allowing progression.
Use explicit state machines. Define allowed state transitions formally (e.g., pending → review → approved, not pending → approved directly) and enforce them in code.
Log state changes. Record who changed the workflow state and when, to detect and audit suspicious skips or reversals.
Test workflow paths. Verify that users cannot reach later steps by manipulating URLs, form fields, or session data without completing earlier steps.
06Signs You May Already Be Affected
Check application logs for requests that jump between workflow steps without intermediate steps being recorded. Look for completed transactions or approvals that lack corresponding prerequisite records (e.g., payments without shipping addresses, approvals without review timestamps). Monitor for unusual patterns in workflow completion times — if some users complete multi-step processes in unrealistically short timeframes, they may be skipping steps.