Business Logic Exploitation
Why This Is the Hardest Category
Business logic bugs are:
- Invisible to scanners — no CVE, no OWASP signature, no regex pattern
- Invisible to type checkers — TypeScript says it's valid
- Invisible to tests — unit tests verify the happy path, not adversarial paths
- Unique to each application — can't be cataloged like XSS or SQLi
- Often the highest impact — directly affect money, data, and trust
Stat: ~50% of all high/critical bug bounty findings are now broken access control and business logic flaws.
Category 1: IDOR (Insecure Direct Object Reference)
The Pattern
User-supplied ID determines which resource to access, without verifying the user owns/has access to that resource.
// VULNERABLE — user supplies profile ID, no ownership check
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const profileId = searchParams.get('id')
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', profileId) // Attacker changes ID to anyone's profile
.single()
return Response.json(data)
}
Detection Questions
For EVERY endpoint that takes an ID parameter:
- Who can supply this ID? Any user? Only authenticated users?
- Is ownership verified? Does the code check that the authenticated user owns/has access to this resource?
- Can the ID be enumerated? Sequential integers are trivially enumerable. UUIDs are harder but not impossible.
- Does RLS handle it? If using Supabase with RLS, is the policy correctly scoped?
Fix Pattern
// SAFE — derive resource access from auth, not user input
export async function GET(request: Request) {
const user = await getUser()
if (!user) return new Response('Unauthorized', { status: 401 })
const { data } = await supabase
.from('profiles')
.select('*')
.eq('user_id', user.id) // Scoped to authenticated user
.single()
return Response.json(data)
}
Rule: Prefer deriving the resource scope from auth context rather than user-supplied IDs. If IDs must come from user input, always verify ownership.
Category 2: State Machine Violations
The Pattern
Multi-step workflows that can be accessed out of order, or state transitions that skip required steps.
Expected flow: CREATE → SUBMIT → REVIEW → APPROVE → PUBLISH
Attacker flow: CREATE → PUBLISH (skip review)
Real Examples
Payment bypass:
Expected: Select plan → Enter payment → Confirm → Activate subscription
Attack: Select plan → directly call activate endpoint → Free subscription
Contest manipulation:
Expected: Register → Submit before deadline → Wait for judging → See results
Attack: Submit after deadline (endpoint doesn't check time)
Or: Modify submission after judging started
Approval bypass:
Expected: User creates document → Manager approves → Document published
Attack: User calls publish endpoint directly (no approval check)
Detection Questions
- Can any step be skipped? Try calling endpoint N+1 without completing step N
- Can any step be repeated? Try submitting the same step multiple times
- Can any step be reversed? Try going back to a previous state after advancing
- Are time constraints enforced server-side? Deadlines, cooldowns, rate limits
- Is the current state checked before transitions? Or just the action?
Fix Pattern
// Validate state BEFORE allowing transition
async function publishDocument(docId: string, userId: string) {
const doc = await getDocument(docId)
// Check ownership
if (doc.author_id !== userId) throw new Error('Not your document')
// Check current state allows this transition
if (doc.status !== 'approved') throw new Error('Document must be approved first')
// Check approval exists and is valid
const approval = await getApproval(docId)
if (!approval || approval.revoked) throw new Error('Valid approval required')
// Now safe to transition
await updateDocument(docId, { status: 'published' })
}
Category 3: Parameter Tampering
Price / Quantity / Amount Manipulation
// VULNERABLE — trusts client-sent price
export async function POST(request: Request) {
const { productId, quantity, price } = await request.json()
const total = quantity * price // Attacker sends price: 0.01
await createOrder(productId, quantity, total)
}
Detection Questions
- Does the server trust client-sent prices? Always look up prices server-side
- Can quantities be negative?
-1 * $50 = -$50credit to attacker - Can quantities be zero?
0 * any_price = $0order - Can quantities be fractional?
0.001 * $100 = $0.10(rounding exploits) - Are discount codes applied correctly? Can they be stacked? Applied to wrong products? Used after expiration?
- Are there integer overflow possibilities? Extremely large quantities wrapping to negative
Fix Pattern
// SAFE — server-side price lookup, input validation
const ProductSchema = z.object({
productId: z.string().uuid(),
quantity: z.number().int().positive().max(100),
// NO price field — never trust client prices
})
export async function POST(request: Request) {
const input = ProductSchema.parse(await request.json())
// Look up price server-side
const product = await getProduct(input.productId)
if (!product) throw new Error('Product not found')
const total = input.quantity * product.price // Server-side calculation
await createOrder(input.productId, input.quantity, total)
}
Category 4: Privilege Escalation via Logic
Role Assignment Manipulation
// VULNERABLE — user can set their own role during registration
export async function register(data: FormData) {
const user = await supabase.auth.signUp({
email: data.get('email'),
password: data.get('password'),
options: {
data: {
role: data.get('role') || 'user' // Attacker sends role: 'admin'
}
}
})
}
Feature Gate Bypass
// VULNERABLE — plan check only on frontend
export function PremiumFeature() {
const { plan } = useUser()
if (plan !== 'premium') return <UpgradePrompt />
return <ActualFeature /> // API calls made from here still work for free users
}
// The API endpoint doesn't check the plan
export async function POST(request: Request) {
// Missing: verify user has premium plan
const result = await runPremiumFeature(request) // Free users can call this directly
return Response.json(result)
}
Detection Questions
- Can users set their own role/permissions? During signup, profile update, or any mutation?
- Are permission checks server-side or client-side only? Client-side = no check
- Can users access admin endpoints? Try calling admin API routes with regular auth
- Can free users access paid features? Call the API directly, bypassing frontend gates
- Can users modify their own permission-related fields? Supabase RLS: can users UPDATE their own role column?
Category 5: Financial Calculation Errors
Rounding Exploitation
// VULNERABLE — floating point arithmetic
const discount = price * 0.1 // $9.999999... rounds to?
const total = price - discount
// With many small transactions, rounding errors accumulate
Currency/Precision Issues
// VULNERABLE — mixing cents and dollars
const amount = req.body.amount // Is this 10.00 (dollars) or 1000 (cents)?
await stripe.paymentIntents.create({ amount }) // Stripe expects cents
// If user sends 1 thinking it's $1, Stripe charges $0.01
// If user sends 100 thinking it's cents, Stripe charges $1.00
Detection Questions
- Are all monetary calculations using integers (cents)? Not floating point
- Is rounding consistent? Same rounding direction everywhere
- Are currency conversions validated? Can user claim different currency than charged?
- Are refund amounts validated? Can user get refund > original purchase?
- Are partial refunds calculated correctly? Can rounding create money?
Category 6: Resource Abuse
Unlimited Resource Creation
// VULNERABLE — no limit on API key creation
export async function createApiKey(userId: string) {
return supabase.from('api_keys').insert({ user_id: userId, key: generateKey() })
// No check on how many keys the user already has
}
Free Tier Abuse
- Create multiple accounts to multiply free tier allowances
- Use free trial repeatedly (same email with +alias, different domains)
- Exhaust shared resources (storage, compute) within free tier
Detection Questions
- Are there limits on resource creation? API keys, projects, teams, invites
- Are limits enforced server-side? Not just frontend counters
- Can free trials be repeated? Same person, new account?
- Are shared resources protected? Can one user exhaust resources for all?
Agent Arena-Specific Logic Checks
Given our product stack, specifically check:
- Challenge submission: Can entries be modified after deadline?
- Judging: Can users influence their own scores? Access judge criteria early?
- Leaderboard: Can scores be manipulated? Can fake entries inflate rankings?
- Wallet/Credits: Double-spend? Negative amounts? Transfer to self for profit?
- API Keys: Unlimited creation? Key sharing detection? Usage beyond plan?
- Replay: Can past submissions be replayed against new challenges?
- Admin: Can non-admin users access admin functions?
Master Checklist for Logic Review
For every feature, ask:
- What's the intended flow? Map the happy path state machine
- What happens if I skip a step? Try accessing later steps directly
- What happens if I go backwards? Try reverting to a previous state
- What happens with boundary values? 0, -1, MAX_INT, empty string, null
- What happens if I do it twice? Idempotency? Double-charge? Double-claim?
- What happens if I do it concurrently? See
race-condition-asyncskill - What if I'm not who I claim to be? Access as wrong user, wrong role
- What if I supply unexpected types? String where number expected, array where object expected
- Who benefits from this bug? Follow the money/power/data
- What would a motivated attacker try? Not just random fuzzing — targeted exploitation
References
For race condition specific patterns, see race-condition-async skill.
For access control via RLS, see rls-bypass-testing skill.
For IDOR detection at scale, see references/idor-patterns.md.