Unzureichende Durchsetzung des Verhaltens-Workflows

Beschreibung

Unzureichende Durchsetzung des Verhaltens-Workflows ist eine Geschäftslogik-Schwachstelle, bei der Software mehrstufige Sitzungen erlaubt, die erfordern, dass Akteure einer bestimmten Reihenfolge folgen, aber diese Reihenfolge nicht ordnungsgemäß validiert. Diese Schwäche tritt auf, wenn Anwendungen nicht durchsetzen, dass Schritte in der erwarteten Reihenfolge ausgeführt werden, dass erforderliche Schritte nicht ausgelassen werden, dass Schritte nicht unterbrochen werden und dass Schritte innerhalb angemessener Zeitlimits ausgeführt werden. Angreifer können dies ausnutzen, indem sie Geschäftslogik manipulieren durch Ausführen von Aktionen außerhalb der Reihenfolge, Überspringen erforderlicher Schritte oder direktes Springen zu finalen Schritten ohne Abschluss der Voraussetzungen.

Risiko

Diese Schwachstelle kann schwerwiegende Sicherheitsauswirkungen haben, indem sie Angreifern erlaubt, kritische Validierungs-, Authentifizierungs- oder Geschäftslogik-Schritte zu umgehen. Ein Angreifer könnte Authentifizierung vollständig überspringen, um auf geschützte Funktionalität zuzugreifen, Zahlungsverifikation in E-Commerce-Transaktionen umgehen, Nutzungsbedingungen-Zustimmungsschritte überspringen, auf administrative Funktionen ohne ordnungsgemäßen Autorisierungs-Workflow zugreifen oder Zustandsmaschinen manipulieren, um privilegierte Zustände zu erreichen. Das Risiko ist besonders hoch in Finanzanwendungen, Authentifizierungsabläufen, mehrstufigen Genehmigungsprozessen und jedem Workflow, bei dem die Schrittreihenfolge Sicherheitsimplikationen hat.

Lösung

Implementieren Sie serverseitige Workflow-Zustandsverwaltung, die den aktuellen Schritt verfolgt und Übergänge validiert. Verwenden Sie sitzungsgebundene Zustandsmaschinen, um gültige Schrittsequenzen durchzusetzen. Verifizieren Sie, dass alle Voraussetzungsschritte abgeschlossen wurden, bevor Sie Zugriff auf nachfolgende Schritte erlauben. Implementieren Sie Schrittabschluss-Tokens, die serverseitig validiert werden. Verlassen Sie sich nicht auf clientseitigen State oder versteckte Formularfelder für Workflow-Durchsetzung. Verwenden Sie datenbankgestützte Workflow-Verfolgung für kritische Prozesse. Implementieren Sie Timeouts für teilweise abgeschlossene Workflows. Erwägen Sie die Verwendung etablierter Workflow-Engines für komplexe mehrstufige Prozesse.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Ausführungslogik ändern - Angreifer können das Produkt dazu bringen, kritische Schritte zu überspringen oder sie falsch auszuführen, wodurch beabsichtigte Geschäftslogik umgangen wird.
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Sicherheitsprüfungen in übersprungenen Schritten ermöglichen unautorisierten Zugriff auf geschützte Funktionalität.
SonstigesBereich: Sonstiges

Geschäftliche Auswirkung - Finanzielle Verluste, Datenintegritätsprobleme oder Compliance-Verletzungen durch umgangene Workflows.

Beispielcode

Anfälliger Code

# Anfällig: FTP-Server ohne Authentifizierungsdurchsetzung
class VulnerableFTPServer:

    def __init__(self):
        self.authenticated = False

    def handle_command(self, command, args):
        # Anfällig: Keine Authentifizierungsprüfung für sensible Befehle
        if command == 'USER':
            self.current_user = args
            return "331 Passwort erforderlich"

        elif command == 'PASS':
            if self.verify_password(self.current_user, args):
                self.authenticated = True
                return "230 Login erfolgreich"
            return "530 Login fehlgeschlagen"

        elif command == 'LIST':
            # Anfällig: Sollte Authentifizierung erfordern
            return self.list_files(args)

        elif command == 'RETR':
            # Anfällig: Sollte Authentifizierung erfordern
            return self.retrieve_file(args)

        elif command == 'STOR':
            # Anfällig: Sollte Authentifizierung erfordern
            return self.store_file(args)

