Nicht abgefangene Ausnahme im Servlet

Beschreibung

Nicht abgefangene Ausnahme im Servlet tritt auf, wenn ein Java-Servlet Ausnahmen über den Servlet-Code hinaus propagieren lässt, ohne sie abzufangen und zu behandeln. Wenn eine Ausnahme einem Servlet entkommt, generiert der Anwendungsserver eine Standard-Fehlerantwort, die oft sensible Informationen wie Stack-Traces, interne Klassennamen, Datenbankabfragen, Dateipfade und Server-Konfigurationsdetails enthält. Diese Informationsoffenlegung hilft Angreifern, die Anwendung zu verstehen und auszunutzen.

Risiko

Stack-Traces offenbaren die interne Anwendungsstruktur, Klassennamen und Methodensignaturen. Ausnahmemeldungen können sensible Daten wie SQL-Abfragen, Dateipfade oder Benutzerinformationen enthalten. Angreifer nutzen diese Informationen für gezielte Angriffe gegen bekannte Schwachstellen. Datenbankausnahmen können Tabellen- und Spaltennamen offenlegen. Fehlermeldungen zeigen an, welche Technologien und Versionen verwendet werden. Die Informationen helfen Angreifern, effektivere Exploits zu erstellen.

Lösung

Implementieren Sie ordnungsgemäße Ausnahmebehandlung in allen Servlets mit try-catch-Blöcken. Konfigurieren Sie benutzerdefinierte Fehlerseiten in web.xml, um Standard-Fehlerantworten zu verbergen. Protokollieren Sie Ausnahmen serverseitig zum Debugging, während generische Meldungen für Benutzer angezeigt werden. Verwenden Sie Servlet-Filter für zentralisierte Ausnahmebehandlung. Exponieren Sie niemals Stack-Traces oder technische Details in Produktion. Implementieren Sie ordnungsgemäßes Logging, das Details serverseitig erfasst.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Informationsoffenlegung

Stack-Traces und Fehlermeldungen offenbaren interne Details.
SicherheitBereich: Angriffsflächen-Exposure

Technische Details helfen Angreifern, Schwachstellen zu identifizieren.
VerfügbarkeitBereich: Instabile Anwendung

Unbehandelte Ausnahmen können Anwendung in inkonsistentem Zustand belassen.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Keine Ausnahmebehandlung
@WebServlet("/user")
public class VulnerableUserServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {

        String userId = request.getParameter("id");

        // Kein try-catch - Ausnahmen propagieren zum Container
        Connection conn = dataSource.getConnection();
        PreparedStatement stmt = conn.prepareStatement(
            "SELECT * FROM users WHERE id = ?"
        );
        stmt.setInt(1, Integer.parseInt(userId));

        // NumberFormatException wenn userId keine Zahl ist
        // SQLException bei Datenbankfehler
        // NullPointerException wenn dataSource null ist

        ResultSet rs = stmt.executeQuery();
        // ... Ergebnisse verarbeiten
    }
}

// Stack-Trace dem Benutzer exponiert:
// java.lang.NumberFormatException: For input string: "abc"
//     at java.lang.NumberFormatException.forInputString(...)
//     at java.lang.Integer.parseInt(...)
//     at VulnerableUserServlet.doGet(VulnerableUserServlet.java:15)

// VERWUNDBAR: Partielle Ausnahmebehandlung
@WebServlet("/data")
public class PartiallyVulnerableServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {

        try {
            processRequest(request, response);
        } catch (SQLException e) {
            // Fängt nur SQLException, andere Ausnahmen propagieren!
            response.getWriter().println("Datenbankfehler");
        }

        // RuntimeExceptions entkommen trotzdem!
    }

    private void processRequest(HttpServletRequest request,
                                HttpServletResponse response)
            throws SQLException {
        String id = request.getParameter("id");
        // NullPointerException wenn id null ist - nicht gefangen!
        int userId = Integer.parseInt(id.trim());
        // NumberFormatException - nicht gefangen!

        // ... Datenbankoperationen
    }
}

