• 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:

  1. Horizontal β€” User A can't access User B's data

  2. Vertical β€” Regular users can't access admin functions

  3. 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:

Testing:

Rate Limiting:

Monitoring:

πŸ“š Learning Resources

Must-reads:

Practice:

βœ… 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 Matters

Did You Enjoy This Week’s Defensive Tutorial?

Login or Subscribe to participate in polls.

β€” Zwire ✌️

P.S. Questions? Reply to this email. I read everything.

Reply

or to participate.