Unsachgemäße Neutralisierung spezieller Elemente in einer Template-Engine

Beschreibung

Unsachgemäße Neutralisierung spezieller Elemente in einer Template-Engine tritt auf, wenn Produkte Template-Engines verwenden, um extern beeinflusste Eingaben zu verarbeiten, ohne spezielle Elemente oder Syntax, die Template-Engines als Code-Direktiven interpretieren können, ordnungsgemäß zu neutralisieren. Template-Engines (Twig, Jinja2, Pug, JSP, FreeMarker, Velocity, ColdFusion, Smarty, PHP) ermöglichen es Entwicklern, externe Werte in Dokumente einzufügen, aber Angreifer können bösartige Ausdrücke wie "{{7*7}}" injizieren, um beliebigen Code auszuführen. Sowohl serverseitige (SSTI) als auch clientseitige (CSTI) Template-Injection-Varianten existieren.

Risiko

Template Injection hat schwerwiegende Auswirkungen. Remote Code Execution möglich. Serverkompromittierung. Datenexfiltration. Dateisystemzugriff. Beliebige Befehlsausführung. Cross-Site Scripting bei clientseitigen Varianten. Informationsoffenlegung. Dienstunterbrechung. Hohe Wahrscheinlichkeit, wenn Benutzereingaben ohne Bereinigung in Templates fließen.

Lösung

Template-Engines auswählen, die Sandbox- oder eingeschränkte Modi bieten, während der Architektur- und Designphase. Verfügbare Sandbox-/eingeschränkte Funktionen während der Implementierungsphase implementieren. Niemals nicht vertrauenswürdige Eingaben direkt in Template-Ausdrücke übergeben. Benutzereingaben vor der Template-Verarbeitung escapen oder bereinigen. Logiklose Templates verwenden, wo möglich.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Ausführung von unautorisiertem Code oder Befehlen, die die Systemintegrität beeinträchtigen.
VertraulichkeitBereich: Vertraulichkeit

Zugriff auf sensible Daten durch Auswertung von Template-Ausdrücken.

Beispielcode

Verwundbarer Code

# VERWUNDBAR: Python Jinja2 Template Injection

from flask import Flask, request, render_template_string

app = Flask(__name__)

# VERWUNDBAR: Benutzereingabe direkt im Template
@app.route('/greet')
def vulnerable_greet():
    name = request.args.get('name', 'Guest')

    # VERWUNDBAR: Benutzereingabe in Template-String eingefügt
    template = f"<h1>Hello {name}!</h1>"
    return render_template_string(template)

    # Angriff: ?name={{config}}
    # Gibt Flask-Konfiguration einschließlich SECRET_KEY zurück

    # Angriff: ?name={{''.__class__.__mro__[1].__subclasses__()}}
    # Listet alle Python-Klassen auf - Pfad zu RCE

# VERWUNDBAR: Schwerwiegendere Jinja2-Injection
@app.route('/render')
def vulnerable_render():
    user_template = request.args.get('template', '')

    # VERWUNDBAR: Benutzer kontrolliert das gesamte Template
    return render_template_string(user_template)

    # Angriff: ?template={{config.items()}}
    # Angriff: ?template={{''.class.__mro__[1].__subclasses__()[408]('id',shell=True,stdout=-1).communicate()}}
    # Führt Shell-Befehl 'id' aus

# VERWUNDBAR: Django-Template (weniger schwerwiegend, aber dennoch gefährlich)
from django.template import Template, Context

def vulnerable_django_view(request):
    user_input = request.GET.get('input', '')

    # VERWUNDBAR: Benutzereingabe im Template
    template = Template(f"Hello {user_input}")
    return template.render(Context({}))
// VERWUNDBAR: Node.js Template Injection

const express = require('express');
const nunjucks = require('nunjucks');
const pug = require('pug');

const app = express();

// VERWUNDBAR: Nunjucks-Injection
app.get('/greet', (req, res) => {
    const name = req.query.name || 'Guest';

    // VERWUNDBAR: Benutzereingabe im Template-String
    const template = `<h1>Hello ${name}!</h1>`;
    const result = nunjucks.renderString(template);
    res.send(result);

    // Angriff: ?name={{range(10)}}
    // Angriff: ?name={{''.__class__.__mro__[1].__subclasses__()}}
});