// VERWUNDBAR: Ausnahmedetails exponieren
@WebServlet("/process")
public class ExposingServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

        try {
            processData(request);
        } catch (Exception e) {
            // Exponiert interne Details!
            response.getWriter().println("Fehler: " + e.getMessage());
            response.getWriter().println("Stack-Trace:");
            e.printStackTrace(response.getWriter());
        }
    }
}

// VERWUNDBAR: Erneutes Werfen mit sensiblen Informationen
@WebServlet("/api")
public class RethrowingServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {

        try {
            queryDatabase(request.getParameter("query"));
        } catch (SQLException e) {
            // Verpackt aber enthält sensible SQL-Ausnahmemeldung
            throw new ServletException(
                "Datenbankabfrage fehlgeschlagen: " + e.getMessage(), e
            );
            // Meldung könnte SQL-Abfrage oder Tabellennamen enthalten
        }
    }
}
<!-- VERWUNDBAR: Keine benutzerdefinierten Fehlerseiten konfiguriert -->
<!-- web.xml mit fehlenden error-page-Elementen -->
<web-app>
    <servlet>
        <servlet-name>myServlet</servlet-name>
        <servlet-class>com.example.MyServlet</servlet-class>
    </servlet>
    <!-- Keine error-page-Konfiguration! -->
    <!-- Standard-Container-Fehlerseiten zeigen Stack-Traces -->
</web-app>

Lösungscode

// SICHER: Umfassende Ausnahmebehandlung
@WebServlet("/user")
public class SafeUserServlet extends HttpServlet {

    private static final Logger logger =
        LoggerFactory.getLogger(SafeUserServlet.class);

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {

        try {
            String userId = request.getParameter("id");

            if (userId == null || userId.isEmpty()) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                                   "Benutzer-ID erforderlich");
                return;
            }

            int id;
            try {
                id = Integer.parseInt(userId);
            } catch (NumberFormatException e) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                                   "Ungültiges Benutzer-ID-Format");
                return;
            }

            User user = userService.findById(id);

            if (user == null) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND,
                                   "Benutzer nicht gefunden");
                return;
            }

            writeUserResponse(response, user);

        } catch (SQLException e) {
            // Vollständige Details serverseitig protokollieren
            logger.error("Datenbankfehler beim Abrufen des Benutzers", e);
            // Generische Meldung an Client
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                               "Bei der Verarbeitung Ihrer Anfrage ist ein Fehler aufgetreten");

        } catch (Exception e) {
            // Catch-all für unerwartete Ausnahmen
            logger.error("Unerwarteter Fehler im User-Servlet", e);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                               "Ein unerwarteter Fehler ist aufgetreten");
        }
    }
}

// SICHER: Ausnahmebehandlungsfilter für alle Servlets
@WebFilter("/*")
public class ExceptionHandlingFilter implements Filter {

    private static final Logger logger =
        LoggerFactory.getLogger(ExceptionHandlingFilter.class);

    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException {

        try {
            chain.doFilter(request, response);

        } catch (Exception e) {
            logger.error("Unbehandelte Ausnahme bei Anfrageverarbeitung", e);

            if (response instanceof HttpServletResponse) {
                HttpServletResponse httpResponse = (HttpServletResponse) response;

                if (!httpResponse.isCommitted()) {
                    httpResponse.reset();
                    httpResponse.sendError(
                        HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                        "Ein Fehler ist aufgetreten"
                    );
                }
            }
        }
    }

    @Override
    public void init(FilterConfig filterConfig) {}

    @Override
    public void destroy() {}
}

// SICHER: Benutzerdefinierte Ausnahme mit sicherer Nachricht
public class ApplicationException extends Exception {

    private final String userMessage;
    private final String internalDetails;

    public ApplicationException(String userMessage, String internalDetails) {
        super(internalDetails);
        this.userMessage = userMessage;
        this.internalDetails = internalDetails;
    }

    public String getUserMessage() {
        return userMessage;  // Sicher für Benutzer anzuzeigen
    }

    public String getInternalDetails() {
        return internalDetails;  // Nur für Logging
    }
}

