Intermediate Web — OWASP Top 10
Broken Access Control and IDOR
Broken Access Control is the top category in the OWASP Top 10. It occurs when an application does not properly verify whether the authenticated user is permitted to perform an action or access a specific resource.
IDOR — Insecure Direct Object Reference
The object identified in the URL or request body is accessible without checking whether it belongs to the current user.
User A (id=42) accesses their own order:
GET /api/orders/1001
Authorization: Bearer <user 42 token>
Changes the id:
GET /api/orders/1002
Response: data from another user's order — IDOR confirmed
Horizontal vs Vertical Privilege Escalation
Horizontal: user A accesses user B’s data (same privilege level). Vertical: regular user accesses admin functionality.
// Vertical — role parameter in the request
POST /api/users/update
{"user_id": 99, "role": "admin"}
If the server accepts it without checking that the requester is an admin → vertical escalation.
Vulnerable vs Secure Code
# VULNERABLE — uses the id from the URL directly
@app.route('/api/orders/<int:order_id>')
def get_order(order_id):
order = Order.query.get(order_id)
return jsonify(order.to_dict())
# SECURE — filters by the authenticated user
@app.route('/api/orders/<int:order_id>')
@login_required
def get_order(order_id):
order = Order.query.filter_by(
id=order_id,
user_id=current_user.id # ensures it belongs to the user
).first_or_404()
return jsonify(order.to_dict())
Indirect References — IDOR Mitigation
Replace sequential IDs with UUIDs or opaque tokens. UUIDv4 values are random and unpredictable, making enumeration harder.
// Enumerable — attacker tries 1001, 1002, 1003...
GET /api/orders/1001
// Not enumerable
GET /api/orders/f47ac10b-58cc-4372-a567-0e02b2c3d479
This does not replace server-side ownership checks — it is a defense-in-depth measure.
Role-Based Access Control (RBAC)
def require_role(*roles):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
if current_user.role not in roles:
abort(403)
return f(*args, **kwargs)
return wrapper
return decorator
@app.route('/admin/users')
@login_required
@require_role('admin', 'superadmin')
def list_users():
...
Best Practices
- Deny by default: if there is no explicit allow rule, reject.
- Enforce authorization server-side — never rely on frontend controls.
- Log all 403s and alert on high volumes (may indicate IDOR scanning).
- Automated authorization tests: call endpoints with other users’ tokens and verify they return 403.
- Periodic review of the permission matrix vs feature set.