HIGH server side template injectionbasic auth

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:

# Install middleBrick CLI npm install -g middlebrick # Scan a Basic Auth endpoint middlebrick scan https://api.example.com/auth \ --basic-auth \ --template-injection \ --output json > security-report.json # Scan with specific template engine patterns middlebrick scan https://api.example.com/auth \ --template-engines jinja2,twig,mustache \ --max-depth 3 \ --fail-below B

The CLI integrates with CI/CD pipelines to fail builds if new vulnerabilities are detected:

name: API Security Scan on: pull_request: paths: - 'src/auth/**' jobs: security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run middleBrick Scan run: | npm install -g middlebrick middlebrick scan ${{ secrets.API_URL }} \ --basic-auth \ --template-injection \ --fail-below B env: BASIC_AUTH_USER: ${{ secrets.BASIC_USER }} BASIC_AUTH_PASS: ${{ secrets.BASIC_PASS }}

These remediation strategies, combined with continuous scanning, provide defense-in-depth against SSTI vulnerabilities in Basic Auth implementations.

Frequently Asked Questions

How can I tell if my Basic Auth endpoint is vulnerable to Server Side Template Injection?
Look for patterns where authentication credentials flow into template contexts. Common indicators include error pages that display attempted usernames, logging interfaces that show auth attempts, or API documentation that renders example headers with user-supplied values. Use automated scanning tools like middleBrick that test for template injection patterns specifically in Basic Auth contexts. The vulnerability manifests when template engines evaluate expressions embedded in credentials rather than treating them as literal strings.
What's the difference between Server Side Template Injection and Cross-Site Scripting in Basic Auth scenarios?
SSTI occurs on the server when template engines evaluate malicious expressions in credentials, potentially leading to remote code execution. XSS occurs in the client when malicious scripts execute in users' browsers. In Basic Auth, SSTI is more dangerous because credentials are processed server-side before authentication, giving attackers direct access to template evaluation contexts. XSS would require the malicious payload to survive the authentication process and be reflected in client-side content, which is less common in Basic Auth flows.