Deklaration von Throws für generische Exceptions

Beschreibung

Deklaration von Throws für generische Exceptions ist eine Schwachstelle, die auftritt, wenn Methoden deklarieren, dass sie übermäßig breite Exceptions wie Exception oder Throwable werfen, anstatt spezifische Exception-Typen zu verwenden. Diese Praxis verschleiert wichtige Details darüber, was schiefgehen kann, und erzeugt unangemessene Reaktionen auf spezifische Fehlerbedingungen. Javas Exception-Mechanismus ist darauf ausgelegt, Entwicklern das Abfangen und Reagieren auf bestimmte Exception-Typen zu ermöglichen, und die Deklaration generischer Exceptions unterläuft diese Fähigkeit, wodurch eine ordnungsgemäße Fehlerbehandlung durch Aufrufer schwierig oder unmöglich wird.

Risiko

Wenn Methoden deklarieren, dass sie generische Exceptions werfen, können Aufrufer spezifische Fehlerbedingungen nicht angemessen antizipieren oder behandeln. Der Aufrufer ist gezwungen, entweder die generische Exception abzufangen (was zu CWE-396 führt) oder zu deklarieren, dass er ebenfalls die generische Exception wirft, wodurch das Problem propagiert wird. Sicherheitsrelevante Exceptions vermischen sich mit alltäglichen Fehlern, was die Implementierung angemessener Sicherheitsreaktionen erschwert. Generische Throw-Deklarationen können Details über unerwartete Angreiferaktivitäten verbergen, indem sie die Fehlersuche bei spezifischen Fehlerbedingungen erschweren. Die Code-Wartung wird schwieriger, da die tatsächlichen Exceptions, die auftreten können, undokumentiert sind.

Lösung

Deklarieren Sie spezifische Exception-Typen, die Methoden werfen können. Erstellen Sie benutzerdefinierte Exception-Hierarchien, die verschiedene Fehlerbedingungen sinnvoll kategorisieren. Dokumentieren Sie, welche Exceptions jede Methode mit throws-Klauseln für geprüfte Exceptions und Javadoc für ungeprüfte Exceptions werfen kann. Bewahren Sie beim Einwickeln von Exceptions auf niedrigerer Ebene die ursprüngliche Exception als Ursache. Überlegen Sie, ob jede Exception geprüft oder ungeprüft sein sollte, basierend darauf, ob Aufrufer sich vernünftigerweise davon erholen können. Refaktorisieren Sie bestehenden Code, der generische Exception-Deklarationen verwendet, um spezifische Typen zu verwenden.

Häufige Auswirkungen

AuswirkungDetails
NichtabstreitbarkeitBereich: Nichtabstreitbarkeit, Sonstiges

Aktivitäten verbergen, Ausführungslogik ändern -- Das Werfen einer generischen Exception kann Details über unerwartete Angreiferaktivitäten verbergen, indem es die ordnungsgemäße Fehlersuche bei Fehlerbedingungen während der Ausführung erschwert.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Generische throws-Deklaration
public class VulnerableService {

    // VERWUNDBAR: Wirft generische Exception
    public void doExchange() throws Exception {
        // Aufrufer hat keine Ahnung, welche spezifischen Exceptions zu erwarten sind
        connectToServer();
        sendData();
        receiveResponse();
    }

    // VERWUNDBAR: Wirft Throwable (noch schlimmer)
    public void processRequest() throws Throwable {
        // Schließt Error-Typen ein, die nicht deklariert werden sollten
        handleRequest();
    }

    // VERWUNDBAR: Generische Exception verbirgt, was schiefgehen kann
    public User authenticate(String username, String password)
            throws Exception {
        // Könnte sein: UserNotFoundException, InvalidPasswordException,
        // DatabaseException, AccountLockedException usw.
        User user = userRepository.findByUsername(username);
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new Exception("Authentication failed");
        }
        return user;
    }

    // VERWUNDBAR: Kette generischer Exceptions
    public void processOrder(Order order) throws Exception {
        validateOrder(order);  // throws Exception
        chargePayment(order);  // throws Exception
        shipOrder(order);      // throws Exception
    }
}
// VERWUNDBAR: C++ generische Exception-Spezifikation
class VulnerableProcessor {
public:
    // VERWUNDBAR: Übermäßig breite Exception-Spezifikation
    int process() throw(std::exception) {
        // Aufrufer kann nicht zwischen Exception-Typen unterscheiden
        return doProcessing();
    }

    // VERWUNDBAR: Wirft generische Exception
    void validate() {
        if (!isValid()) {
            throw std::runtime_error("Validation failed");
            // Sollte spezifische Validierungs-Exception werfen
        }
    }
};

