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
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Ausführung von unautorisiertem Code oder Befehlen, die die Systemintegrität beeinträchtigen. |
| Vertraulichkeit | Bereich: 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
- MITRE Corporation. "CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine." https://cwe.mitre.org/data/definitions/1336.html
- PortSwigger. "Server-Side Template Injection"
- OWASP. "Template Injection"