I am seeing a ton of RequestRejectedException
entries in my Tomcat log (sample pasted below). These started appearing in my log file after a minor version upgrade (Spring Security 4.2.4, IIRC) a few months ago, so this is clearly a new security feature in Spring that is enabled by default. A similar issue is reported here, but my question involves specifically how to intercept these exceptions in a controller. There is a Spring Security bug documented for this problem (Provide a way to handle RequestRejectedException). However, they aren't targeting a fix for this problem until Spring 5.1.
I understand why these exceptions are being thrown, and I do not want to disable this security feature.
I want to gain some control over this feature so that:
500 Internal Server Error
(which is wildly incorrect, this should be a 400 Bad Request
).I want to find a way to log the URL that was requested, but also suppress the stack trace specifically for these exceptions because they are polluting my log files without giving me any helpful information. Optimally, I'd like to intercept these exceptions and handle them in my application layer instead of reporting them in the Tomcat log at all.
For example, this is one of thousands of these log entries that appear every day in my catalina.out
:
Aug 10, 2018 2:01:36 PM org.apache.catalina.core.StandardWrapperValve invoke SEVERE: Servlet.service() for servlet [dispatcher] in context with path [] threw exception org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";" at org.springframework.security.web.firewall.StrictHttpFirewall.rejectedBlacklistedUrls(StrictHttpFirewall.java:265) at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:245) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:193) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:486) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)
I'm seeing over 3,200 of these in a two day period, and it has quickly become the single largest contributor to my catalina.out
log file, to the point that it prevents me from seeing other, legitimate problems. Essentially, this new Spring Security feature is a form of built-in Denial-of-Service, and it has wasted hours of my time since April. I am not saying that it is not an important feature, simply that the default implementation is completely botched, and I want to find a way gain some control over it, both as a developer and as a systems administrator.
I use a custom Error Controller for intercepting many other Exception types (including IOException
) in Spring. However, RequestRejectedException
seems to be falling through for some reason.
This is the relevant part of my ErrorController.java
, to give an idea of what I'm trying to accomplish:
@ControllerAdvice public final class ErrorController { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(ErrorController.class.getName()); /** * Generates an Error page by intercepting exceptions generated from HttpFirewall. * * @param ex A RequestRejectedException exception. * @return The tile definition name for the page. */ @ExceptionHandler(RequestRejectedException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public String handleRequestRejectedException(final HttpServletRequest request, final RequestRejectedException ex) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, "Request Rejected", ex); } LOGGER.log(Level.WARNING, "Rejected request for [" + request.getRequestURL().toString() + "]. Reason: " + ex.getMessage()); return "errorPage"; } /** * Generates a Server Error page. * * @param ex An exception. * @return The tile definition name for the page. */ @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public String handleException(final Exception ex) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Server Error", ex); } return "errorPage"; } }
This error controller works for many exceptions. For example, it successfully intercepted this IllegalStateException
:
Aug 05, 2018 7:50:30 AM com.mycompany.spring.controller.ErrorController handleException SEVERE: Server Error java.lang.IllegalStateException: Cannot create a session after the response has been committed at org.apache.catalina.connector.Request.doGetSession(Request.java:2999) ...
However, this is not intercepting RequestRejectedException
(as indicated by the lack of "Server Error" in the first log sample above).
How can I intercept RequestRejectedException
in an error controller?
In Spring Boot 2, if we want our own security configuration, we can simply add a custom WebSecurityConfigurerAdapter. This will disable the default auto-configuration and enable our custom security configuration. Spring Boot 2 also uses most of Spring Security's defaults.
It can be also handled by a simple filter, which will lead to 404 error response
@Component @Slf4j @Order(Ordered.HIGHEST_PRECEDENCE) public class LogAndSuppressRequestRejectedExceptionFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { try { chain.doFilter(req, res); } catch (RequestRejectedException e) { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; log .warn( "request_rejected: remote={}, user_agent={}, request_url={}", request.getRemoteHost(), request.getHeader(HttpHeaders.USER_AGENT), request.getRequestURL(), e ); response.sendError(HttpServletResponse.SC_NOT_FOUND); } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With