Uncaught Exception in Servlet
Description
Uncaught Exception in Servlet occurs when a Java servlet allows exceptions to propagate beyond the servlet code without being caught and handled. When an exception escapes a servlet, the application server generates a default error response that often includes sensitive information such as stack traces, internal class names, database queries, file paths, and server configuration details. This information disclosure aids attackers in understanding and exploiting the application.
Risk
Stack traces reveal internal application structure, class names, and method signatures. Exception messages may contain sensitive data like SQL queries, file paths, or user information. Attackers use this information for targeted attacks against known vulnerabilities. Database exceptions may reveal table and column names. Error messages indicate which technologies and versions are in use. The information helps attackers craft more effective exploits.
Solution
Implement proper exception handling in all servlets with try-catch blocks. Configure custom error pages in web.xml to hide default error responses. Log exceptions server-side for debugging while showing generic messages to users. Use servlet filters for centralized exception handling. Never expose stack traces or technical details in production. Implement proper logging that captures details server-side.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Information Disclosure Stack traces and error messages reveal internal details. |
| Security | Scope: Attack Surface Exposure Technical details help attackers identify vulnerabilities. |
| Availability | Scope: Unstable Application Unhandled exceptions may leave application in inconsistent state. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: No exception handling
@WebServlet("/user")
public class VulnerableUserServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String userId = request.getParameter("id");
// No try-catch - exceptions propagate to container
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
stmt.setInt(1, Integer.parseInt(userId));
// NumberFormatException if userId is not a number
// SQLException if database error
// NullPointerException if dataSource is null
ResultSet rs = stmt.executeQuery();
// ... process results
}
}
// Stack trace exposed to user:
// java.lang.NumberFormatException: For input string: "abc"
// at java.lang.NumberFormatException.forInputString(...)
// at java.lang.Integer.parseInt(...)
// at VulnerableUserServlet.doGet(VulnerableUserServlet.java:15)
// VULNERABLE: Partial exception handling
@WebServlet("/data")
public class PartiallyVulnerableServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
try {
processRequest(request, response);
} catch (SQLException e) {
// Only catches SQLException, other exceptions propagate!
response.getWriter().println("Database error");
}
// RuntimeExceptions still escape!
}
private void processRequest(HttpServletRequest request,
HttpServletResponse response)
throws SQLException {
String id = request.getParameter("id");
// NullPointerException if id is null - not caught!
int userId = Integer.parseInt(id.trim());
// NumberFormatException - not caught!
// ... database operations
}
}
// VULNERABLE: Exposing exception details
@WebServlet("/process")
public class ExposingServlet extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
try {
processData(request);
} catch (Exception e) {
// Exposes internal details!
response.getWriter().println("Error: " + e.getMessage());
response.getWriter().println("Stack trace:");
e.printStackTrace(response.getWriter());
}
}
}
// VULNERABLE: Re-throwing with sensitive information
@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) {
// Wraps but includes sensitive SQL exception message
throw new ServletException(
"Database query failed: " + e.getMessage(), e
);
// Message might include SQL query or table names
}
}
}
<!-- VULNERABLE: No custom error pages configured -->
<!-- web.xml with missing error-page elements -->
<web-app>
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.example.MyServlet</servlet-class>
</servlet>
<!-- No error-page configuration! -->
<!-- Default container error pages will show stack traces -->
</web-app>
Fixed Code
// SAFE: Comprehensive exception handling
@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,
"User ID required");
return;
}
int id;
try {
id = Integer.parseInt(userId);
} catch (NumberFormatException e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Invalid user ID format");
return;
}
User user = userService.findById(id);
if (user == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
"User not found");
return;
}
writeUserResponse(response, user);
} catch (SQLException e) {
// Log full details server-side
logger.error("Database error retrieving user", e);
// Generic message to client
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"An error occurred processing your request");
} catch (Exception e) {
// Catch-all for unexpected exceptions
logger.error("Unexpected error in user servlet", e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"An unexpected error occurred");
}
}
}
// SAFE: Exception handling filter for all 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("Unhandled exception in request processing", e);
if (response instanceof HttpServletResponse) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (!httpResponse.isCommitted()) {
httpResponse.reset();
httpResponse.sendError(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"An error occurred"
);
}
}
}
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
// SAFE: Custom exception with safe messaging
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; // Safe to show to user
}
public String getInternalDetails() {
return internalDetails; // Only for logging
}
}
// Usage in 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("Processing failed", e);
sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Processing failed. Please try again.");
}
}
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));
}
}
<!-- SAFE: Custom error pages configured -->
<web-app>
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.example.MyServlet</servlet-class>
</servlet>
<!-- Custom error pages hide 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 for exceptions -->
<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>
<!-- SAFE: Custom error page -->
<%-- /error/500.jsp --%>
<%@ page isErrorPage="true" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<h1>An Error Occurred</h1>
<p>We're sorry, but something went wrong. Please try again later.</p>
<p>If the problem persists, please contact support with reference:
<%= java.util.UUID.randomUUID().toString().substring(0, 8) %></p>
<%-- Log error server-side, don't display to user --%>
<%
if (exception != null) {
org.slf4j.LoggerFactory.getLogger("ErrorPage")
.error("Error page displayed", exception);
}
%>
</body>
</html>
// SAFE: Spring Boot exception handling
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger =
LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
logger.error("Unhandled exception", e);
ErrorResponse error = new ErrorResponse(
"An unexpected error occurred",
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(), // Safe user message
null
);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(error);
}
}
public class ErrorResponse {
private String message;
private String referenceId;
// Constructor, getters, setters
}
Exploited in the Wild
Database Structure Disclosure
SQL exceptions revealed table names, column names, and query structure to attackers.
Framework Version Exposure
Stack traces disclosed specific framework versions with known vulnerabilities.
File Path Disclosure
FileNotFoundException messages revealed server file system structure.
Tools to test/exploit
-
Burp Suite — trigger and analyze error responses.
-
OWASP ZAP — automated error page detection.
-
Fuzzing tools — generate unexpected inputs to trigger exceptions.
-
Static analysis tools — detect unhandled exceptions.
CVE Examples
-
Information disclosure CVEs from stack trace exposure.
-
Application enumeration through error messages.
References
-
MITRE. "CWE-600: Uncaught Exception in Servlet." https://cwe.mitre.org/data/definitions/600.html
-
OWASP. "Improper Error Handling." https://owasp.org/