// VERWUNDBAR: Pug (Jade) Template Injection
app.get('/page', (req, res) => {
    const title = req.query.title || 'Page';

    // VERWUNDBAR: Benutzereingabe an Pug übergeben
    const html = pug.render(`h1= title`, { title: title });
    res.send(html);

    // Könnte je nach Eingabeverarbeitung verwundbar sein
});

// VERWUNDBAR: EJS-Injection
const ejs = require('ejs');

app.get('/render', (req, res) => {
    const data = req.query.data || '';

    // VERWUNDBAR: Benutzereingabe im EJS-Template
    const template = `<div><%= data %></div>`;
    const html = ejs.render(template, { data: data });
    res.send(html);

    // Angriffsmuster hängen von der spezifischen EJS-Version ab
});

// VERWUNDBAR: Handlebars mit Helfern
const Handlebars = require('handlebars');

app.get('/helper', (req, res) => {
    const template = req.query.template || 'Hello';

    // VERWUNDBAR: Benutzer kontrolliert das Template
    const compiled = Handlebars.compile(template);
    res.send(compiled({}));
});
// VERWUNDBAR: Java Template Injection

import freemarker.template.*;
import org.apache.velocity.*;

public class VulnerableTemplates {

    // VERWUNDBAR: FreeMarker-Injection
    public String vulnerableFreeMarker(String userInput) throws Exception {
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);

        // VERWUNDBAR: Benutzereingabe im Template-String
        String templateStr = "Hello " + userInput;
        Template template = new Template("temp", new StringReader(templateStr), cfg);

        StringWriter out = new StringWriter();
        template.process(new HashMap<>(), out);
        return out.toString();

        // Angriff: ${7*7} -> 49
        // Angriff: ${"freemarker.template.utility.Execute"?new()("id")}
        // Führt Shell-Befehl aus
    }

    // VERWUNDBAR: Apache Velocity-Injection
    public String vulnerableVelocity(String userInput) {
        VelocityEngine ve = new VelocityEngine();
        ve.init();

        // VERWUNDBAR: Benutzereingabe im Velocity-Template
        String templateStr = "Hello " + userInput;

        VelocityContext context = new VelocityContext();
        StringWriter writer = new StringWriter();
        ve.evaluate(context, writer, "temp", templateStr);

        return writer.toString();

        // Angriff: #set($x='')#set($rt=$x.class.forName('java.lang.Runtime'))
        // Angriff: $class.inspect("java.lang.Runtime").type.getRuntime().exec("id")
    }

    // VERWUNDBAR: Thymeleaf-Injection
    public String vulnerableThymeleaf(String userInput) {
        // Bei Verwendung von Thymeleaf mit Ausdrucksvorverarbeitung
        // [[${userInput}]] kann verwundbar sein

        // Angriff: __${T(java.lang.Runtime).getRuntime().exec('id')}__
        return "";
    }
}

Sichere Lösung

# SICHER: Sichere Python-Template-Behandlung

from flask import Flask, request, render_template_string, escape
from markupsafe import Markup
import jinja2

app = Flask(__name__)

# SICHER: Benutzereingabe escapen
@app.route('/greet')
def safe_greet():
    name = request.args.get('name', 'Guest')

    # SICHER: Benutzereingabe vor dem Einfügen in das Template escapen
    safe_name = escape(name)
    template = f"<h1>Hello {safe_name}!</h1>"
    return template  # render_template_string überhaupt nicht verwenden

# SICHER: Korrektes Template mit Variablen verwenden
@app.route('/greet_safe')
def safe_greet_proper():
    name = request.args.get('name', 'Guest')

    # SICHER: Daten als Kontext übergeben, nicht im Template-String
    return render_template_string(
        "<h1>Hello {{ name }}!</h1>",
        name=name  # Jinja2 escapt automatisch im HTML-Kontext
    )

# SICHER: Sandbox-geschützte Jinja2-Umgebung
from jinja2.sandbox import SandboxedEnvironment

def safe_render_sandboxed(template_str, context):
    # SICHER: Sandbox verwenden, um Template-Fähigkeiten einzuschränken
    env = SandboxedEnvironment()

    # SICHER: Gefährliche Funktionen deaktivieren
    env.globals = {}  # Keine globalen Funktionen

    try:
        template = env.from_string(template_str)
        return template.render(context)
    except jinja2.exceptions.SecurityError:
        return "Template-Sicherheitsverletzung"

# SICHER: Erlaubte Template-Syntax per Whitelist
import re

def safe_render_restricted(user_template, context):
    # SICHER: Nur einfache Variablensubstitution erlauben
    # Jede Template-Syntax ablehnen, die gefährlich sein könnte

    # Gefährliche Muster blockieren
    dangerous_patterns = [
        r'\{\{.*\.__',           # Attributzugriff
        r'\{\{.*\[',             # Elementzugriff
        r'\{\{.*\(',             # Funktionsaufrufe
        r'\{%',                   # Template-Tags
        r'config',               # Flask-Konfiguration
        r'request',              # Flask-Request
        r'self',                 # Selbstreferenz
    ]

    for pattern in dangerous_patterns:
        if re.search(pattern, user_template, re.IGNORECASE):
            raise ValueError("Gefährliches Template-Muster erkannt")

    # Nur einfache {{ variable }}-Syntax erlauben
    allowed_pattern = r'\{\{\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\}\}'
    if re.sub(allowed_pattern, '', user_template).find('{{') != -1:
        raise ValueError("Komplexe Template-Ausdrücke nicht erlaubt")

    env = SandboxedEnvironment()
    template = env.from_string(user_template)
    return template.render(context)
// SICHER: Sichere Node.js-Template-Behandlung

const express = require('express');
const nunjucks = require('nunjucks');
const xss = require('xss');

const app = express();

// SICHER: Nunjucks mit Auto-Escaping konfigurieren
const env = nunjucks.configure('views', {
    autoescape: true,  // SICHER: Ausgabe automatisch escapen
    express: app
});

// SICHER: Template-Dateien verwenden, keine Inline-Strings
app.get('/greet', (req, res) => {
    const name = req.query.name || 'Guest';

    // SICHER: Aus Datei mit automatisch escaptem Kontext rendern
    res.render('greet.html', { name: name });
    // In greet.html: <h1>Hello {{ name }}!</h1>
    // Nunjucks escapt die name-Variable automatisch
});

// SICHER: Escaping beim Erstellen dynamischer Templates
app.get('/dynamic', (req, res) => {
    const name = req.query.name || 'Guest';

    // SICHER: Benutzereingabe manuell escapen
    const safeName = xss(name);

    // SICHER: Benutzereingabe überhaupt nicht im Template-String verwenden
    const html = `<h1>Hello ${safeName}!</h1>`;
    res.send(html);  // Einfacher String, kein Template
});

// SICHER: Template-Eingabe validieren und bereinigen
function safeRenderTemplate(templateStr, context) {
    // SICHER: Erlaubte Syntax per Whitelist
    const dangerousPatterns = [
        /\{\{.*\.__/,           // Attributzugriff
        /\{\{.*\[.*\]/,         // Array-/Objektzugriff
        /\{\{.*\(.*\)/,         // Funktionsaufrufe
        /\{%/,                   // Block-Tags
        /import\s/,             // Import-Anweisungen
        /require\s*\(/,         // Require-Aufrufe
    ];

    for (const pattern of dangerousPatterns) {
        if (pattern.test(templateStr)) {
            throw new Error('Gefährliches Template-Muster');
        }
    }

    // SICHER: Sichere Teilmenge von Nunjucks verwenden
    const safeEnv = new nunjucks.Environment(null, {
        autoescape: true
    });

    // SICHER: Alle Globals und Filter entfernen
    safeEnv.globals = {};

    return safeEnv.renderString(templateStr, context);
}

// SICHER: Logiklose Templates verwenden (Mustache)
const Mustache = require('mustache');

app.get('/safe', (req, res) => {
    const name = req.query.name || 'Guest';

    // SICHER: Mustache ist logiklos und sicherer
    const template = '<h1>Hello {{name}}!</h1>';
    const html = Mustache.render(template, { name: name });
    res.send(html);
    // Mustache escapt standardmäßig und hat keine Codeausführung
});
// SICHER: Sichere Java-Template-Behandlung

import freemarker.template.*;
import freemarker.core.TemplateClassResolver;

public class SafeTemplates {

    // SICHER: Sichere FreeMarker-Konfiguration
    public Configuration getSecureFreemarkerConfig() {
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);

        // SICHER: Gefährliche Built-ins deaktivieren
        cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);

        // SICHER: ?new und ?api deaktivieren
        cfg.setAPIBuiltinEnabled(false);

        // SICHER: Strikten Syntax-Modus setzen
        cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);

        return cfg;
    }

    // SICHER: Sicheres FreeMarker-Rendering
    public String safeFreeMarker(String userInput) throws Exception {
        Configuration cfg = getSecureFreemarkerConfig();

        // SICHER: Template-Datei verwenden, Eingabe als Daten übergeben
        Map<String, Object> data = new HashMap<>();
        data.put("name", userInput);  // FreeMarker escapt im HTML

        Template template = cfg.getTemplate("greeting.ftl");
        StringWriter out = new StringWriter();
        template.process(data, out);
        return out.toString();
    }

    // SICHER: Eingabevalidierung vor dem Template
    public String safeRenderWithValidation(String userInput) throws Exception {
        // SICHER: Jede Template-Syntax in der Eingabe ablehnen
        if (userInput.contains("${") || userInput.contains("#{") ||
            userInput.contains("<#") || userInput.contains("[#")) {
            throw new IllegalArgumentException("Template-Syntax in Eingabe nicht erlaubt");
        }

        // SICHER: Sonderzeichen escapen
        String escaped = StringEscapeUtils.escapeHtml4(userInput);

        Configuration cfg = getSecureFreemarkerConfig();
        Map<String, Object> data = new HashMap<>();
        data.put("name", escaped);

        Template template = cfg.getTemplate("greeting.ftl");
        StringWriter out = new StringWriter();
        template.process(data, out);
        return out.toString();
    }

    // SICHER: Whitelist für erlaubte Variablen verwenden
    public String safeTemplateWithWhitelist(String templateStr, Map<String, Object> context) {
        Set<String> allowedVariables = Set.of("name", "title", "message");

        // SICHER: Nur Referenzen auf erlaubte Variablen zulassen
        Pattern varPattern = Pattern.compile("\\$\\{\\s*([a-zA-Z_]+)\\s*\\}");
        Matcher matcher = varPattern.matcher(templateStr);

        while (matcher.find()) {
            String varName = matcher.group(1);
            if (!allowedVariables.contains(varName)) {
                throw new IllegalArgumentException("Variable nicht erlaubt: " + varName);
            }
        }

        // Zusätzliche Prüfung auf andere FreeMarker-Syntax
        if (templateStr.matches(".*(<#|\\[#|\\?\\w+).*")) {
            throw new IllegalArgumentException("Erweiterte Template-Syntax nicht erlaubt");
        }

        // Mit sicherem Rendering fortfahren
        // ...
    }
}

CVE-Beispiele

  • CVE-2024-34359: Jinja2 Prompt Injection in LLM-Anwendung.
  • CVE-2020-12790: Twig Template Injection, die Remote Code Execution ermöglicht.
  • CVE-2020-4027: Apache Velocity Makro-Umgehung, die Codeausführung ermöglicht.
  • CVE-2019-10744: Lodash Template Injection, die zu Codeausführung führt.

Verwandte CWEs

  • CWE-94: Improper Control of Generation of Code ('Code Injection') (übergeordnet)
  • CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection') (verwandt)
  • CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine." https://cwe.mitre.org/data/definitions/1336.html
  2. PortSwigger. "Server-Side Template Injection"
  3. OWASP. "Template Injection"