- Zero-Day Wire
- Posts
- Defend the Web (Week 3): Access Control & IDOR Defense
Defend the Web (Week 3): Access Control & IDOR Defense
How to Build Bulletproof Access Control & Stop IDOR Attacks
π‘οΈ Week 3: Secure Access Control
Defend the Web: Part 3 of 8
Welcome to Defensive Wednesday.
Hey π
Yesterday we broke access control. You changed IDs in URLs, manipulated API parameters, and accessed data you shouldn't have.
If you missed Week 3 Offensive, read it here π link
Today we fix it.
Reality check: Broken Access Control is #1 on the OWASP Top 10. 34% of applications tested had at least one vulnerability here.
The problem? Most developers check authentication (who you are) but forget authorization (what you can do).
Let's fix that. π
β οΈEver Google yourself? Try it. Your address, phone number, and relatives are all for sale.
Data brokers like Spokeo, WhitePages, and BeenVerified are selling your info right now. Anyone with $50 can find where you live.
MyDataRemoval scrubs your data from 200+ broker sites automatically. They submit removal requests, track re-listings, and keep monitoring so your info stays gone.
Free scan (limited time) >
π§ What Makes Access Control Secure?
The golden rule: Check permissions server-side, every time, no exceptions.
Never trust:
URL parameters
Form data
API requests
Cookies
Headers
Three layers you need:
Horizontal β User A can't access User B's data
Vertical β Regular users can't access admin functions
Context-based β Actions allowed only in specific states
π§ The Defense Strategy
1. Never Trust Client Data
β Bad:
def get_user(user_id):
return User.query.get(user_id) # No check!
β Good:
def get_user(user_id):
if current_user.id != user_id and not current_user.is_admin:
abort(403)
return User.query.get(user_id)
Always validate ownership before returning data.
2. Use Indirect Object References
Instead of /api/order/12345, use /api/order/8f3a9d2c-4b1e-4f9a
Why? Sequential IDs are guessable. UUIDs aren't.
But still validate ownership β obscurity isn't security.
# Generate UUID for each resource
public_id = str(uuid.uuid4())
# Still check ownership
if order.user_id != current_user.id:
abort(403)
3. Implement Role-Based Access Control (RBAC)
Define what each role can do:
ROLES = {
'user': ['read_own', 'write_own'],
'moderator': ['read_own', 'write_own', 'read_all'],
'admin': ['read_all', 'write_all', 'delete_all']
}
Check before every action:
if 'delete_all' not in ROLES[current_user.role]:
abort(403)
4. Prevent Mass Assignment
Attackers add extra fields hoping you'll accept them:
{
"email": "[email protected]",
"role": "admin",
"credits": 999999
}
Defense: Whitelist allowed fields
ALLOWED = ['email', 'name', 'bio']
updates = {k: v for k, v in data.items() if k in ALLOWED}
Only update fields users should change.
5. Lock Down Admin Endpoints
# Require admin for entire blueprint
@admin_bp.before_request
def require_admin():
if not current_user.is_admin:
abort(403)
Bonus: IP whitelist for extra protection
ADMIN_IPS = ['203.0.113.0/24']
# Only allow admin access from office IPs
6. Context-Based Access Control
Some actions are only valid in certain states.
Example: Can't edit an invoice after it's paid.
def can_edit(self, user):
return (
self.user_id == user.id and
self.status == 'draft'
)
Check state before allowing actions.
7. Prevent Parameter Pollution
Attack: ?id=42&id=100
Defense:
user_ids = request.args.getlist('id')
if len(user_ids) > 1:
abort(400, 'Multiple IDs not allowed')
Reject duplicate parameters.
8. Validate HTTP Methods
Don't assume GET is safe.
if request.method == 'DELETE':
if not current_user.is_admin:
abort(403)
Different methods = different permission checks.
9. Test Everything
Write automated tests:
def test_cannot_access_other_user():
# Login as user1
# Try to access user2's data
response = client.get(f'/api/user/{user2.id}')
assert response.status_code == 403
Test with different user accounts. If user A can access user B's data, you have IDOR.
10. Log Access Violations
Know when attacks happen:
logger.warning(
f"User {user.id} tried to access user {other_id}. "
f"IP: {request.remote_addr}"
)
Set up alerts for multiple violations in short time.
11. Rate Limit by User
@limiter.limit("100/hour")
def get_data():
pass
@limiter.limit("3/day") # Stricter for critical ops
def delete_account():
pass
Different limits for different operations.
12. Add Security Headers
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Strict-Transport-Security'] = 'max-age=31536000'
Defense in depth.
π οΈ Essential Tools
Access Control Libraries:
Flask-Principal (Python) β pypi.org/project/Flask-Principal
django-guardian (Python) β django-guardian.readthedocs.io
Pundit (Ruby) β github.com/varvet/pundit
casl (Node.js) β casl.js.org
Testing:
pytest β docs.pytest.org
Postman β postman.com
Rate Limiting:
Flask-Limiter β flask-limiter.readthedocs.io
Redis β redis.io
Monitoring:
Sentry β sentry.io
Datadog β datadoghq.com
π Learning Resources
Must-reads:
OWASP Access Control Cheat Sheet β cheatsheetseries.owasp.org
PortSwigger Access Control Guide β portswigger.net/web-security/access-control
Practice:
PortSwigger Academy Labs β portswigger.net/web-security/all-labs
OWASP WebGoat β owasp.org/www-project-webgoat
β Defense Checklist
Basic Authorization:
[ ] Server-side checks on every request
[ ] Validate ownership before access
[ ] Never trust client data
IDOR Prevention:
[ ] Use UUIDs instead of sequential IDs
[ ] Still validate ownership (obscurity β security)
[ ] Test with different user accounts
Advanced Security:
[ ] Implement RBAC with clear roles
[ ] Whitelist fields for mass assignment
[ ] Context-based access control
[ ] Lock down admin endpoints
[ ] Prevent parameter pollution
Monitoring:
[ ] Log all access violations
[ ] Alert on suspicious activity
[ ] Rate limit by user
[ ] Write automated tests
π― Key Takeaways
β Check permissions server-side β Every request, no exceptions
β Use UUIDs β But still validate ownership
β Whitelist everything β Never trust input
β Test with different users β Can A access B's data?
β Log violations β Know when attacks happen
β Defense in depth β Multiple layers of protection
Remember: Authentication = who you are. Authorization = what you can do.
Most apps nail authentication but fail at authorization. Don't be most apps.
The Bottom Line
Access control bugs are everywhere because developers forget one simple rule:
Just because someone is logged in doesn't mean they should see everything.
Check. Every. Request.
Next Tuesday: Injection Attacks β SQL & command injection exploitation
Next Wednesday: Input validation & injection defense
Stay secure π‘οΈ
Your Feedback MattersDid You Enjoy This Weekβs Defensive Tutorial? |
β Zwire βοΈ
P.S. Questions? Reply to this email. I read everything.
Reply