# Angreifer kann direkt LIST, RETR, STOR senden ohne Authentifizierung
// Anfällig: E-Commerce-Checkout ohne Schrittvalidierung
public class VulnerableCheckout {

    public void processCheckout(HttpServletRequest request) {
        String step = request.getParameter("step");

        // Anfällig: Keine Validierung, dass vorherige Schritte abgeschlossen wurden
        switch (step) {
            case "cart":
                displayCart(request);
                break;
            case "shipping":
                processShipping(request);
                break;
            case "payment":
                // Anfällig: Kann direkt hierher springen
                processPayment(request);
                break;
            case "confirm":
                // Anfällig: Kann Zahlung vollständig überspringen!
                confirmOrder(request);
                break;
        }
    }

    // Angreifer kann direkt POST zu step=confirm senden und kostenlose Artikel erhalten
}
// Anfällig: Konto-Einrichtung ohne Schrittdurchsetzung
<?php
class VulnerableAccountSetup {

    public function handleSetup($step, $data) {
        // Anfällig: Schritte können in beliebiger Reihenfolge ausgeführt werden
        switch ($step) {
            case 'email':
                return $this->setEmail($data['email']);

            case 'password':
                return $this->setPassword($data['password']);

            case 'profile':
                return $this->setProfile($data);

            case 'activate':
                // Anfällig: Kann E-Mail-Verifizierung überspringen
                // und Konto direkt aktivieren
                return $this->activateAccount();
        }
    }
}

// Angreifer erstellt Konto und springt zur Aktivierung ohne E-Mail-Verifizierung
?>
// Anfällig: Kreditantrag ohne Workflow-Validierung
class VulnerableLoanApplication {

    async processStep(req, res) {
        const { step, applicationId, data } = req.body;

        // Anfällig: Keine Workflow-Zustandsvalidierung
        switch (step) {
            case 'apply':
                return this.submitApplication(data);

            case 'documents':
                return this.uploadDocuments(applicationId, data);

            case 'verify':
                return this.verifyApplication(applicationId);

            case 'approve':
                // Anfällig: Kann Verifizierung überspringen!
                return this.approveApplication(applicationId);

            case 'disburse':
                // Anfällig: Kann Genehmigung überspringen!
                return this.disburseFunds(applicationId);
        }
    }
}

// Angreifer kann disburse direkt aufrufen ohne Genehmigungsprozess

Korrigierter Code

# Korrigiert: FTP-Server mit ordnungsgemäßer Authentifizierungsdurchsetzung
class FixedFTPServer:

    def __init__(self):
        self.authenticated = False
        self.current_user = None

    # Befehle, die Authentifizierung erfordern
    AUTHENTICATED_COMMANDS = {'LIST', 'RETR', 'STOR', 'DELE', 'MKD', 'RMD'}

    def handle_command(self, command, args):
        # Korrigiert: Authentifizierung für geschützte Befehle prüfen
        if command in self.AUTHENTICATED_COMMANDS:
            if not self.authenticated:
                return "530 Bitte zuerst einloggen"

        if command == 'USER':
            # Authentifizierungszustand zurücksetzen
            self.authenticated = False
            self.current_user = args
            return "331 Passwort erforderlich"

        elif command == 'PASS':
            if not self.current_user:
                return "503 Zuerst mit USER anmelden"

            if self.verify_password(self.current_user, args):
                self.authenticated = True
                return "230 Login erfolgreich"
            return "530 Login fehlgeschlagen"

        elif command == 'LIST':
            return self.list_files(args)

        elif command == 'RETR':
            return self.retrieve_file(args)

        elif command == 'STOR':
            return self.store_file(args)
