Server Side Template Injection with Basic Auth
How Server Side Template Injection Manifests in Basic Auth
Server Side Template Injection (SSTI) in Basic Auth contexts typically occurs when authentication credentials or related metadata are dynamically rendered in templates without proper sanitization. This vulnerability is particularly dangerous in Basic Auth implementations because the username and password fields are often used as dynamic parameters in template rendering engines.
Consider a common pattern where authentication failure messages include the attempted username. A vulnerable implementation might look like this in Python/Flask:
from flask import Flask, request, render_template_string
app = Flask(__name__)
def check_auth(username, password):
# Simulated user database
valid_users = {'admin': 'password123', 'user': 'pass456'}
return valid_users.get(username) == password
@app.route('/basic-auth', methods=['GET', 'POST'])
def basic_auth():
if request.method == 'POST':
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
# VULNERABLE: Direct template injection
return render_template_string(
"Authentication failed for {{ username }}",
username=auth.username
)
return "Authenticated successfully"
return render_template_string("<form method='post'>...</form>")
The critical vulnerability here is that the username parameter flows directly into the template context without validation. If an attacker submits a username containing template expressions like {{ 7 * 7 }}, the template engine will evaluate it, returning 49 instead of the literal string.
Basic Auth's stateless nature makes this particularly insidious. Since credentials are sent with every request, attackers can continuously probe for template injection vulnerabilities without establishing a persistent session. The base64-encoded credentials in the Authorization header can contain any characters, making injection attempts straightforward.
Real-world scenarios include:
- Logging systems that display attempted usernames in web interfaces
- Authentication middleware that renders error pages with credential details
- API documentation tools that show example Basic Auth headers with user-supplied values
- Configuration interfaces that display authentication attempts in admin panels
The impact extends beyond simple information disclosure. Successful SSTI can lead to remote code execution, data exfiltration, and complete system compromise, especially when the template engine has access to sensitive application data or system resources.
Basic Auth-Specific Detection
Detecting SSTI in Basic Auth implementations requires a combination of static analysis and dynamic testing. The most effective approach is to use automated scanning tools that understand Basic Auth's unique characteristics.
Static detection focuses on identifying template rendering patterns where authentication data flows into templates:
import re
def detect_ssti_vulnerabilities(code):
patterns = [
# Pattern 1: Direct template rendering with auth data
r'render_template.*auth\.username',
r'render_template.*auth\.password',
r'render_template_string.*auth\.username',
# Pattern 2: Template context with auth variables
r'context.*=.*{[^}]*username[^}]*}',
r'context.*=.*{[^}]*password[^}]*}',
# Pattern 3: String formatting with auth data
r'f"[^"]*\{auth\.username[^}]*\}'
]
matches = []
for pattern in patterns:
matches.extend(re.findall(pattern, code, re.DOTALL))
return matches
# Example usage
code = '''
if not auth or not check_auth(auth.username, auth.password):
return render_template_string(
"Authentication failed for {{ username }}",
username=auth.username
)
'''
vulnerabilities = detect_ssti_vulnerabilities(code)
print(f"Found {len(vulnerabilities)} potential SSTI patterns")
Dynamic testing involves submitting crafted payloads through the Basic Auth mechanism:
import base64
import requests
from urllib.parse import quote
def test_ssti(url, username_template, password='test'):
# Create payload with template expression
payload = username_template
# Base64 encode credentials
credentials = f"{payload}:{password}".encode()
encoded = base64.b64encode(credentials).decode()
# Make request with crafted Authorization header
headers = {'Authorization': f'Basic {encoded}'}
response = requests.get(url, headers=headers)
# Check for template engine artifacts
indicators = [
'Traceback', 'Exception', 'Error',
'Jinja', 'Twig', 'Mustache', 'Handlebars'
]
for indicator in indicators:
if indicator.lower() in response.text.lower():
return {
'status': 'vulnerable',
'indicator': indicator,
'response_snippet': response.text[:500]
}
return {'status': 'not_vulnerable'}
# Test with common SSTI payloads
payloads = [
'{{7*7}}', # Simple arithmetic
'{{config}}', # Configuration access
'{{[].__class__}}', # Class access
'{{request}}', # Request object access
]
for payload in payloads:
result = test_ssti('https://example.com/api/auth', payload)
print(f"Payload: {payload} - {result['status']}")
middleBrick's scanning approach specifically targets Basic Auth endpoints by:
- Automatically detecting Basic Auth endpoints from OpenAPI specs or crawling
- Testing 27 template injection patterns across common engines (Jinja2, Twig, Mustache, Handlebars)
- Analyzing response content for template engine artifacts and error messages
- Checking for timing differences that indicate template evaluation
- Providing severity ratings based on the template engine's capabilities and the data exposed
The scanner's black-box approach means it tests the actual running service without requiring source code access, making it ideal for production environments where you need to verify security without disrupting operations.
Basic Auth-Specific Remediation
Remediating SSTI in Basic Auth contexts requires both immediate fixes and architectural changes to prevent credential data from reaching template contexts. Here are specific remediation strategies:
1. Input Validation and Sanitization
The most direct approach is to sanitize authentication credentials before they enter any template context:
import re
from flask import Flask, request, render_template_string
def sanitize_template_input(value):
# Remove template expressions and dangerous characters
dangerous_patterns = [
r'{{.*?}}', r'{%.*?%}', r'{{\.}}', r'{%\. %}',
r'__.*__', r'\.\.\.', r'\$', r'\{', r'\}',
r'\[', r'\]', r'\(', r'\)', r'\.', r'\@'
]
sanitized = value
for pattern in dangerous_patterns:
sanitized = re.sub(pattern, '', sanitized)
return sanitized
@app.route('/basic-auth', methods=['GET', 'POST'])
def basic_auth():
if request.method == 'POST':
auth = request.authorization
if not auth:
return "Missing credentials", 401
# Sanitize inputs before template rendering
safe_username = sanitize_template_input(auth.username)
safe_password = sanitize_template_input(auth.password)
if not check_auth(safe_username, safe_password):
# Safe template rendering
return render_template_string(
"Authentication failed for {{ username }}",
username=safe_username
)
return "Authenticated successfully"
2. Avoid Template Rendering for Authentication Errors
The most secure approach is to eliminate template rendering entirely for authentication flows:
from flask import Flask, request, jsonify
@app.route('/basic-auth', methods=['GET', 'POST'])
def basic_auth():
if request.method == 'POST':
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
# Return simple JSON response - no template rendering
return jsonify({
'error': 'Authentication failed',
'message': 'Invalid credentials provided'
}), 401
return jsonify({'message': 'Authenticated successfully'}), 200
3. Use Safe Template Engines and Context Isolation
When template rendering is necessary, use engines with sandboxing and strict context isolation:
from jinja2 import Environment, StrictUndefined
from flask import Flask, request
# Create a sandboxed Jinja2 environment
env = Environment(
loader=None,
undefined=StrictUndefined,
autoescape=True,
optimized=False
)
@app.route('/basic-auth', methods=['GET', 'POST'])
def basic_auth():
if request.method == 'POST':
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
# Use safe template rendering
template = env.from_string(
"Authentication failed for {{ username }}"
)
return template.render(username=auth.username)
return "Authenticated successfully"
4. Implement Comprehensive Logging Without Template Exposure
Log authentication attempts securely without exposing them to template contexts:
import logging
from datetime import datetime
logging.basicConfig(
filename='auth_attempts.log',
level=logging.INFO,
format='%(asctime)s - %(message)s'
)
def log_auth_attempt(username, success, ip_address):
# Log without template exposure
log_message = f"Auth attempt from {ip_address}: "
log_message += f"{'SUCCESS' if success else 'FAILURE'} - User: {username}"
logging.info(log_message)
# Also log to monitoring system
# (implementation depends on your monitoring setup)
@app.route('/basic-auth', methods=['GET', 'POST'])
def basic_auth():
if request.method == 'POST':
auth = request.authorization
client_ip = request.remote_addr
if not auth or not check_auth(auth.username, auth.password):
log_auth_attempt(auth.username, False, client_ip)
return "Authentication failed", 401
log_auth_attempt(auth.username, True, client_ip)
return "Authenticated successfully"
5. Use middleBrick for Continuous Security Validation
middleBrick's automated scanning can continuously validate that your Basic Auth endpoints remain secure:
The CLI integrates with CI/CD pipelines to fail builds if new vulnerabilities are detected:
These remediation strategies, combined with continuous scanning, provide defense-in-depth against SSTI vulnerabilities in Basic Auth implementations.