Multi-Step Form Tampering: Breaking Wizards That Trust the Client
Multi-step forms train users to behave. Step 1 asks income; step 2 asks ID upload; step 3 asks product selection. Each page validates locally. The final POST /apply often accepts the whole JSON blob—including fields you were never shown.
That is mass assignment wearing a progress bar.
Replay the final package
Complete the wizard honestly once. Save the last request. Then resend it with edits:
- Eligibility flags:
accredited=true,ageVerified=true,kycStatus=passed - Pricing inputs:
incomelowered after a rate was quoted,student=true - IDs of documents you never uploaded
currentSteporwizardSessionvalues pointing at a finished state
If the server does not re-validate each field against its own session store, you skipped the gate.
Try posting step 3's payload without ever calling step 1. Some apps create a draftId early; others will happily finalize a body with only the fields you supply.
PATCH-per-step designs fail differently. If each step writes independently, jump to the last PATCH with a crafted body and call submit next. The missing middle writes never happened—and nobody checked.
Step tokens that are not tokens
Hidden inputs like stepSignature or hmac look serious. Check whether they actually cover the field values. If the signature is only over draftId and exp, you can change answers freely. If there is no signature—just step=3—believe the server is polite, not secure.
Browser-only required fields fail the moment you use Repeater. Ask: would a disabled dropdown value still be accepted if I enable it and pick the expensive option I was blocked from?
File uploads tied to wizards need a separate pass. Replace a documentId with one from another draft you own, or with an empty string, and see whether submit still passes "document present" checks.
Back-button edits are underrated. Finish step 3, go back in the SPA, change step 1, and submit without revisiting step 2 validators. If the final payload keeps stale middle-step data plus your new step 1 answers, you have a consistency bug even without Repeater.
Proof that stays clean
Use your own application, loan sandbox, or demo tenant. Do not submit forged government ID imagery to a production KYC vendor; prefer flags and eligibility booleans that never leave the app database. For commerce wizards, a changed shipping tier or warranty add-on is enough.
Capture the draft ID, the tampered JSON, and the resulting object showing the privileged field stuck.
How fixes read in tickets
Store answers server-side per step; final submit should load from that store, not from client echo. Re-run validators on the aggregated record. Sign the full payload if you must be stateless. Never take security decisions from hidden fields alone.
Vary your testing across onboarding, insurance quotes, B2B account setup, and "customize your plan" flows—the same bug pattern wears different CSS.
When you write it up, lead with the business rule you broke ("students-only SKU sold to a normal account") rather than "I edited a hidden input." Hidden inputs are the technique. The rule break is the vulnerability.