// Korrigiert: E-Commerce-Checkout mit Workflow-Zustandsmaschine
public class FixedCheckout {

    enum CheckoutState {
        CART, SHIPPING, PAYMENT, CONFIRM, COMPLETE
    }

    public void processCheckout(HttpServletRequest request, HttpSession session) {
        String requestedStep = request.getParameter("step");

        // Korrigiert: Aktuellen Workflow-Zustand aus serverseitiger Sitzung holen
        CheckoutState currentState = (CheckoutState) session.getAttribute("checkoutState");
        if (currentState == null) {
            currentState = CheckoutState.CART;
            session.setAttribute("checkoutState", currentState);
        }

        // Korrigiert: Validieren, dass angeforderter Schritt gültige Transition ist
        CheckoutState requestedState = CheckoutState.valueOf(requestedStep.toUpperCase());

        if (!isValidTransition(currentState, requestedState)) {
            throw new InvalidWorkflowException(
                "Kann nicht von " + currentState + " zu " + requestedState + " wechseln");
        }

        switch (requestedState) {
            case CART:
                displayCart(request);
                break;
            case SHIPPING:
                if (processShipping(request)) {
                    session.setAttribute("checkoutState", CheckoutState.SHIPPING);
                }
                break;
            case PAYMENT:
                if (processPayment(request)) {
                    session.setAttribute("checkoutState", CheckoutState.PAYMENT);
                }
                break;
            case CONFIRM:
                // Korrigiert: Kann nur hierher gelangen nach Zahlung
                if (confirmOrder(request)) {
                    session.setAttribute("checkoutState", CheckoutState.COMPLETE);
                }
                break;
        }
    }

    private boolean isValidTransition(CheckoutState from, CheckoutState to) {
        // Korrigiert: Gültige Zustandsübergänge definieren
        switch (from) {
            case CART:
                return to == CheckoutState.SHIPPING;
            case SHIPPING:
                return to == CheckoutState.PAYMENT || to == CheckoutState.CART;
            case PAYMENT:
                return to == CheckoutState.CONFIRM || to == CheckoutState.SHIPPING;
            case CONFIRM:
                return to == CheckoutState.COMPLETE;
            default:
                return false;
        }
    }
}
// Korrigiert: Konto-Einrichtung mit Schrittabschluss-Verfolgung
<?php
class FixedAccountSetup {

    private $requiredSteps = ['email', 'verify_email', 'password', 'profile'];

    public function handleSetup($userId, $step, $data) {
        // Korrigiert: Abgeschlossene Schritte aus Datenbank holen
        $completedSteps = $this->getCompletedSteps($userId);

        // Korrigiert: Validieren, dass Schritt ausgeführt werden kann
        if (!$this->canExecuteStep($step, $completedSteps)) {
            throw new WorkflowException("Kann Schritt nicht ausführen: $step");
        }

        $result = null;

        switch ($step) {
            case 'email':
                $result = $this->setEmail($userId, $data['email']);
                if ($result) {
                    $this->sendVerificationEmail($userId, $data['email']);
                }
                break;

            case 'verify_email':
                $result = $this->verifyEmail($userId, $data['token']);
                break;

            case 'password':
                $result = $this->setPassword($userId, $data['password']);
                break;

            case 'profile':
                $result = $this->setProfile($userId, $data);
                break;

            case 'activate':
                // Korrigiert: Alle Schritte müssen abgeschlossen sein
                if (count($completedSteps) === count($this->requiredSteps)) {
                    $result = $this->activateAccount($userId);
                } else {
                    throw new WorkflowException("Alle Schritte vor Aktivierung abschließen");
                }
                break;
        }

        if ($result) {
            $this->markStepComplete($userId, $step);
        }

        return $result;
    }

