Description
Stack trace exposure occurs when an application's internal exception trace is sent directly to a client. A trace can reveal class and package structures, file paths, method calls, and clues about framework or library versions. An attacker can deliberately trigger exceptions, including through boundary values, then use the collected details to find and exploit other weaknesses such as SQL injection or path traversal.
Potential impact
- Internal structure disclosure: class names, call paths, and file system paths can provide clues for bypassing defenses.
- Framework and version identification: inferred library versions can help an attacker target known CVEs.
- More targeted attacks: error locations and input flows can assist SQL injection, remote code execution, and path manipulation attacks.
- Configuration disclosure: messages may contain hostnames, configuration values, or differences between development and production environments.
Remediation
- Return generic client messages such as “Unable to process the request.” Do not include a stack trace in the response.
- Record details only in server logs, for example with
logger.error("message", ex), and apply access and retention controls. - Use a common exception handling layer, such as a Servlet Filter, Spring
@ControllerAdvice, or JAX-RSExceptionMapper, to produce consistent error responses. - Disable detailed production errors, for example with Spring Boot
server.error.include-stacktrace=never, and configure custom error pages. - Check user-facing messages for class names, paths, SQL, and configuration values.
Examples
Before
java
import javax.servlet.http.*;
import java.io.IOException;
public class ErrorEchoServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
String v = req.getParameter("div");
if (v != null) {
int n = Integer.parseInt(v);
int r = 100 / n; // 0 입력 시 예외 발생 가능
resp.getWriter().println("result=" + r);
}
} catch (Exception ex) {
// BAD: 스택 트레이스를 사용자 응답에 직접 출력
ex.printStackTrace(resp.getWriter());
}
}
}
After
java
import javax.servlet.http.*;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.*;
public class ErrorSafeServlet extends HttpServlet {
private static final Logger LOG = Logger.getLogger(ErrorSafeServlet.class.getName());
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
String v = req.getParameter("div");
if (v != null) {
int n = Integer.parseInt(v);
int r = 100 / n;
resp.getWriter().println("ok");
return;
}
resp.getWriter().println("ok");
} catch (Exception ex) {
String errorId = UUID.randomUUID().toString();
// 서버 로그에만 상세 스택 트레이스 기록
LOG.log(Level.SEVERE, "Unhandled exception, errorId=" + errorId, ex);
// 사용자에게는 일반화된 오류 메시지 전송
resp.resetBuffer();
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "요청 처리 중 오류가 발생했습니다. (id=" + errorId + ")");
}
}
}
Explanation:
- Before:
ex.printStackTrace(resp.getWriter())sends the trace in the HTTP response, exposing internal names, paths, and call flows. - After: The logger records the trace on the server. The client receives a generic message and an opaque correlation ID so that operators can locate the details without exposing them in the response.