cybersecurity
pentesting
web security
red team
owasp top 10
In September 2018, an estimated 50 million Facebook accounts were exposed to a vulnerability. Any web developer with some HTML/CSS knowledge could generate login tokens for those users just by clicking on a button. This incident highlights the severe consequences of Broken Access Control, a critical web security vulnerability that continues to plague many applications today.
Broken access control is when users can access more data than they should or perform actions beyond what they are authorized to, due to insufficient enforcement of permissions or security rules.
This can lead to unauthorized access to functionality or data, potentially compromising the entire system. Additionally, it can result in:
You don't have a broken access control (BAC) if a user finds and exploits a vulnerability in the login mechanism to bypass authentication entirely and gain access to admin functions.
BAC happens when access controls (permissions) are improperly implemented or enforced by the system itself, this is differnt than having users that are intentionally manipulating the system through malicious techniques to access features or data that would otherwise be securely protected.
BAC occurs due to system misconfiguration or design flaws, allowing unintended access with minimal or no manipulation.
Hacking involves exploiting vulnerabilities or security flaws through intentional, often sophisticated, techniques to break into protected areas of the system.
Broken Access Control vulnerabilities typically arise when:
In the Facebook case, the vulnerability allowed attackers to steal access tokens, which are digital keys that keep users logged in to Facebook. This gave the attackers full control over the compromised accounts.
Insecure Direct Object References (IDOR) is a type of access control vulnerability that occurs when an application uses user-supplied input to access objects directly. This can lead to unauthorized access to data or functionality if proper authorization checks are not in place.
Consider an e-commerce website where users can view their order details:
A legitimate user, Alice, logs in and accesses her order at:
https://example.com/orders?id=1234
Alice notices the "id" parameter in the URL and wonders what happens if she changes it:
https://example.com/orders?id=1235
To Alice's surprise, she can now view Bob's order details, including his personal information and purchase history.
Alice realizes she can iterate through different IDs to access any user's order information.
This IDOR vulnerability exists because the application doesn't verify that the logged-in user has the right to access the requested order ID. It assumes that if a user is logged in and provides an order ID, they must be authorized to view it.
To prevent this, the application should implement proper access controls, such as:
By addressing IDOR vulnerabilities, developers can significantly improve the security of their applications and protect sensitive user data from unauthorized access.
Vertical Privilege Escalation is a type of access control vulnerability where an attacker gains access to resources or functionality that should be restricted to users with higher privileges or different roles.
Consider a content management system (CMS) for a news website:
A regular user, Charlie, logs in to his account to read articles.
Charlie notices that the URL for editing an article looks like this:
https://news-cms.com/edit-article?id=5678
Although Charlie doesn't see an "Edit" button in his interface, he tries accessing this URL directly.
To Charlie's surprise, he can now edit any article on the website, a function intended only for editors and administrators.
Charlie realizes he can perform other administrative actions by guessing or inferring the URLs for those functions.
This Vertical Privilege Escalation vulnerability exists because the application doesn't properly verify the user's role before allowing access to administrative functions. It only checks if a user is logged in, not whether they have the necessary privileges.
To prevent this, the application should implement proper access controls, such as:
By addressing Vertical Privilege Escalation vulnerabilities, developers can ensure that users can only access resources and perform actions appropriate to their assigned roles, maintaining the integrity and security of the system.
Horizontal Privilege Escalation is a type of access control vulnerability where an attacker gains unauthorized access to resources or data belonging to other users with the same privilege level. While closely related to Insecure Direct Object References (IDOR), it has some distinct characteristics.
While Horizontal Privilege Escalation often involves IDOR, it's important to note the distinctions:
Scope: IDOR is a specific vulnerability where an internal object reference is exposed to users who can manipulate it. Horizontal Privilege Escalation is a broader concept that can involve IDOR but may also use other techniques.
Method: IDOR typically involves manipulating identifiers in requests. Horizontal Privilege Escalation might also involve session hijacking, cookie manipulation, or exploiting flaws in access control logic.
Intent: IDOR is about accessing resources by manipulating references. Horizontal Privilege Escalation specifically aims to access other users' data or functionality at the same privilege level.
Consider an e-commerce platform:
Alice logs into her account and views her profile at:
https://shop.example.com/profile?token=a1b2c3d4
Alice notices the token
parameter in the URL and wonders if she can view other users' profiles.
She intercepts her requests using a proxy tool and notices that the server also accepts a user_id
parameter:
https://shop.example.com/profile?token=a1b2c3d4&user_id=5678
When Alice adds this parameter with a different user ID, she can view other users' profiles, including their personal information and purchase history.
This Horizontal Privilege Escalation vulnerability exists because the application doesn't properly verify that the logged-in user should only access their own profile information. While it involves an element of IDOR (the user_id parameter), the vulnerability is more complex than simple reference manipulation.
To prevent this Horizontal Privilege Escalation, the application should implement proper access controls, such as:
By addressing these vulnerabilities, developers can ensure that users can only access their own data and perform actions on their own accounts, maintaining the privacy and security of all users in the system.
Missing Function Level Access Control is another critical vulnerability within the broader category of Broken Access Control. This vulnerability occurs when an application fails to enforce proper access controls on functions or API endpoints, allowing unauthorized users to access privileged functionality.
While both are types of access control vulnerabilities, they differ in key aspects:
Target: Horizontal Privilege Escalation targets data or actions of other users at the same privilege level. Missing Function Level Access Control targets functions or operations that should be restricted to higher privilege levels.
Method: Horizontal Privilege Escalation often involves manipulating identifiers or parameters. Missing Function Level Access Control typically involves directly accessing unprotected endpoints or functions.
Impact: Horizontal Privilege Escalation results in unauthorized access to peer data. Missing Function Level Access Control can lead to unauthorized execution of privileged operations, potentially affecting the entire system.
Consider an online banking application:
The application has an admin function to transfer funds between any accounts:
POST /api/admin/transfer
{
"fromAccount": "1234567890",
"toAccount": "0987654321",
"amount": 1000
}
This function is intended only for admin use and should not be accessible to regular users.
However, the developers forgot to implement access control checks on this endpoint.
A malicious user, Bob, discovers this endpoint through API documentation or by analyzing the application's JavaScript code.
Despite being a regular user, Bob can now make POST requests to this endpoint and transfer funds between any accounts in the system.
This Missing Function Level Access Control vulnerability exists because the application doesn't verify that the user has admin privileges before allowing access to the admin transfer function.
To prevent this vulnerability, the application should:
By addressing these vulnerabilities, developers can ensure that sensitive functions are only accessible to authorized users, maintaining the integrity and security of the system.
Broken Access Control has consistently ranked high in the OWASP Top 10, a list of the most critical web application security risks. In the 2021 edition, it claimed the top spot. This prominence is due to several factors:
The Facebook vulnerability mentioned earlier was a critical example of Broken Access Control, specifically a form of Vertical Privilege Escalation. This vulnerability was discovered through manual testing during an internal security review led by Guy Rosen, Facebook's vice president of product management, and his team.
The vulnerability allowed attackers to obtain user access tokens, effectively granting them full control over the affected accounts. This case highlights the importance of regular manual security testing, as automated tools might have missed this complex, logic-based vulnerability.
To mitigate Broken Access Control vulnerabilities, developers and security teams should:
Implement a robust access control model (RBAC or ABAC)
A robust access control model typically involves implementing Role-Based Access Control (RBAC) or Attribute-Based Access Control (ABAC). Here are clear examples of how to implement and enforce these in your API and front-end:
a. Role-Based Access Control (RBAC):
API Example (Flask):
1from flask import Flask, request, jsonify 2from functools import wraps 3 4app = Flask(__name__) 5 6roles = { 7 'user': ['read'], 8 'editor': ['read', 'write'], 9 'admin': ['read', 'write', 'delete'] 10} 11 12def require_permission(permission): 13 def decorator(f): 14 @wraps(f) 15 def decorated_function(*args, **kwargs): 16 user_role = request.headers.get('X-User-Role') 17 if user_role not in roles or permission not in roles[user_role]: 18 return jsonify({"error": "Permission denied"}), 403 19 return f(*args, **kwargs) 20 return decorated_function 21 return decorator 22 23@app.route('/posts', methods=['GET']) 24@require_permission('read') 25def get_posts(): 26 return jsonify({"message": "Posts retrieved"}) 27 28@app.route('/posts', methods=['POST']) 29@require_permission('write') 30def create_post(): 31 return jsonify({"message": "Post created"}) 32 33@app.route('/posts/<int:post_id>', methods=['DELETE']) 34@require_permission('delete') 35def delete_post(post_id): 36 return jsonify({"message": f"Post {post_id} deleted"}) 37 38if __name__ == '__main__': 39 app.run(debug=True)
1const express = require('express'); 2const app = express(); 3 4const roles = { 5 user: ['read'], 6 editor: ['read', 'write'], 7 admin: ['read', 'write', 'delete'] 8}; 9 10function requirePermission(permission) { 11 return (req, res, next) => { 12 const userRole = req.headers['x-user-role']; 13 if (!roles[userRole] || !roles[userRole].includes(permission)) { 14 return res.status(403).json({ error: 'Permission denied' }); 15 } 16 next(); 17 }; 18} 19 20app.get('/posts', requirePermission('read'), (req, res) => { 21 res.json({ message: 'Posts retrieved' }); 22}); 23 24app.post('/posts', requirePermission('write'), (req, res) => { 25 res.json({ message: 'Post created' }); 26}); 27 28app.delete('/posts/:id', requirePermission('delete'), (req, res) => { 29 res.json({ message: `Post ${req.params.id} deleted` }); 30}); 31 32app.listen(3000, () => console.log('Server running on port 3000'));
Front-end Example (React):
1import React from 'react'; 2import { useUser } from './userContext'; // Assume this hook provides user info 3 4const roles = { 5 user: ['read'], 6 editor: ['read', 'write'], 7 admin: ['read', 'write', 'delete'] 8}; 9 10function Button({ requiredPermission, children, onClick }) { 11 const { role } = useUser(); 12 const hasPermission = roles[role]?.includes(requiredPermission); 13 14 if (!hasPermission) return null; 15 16 return <button onClick={onClick}>{children}</button>; 17} 18 19function Dashboard() { 20 return ( 21 <div> 22 <Button requiredPermission="read" onClick={() => console.log('Viewing posts')}> 23 View Posts 24 </Button> 25 <Button requiredPermission="write" onClick={() => console.log('Creating post')}> 26 Create Post 27 </Button> 28 <Button requiredPermission="delete" onClick={() => console.log('Deleting post')}> 29 Delete Post 30 </Button> 31 </div> 32 ); 33} 34 35export default Dashboard;
In these examples, the API enforces access control by checking the user's role and permissions before allowing access to specific endpoints. The front-end uses the same role-based logic to conditionally render UI elements based on the user's permissions.
Remember, while front-end access control improves user experience, it should never be relied upon for security. Always enforce access control on the server-side to ensure proper security.
Enforce access control checks consistently across the application
Example (Python with Flask):
1from functools import wraps 2from flask import abort, session 3 4def require_role(role): 5 def decorator(f): 6 @wraps(f) 7 def decorated_function(*args, **kwargs): 8 if not session.get('user_role') == role: 9 abort(403) 10 return f(*args, **kwargs) 11 return decorated_function 12 return decorator 13 14@app.route('/admin') 15@require_role('admin') 16def admin_page(): 17 return "Welcome to the admin page"
1const requireRole = (role) => { 2 return (req, res, next) => { 3 if (req.user && req.user.role === role) { 4 next(); 5 } else { 6 res.status(403).json({ error: 'Access denied' }); 7 } 8 }; 9}; 10 11app.get('/admin', requireRole('admin'), (req, res) => { 12 res.json({ message: 'Welcome to the admin page' }); 13});
Deny access by default, unless specifically allowed
Example (Python):
1ALLOWED_ROLES = { 2 'view_reports': ['analyst', 'manager', 'admin'], 3 'edit_users': ['admin'], 4 'delete_data': ['admin'] 5} 6 7def check_permission(user, action): 8 if user.role in ALLOWED_ROLES.get(action, []): 9 return True 10 return False 11 12if check_permission(current_user, 'view_reports'): 13 # Allow access to reports 14else: 15 # Deny access
1const ALLOWED_ROLES = { 2 'view_reports': ['analyst', 'manager', 'admin'], 3 'edit_users': ['admin'], 4 'delete_data': ['admin'] 5}; 6 7function checkPermission(user, action) { 8 return ALLOWED_ROLES[action] && ALLOWED_ROLES[action].includes(user.role); 9} 10 11if (checkPermission(req.user, 'view_reports')) { 12 // Allow access to reports 13} else { 14 // Deny access 15}
Regularly audit and test access control mechanisms
Example (Python):
1import logging 2 3logging.basicConfig(filename='access_log.txt', level=logging.INFO) 4 5def log_access(user, resource, action): 6 logging.info(f"User {user.id} attempted to {action} {resource}") 7 8@app.route('/sensitive_data/<int:data_id>', methods=['GET']) 9@require_role('admin') 10def access_sensitive_data(data_id): 11 log_access(current_user, f'sensitive_data/{data_id}', 'view') 12 # ... rest of the function
1const winston = require('winston'); 2 3const logger = winston.createLogger({ 4 level: 'info', 5 format: winston.format.simple(), 6 transports: [ 7 new winston.transports.File({ filename: 'access_log.txt' }) 8 ] 9}); 10 11function logAccess(user, resource, action) { 12 logger.info(`User ${user.id} attempted to ${action} ${resource}`); 13} 14 15app.get('/sensitive_data/:dataId', requireRole('admin'), (req, res) => { 16 logAccess(req.user, `sensitive_data/${req.params.dataId}`, 'view'); 17 // ... rest of the function 18});
These examples demonstrate how to implement consistent access control checks, deny access by default, and log access attempts for auditing purposes in both Python (using Flask) and Node.js (using Express) environments.
Here's a table listing some popular free and paid tools for detecting Broken Access Control (BAC) vulnerabilities:
Tool Name | Type | Description |
---|---|---|
OWASP ZAP | Free | Open-source web application security scanner that can detect various vulnerabilities, including BAC issues. |
Burp Suite Community Edition | Free | Limited version offering useful features for manual testing of access control. |
Acunetix Online Free | Free | Limited free version of Acunetix web vulnerability scanner, can detect some access control issues. |
w3af | Free | Open-source web application attack and audit framework with plugins for detecting access control problems. |
Skipfish | Free | Active web application security reconnaissance tool that can identify some access control flaws. |
Burp Suite Professional | Paid | Comprehensive web application security testing platform with advanced features for BAC detection and exploitation. |
Acunetix Premium | Paid | Full-featured web vulnerability scanner with robust capabilities for identifying access control issues. |
Netsparker | Paid | Automated web application security scanner that can detect various vulnerabilities, including BAC. |
AppScan | Paid | IBM's application security testing suite that includes features for identifying access control problems. |
Fortify WebInspect | Paid | HP's web application security assessment tool that can detect various security issues, including BAC vulnerabilities. |
Note: While these tools can be helpful in identifying potential vulnerabilities, they should be used in conjunction with manual testing and code review for comprehensive security assessment. Always ensure you have proper authorization before testing any systems or applications you don't own.
Manual testing for Broken Access Control is crucial due to its ability to uncover complex vulnerabilities that automated tools often miss. Here's an elaboration on why manual testing is essential, with detailed examples of situations where only manual testing is effective:
Complex Logic Analysis: Manual testing allows for understanding intricate business logic and identifying subtle flaws.
Detailed Example: In a corporate expense reporting system, there's a multi-step approval process:
A manual tester discovers that by changing the URL from:
https://example.com/expense/approve/1234?status=submitted
to:
https://example.com/expense/approve/1234?status=cfo_approved
They can bypass all intermediate steps and grant CFO approval to any expense report, regardless of amount or their role in the company.
Context-Aware Testing: Human testers can understand the application's context and test accordingly.
Detailed Example: In a healthcare application, patient records are accessible via:
https://hospital.com/patient/view/12345
The application should allow access only if: a) The user is a doctor (role = "doctor") b) The doctor is currently treating the patient (active_patients includes 12345)
A manual tester, understanding the healthcare context, might try:
They might find that changing their session cookie from:
role=nurse; active_patients=[]
to:
role=doctor; active_patients=[12345]
Grants unauthorized access, bypassing server-side checks.
Creative Exploitation: Manual testers can think outside the box to find unique vulnerabilities.
Detailed Example: In an e-commerce platform, users add items to their cart, which is associated with their session:
https://shop.com/cart?session_id=abcd1234
A creative manual tester might try:
They might even find that they can complete the purchase using the other user's stored payment method, a serious flaw that automated tools would likely miss.
Chained Vulnerability Exploitation: Manual testing can identify vulnerabilities that require multiple steps or conditions.
Detailed Example: In a content management system:
https://cms.com/profile?name=<script>alert('XSS')</script>
https://cms.com/admin/users
A manual tester might discover:
This chain of exploits, combining XSS and broken access control, would be extremely difficult for automated tools to discover.
Time-Based Access Control Testing: Manual testing is crucial for identifying time-sensitive access control issues.
Detailed Example: In a banking application, certain reports are only accessible during business hours (9 AM to 5 PM):
https://bank.com/reports/daily-transactions
A manual tester might:
Date: Tue, 15 Nov 2023 14:00:00 GMT
This time-based access control flaw would likely go unnoticed by automated scanning tools.
These detailed examples highlight scenarios where the human element in testing is irreplaceable, showcasing the critical role of manual testing in identifying and mitigating Broken Access Control vulnerabilities.
Role-based testing: Logging in as different user roles and attempting to access resources or perform actions beyond the intended permissions.
Manipulating parameters: Modifying URL parameters, form fields, or API requests to try accessing unauthorized resources.
Session management testing: Attempting to reuse old session tokens or manipulate session-related data to bypass access controls.
Direct object reference testing: Trying to access resources by guessing or incrementing object identifiers.
API endpoint testing: Systematically testing API endpoints with different user roles and manipulated requests.
Business logic testing: Identifying and exploiting flaws in the application's business logic that may lead to unauthorized access.
Manual testing allows for a more nuanced and context-aware approach, often uncovering vulnerabilities that automated tools might miss. It requires a deep understanding of the application's functionality and creative thinking to identify potential security gaps.
Remember to document all findings and always obtain proper authorization before conducting any security testing on systems or applications you don't own.
The Facebook incident serves as a stark reminder of the importance of proper access control in web applications. As attackers become more sophisticated, it's crucial for developers and security professionals to understand, identify, and mitigate Broken Access Control vulnerabilities. By doing so, we can build more secure applications and protect user data from unauthorized access.