    private function canExecuteStep($step, $completedSteps) {
        // Korrigiert: Voraussetzungen für jeden Schritt definieren
        $prerequisites = [
            'email' => [],
            'verify_email' => ['email'],
            'password' => ['verify_email'],
            'profile' => ['password'],
            'activate' => ['email', 'verify_email', 'password', 'profile']
        ];

        $required = $prerequisites[$step] ?? [];

        foreach ($required as $prereq) {
            if (!in_array($prereq, $completedSteps)) {
                return false;
            }
        }

        return true;
    }
}
?>
// Korrigiert: Kreditantrag mit Workflow-Engine
class FixedLoanApplication {

    constructor() {
        // Korrigiert: Workflow-Zustände und gültige Transitionen definieren
        this.workflow = {
            states: {
                'submitted': { next: ['documents_uploaded'] },
                'documents_uploaded': { next: ['verified'] },
                'verified': { next: ['approved', 'rejected'] },
                'approved': { next: ['disbursed'] },
                'rejected': { next: [] },
                'disbursed': { next: [] }
            }
        };
    }

    async processStep(req, res) {
        const { step, applicationId, data } = req.body;

        // Korrigiert: Aktuellen Antragszustand aus Datenbank holen
        const application = await this.getApplication(applicationId);

        if (!application) {
            return res.status(404).json({ error: 'Antrag nicht gefunden' });
        }

        // Korrigiert: Zustandstransition validieren
        const targetState = this.getTargetState(step);
        if (!this.isValidTransition(application.state, targetState)) {
            return res.status(400).json({
                error: `Kann nicht von ${application.state} zu ${targetState} wechseln`
            });
        }

        let result;

        switch (step) {
            case 'apply':
                result = await this.submitApplication(data);
                break;

            case 'documents':
                result = await this.uploadDocuments(applicationId, data);
                break;

            case 'verify':
                result = await this.verifyApplication(applicationId);
                break;

            case 'approve':
                // Korrigiert: Erfordert, dass Verifizierung abgeschlossen ist
                result = await this.approveApplication(applicationId);
                break;

            case 'disburse':
                // Korrigiert: Erfordert, dass Genehmigung abgeschlossen ist
                result = await this.disburseFunds(applicationId);
                break;
        }

        if (result.success) {
            // Korrigiert: Zustand in Datenbank aktualisieren
            await this.updateApplicationState(applicationId, targetState);
        }

        return res.json(result);
    }

    isValidTransition(currentState, targetState) {
        const allowed = this.workflow.states[currentState]?.next || [];
        return allowed.includes(targetState);
    }
}

CVE-Beispiele

  • CVE-2010-2620: FTP-Server erlaubte Zugriff auf Dateien ohne ordnungsgemäßen Abschluss des Authentifizierungsschritts.
  • CVE-2005-3296: FTP-Server erlaubte Verzeichnisauflistung ohne vorherigen Login-Schritt.
  • CVE-2005-3327: Authentifizierungsumgehung möglich durch Überspringen von Startsequenz-Schritten.
  • CVE-2004-0829: Server stürzte ab, wenn "find next" ohne vorherige "find first"-Suche ausgeführt wurde.

Verwandte CWEs

  • CWE-691: Unzureichendes Kontrollfluss-Management (Eltern)
  • CWE-840: Geschäftslogik-Fehler (verwandte Kategorie)
  • CWE-696: Falsche Verhaltensreihenfolge (verwandt - fokussiert auf Produktaktionen, nicht Akteursaktionen)
  • CWE-362: Nebenläufige Ausführung mit geteilter Ressource und unzureichender Synchronisation (kann beitragen)

Referenzen

  1. MITRE Corporation. "CWE-841: Improper Enforcement of Behavioral Workflow." https://cwe.mitre.org/data/definitions/841.html
  2. OWASP. "Business Logic Security Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Business_Logic_Security_Cheat_Sheet.html
  3. OWASP. "Testing for Business Logic." OWASP Testing Guide.