// Verwendung im Servlet
@WebServlet("/process")
public class SafeProcessServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

        try {
            processData(request);
            response.getWriter().println("{\"status\": \"success\"}");

        } catch (ApplicationException e) {
            logger.error(e.getInternalDetails(), e);
            sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST,
                              e.getUserMessage());

        } catch (Exception e) {
            logger.error("Verarbeitung fehlgeschlagen", e);
            sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                              "Verarbeitung fehlgeschlagen. Bitte versuchen Sie es erneut.");
        }
    }

    private void sendErrorResponse(HttpServletResponse response,
                                   int status, String message)
            throws IOException {
        response.setStatus(status);
        response.setContentType("application/json");
        response.getWriter().printf("{\"error\": \"%s\"}", escapeJson(message));
    }
}
<!-- SICHER: Benutzerdefinierte Fehlerseiten konfiguriert -->
<web-app>
    <servlet>
        <servlet-name>myServlet</servlet-name>
        <servlet-class>com.example.MyServlet</servlet-class>
    </servlet>

    <!-- Benutzerdefinierte Fehlerseiten verbergen Stack-Traces -->
    <error-page>
        <error-code>404</error-code>
        <location>/error/404.jsp</location>
    </error-page>

    <error-page>
        <error-code>500</error-code>
        <location>/error/500.jsp</location>
    </error-page>

    <!-- Catch-all für Ausnahmen -->
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/error/general.jsp</location>
    </error-page>

    <error-page>
        <exception-type>java.lang.Throwable</exception-type>
        <location>/error/general.jsp</location>
    </error-page>
</web-app>
<!-- SICHER: Benutzerdefinierte Fehlerseite -->
<%-- /error/500.jsp --%>
<%@ page isErrorPage="true" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <title>Fehler</title>
</head>
<body>
    <h1>Ein Fehler ist aufgetreten</h1>
    <p>Es tut uns leid, aber etwas ist schiefgelaufen. Bitte versuchen Sie es später erneut.</p>
    <p>Falls das Problem weiterhin besteht, kontaktieren Sie bitte den Support mit Referenz:
       <%= java.util.UUID.randomUUID().toString().substring(0, 8) %></p>

    <%-- Fehler serverseitig protokollieren, nicht dem Benutzer anzeigen --%>
    <%
        if (exception != null) {
            org.slf4j.LoggerFactory.getLogger("ErrorPage")
                .error("Fehlerseite angezeigt", exception);
        }
    %>
</body>
</html>
// SICHER: Spring Boot Ausnahmebehandlung
@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger =
        LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        logger.error("Unbehandelte Ausnahme", e);

        ErrorResponse error = new ErrorResponse(
            "Ein unerwarteter Fehler ist aufgetreten",
            UUID.randomUUID().toString()
        );

        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(error);
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException e) {
        ErrorResponse error = new ErrorResponse(
            e.getMessage(),  // Sichere Benutzernachricht
            null
        );

        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(error);
    }
}

public class ErrorResponse {
    private String message;
    private String referenceId;

    // Konstruktor, Getter, Setter
}

Ausgenutzt in der Praxis

Datenbankstruktur-Offenlegung

SQL-Ausnahmen offenbarten Tabellennamen, Spaltennamen und Abfragestruktur an Angreifer.

Framework-Versions-Exposure

Stack-Traces offenbarten spezifische Framework-Versionen mit bekannten Schwachstellen.

Dateipfad-Offenlegung

FileNotFoundException-Meldungen offenbarten die Server-Dateisystemstruktur.


Tools zum Testen/Ausnutzen

  • Burp Suite — Fehlerantworten auslösen und analysieren
  • OWASP ZAP — automatisierte Fehlerseitenerkennung
  • Fuzzing-Tools — unerwartete Eingaben generieren, um Ausnahmen auszulösen
  • Statische Analysetools — unbehandelte Ausnahmen erkennen

CVE-Beispiele

  • Informationsoffenlegungs-CVEs durch Stack-Trace-Exposure
  • Anwendungsenumeration durch Fehlermeldungen

Referenzen

  1. MITRE. "CWE-600: Uncaught Exception in Servlet." https://cwe.mitre.org/data/definitions/600.html
  2. OWASP. "Improper Error Handling." https://owasp.org/