Api Key Exposure in Flask with Saml
Api Key Exposure in Flask with Saml — how this specific combination creates or exposes the vulnerability
When a Flask application handles API keys and integrates SAML-based authentication, misconfigurations can expose secrets through logs, error messages, or session handling. API keys are often stored in environment variables or configuration files and injected into requests as bearer tokens or custom headers. SAML in Flask is typically implemented using libraries such as python3-saml, where the service provider (SP) configuration defines endpoints, certificates, and bindings. If the SP configuration is incomplete or debug output is enabled, the framework may inadvertently include sensitive headers or keys in SAML requests or responses.
For example, consider a Flask route that calls an external service using an API key stored in app.config["API_KEY"]. If the route builds a SAML AuthNRequest without sanitizing headers, the API key might be reflected in logs when the request is printed or when an error occurs during signing or serialization. Additionally, Flask’s default development server provides verbose tracebacks that can reveal the full environment, including configuration variables. A compromised SAML endpoint or an improperly secured metadata endpoint could also expose the SP’s entity ID or certificate, indirectly weakening the boundary between authentication and resource access where API keys are used.
Real-world attack patterns include log injection via crafted SAML responses that trigger exceptions, leading to stack traces that contain API key values. OWASP API Security Top 10 category API1:2023 Broken Object Level Authorization can intersect here if access control checks are bypassed due to weak session binding between SAML assertions and API key usage. PCI-DSS and SOC2 controls emphasize protection of credentials; storing or transmitting API keys alongside SAML metadata without encryption or strict scoping increases compliance risk. The interaction between Flask’s routing, session management, and SAML message construction must ensure that sensitive values are never serialized into logs, query strings, or assertion attributes.
Saml-Specific Remediation in Flask — concrete code fixes
Remediation centers on strict separation between authentication context and API key usage, secure configuration of the SAML library, and defensive coding in Flask. Use environment-based secrets with runtime validation, disable debug mode in production, and ensure that SAML settings do not leak into logs or error pages. The following example shows a hardened Flask configuration using python3-saml.
from flask import Flask, request, jsonify
from onelogin.saml2.auth import OneLogin_Saml2_Auth
import os
import logging
app = Flask(__name__)
# Load secrets securely; do not commit to source control
SAML_SETTINGS = {
'strict': True,
'debug': False,
'sp': {
'entityId': 'https://sp.example.com/metadata',
'assertionConsumerService': {
'url': 'https://sp.example.com/acs',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
},
'singleLogoutService': {
'url': 'https://sp.example.com/sls',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
},
'x509cert': '',
'privateKey': ''
},
'idp': {
'entityId': 'https://idp.example.com/metadata',
'singleSignOnService': {
'url': 'https://idp.example.com/sso',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
},
'x509cert': 'IDP_CERTIFICATE_HERE'
},
'security': {
'nameuid': False,
'nameid_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
'authn_requests_signed': False,
'logout_request_signed': False,
'logout_response_signed': False,
'sign_metadata': False,
'want_messages_signed': True,
'want_assertions_signed': True,
'want_assertions_encrypted': True,
'want_nameid': True,
'want_nameid_encrypted': False
}
}
def init_saml_auth(req):
return OneLogin_Saml2_Auth(req, custom_base_path='/tmp/saml/')
@app.route('/login')
def login():
req = prepare_flask_request(request)
auth = init_saml_auth(req)
auth.login()
return '', 200
@app.route('/acs', methods=['POST'])
def acs():
req = prepare_flask_request(request)
auth = init_saml_auth(req)
auth.process_response()
errors = auth.get_errors()
if len(errors) == 0:
attributes = auth.get_attributes()
# Use API key in a controlled context, e.g., call downstream service
api_key = os.getenv('EXTERNAL_API_KEY')
if not api_key:
app.logger.error('API key not configured')
return jsonify({'error': 'configuration'}), 500
# Example: call external service with key in header, isolated from SAML flow
# response = external_service.get('/resource', headers={'Authorization': f'Bearer {api_key}'})
return jsonify({'attributes': attributes})
else:
app.logger.warning('SAML processing errors: %s', errors)
return jsonify({'error': 'invalid_saml'}), 400
def prepare_flask_request(request):
url_data = {
'https': 'on' if request.scheme == 'https' else 'off',
'http_host': request.host,
'script_name': request.path,
'get_data': request.args.copy(),
'post_data': request.form.copy()
}
return url_data
if __name__ == '__main__':
app.run(debug=False)
Key remediation practices:
- Set
debug: Falsein SAML settings and Flask’sapp.run(debug=False)to prevent verbose error output that may leak API keys or configuration. - Never log raw SAML requests, responses, or headers that could contain or reflect API keys; use structured logging with redaction.
- Store API keys in environment variables or a secrets manager, and access them only at the point of use, keeping them out of SAML session objects or assertion attributes.
- Enforce strict signature and encryption expectations in SAML settings (
want_assertions_signed,want_assertions_encrypted) to prevent tampering that could lead to privilege escalation or token substitution. - Apply defense in depth: use short-lived SAML assertions, validate audience and issuer, and ensure Flask routes that handle API keys also enforce proper access controls independent of SAML session state.