Workflow Abuse
Workflow Abuse at a glance
Overview
Workflow abuse exploits flaws in multi-step business processes like checkout flows, approval workflows, onboarding processes, or order fulfillment. Attackers manipulate the sequence of steps to bypass required actions, access unauthorized states, or repeat beneficial steps.
Common vulnerabilities include missing server-side state validation, trusting client-side workflow state, allowing arbitrary state transitions, not validating prerequisites for each step, and failing to verify the complete workflow was followed. These attacks work by directly accessing later workflow steps, manipulating state parameters, or repeating steps out of order.
Where it occurs
Workflow abuse occurs in multi-step processes that rely on client-managed state or lack proper server-side validation of transitions, prerequisites, or step completion.
Impact
Workflow abuse leads to payment bypass where goods are obtained without payment, approval process circumvention, unauthorized account creation or privilege grants, inventory manipulation, fraud through repeated beneficial steps, and data integrity issues from incomplete workflows.
Prevention
Prevent logic flaws by enforcing server-side state machines with validated transitions, trusted state storage, unpredictable step IDs, atomic transactions, audit logging, timeouts, and verification of all required steps before completion.
Examples
Switch tabs to view language/framework variants.
E-commerce checkout allows skipping payment step
Direct URL access bypasses required workflow steps.
from flask import Flask, request, session
app = Flask(__name__)
@app.route('/checkout/step/<int:step>')
def checkout_step(step):
# BUG: No validation of workflow progression
if step == 1:
return show_cart()
elif step == 2:
return show_payment()
elif step == 3:
# No check that payment was completed!
create_order(session.get('cart'))
return show_confirmation()- Line 13: No payment verification
Direct URL access bypasses required steps, allowing payment bypass.
from flask import Flask, request, session
from enum import Enum
class CheckoutState(Enum):
CART = 1
PAYMENT = 2
CONFIRMATION = 3
ALLOWED_TRANSITIONS = {
CheckoutState.CART: [CheckoutState.PAYMENT],
CheckoutState.PAYMENT: [CheckoutState.CONFIRMATION]
}
@app.route('/checkout/step/<int:step>')
def checkout_step(step):
current = session.get('checkout_state', CheckoutState.CART)
requested = CheckoutState(step)
# Validate transition
if requested not in ALLOWED_TRANSITIONS.get(current, []):
return 'Invalid workflow transition', 403
if requested == CheckoutState.CONFIRMATION:
if not session.get('payment_completed'):
return 'Payment required', 403
create_order(session['cart'])
session['checkout_state'] = requested
return render_step(requested)- Line 19: Transition validation
- Line 23: Payment prerequisite check
Implement state machine with allowed transitions and prerequisite validation.
Engineer Checklist
-
Implement state machine with defined states and transitions
-
Validate prerequisites for each state transition
-
Store workflow state server-side, not in client
-
Use non-sequential, unpredictable step identifiers
-
Verify all required steps completed before finalization
-
Validate business rules at each transition
-
Log all state transitions with user context
-
Implement workflow timeouts
-
Use database transactions for atomic operations
-
Test by directly accessing intermediate URLs
-
Prevent duplicate submissions with idempotency
-
Add CSRF tokens to workflow forms
End-to-End Example
An e-commerce checkout workflow allows users to skip the payment step by directly accessing the order confirmation page.
# Vulnerable: No state validation
@app.route('/checkout/<step>')
def checkout(step):
if step == '1':
return render_cart()
elif step == '2':
return render_payment()
elif step == '3':
# No validation that payment was completed!
create_order(request.args.get('items'))
return render_confirmation()# Secure: Server-side state machine
from enum import Enum
class CheckoutState(Enum):
CART = 1
PAYMENT = 2
CONFIRMATION = 3
ALLOWED_TRANSITIONS = {
CheckoutState.CART: [CheckoutState.PAYMENT],
CheckoutState.PAYMENT: [CheckoutState.CONFIRMATION],
}
@app.route('/checkout/<step>', methods=['GET', 'POST'])
@login_required
def checkout(step):
# Get current workflow state from session
current_state = session.get('checkout_state', CheckoutState.CART)
requested_state = CheckoutState[step.upper()]
# Validate transition is allowed
if requested_state not in ALLOWED_TRANSITIONS.get(current_state, []):
return "Invalid workflow transition", 403
if requested_state == CheckoutState.PAYMENT:
# Validate cart not empty
if not session.get('cart_items'):
return redirect('/checkout/CART')
session['checkout_state'] = CheckoutState.PAYMENT
return render_payment()
elif requested_state == CheckoutState.CONFIRMATION:
# Validate payment was completed
if not session.get('payment_completed'):
return redirect('/checkout/PAYMENT')
# Create order atomically
with db.transaction():
order = create_order(
user_id=current_user.id,
items=session['cart_items'],
payment_id=session['payment_id']
)
session.pop('cart_items')
session.pop('payment_id')
session['checkout_state'] = CheckoutState.CART
return render_confirmation(order)Discovery
Map all workflow URLs and try accessing later steps directly. Manipulate step parameters or state identifiers.
-
1. Map workflow endpoints
httpAction
Enumerate all workflow step URLs through normal flow
Request
GET https://app.example.com/checkout?step=1,2,3,4Response
Status: 200Body:{ "note": "Sequential step URLs discovered, identifying progression pattern" }Artifacts
workflow_map step_urls state_parameters -
2. Test direct step access
httpAction
Attempt to access later workflow steps without completing earlier ones
Request
GET https://app.example.com/checkout?step=4Response
Status: 200Body:{ "note": "Access granted to confirmation step without completing payment" }Artifacts
step_bypass missing_validation -
3. Test state parameter manipulation
httpAction
Manipulate workflow state identifiers to skip steps
Request
POST https://app.example.com/checkout/submitBody:"{'workflow_state': 'completed', 'payment_done': true}"Response
Status: 200Body:{ "note": "Application accepts client-provided state without validation" }Artifacts
state_manipulation client_state_trust
Exploit steps
Attacker identifies workflow steps and directly accesses later steps bypassing required actions like payment, approval, or verification.
-
1. Bypass payment step in checkout
Skip to order confirmation
httpAction
Directly access order confirmation without completing payment
Request
POST https://app.example.com/checkout/confirmBody:"{'items': [{'id': 123, 'quantity': 1}]}"Response
Status: 200Body:{ "note": "Order created and confirmed without payment processing" }Artifacts
unpaid_order payment_bypass order_confirmation -
2. Skip approval workflow
Direct access to approved state
httpAction
Access final approval step without manager review
Request
GET https://app.example.com/request/123/approve?bypass=trueResponse
Status: 200Body:{ "note": "Request marked as approved without authorization" }Artifacts
approval_bypass unauthorized_approval -
3. Manipulate multi-step registration
Skip verification steps
httpAction
Complete registration without email/phone verification
Request
POST https://app.example.com/register/completeBody:"{'verified': true, 'step': 'final'}"Response
Status: 200Body:{ "note": "Account created with full privileges without verification" }Artifacts
unverified_account verification_bypass
Specific Impact
Financial loss from payment bypass, unauthorized account privileges, inventory fraud, and compromised business processes.
Fix
Implement a proper state machine with defined states and allowed transitions. Store workflow state server-side in sessions. Validate all prerequisites before allowing state transitions. Use database transactions for atomicity.
Detect This Vulnerability in Your Code
Sourcery automatically identifies workflow abuse vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free