๐ What Is It?
Broken Object Level Authorization (BOLA), also known as Insecure Direct Object Reference (IDOR), occurs when an API endpoint receives an object identifier and performs actions on that object without properly validating whether the requesting user has permission to access it. This is the #1 API security vulnerability.
API01
OWASP Rank
95%
APIs Affected
<30min
Time to Exploit
โ ๏ธ Common Exploits
- Parameter Tampering: Modify user_id, order_id, or document_id in API requests
- ID Enumeration: Iterate through sequential IDs to access all objects
- URL Manipulation: Change resource identifiers in API endpoints
- Body Manipulation: Modify object IDs in JSON request bodies
- Batch Operations: Include unauthorized object IDs in bulk requests
๐ด Attack Flow
1. Attacker authenticates as regular user
โ
2. Observes API call: GET /api/orders/1234
โ
3. Changes ID: GET /api/orders/1235
โ
4. API doesn't verify ownership
โ
5. BREACH: Access to other user's order!
โ
2. Observes API call: GET /api/orders/1234
โ
3. Changes ID: GET /api/orders/1235
โ
4. API doesn't verify ownership
โ
5. BREACH: Access to other user's order!
โ Vulnerable Code
// Bad: No ownership check!
GET /api/orders/5827
Authorization: Bearer <token>
// Backend code (Python Flask)
@app.route('/api/orders/<order_id>')
@login_required
def get_order(order_id):
# VULNERABLE: No ownership verification!
order = Order.query.get(order_id)
return jsonify(order)
// Bad: Trusting client-provided object IDs
POST /api/documents/share
{
"document_id": 9812,
"share_with": "attacker@example.com"
}
โ Secure Code
// Good: Verify ownership before access
@app.route('/api/orders/<order_id>')
@login_required
def get_order(order_id):
order = Order.query.get_or_404(order_id)
# Verify user owns this order
if order.user_id != current_user.id and not current_user.is_admin:
abort(403) # Forbidden
return jsonify(order)
// Good: Filter by ownership in query
@app.route('/api/orders')
@login_required
def get_user_orders():
# Only return user's own orders
orders = Order.query.filter_by(user_id=current_user.id).all()
return jsonify(orders)
// Good: Use UUIDs + ownership checks
def verify_ownership(resource, user):
if resource.owner_id != user.id:
raise PermissionError("Access denied")
โ Prevention Checklist
- Verify object ownership on every API request
- Use user context from authentication token, never request
- Implement database queries with ownership filters
- Use random UUIDs instead of sequential IDs
- Implement centralized authorization logic
- Test with different user contexts
- Log authorization failures for monitoring
- Never trust client-provided object IDs
- Use framework authorization libraries
- Conduct regular security testing
๐ Detection & Tools
Testing Tools:
Burp Suite
OWASP ZAP
Postman
curl
Insomnia
APIFuzzer
Prevention Libraries:
Pundit (Ruby)
CanCanCan (Ruby)
Casbin
Spring Security
Django Guardian
How to Test:
- Create two test accounts (User A and User B)
- Capture API requests as User A
- Replay requests as User B with User A's object IDs
- Iterate through ID ranges to test enumeration
- Test all HTTP methods (GET, POST, PUT, DELETE, PATCH)
๐ Real-World Breaches
- T-Mobile (2021): 40M+ customer records exposed via BOLA in customer API
- Peloton (2021): API allowed access to any user's profile, workout data, and location
- Parler (2021): 70TB of data scraped via sequential post ID enumeration
- USPS (2018): 60M user accounts accessible by changing account parameter
- Bumble (2019): User data accessible by iterating through user IDs
๐ Quick Tips
- DO NOT trust any client-provided object IDs
- DO NOT rely on UUIDs alone for security
- DO NOT skip authorization checks for internal APIs
- DO verify ownership on every request
- DO use WHERE user_id = ? in database queries
- DO log and monitor authorization failures
๐ Compliance
Related Standards:
- PCI-DSS Requirement 6.5.8
- GDPR Art. 32 - Security of Processing
- HIPAA ยง164.312 - Access Control
- SOC 2 CC6.1, CC6.3
- ISO 27001 A.9.4.1
- NIST 800-53 AC-3