Bola Idor in Flask with Basic Auth
Bola Idor in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) occurs when an API fails to enforce proper ownership checks between a subject and an object, allowing one user to access or modify another user’s resources. In Flask applications that rely on HTTP Basic Authentication, this risk is heightened because the authentication boundary does not automatically enforce object-level permissions. A developer may assume that requiring a username and password is sufficient, but once authentication succeeds, the application must still ensure that User A cannot access /users/123 if they are not User 123.
Consider a Flask route that retrieves a user profile using a numeric identifier taken directly from the URL:
@app.route('/users/<int:user_id>')
def get_user(user_id):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return 'Unauthorized', 401
user = db.session.query(User).filter(User.id == user_id).first()
return jsonify(user.to_dict())
In this example, Basic Auth validates the identity of the requester, but the query User.id == user_id does not verify that the authenticated user matches the requested user_id. An authenticated attacker can simply change the URL path to access any other user’s record, provided they can guess or enumerate valid identifiers. This is a classic BOLA/IDOR flaw in the authorization layer, despite the presence of Basic Auth.
The issue is further compounded when identifiers are non-sequential or weakly random. Predictable numeric IDs make automated enumeration straightforward, enabling attackers to iterate through user accounts and harvest sensitive data such as email addresses, phone numbers, or billing information. Even with Basic Auth, the application’s failure to bind the subject to the object transforms a controlled access mechanism into a direct exposure path.
In API security testing, this pattern is flagged as a BOLA/IDOR finding because the control over who can access which resource is missing at the object level. middleBrick’s scanner would highlight this as a high-severity issue, noting that authentication does not imply authorization and that object ownership must be validated explicitly for each request.
Additional risk arises when related endpoints share the same vulnerability. For example, an account update route that uses the same pattern:
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return 'Unauthorized', 401
user = db.session.query(User).filter(User.id == user_id).first()
if not user:
return 'Not found', 404
# apply updates from request JSON
return jsonify(success=True)
Without a check such as auth.username == user.username, any authenticated user can modify any account. This demonstrates that BOLA in Flask with Basic Auth is not a theoretical edge case but a practical, exploitable weakness when object-level checks are omitted.
Basic Auth-Specific Remediation in Flask — concrete code fixes
To resolve BOLA in Flask when using Basic Authentication, you must explicitly verify that the authenticated user is allowed to access the requested resource. The simplest and most effective pattern is to bind the authenticated identity to the database query.
Assuming you store the username in the User model, revise the route to filter by both ID and username:
@app.route('/users/<int:user_id>')
def get_user(user_id):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return 'Unauthorized', 401
user = db.session.query(User).filter(User.id == user_id, User.username == auth.username).first()
if not user:
return 'Forbidden or Not found', 404
return jsonify(user.to_dict())
This ensures that even if an attacker guesses another user’s ID, the query returns no row because the username does not match, effectively enforcing object-level ownership.
For a more maintainable approach, consider extracting the user-fetching logic into a helper that centralizes authorization:
def get_current_user(auth):
if not auth:
return None
return db.session.query(User).filter(User.username == auth.username, User.password_hash == generate_hash(auth.password)).first()
@app.route('/users/<int:user_id>')
def get_user(user_id):
auth = request.authorization
user = get_current_user(auth)
if not user or user.id != user_id:
return 'Forbidden', 403
return jsonify(user.to_dict())
This pattern improves readability and reduces duplication across endpoints. The critical point is that the authenticated subject must be part of the data access condition, not merely a gateway check.
When using tokens or session-based auth in the future, the same principle applies: always include the user identifier in the query filter. For Basic Auth, ensure passwords are verified securely (e.g., using check_password_hash), and avoid leaking user existence through timing differences by using consistent error responses.
| Approach | Risk | Remediation |
|---|---|---|
| ID only query | High — BOLA/IDOR | Add username equality check |
| Username-bound query | Low — proper ownership enforcement | Filter by authenticated identity |
middleBrick’s scanner would flag the first approach as a high-severity finding and would recognize the second as a validated remediation, demonstrating how design decisions directly affect authorization safety.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |