Servlet-Laufzeitfehlermeldung mit sensiblen Informationen

Beschreibung

Servlet-Laufzeitfehlermeldung mit sensiblen Informationen ist eine Schwachstelle, bei der eine Servlet-Anwendung Fehlermeldungen erzeugt, die sensible Systemdaten an Benutzer offenlegen. Wenn unbehandelte Ausnahmen im Servlet-Code auftreten, kann die Laufzeitumgebung detaillierte Stack-Traces, Dateipfade, Konfigurationsdetails und Code-Ausschnitte an Endbenutzer anzeigen. Diese Informationsoffenlegung hilft Angreifern, die Anwendungsarchitektur zu verstehen, spezifische verwendete Technologien und Versionen zu identifizieren und potenzielle Angriffsvektoren zu entdecken.

Risiko

Servlet-Laufzeitfehlermeldungen stellen erhebliche Aufklärungsrisiken dar. Stack-Traces offenbaren Paketnamen, Klassenhierarchien und Methodennamen, die Anwendungsinterna preisgeben. Dateipfade in Fehlern zeigen die Verzeichnisstruktur und den Installationsort der Anwendung. Konfigurationsfehler können Datenbankverbindungsstrings, Servernamen oder Authentifizierungsdetails offenlegen. Ausnahmemeldungen können Benutzereingaben enthalten, die verarbeitet wurden, und potenziell Validierungslogik oder erwartete Datenformate offenbaren. Angreifer können gezielt Fehler durch fehlerhafte Eingaben auslösen, um systematisch die interne Struktur der Anwendung zu kartieren.

Lösung

Implementieren Sie umfassende Ausnahmebehandlung in allen Servlet-Methoden. Konfigurieren Sie den Servlet-Container, um benutzerdefinierte Fehlerseiten anstelle der standardmäßigen Ausnahmeausgabe anzuzeigen. Fangen Sie alle Laufzeitausnahmen innerhalb des Servlet-Codes ab und protokollieren Sie detaillierte Informationen serverseitig, während Sie Benutzern generische Fehlermeldungen präsentieren. Verwenden Sie web.xml error-page-Konfigurationen zur Behandlung spezifischer Ausnahmetypen und HTTP-Fehlercodes. Vermeiden Sie die Verwendung von Ausnahmemeldungen oder Stack-Traces in sichtbarer Benutzerausgabe. Implementieren Sie einen globalen Ausnahmebehandlungs-Filter, der unbehandelte Ausnahmen abfängt, bevor sie den Benutzer erreichen.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Fehlermeldungen können Dateispeicherorte, absolute Pfade des Web-Roots, Anwendungsdateispeicherorte, Konfigurationsinformationen und Teile des fehlgeschlagenen Codes offenlegen.

Beispielcode + Lösungscode

Verwundbarer Code

// Verwundbar: Servlet mit unbehandelten Ausnahmen
public class VerwundbaresServlet extends HttpServlet {

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

        // Verwundbar: NullPointerException wenn username null ist
        String username = request.getParameter("username");
        if (username.length() < 10) {  // NPE hier legt Stack-Trace offen
            response.getWriter().println("Benutzername zu kurz");
        }

        // Verwundbar: Zahlenformatausnahme legt Details offen
        String alterStr = request.getParameter("alter");
        int alter = Integer.parseInt(alterStr);  // Wirft bei ungültiger Eingabe

        // Verwundbar: Dateioperationsfehler offenbart Pfade
        String configPath = "/etc/app/config.properties";
        FileInputStream fis = new FileInputStream(configPath);  // Offenbart Pfad bei Fehler

