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

AuswirkungDetails
ZugriffskontrolleUmfang: Zugriffskontrolle

Privilegien erlangen oder Identität annehmen - Angreifer können methodenbasierte Zugriffskontrollen umgehen, um privilegierte Operationen auszuführen.
IntegritätUmfang: Integrität

Anwendungsdaten modifizieren - Zustandsändernde Operationen über GET ermöglichen nicht autorisierte Datenmodifikation.
VertraulichkeitUmfang: 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

  1. MITRE Corporation. "CWE-650: Trusting HTTP Permission Methods on the Server Side." https://cwe.mitre.org/data/definitions/650.html
  2. OWASP. "REST Security Cheat Sheet."
  3. RFC 7231. "HTTP/1.1 Semantics and Content - Safe Methods."