Sichere Lösung

// SICHER: Spezifische throws-Deklarationen
public class SecureService {

    // SICHER: Deklariert spezifische Exceptions
    public void doExchange()
            throws ConnectionException, DataTransferException {
        connectToServer();   // throws ConnectionException
        sendData();          // throws DataTransferException
        receiveResponse();   // throws DataTransferException
    }

    // SICHER: Spezifische Exception-Hierarchie für Authentifizierung
    public User authenticate(String username, String password)
            throws AuthenticationException {
        try {
            User user = userRepository.findByUsername(username);
            if (user == null) {
                throw new UserNotFoundException(username);
            }
            if (user.isLocked()) {
                throw new AccountLockedException(username);
            }
            if (!passwordEncoder.matches(password, user.getPassword())) {
                throw new InvalidCredentialsException();
            }
            return user;
        } catch (DatabaseException e) {
            // Infrastruktur-Exception einwickeln
            throw new AuthenticationException("Database error", e);
        }
    }

    // SICHER: Klare Exception-Typen für Bestellverarbeitung
    public void processOrder(Order order)
            throws ValidationException, PaymentException, ShippingException {
        validateOrder(order);   // throws ValidationException
        chargePayment(order);   // throws PaymentException
        shipOrder(order);       // throws ShippingException
    }
}

// SICHER: Benutzerdefinierte Exception-Hierarchie
public abstract class AuthenticationException extends Exception {
    public AuthenticationException(String message) {
        super(message);
    }
    public AuthenticationException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class UserNotFoundException extends AuthenticationException {
    private final String username;

    public UserNotFoundException(String username) {
        super("User not found: " + username);
        this.username = username;
    }

    public String getUsername() { return username; }
}

public class InvalidCredentialsException extends AuthenticationException {
    public InvalidCredentialsException() {
        super("Invalid credentials");
    }
}

public class AccountLockedException extends AuthenticationException {
    private final String username;

    public AccountLockedException(String username) {
        super("Account locked: " + username);
        this.username = username;
    }

    public String getUsername() { return username; }
}

// SICHER: Ordnungsgemäße Exception-Behandlung ermöglicht durch spezifische Deklarationen
public class SecureController {

    public Response handleLogin(String username, String password) {
        try {
            User user = authService.authenticate(username, password);
            return Response.ok(user);
        } catch (UserNotFoundException e) {
            // SICHER: Kann bei Bedarf unterschiedlich behandelt werden
            // (aber möglicherweise gleiche Antwort aus Sicherheitsgründen)
            return Response.unauthorized("Invalid credentials");
        } catch (InvalidCredentialsException e) {
            // SICHER: Kann Rate-Limiting implementieren
            rateLimiter.recordFailedAttempt(username);
            return Response.unauthorized("Invalid credentials");
        } catch (AccountLockedException e) {
            // SICHER: Spezifische Behandlung für gesperrte Konten
            auditLog.logLockedAccountAttempt(e.getUsername());
            return Response.forbidden("Account is locked");
        }
    }
}
// SICHER: Spezifische Exception-Typen in C++
#include <stdexcept>

class ValidationException : public std::runtime_error {
public:
    explicit ValidationException(const std::string& msg)
        : std::runtime_error(msg) {}
};

class ProcessingException : public std::runtime_error {
public:
    explicit ProcessingException(const std::string& msg)
        : std::runtime_error(msg) {}
};

class SecureProcessor {
public:
    // SICHER: Keine Exception-Spezifikation (moderner C++-Stil)
    // Exceptions in Kommentaren dokumentieren oder noexcept verwenden wo angemessen
    int process() {
        // Wirft spezifische Exceptions
        validate();    // throws ValidationException
        return execute();  // throws ProcessingException
    }

    void validate() {
        if (!isValid()) {
            // SICHER: Wirft spezifische Exception
            throw ValidationException("Input validation failed");
        }
    }
};

CVE-Beispiele

Für diese CWE sind keine spezifischen CVEs aufgeführt. Das Schwachstellenmuster tritt auf in:

  • Java-APIs mit throws-Exception-Deklarationen
  • Bibliotheken, die generische Exceptions an Aufrufer propagieren
  • Code, der spezifische Exceptions in generische Typen einwickelt

Referenzen

  1. MITRE Corporation. "CWE-397: Declaration of Throws for Generic Exception." https://cwe.mitre.org/data/definitions/397.html
  2. Joshua Bloch. "Effective Java." Punkt 73: Wirf Exceptions, die der Abstraktion angemessen sind.