        // Verwundbar: Datenbankfehler legt Verbindungsdetails offen
        String dbUrl = "jdbc:mysql://prod-db:3306/users";
        Connection conn = DriverManager.getConnection(dbUrl, "admin", "password");
        // Verbindungsfehler offenbart Datenbank-Hostnamen
    }

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

        // Verwundbar: Array-Index-Fehler offenbart Anwendungslogik
        String[] items = request.getParameterValues("items");
        String first = items[0];  // ArrayIndexOutOfBoundsException

        // Verwundbar: Division durch Null offenbart Verarbeitungslogik
        int total = Integer.parseInt(request.getParameter("total"));
        int count = Integer.parseInt(request.getParameter("count"));
        int average = total / count;  // ArithmeticException bei Null
    }
}

// Beispiel-Fehlerausgabe für Benutzer sichtbar:
// java.lang.NullPointerException
//     at com.example.app.servlet.VerwundbaresServlet.doPost(VerwundbaresServlet.java:25)
//     at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
//     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(...)
// Offenbart: Paketstruktur, Klassennamen, Zeilennummern, Serverdetails
// Verwundbar: JSP mit Inline-Ausnahmeoffenlegung
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<%
    // Verwundbar: Keine Ausnahmebehandlung
    String userId = request.getParameter("id");
    int id = Integer.parseInt(userId);

    // Verwundbar: Direkter Datenbankzugriff mit offengelegten Fehlern
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection(
        "jdbc:mysql://db.internal:3306/users", "webapp", "secret123"
    );

    // Fehler bei ungültigem Treiber legt offen:
    // java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
    // at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    // ...

    // Fehler bei Verbindungsfehler legt offen:
    // com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:
    // Communications link failure to db.internal:3306
%>
<!-- Verwundbar: web.xml ohne benutzerdefinierte Fehlerseiten -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>com.example.VerwundbaresServlet</servlet-class>
    </servlet>

    <!-- Verwundbar: Keine error-page-Konfigurationen -->
    <!-- Standard-Container-Fehlerseiten zeigen Stack-Traces -->
</web-app>

Lösungscode

// Behoben: Servlet mit umfassender Ausnahmebehandlung
public class SicheresServlet extends HttpServlet {
    private static final Logger logger = LoggerFactory.getLogger(SicheresServlet.class);

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

        try {
            // Behoben: Null-sichere Parameterbehandlung
            String username = request.getParameter("username");
            if (username == null || username.isEmpty()) {
                sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST,
                    "Benutzername ist erforderlich");
                return;
            }

