Excessive Data Exposure in Flask with Bearer Tokens
Excessive Data Exposure in Flask with Bearer Tokens
Excessive Data Exposure occurs when an API returns more information than necessary in a response, such as sensitive fields, internal identifiers, or debugging data. In Flask applications that rely on Bearer Tokens for authentication, this risk is amplified when endpoints return full database records, stack traces, or verbose error messages without filtering. For example, a user profile endpoint might return the complete user row from a database, including password hashes, internal IDs, or email addresses, even when the client only needs the display name and user ID. Because Bearer Tokens are often stored in JavaScript code, browser storage, or mobile apps, any additional data returned increases the attack surface if the token is compromised.
When Bearer Tokens are used, the API typically trusts the token and returns data based on the associated identity without additional authorization checks at the field level. If the endpoint lacks field-level filtering, an attacker who obtains a valid Bearer Token can harvest sensitive information incrementally through seemingly normal requests. Consider a Flask route that queries a SQLAlchemy model and returns the entire serialized object:
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120))
password_hash = db.Column(db.String(128))
role = db.Column(db.String(20))
@app.route('/api/profile')
def get_profile():
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
return jsonify({'error': 'Unauthorized'}), 401
token = auth.split(' ')[1]
# In a real app, token would be validated and user looked up
user = User.query.get(1) # Simplified for example
return jsonify({
'id': user.id,
'username': user.username,
'email': user.email,
'password_hash': user.password_hash,
'role': user.role
})
This pattern exposes sensitive fields such as password_hash and role with every profile request. Even if the Bearer Token is transmitted over TLS, the response data itself becomes a leakage channel if the token is inadvertently logged, exposed in client-side code, or captured in browser history. Additionally, overly verbose error messages in Flask can reveal stack traces or configuration details, further aiding an attacker. The combination of weak field-level authorization and verbose responses means that a single compromised Bearer Token can lead to significant data exposure beyond what the token owner intended.
Another common scenario involves list endpoints that return collections with embedded sensitive attributes. If a Flask route returns a list of resources without stripping unnecessary fields, an attacker can enumerate records and harvest data across multiple requests. This is particularly dangerous when the API uses predictable identifiers and does not enforce strict access controls on each item. The risk is not limited to authentication tokens; if logs or monitoring tools capture full responses, sensitive data may be persisted unintentionally.
Bearer Tokens-Specific Remediation in Flask
Remediation focuses on minimizing the data returned in responses and ensuring that Bearer Token validation does not implicitly grant broad data access. The first step is to implement explicit serialization that excludes sensitive fields. Instead of serializing entire database models, define a projection that includes only the fields required by the client. Below is a secure example using a dictionary-based response in Flask:
from flask import Flask, jsonify, request, abort
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120))
password_hash = db.Column(db.String(128))
role = db.Column(db.String(20))
def serialize_user_public(user):
return {
'id': user.id,
'username': user.username,
'email': user.email
}
@app.route('/api/profile')
def get_profile():
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
abort(401, description='Missing or invalid token')
token = auth.split(' ')[1]
# Validate token and derive user_id securely
user_id = validate_token_get_user_id(token) # Placeholder for real logic
if user_id is None:
abort(401, description='Invalid token')
user = User.query.get(user_id)
if user is None:
abort(404, description='User not found')
return jsonify(serialize_user_public(user))
def validate_token_get_user_id(token):
# In practice, decode and verify the token, then fetch the user ID
# This is a simplified placeholder
return 1
In this example, the serialize_user_public function explicitly omits password_hash and role, reducing the data exposure per request. For endpoints that return multiple resources, apply the same filtering pattern to each item in the collection. Additionally, ensure that error messages do not include sensitive context. Flask’s abort function can be used with custom messages that avoid revealing internal paths or database details:
@app.errorhandler(400)
def bad_request(e):
return jsonify(error='Bad request'), 400
@app.errorhandler(404)
def page_not_found(e):
return jsonify(error='Not found'), 404
@app.errorhandler(500)
def internal_error(e):
return jsonify(error='Internal server error'), 500
Another important practice is to enforce scope-based access within the token validation logic. If a Bearer Token includes scopes or roles, the server should still apply field-level filtering based on the requested resource type. This ensures that even if a token has broader privileges than necessary, the response data remains limited to what the client is allowed to see. Combining token validation with explicit serialization and strict error handling significantly reduces the risk of Excessive Data Exposure in Flask applications using Bearer Tokens.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |