Vertrauen auf HTTP-Berechtigungsmethoden auf der Serverseite
Beschreibung
Das Vertrauen auf HTTP-Berechtigungsmethoden auf der Serverseite ist eine Schwachstelle, bei der ein Webserver oder eine Anwendung fälschlicherweise annimmt, dass HTTP-GET-Anfragen keine serverseitigen Ressourcen oder Zustände ändern können. Die HTTP-Spezifikation definiert GET als eine "sichere" Methode, die nur Daten abrufen sollte, ohne Nebeneffekte zu verursachen. Technisch hindert jedoch nichts Anwendungen daran, GET-Anfragen zu implementieren, die Daten erstellen, aktualisieren oder löschen. Wenn Server sich allein auf HTTP-Methodenbeschränkungen für die Zugriffskontrolle verlassen - GET erlauben, aber POST für bestimmte Ressourcen blockieren - können Angreifer diese Kontrollen umgehen, indem sie GET-Anfragen verwenden, um zustandsändernde Operationen durchzuführen.
Risiko
Das Verlassen auf HTTP-Methoden für Sicherheit schafft erhebliche Zugriffskontroll-Schwachstellen. Angreifer können Ressourcen modifizieren, erstellen oder löschen, indem sie GET-Anfragen an Endpunkte senden, die sie akzeptieren. REST-APIs, die GET für Modifikationen verwenden, sind besonders anfällig. Administrative Funktionen, die nur durch Methodenbeschränkungen geschützt sind, können aufgerufen werden. Cross-Site Request Forgery (CSRF)-Angriffe werden einfacher, da GET-Anfragen über Bilder oder Links ausgelöst werden können. Caching-Proxies können gefährliche Operationen cachen und sie unbeabsichtigt wiederholen. Suchmaschinen-Crawler, die Links folgen, können destruktive Operationen auslösen. Das falsche Sicherheitsgefühl führt dazu, dass Entwickler ordnungsgemäße Autorisierungsprüfungen vernachlässigen.
Lösung
Verlassen Sie sich niemals allein auf HTTP-Methoden für die Zugriffskontrolle. Implementieren Sie ordnungsgemäße Authentifizierungs- und Autorisierungsprüfungen für jede Operation, unabhängig von der verwendeten HTTP-Methode. Befolgen Sie REST-Konventionen: Verwenden Sie GET nur für Leseoperationen, POST/PUT für Erstellung und Aktualisierungen, DELETE für Entfernung. Konfigurieren Sie Web Application Firewalls, um zustandsändernde GET-Anfragen an sensible Endpunkte zu blockieren. Implementieren Sie CSRF-Schutz für alle zustandsändernden Operationen. Überprüfen und auditieren Sie API-Designs, um sicherzustellen, dass HTTP-Methoden mit ihrer beabsichtigten Semantik übereinstimmen. Verwenden Sie POST für jede Operation mit Nebeneffekten, auch wenn sie technisch als GET funktionieren könnte.
Häufige Konsequenzen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Umfang: Zugriffskontrolle Privilegien erlangen oder Identität annehmen - Angreifer können methodenbasierte Zugriffskontrollen umgehen, um privilegierte Operationen auszuführen. |
| Integrität | Umfang: Integrität Anwendungsdaten modifizieren - Zustandsändernde Operationen über GET ermöglichen nicht autorisierte Datenmodifikation. |
| Vertraulichkeit | Umfang: Vertraulichkeit Anwendungsdaten lesen - Sensible Datenabrufoperationen können durch GET-Anfragen exponiert werden, die an verschiedenen Stellen protokolliert werden. |
Beispielcode
Anfälliger Code
// Anfällig: Vertrauen auf HTTP-Methode für Zugriffskontrolle
@WebServlet("/admin/*")
public class VulnerableAdminServlet extends HttpServlet {
// Anfällig: Annahme dass GET sicher ist, Erlaubnis ohne Auth-Prüfung
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
// Anfällig: Zustandsändernde Operationen über GET
if ("delete".equals(action)) {
String userId = request.getParameter("userId");
userService.deleteUser(userId); // Gefährliche Operation über GET!
response.getWriter().println("Benutzer gelöscht");
}
else if ("promote".equals(action)) {
String userId = request.getParameter("userId");
userService.promoteToAdmin(userId); // Privilegieneskalation über GET!
response.getWriter().println("Benutzer befördert");
}
}
// POST ist geschützt, aber GET erlaubt dieselben Operationen
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Prüfe Admin-Authentifizierung nur für POST
if (!isAdmin(request)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
doGet(request, response); // Delegiert an anfälligen GET-Handler
}
}
// Angriff: Einfach GET verwenden um POST-Beschränkungen zu umgehen
// http://example.com/admin?action=delete&userId=12345
// http://example.com/admin?action=promote&userId=attacker123
# Anfällig: Flask-App vertraut HTTP-Methoden
from flask import Flask, request, redirect
app = Flask(__name__)
# Anfällig: Zustandsändernde Operation über GET
@app.route('/account/close', methods=['GET'])
def close_account():
# Anfällig: Keine Auth-Prüfung, nimmt an GET ist sicher
user_id = request.args.get('user_id')
db.execute("DELETE FROM accounts WHERE user_id = ?", [user_id])
return "Konto geschlossen"
# Anfällig: Geld überweisen über GET
@app.route('/transfer', methods=['GET'])
def transfer():
# Anfällig: Finanzielle Operation über GET
from_account = request.args.get('from')
to_account = request.args.get('to')
amount = request.args.get('amount')
# Dies kann durch Besuch eines Links oder Laden eines Bildes ausgelöst werden:
# <img src="http://bank.com/transfer?from=victim&to=attacker&amount=10000">
db.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?",
[amount, from_account])
db.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?",
[amount, to_account])
return redirect('/success')
# Anfällig: Webserver-Konfiguration vertraut Methodenbeschränkungen
# Apache-Konfiguration die unzureichend ist:
# <Location /admin>
# <LimitExcept GET>
# Require user admin
# </LimitExcept>
# </Location>
# Dies erlaubt GET ohne Authentifizierung!
<?php
// Anfällig: PHP-Skript vertraut Request-Methode
// Anfällig: Prüft nur Methode, nicht Autorisierung
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Prüfe Admin-Sitzung nur für POST
if (!isset($_SESSION['is_admin']) || !$_SESSION['is_admin']) {
die('Zugriff verweigert');
}
}
// Anfällig: GET-Anfragen umgehen die Admin-Prüfung oben
$action = $_GET['action'] ?? $_POST['action'] ?? '';
switch ($action) {
case 'delete_user':
// Anfällig: Kann über GET ausgelöst werden, umgeht Admin-Prüfung
$userId = $_GET['user_id'] ?? $_POST['user_id'];
deleteUser($userId);
break;
case 'reset_password':
// Anfällig: Passwort-Reset über GET
$userId = $_GET['user_id'] ?? $_POST['user_id'];
resetPassword($userId);
break;
}
Korrigierter Code
// Korrigiert: Ordnungsgemäße Authentifizierung für alle Operationen
@WebServlet("/admin/*")
public class SecureAdminServlet extends HttpServlet {
// Korrigiert: Keine zuständsändernden Operationen in GET
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// GET gibt nur Informationen zurück, modifiziert niemals Zustand
if (!isAuthenticated(request) || !isAdmin(request)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
String action = request.getParameter("action");
// Korrigiert: GET nur für Leseoperationen
if ("list".equals(action)) {
List<User> users = userService.listUsers();
writeJsonResponse(response, users);
}
else if ("view".equals(action)) {
String userId = request.getParameter("userId");
User user = userService.getUser(userId);
writeJsonResponse(response, user);
}
else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Ungültige Aktion für GET-Anfrage");
}
}
// Korrigiert: Zustandsändernde Operationen nur über POST mit Auth und CSRF
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Korrigiert: Prüfe immer Authentifizierung
if (!isAuthenticated(request) || !isAdmin(request)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
// Korrigiert: Verifiziere CSRF-Token
if (!verifyCSRFToken(request)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Ungültiges CSRF-Token");
return;
}
String action = request.getParameter("action");
if ("delete".equals(action)) {
String userId = request.getParameter("userId");
// Zusätzliche Autorisierungsprüfung
if (!canDeleteUser(request, userId)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
userService.deleteUser(userId);
writeJsonResponse(response, Map.of("status", "gelöscht"));
}
else if ("promote".equals(action)) {
String userId = request.getParameter("userId");
userService.promoteToAdmin(userId);
writeJsonResponse(response, Map.of("status", "befördert"));
}
}
// Korrigiert: DELETE-Methode für Löschoperationen
@Override
protected void doDelete(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
if (!isAuthenticated(request) || !isAdmin(request)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
String userId = request.getParameter("userId");
userService.deleteUser(userId);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
}
# Korrigiert: Flask-App mit ordnungsgemäßer Methodenhandhabung
from flask import Flask, request, redirect, session, abort
from functools import wraps
app = Flask(__name__)
app.secret_key = 'secure-secret-key'
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'user_id' not in session:
abort(401)
return f(*args, **kwargs)
return decorated
def require_csrf(f):
@wraps(f)
def decorated(*args, **kwargs):
if request.method == 'POST':
token = request.form.get('csrf_token')
if not token or token != session.get('csrf_token'):
abort(403)
return f(*args, **kwargs)
return decorated
# Korrigiert: Zustandsändernde Operationen nur über POST
@app.route('/account/close', methods=['POST']) # Nicht GET!
@require_auth
@require_csrf
def close_account():
# Korrigiert: Ordnungsgemäße Auth, CSRF-Schutz, nur POST
user_id = session['user_id'] # Verwende authentifizierten Benutzer, nicht Parameter
db.execute("DELETE FROM accounts WHERE user_id = ?", [user_id])
return jsonify({"status": "geschlossen"})
# Korrigiert: Überweisung nur über POST mit ordnungsgemäßer Validierung
@app.route('/transfer', methods=['POST']) # Nicht GET!
@require_auth
@require_csrf
def transfer():
# Korrigiert: Verifiziere from_account gehört authentifiziertem Benutzer
from_account = request.form.get('from')
if not user_owns_account(session['user_id'], from_account):
abort(403)
to_account = request.form.get('to')
amount = float(request.form.get('amount'))
# Korrigiert: Zusätzliche Validierung
if amount <= 0:
abort(400)
# Korrigiert: Verwende Transaktion
with db.transaction():
db.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?",
[amount, from_account])
db.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?",
[amount, to_account])
return jsonify({"status": "erfolg"})
<?php
// Korrigiert: PHP mit ordnungsgemäßen Methoden- und Autorisierungsprüfungen
session_start();
// Korrigiert: Prüfe Authentifizierung für ALLE Anfragen
function requireAuth() {
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
die('Authentifizierung erforderlich');
}
}
function requireAdmin() {
requireAuth();
if (!isset($_SESSION['is_admin']) || !$_SESSION['is_admin']) {
http_response_code(403);
die('Admin-Zugriff erforderlich');
}
}
function verifyCSRF() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['csrf_token'] ?? '';
if ($token !== $_SESSION['csrf_token']) {
http_response_code(403);
die('Ungültiges CSRF-Token');
}
}
}
// Korrigiert: Erlaube zuständsändernde Operationen nur über POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
die('Methode für diese Operation nicht erlaubt');
}
// Korrigiert: Verifiziere Auth und CSRF für alle Operationen
requireAdmin();
verifyCSRF();
$action = $_POST['action'] ?? '';
switch ($action) {
case 'delete_user':
$userId = $_POST['user_id'];
// Zusätzliche Autorisierungsprüfung
if (!canDeleteUser($_SESSION['user_id'], $userId)) {
http_response_code(403);
die('Nicht autorisiert diesen Benutzer zu löschen');
}
deleteUser($userId);
echo json_encode(['status' => 'gelöscht']);
break;
case 'reset_password':
$userId = $_POST['user_id'];
resetPassword($userId);
echo json_encode(['status' => 'zurückgesetzt']);
break;
default:
http_response_code(400);
die('Ungültige Aktion');
}
CVE-Beispiele
Keine spezifischen CVEs werden dieser CWE direkt zugeordnet, obwohl das Muster zu zahlreichen Zugriffskontroll-Umgehungs-Schwachstellen in Webanwendungen beigetragen hat.
Referenzen
- MITRE Corporation. "CWE-650: Trusting HTTP Permission Methods on the Server Side." https://cwe.mitre.org/data/definitions/650.html
- OWASP. "REST Security Cheat Sheet."
- RFC 7231. "HTTP/1.1 Semantics and Content - Safe Methods."