            if (username.length() < 10) {
                sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST,
                    "Benutzername muss mindestens 10 Zeichen haben");
                return;
            }

            // Behoben: Sichere Zahlenanalyse mit Validierung
            String alterStr = request.getParameter("alter");
            int alter;
            try {
                alter = Integer.parseInt(alterStr);
                if (alter < 0 || alter > 150) {
                    sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST,
                        "Ungültiger Alterswert");
                    return;
                }
            } catch (NumberFormatException e) {
                logger.warn("Ungültiger Alter-Parameter: {}", sanitize(alterStr));
                sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST,
                    "Alter muss eine gültige Zahl sein");
                return;
            }

            // Behoben: Dateioperationen mit generischem Fehler
            try {
                processConfigFile();
            } catch (IOException e) {
                logger.error("Konfigurationsdateifehler", e);
                sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Konfigurationsfehler. Bitte kontaktieren Sie den Support.");
                return;
            }

            // Behoben: Datenbankoperationen mit sicherer Fehlerbehandlung
            try {
                processUserData(username, alter);
            } catch (SQLException e) {
                logger.error("Datenbankfehler bei Benutzerdatenverarbeitung", e);
                sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Anfrage könnte nicht verarbeitet werden. Bitte versuchen Sie es später erneut.");
                return;
            }

            response.getWriter().println("Erfolg");

        } catch (Exception e) {
            // Behoben: Catch-all für unerwartete Fehler
            logger.error("Unerwarteter Fehler in doPost", e);
            sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                "Ein unerwarteter Fehler ist aufgetreten. Referenz: " + generateErrorId());
        }
    }

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

        try {
            // Behoben: Sichere Array-Behandlung
            String[] items = request.getParameterValues("items");
            if (items == null || items.length == 0) {
                sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST,
                    "Mindestens ein Element ist erforderlich");
                return;
            }
            String first = items[0];

            // Behoben: Division mit Null-Prüfung
            int total = parseIntSafe(request.getParameter("total"), 0);
            int count = parseIntSafe(request.getParameter("count"), 0);

            if (count == 0) {
                sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST,
                    "Anzahl darf nicht null sein");
                return;
            }

            int average = total / count;
            response.getWriter().println("Durchschnitt: " + average);

        } catch (Exception e) {
            logger.error("Unerwarteter Fehler in doGet", e);
            sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                "Ein Fehler ist aufgetreten");
        }
    }

    private void sendErrorResponse(HttpServletResponse response, int statusCode,
            String message) throws IOException {
        response.setStatus(statusCode);
        response.setContentType("application/json");
        response.getWriter().println("{\"error\": \"" + escapeJson(message) + "\"}");
    }

    private int parseIntSafe(String value, int defaultValue) {
        try {
            return value != null ? Integer.parseInt(value) : defaultValue;
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    private String sanitize(String input) {
        if (input == null) return "null";
        return input.replaceAll("[^a-zA-Z0-9]", "_").substring(0, Math.min(input.length(), 50));
    }

    private String generateErrorId() {
        return UUID.randomUUID().toString().substring(0, 8);
    }

    private String escapeJson(String text) {
        return text.replace("\"", "\\\"");
    }
}
// Behoben: Globaler Ausnahmebehandlungs-Filter
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) {
            String errorId = UUID.randomUUID().toString().substring(0, 8);

            // Behoben: Vollständige Details serverseitig protokollieren
            logger.error("Unbehandelte Ausnahme [{}]", errorId, e);

            // Behoben: Generischen Fehler an Benutzer zurückgeben
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            httpResponse.setContentType("text/html");
            httpResponse.getWriter().println(
                "<html><body>" +
                "<h1>Ein Fehler ist aufgetreten</h1>" +
                "<p>Bitte versuchen Sie es später erneut. Fehlerreferenz: " + errorId + "</p>" +
                "</body></html>"
            );
        }
    }
}
<!-- Behoben: web.xml mit benutzerdefinierten Fehlerseiten -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <servlet>
        <servlet-name>SicheresServlet</servlet-name>
        <servlet-class>com.example.SicheresServlet</servlet-class>
    </servlet>

    <!-- Behoben: Globaler Ausnahmebehandlungs-Filter -->
    <filter>
        <filter-name>ExceptionHandler</filter-name>
        <filter-class>com.example.ExceptionHandlingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ExceptionHandler</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Behoben: Benutzerdefinierte Fehlerseiten für HTTP-Fehler -->
    <error-page>
        <error-code>404</error-code>
        <location>/error/404.html</location>
    </error-page>
    <error-page>
        <error-code>500</error-code>
        <location>/error/500.html</location>
    </error-page>

    <!-- Behoben: Benutzerdefinierte Fehlerseiten für Ausnahmen -->
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/error/generic.html</location>
    </error-page>
    <error-page>
        <exception-type>java.lang.RuntimeException</exception-type>
        <location>/error/generic.html</location>
    </error-page>
    <error-page>
        <exception-type>java.sql.SQLException</exception-type>
        <location>/error/generic.html</location>
    </error-page>
</web-app>

CVE-Beispiele

Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE gelistet. Servlet-Fehlerinformationsoffenlegung wird jedoch häufig beobachtet bei:

  • Falsch konfigurierten Apache Tomcat-Installationen
  • Standard-J2EE-Anwendungsdeployments
  • Entwicklungsmodus-Einstellungen in Produktion

Referenzen

  1. MITRE Corporation. "CWE-536: Servlet Runtime Error Message Containing Sensitive Information." https://cwe.mitre.org/data/definitions/536.html
  2. OWASP. "Error Handling Cheat Sheet."
  3. Oracle. "Java Servlet Specification - Error Handling."