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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Informationsoffenlegung Stack-Traces und Fehlermeldungen offenbaren interne Details. |
| Sicherheit | Bereich: Angriffsflächen-Exposure Technische Details helfen Angreifern, Schwachstellen zu identifizieren. |
| Verfügbarkeit | Bereich: 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
- MITRE. "CWE-600: Uncaught Exception in Servlet." https://cwe.mitre.org/data/definitions/600.html
- OWASP. "Improper Error Handling." https://owasp.org/