Ausführung nach Weiterleitung (EAR)
Beschreibung
Ausführung nach Weiterleitung (Execution After Redirect, EAR) ist eine Kontrollfluss-Schwachstelle, bei der eine Webanwendung eine Weiterleitungsantwort an einen anderen Ort sendet, aber anstatt die Ausführung zu beenden, zusätzlichen Code weiter ausführt. Wenn ein Weiterleitungs-Header an den Browser gesendet wird, kann der serverseitige Code nachfolgende Anweisungen weiterhin ausführen, sofern er nicht explizit beendet wird. Dies ermöglicht Angreifern den Zugriff auf Funktionalität, die durch die Weiterleitung hätte blockiert werden sollen, wie das Anzeigen geschützter Inhalte, das Ausführen privilegierter Operationen oder den Zugriff auf administrative Funktionen, obwohl die Weiterleitung den Zugriff verweigern sollte.
Risiko
EAR-Schwachstellen schaffen ernsthafte Sicherheitsrisiken, weil sie die Umgehung von Authentifizierungs- und Autorisierungskontrollen ermöglichen. Wenn Code nach einer Weiterleitung, die den Zugriff verweigern sollte, weiter ausgeführt wird, können Angreifer sensible Informationen einsehen, nicht autorisierte Aktionen durchführen oder administrativen Zugriff erlangen. Die Schwachstelle ist besonders gefährlich, weil sie oft bei Tests unentdeckt bleibt - Browser folgen Weiterleitungen, bevor sie Inhalte anzeigen, sodass Tester die geleakten Informationen nicht sehen. Angreifer, die Tools verwenden, die Weiterleitungen nicht folgen, können jedoch die vollständige Antwort mit sensiblen Daten erfassen. Dies kann zur Offenlegung von Konfigurationsdetails, Sitzungsinformationen oder vollständiger Authentifizierungsumgehung führen.
Lösung
Beenden Sie die Skriptausführung immer sofort nach dem Senden einer Weiterleitung. In PHP rufen Sie exit() oder die() nach header("Location: ...") auf. In Java-Servlets rufen Sie return nach sendRedirect() auf. In ASP.NET verwenden Sie Response.Redirect() mit dem zweiten Parameter auf true gesetzt, oder rufen Sie Response.End() auf. Strukturieren Sie Code so, dass geschützte Inhalte nur in expliziten Erfolgspfaden ausgeführt werden, nicht als Fallthrough nach Weiterleitungslogik. Verwenden Sie Framework-bereitgestellte Weiterleitungsmechanismen, die die Ausführung automatisch beenden. Implementieren Sie Sicherheitstests, die Antworten erfassen, bevor Weiterleitungen gefolgt wird.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Sensible Informationen können im Antwort-Body nach dem Weiterleitungs-Header offengelegt werden. |
| Zugriffskontrolle | Bereich: Zugriffskontrolle Umgehung des Schutzmechanismus - Authentifizierungs- und Autorisierungsprüfungen, die auf Weiterleitungen basieren, können umgangen werden. |
| Integrität | Bereich: Integrität Nicht autorisierten Code oder Befehle ausführen - Aktionen, die blockiert werden sollten, können dennoch ausgeführt werden. |
Beispielcode
Verwundbarer Code
<?php
// Verwundbar: Code wird nach Weiterleitung ausgeführt
$requestingIP = $_SERVER['REMOTE_ADDR'];
$allowedIPs = array('192.168.1.100', '192.168.1.101');
if (!in_array($requestingIP, $allowedIPs)) {
echo "Sie sind nicht berechtigt, diese Seite anzuzeigen";
header("Location: /error.php");
// Verwundbar: Kein exit! Code wird weiter ausgeführt
}
// Sensible Operationen werden trotz Weiterleitung fortgesetzt
$serverStatus = getServerStatus();
$databaseInfo = getDatabaseConnectionDetails();
// Verwundbar: All diese Ausgabe wird gesendet, bevor Browser der Weiterleitung folgt
echo "<h1>Server-Administration</h1>";
echo "<p>Server-Status: " . $serverStatus . "</p>";
echo "<p>DB-Verbindung: " . $databaseInfo . "</p>";
// Angreifer kann dies erfassen, obwohl Browser weiterleiten würde
?>
<!-- Ein weiteres verwundbares Muster -->
<?php
function checkAuthentication() {
if (!isset($_SESSION['user'])) {
header("Location: /login.php");
// Verwundbar: Funktion kehrt zurück, aber Aufrufer macht weiter
return false;
}
return true;
}
// Verwundbar: Rückgabewert wird nicht richtig geprüft
checkAuthentication(); // Weiterleitung gesendet, aber...
// ...Ausführung geht weiter!
processAdminAction($_POST['action']);
displayAdminPanel();
?>
// Verwundbar: Java-Servlet macht nach Weiterleitung weiter
import javax.servlet.http.*;
import java.io.*;
public class VulnerableAdminServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("admin") == null) {
response.sendRedirect("/login.jsp");
// Verwundbar: Kein return! Ausführung geht weiter
}
// Dieser ganze Code läuft auch nach sendRedirect
PrintWriter out = response.getWriter();
out.println("<h1>Admin-Panel</h1>");
out.println("Geheimer Admin-Schlüssel: " + getAdminKey());
out.println("Benutzerliste: " + getAllUsers());
// Sensible Operation wird auch ausgeführt
processAdminTasks();
}
}
// Verwundbar: Exception-basierter Flow stoppt Ausführung nicht
public class VulnerableAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
if (!isAuthenticated((HttpServletRequest) req)) {
response.sendRedirect("/unauthorized.jsp");
// Verwundbar: Filter-Chain geht weiter
}
// Anfrage wird immer noch von nachfolgenden Servlets verarbeitet
chain.doFilter(req, res);
}
}
# Verwundbar: Flask-Weiterleitung ohne return
from flask import Flask, redirect, request, session
app = Flask(__name__)
@app.route('/admin')
def vulnerable_admin():
if 'user' not in session:
redirect('/login') # Verwundbar: Nicht zurückgegeben!
# Ausführung geht weiter
# Sensible Daten werden offengelegt
config = get_app_configuration()
users = get_all_users()
return f"""
<h1>Admin-Panel</h1>
<p>Konfiguration: {config}</p>
<p>Benutzer: {users}</p>
"""
@app.route('/delete/<int:item_id>')
def vulnerable_delete(item_id):
if not is_authorized(session.get('user')):
redirect('/unauthorized')
# Verwundbar: Element wird trotzdem gelöscht!
delete_item(item_id) # Wird unabhängig von Autorisierung ausgeführt
return "Element gelöscht"
<!-- Verwundbar: ASP.NET ohne Response.End -->
<%@ Page Language="C#" %>
<%
if (!User.Identity.IsAuthenticated)
{
Response.Redirect("Login.aspx");
// Verwundbar: Seite wird weiter gerendert
}
// Sensible Inhalte werden offengelegt
Response.Write("<h1>Geheime Administration</h1>");
Response.Write("Admin-Passwort: " + GetAdminPassword());
Response.Write("Systemschlüssel: " + GetSystemKeys());
%>
// Verwundbar: Node.js/Express ohne return
const express = require('express');
const app = express();
app.get('/admin', (req, res) => {
if (!req.session.isAdmin) {
res.redirect('/login');
// Verwundbar: Kein return - Handler macht weiter
}
// Sensible Daten werden nach Weiterleitungs-Header zurückgegeben
const secrets = getSecrets();
res.send(`<h1>Admin</h1><p>${secrets}</p>`);
// Sowohl Weiterleitung als auch Inhalt werden gesendet!
});
Korrigierter Code
<?php
// Korrigiert: Sofort nach Weiterleitung beenden
$requestingIP = $_SERVER['REMOTE_ADDR'];
$allowedIPs = array('192.168.1.100', '192.168.1.101');
if (!in_array($requestingIP, $allowedIPs)) {
header("Location: /error.php");
exit(); // Korrigiert: Ausführung sofort beenden
}
// Wird nur ausgeführt, wenn IP erlaubt ist
$serverStatus = getServerStatus();
$databaseInfo = getDatabaseConnectionDetails();
echo "<h1>Server-Administration</h1>";
echo "<p>Server-Status: " . $serverStatus . "</p>";
?>
<?php
// Korrigiert: Funktion beendet Ausführung
function requireAuthentication() {
if (!isset($_SESSION['user'])) {
header("Location: /login.php");
exit(); // Korrigiert: In Funktion beenden
}
}
// Korrigiert: Aufruf beendet ordnungsgemäß, wenn nicht authentifiziert
requireAuthentication();
// Erreicht hier nur, wenn authentifiziert
processAdminAction($_POST['action']);
displayAdminPanel();
?>
<?php
// Korrigiert: Alternative - Code ordnungsgemäß strukturieren
if (isset($_SESSION['user'])) {
// Geschützte Inhalte nur im authentifizierten Block
processAdminAction($_POST['action']);
displayAdminPanel();
} else {
header("Location: /login.php");
exit();
}
?>
// Korrigiert: Return nach Weiterleitung
import javax.servlet.http.*;
import java.io.*;
public class SecureAdminServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("admin") == null) {
response.sendRedirect("/login.jsp");
return; // Korrigiert: Sofort nach Weiterleitung zurückkehren
}
// Wird nur ausgeführt, wenn authentifiziert
PrintWriter out = response.getWriter();
out.println("<h1>Admin-Panel</h1>");
out.println("Geheimer Admin-Schlüssel: " + getAdminKey());
processAdminTasks();
}
}
// Korrigiert: Filter stoppt Chain ordnungsgemäß
public class SecureAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
if (!isAuthenticated((HttpServletRequest) req)) {
response.sendRedirect("/unauthorized.jsp");
return; // Korrigiert: Filter-Chain nicht fortsetzen
}
// Nur fortsetzen, wenn authentifiziert
chain.doFilter(req, res);
}
}
# Korrigiert: Weiterleitungsantwort zurückgeben
from flask import Flask, redirect, request, session
app = Flask(__name__)
@app.route('/admin')
def secure_admin():
if 'user' not in session:
return redirect('/login') # Korrigiert: Weiterleitung zurückgeben
# Wird nur ausgeführt, wenn authentifiziert
config = get_app_configuration()
users = get_all_users()
return f"""
<h1>Admin-Panel</h1>
<p>Konfiguration: {config}</p>
<p>Benutzer: {users}</p>
"""
@app.route('/delete/<int:item_id>')
def secure_delete(item_id):
if not is_authorized(session.get('user')):
return redirect('/unauthorized') # Korrigiert: Weiterleitung zurückgeben
# Wird nur ausgeführt, wenn autorisiert
delete_item(item_id)
return "Element gelöscht"
# Alternative: Decorator-Muster verwenden
from functools import wraps
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'user' not in session:
return redirect('/login')
return f(*args, **kwargs)
return decorated
@app.route('/admin')
@require_auth
def admin_with_decorator():
# Dies wird nur ausgeführt, wenn authentifiziert
return render_admin_panel()
<%@ Page Language="C#" %>
<%
// Korrigiert: Response.Redirect mit endResponse=true verwenden
if (!User.Identity.IsAuthenticated)
{
Response.Redirect("Login.aspx", true);
// Zweiter Parameter beendet Antwort
// Alternativ: Response.Redirect("Login.aspx"); Response.End();
}
// Wird nur ausgeführt, wenn authentifiziert
Response.Write("<h1>Geheime Administration</h1>");
%>
// Korrigiert: Node.js/Express mit return
const express = require('express');
const app = express();
app.get('/admin', (req, res) => {
if (!req.session.isAdmin) {
return res.redirect('/login'); // Korrigiert: Nach Weiterleitung zurückkehren
}
// Wird nur ausgeführt, wenn Admin
const secrets = getSecrets();
res.send(`<h1>Admin</h1><p>${secrets}</p>`);
});
// Korrigiert: Middleware für Authentifizierung verwenden
function requireAdmin(req, res, next) {
if (!req.session.isAdmin) {
return res.redirect('/login'); // Korrigiert: Return
}
next();
}
app.get('/admin', requireAdmin, (req, res) => {
// Erreicht hier nur, wenn Admin-Prüfung bestanden
res.send(getAdminPanel());
});
CVE-Beispiele
- CVE-2013-1402: Anwendung legte Konfigurationsdetails nach Weiterleitung offen.
- CVE-2009-1936: EAR kombiniert mit Dateieinbindung und Pfadtraversierung.
- CVE-2007-2713: Nicht autorisierter Administrator-Zugriff aufgrund von Ausführung nach Weiterleitung.
- CVE-2007-5578: Authentifizierungsumgehung durch EAR.
- CVE-2007-6652: Nicht-Admin statische Code-Injektion über EAR.
Referenzen
- MITRE Corporation. "CWE-698: Execution After Redirect (EAR)." https://cwe.mitre.org/data/definitions/698.html
- OWASP. "Execution After Redirect."
- CWE-670: Always-Incorrect Control Flow Implementation.