Improper Access Control
Description
Improper Access Control occurs when software does not properly restrict access to a resource from an unauthorized actor. This is a broad category that encompasses many specific access control failures including authentication bypass, authorization bypass, and improper enforcement of access policies. Access control involves determining who can access what resources and what actions they can perform. Failures can occur in authentication (who you are), authorization (what you can do), or accounting (tracking what was done).
Risk
Access control failures are consistently among the most critical security vulnerabilities. They directly lead to unauthorized access to sensitive data, unauthorized modification of systems, and potential complete system compromise. OWASP ranks Broken Access Control as the #1 vulnerability category. Real-world impacts include data breaches exposing millions of records, financial fraud, and system takeovers. Access control bugs are particularly dangerous because they often require no exploitation skills—just knowledge of the flaw.
Solution
Implement the principle of least privilege. Centralize access control logic and avoid duplicating authorization checks. Deny by default—require explicit grants. Validate authorization on every request at the server side. Use established access control frameworks. Implement role-based access control (RBAC) or attribute-based access control (ABAC). Log access control failures for monitoring. Conduct regular access control reviews and testing.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Data Exposure Unauthorized access to sensitive data including personal information, financial data, and trade secrets. |
| Integrity | Scope: Data Modification Unauthorized ability to modify, delete, or corrupt data and system configurations. |
| Availability | Scope: Denial of Service Unauthorized access can be used to disrupt services or destroy data. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: Missing access control
@app.route('/admin/users')
def list_all_users():
# No authentication or authorization check!
return jsonify(User.query.all())
# VULNERABLE: Insecure direct object reference (IDOR)
@app.route('/api/invoice/<invoice_id>')
@login_required
def get_invoice(invoice_id):
# Any logged-in user can access any invoice
invoice = Invoice.query.get(invoice_id)
return jsonify(invoice.to_dict())
# VULNERABLE: Horizontal privilege escalation
@app.route('/api/user/<user_id>/settings', methods=['PUT'])
@login_required
def update_settings(user_id):
# User can update anyone's settings by changing user_id
user = User.query.get(user_id)
user.settings = request.json
db.session.commit()
return 'Updated'
# VULNERABLE: Vertical privilege escalation
@app.route('/api/user/<user_id>/promote', methods=['POST'])
@login_required
def promote_user(user_id):
# No admin check - any user can promote anyone!
user = User.query.get(user_id)
user.role = 'admin'
db.session.commit()
return 'Promoted'
// VULNERABLE: Path traversal bypass
@GetMapping("/files/{filename}")
public ResponseEntity<Resource> getFile(@PathVariable String filename) {
// No validation - attacker uses ../../../etc/passwd
Path filePath = Paths.get("/uploads/" + filename);
Resource resource = new FileSystemResource(filePath);
return ResponseEntity.ok(resource);
}
// VULNERABLE: Parameter manipulation
@PostMapping("/transfer")
public ResponseEntity<?> transfer(@RequestBody TransferRequest request) {
// Trusts 'fromAccount' from request - should use authenticated user's account
accountService.transfer(
request.getFromAccount(), // Attacker specifies victim's account!
request.getToAccount(),
request.getAmount()
);
return ResponseEntity.ok().build();
}
// VULNERABLE: Missing function-level access control
@DeleteMapping("/users/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
// No check if user has delete permission
userRepository.deleteById(id);
return ResponseEntity.ok().build();
}
// VULNERABLE: Client-side only access control
// Frontend hides admin menu but backend doesn't enforce
app.get('/api/admin/config', (req, res) => {
// No server-side check - anyone can access!
res.json(systemConfig);
});
// VULNERABLE: Predictable resource IDs
app.get('/api/documents/:id', async (req, res) => {
// Sequential IDs make enumeration easy
// No ownership check
const doc = await Document.findById(req.params.id);
res.json(doc);
});
// VULNERABLE: Mass assignment
app.put('/api/profile', async (req, res) => {
// User can set any field including 'role' or 'isAdmin'
await User.findByIdAndUpdate(req.userId, req.body);
res.json({ success: true });
});
Fixed Code
# SAFE: Proper access control with authentication and authorization
from functools import wraps
def admin_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated:
abort(401)
if not current_user.has_role('admin'):
abort(403)
return f(*args, **kwargs)
return decorated_function
@app.route('/admin/users')
@admin_required
def list_all_users():
return jsonify([u.to_dict() for u in User.query.all()])
# SAFE: Resource ownership verification
@app.route('/api/invoice/<invoice_id>')
@login_required
def get_invoice(invoice_id):
invoice = Invoice.query.get_or_404(invoice_id)
# Verify ownership or admin access
if invoice.user_id != current_user.id and not current_user.is_admin:
abort(403, 'Access denied')
return jsonify(invoice.to_dict())
# SAFE: Prevent horizontal privilege escalation
@app.route('/api/user/settings', methods=['PUT'])
@login_required
def update_settings():
# Always use authenticated user's ID, not from request
current_user.settings = request.json
db.session.commit()
return jsonify({'status': 'updated'})
# SAFE: Proper authorization for privilege operations
@app.route('/api/user/<user_id>/promote', methods=['POST'])
@login_required
@admin_required
def promote_user(user_id):
# Only admins can reach this (decorator)
user = User.query.get_or_404(user_id)
# Additional business logic checks
if user.role == 'admin':
abort(400, 'User is already an admin')
user.role = 'admin'
db.session.commit()
audit_log.info(f'User {user_id} promoted by {current_user.id}')
return jsonify({'status': 'promoted'})
// SAFE: Path validation to prevent traversal
@GetMapping("/files/{filename}")
public ResponseEntity<Resource> getFile(@PathVariable String filename) {
// Validate filename
if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
return ResponseEntity.badRequest().build();
}
Path basePath = Paths.get("/uploads").toAbsolutePath().normalize();
Path filePath = basePath.resolve(filename).normalize();
// Ensure resolved path is within base directory
if (!filePath.startsWith(basePath)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
Resource resource = new FileSystemResource(filePath);
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(resource);
}
// SAFE: Use authenticated user's account
@PostMapping("/transfer")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> transfer(
@RequestBody TransferRequest request,
@AuthenticationPrincipal UserDetails user) {
// Get account from authenticated user, not request
Account fromAccount = accountRepository.findByUserId(user.getId());
// Verify user owns the source account
if (fromAccount == null) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
accountService.transfer(
fromAccount.getId(),
request.getToAccount(),
request.getAmount()
);
return ResponseEntity.ok().build();
}
// SAFE: Function-level access control
@DeleteMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> deleteUser(@PathVariable Long id, Principal principal) {
// Verify not deleting self
if (id.equals(getUserId(principal))) {
return ResponseEntity.badRequest()
.body("Cannot delete your own account");
}
userRepository.deleteById(id);
auditService.log("USER_DELETED", id, principal.getName());
return ResponseEntity.ok().build();
}
// SAFE: Server-side access control
const isAdmin = (req, res, next) => {
if (!req.user || !req.user.roles.includes('admin')) {
return res.status(403).json({ error: 'Admin access required' });
}
next();
};
app.get('/api/admin/config', authenticate, isAdmin, (req, res) => {
res.json(systemConfig);
});
// SAFE: Ownership verification with UUIDs
app.get('/api/documents/:id', authenticate, async (req, res) => {
const doc = await Document.findById(req.params.id);
if (!doc) {
return res.status(404).json({ error: 'Not found' });
}
// Check ownership or sharing
if (doc.ownerId !== req.user.id &&
!doc.sharedWith.includes(req.user.id) &&
!req.user.roles.includes('admin')) {
return res.status(403).json({ error: 'Access denied' });
}
res.json(doc);
});
// SAFE: Whitelist allowed fields
app.put('/api/profile', authenticate, async (req, res) => {
// Only allow specific fields to be updated
const allowedFields = ['name', 'email', 'preferences'];
const updates = {};
for (const field of allowedFields) {
if (req.body[field] !== undefined) {
updates[field] = req.body[field];
}
}
await User.findByIdAndUpdate(req.user.id, updates);
res.json({ success: true });
});
Exploited in the Wild
Facebook Privacy Bug (2018)
A privacy bug allowed apps to access photos that users uploaded but never shared, affecting 6.8 million users. The access control flaw exposed private photos to third-party applications.
Uber "God View" (2014)
Uber employees had access to a "God View" tool that tracked customer locations in real-time with insufficient access controls, leading to privacy violations and regulatory action.
First American Financial (2019)
885 million customer records were exposed due to an IDOR vulnerability where documents could be accessed by incrementing document IDs in URLs.
Tools to test/exploit
-
Burp Suite — comprehensive access control testing.
-
OWASP ZAP — automated security testing.
-
Autorize — Burp extension for access control testing.
-
Postman — API testing for access control verification.
CVE Examples
-
CVE-2021-22893 — Pulse Secure access control bypass.
-
CVE-2020-3452 — Cisco ASA path traversal.
-
CVE-2019-19781 — Citrix ADC access control bypass.
References
-
MITRE. "CWE-284: Improper Access Control." https://cwe.mitre.org/data/definitions/284.html
-
OWASP. "A01:2021 – Broken Access Control." https://owasp.org/Top10/A01_2021-Broken_Access_Control/