Skip to content

Provide a way to handle RequestRejectedException #5007

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mario-philipps-icw opened this issue Feb 8, 2018 · 27 comments
Closed

Provide a way to handle RequestRejectedException #5007

mario-philipps-icw opened this issue Feb 8, 2018 · 27 comments
Assignees
Labels
in: web An issue in web modules (web, webmvc) status: duplicate A duplicate of another issue type: enhancement A general enhancement

Comments

@mario-philipps-icw
Copy link

Summary

With Spring Security 4.2.4 used in a Spring Boot application, accessing a "non-normalized" URI, e.g. one containing a double //, will cause an HTTP 500.

Actual Behavior

Access to a URI containing a double // causes an HTTP 500 response.

Expected Behavior

Access to such a URI causes some HTTP 4xx response, e.g. 400, 403 or 404. I'd expect that, because it's the client who has to change the request.

Version

Affected version is Spring Security 4.2.4.

Comment

I did some research in the code, and it looks as if the exception thrown in

throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
will finally be caught by the Tomcat embedded in our Spring Boot application, turning it into the HTTP 500.

@hth
Copy link

hth commented Mar 1, 2018

Cannot use Spring Security greater than 5.0.0. Unless fixed, all releases past 5.0.0 are moot

@rwinch rwinch modified the milestones: 5.1.0, 5.1.0.M1 Mar 1, 2018
@rwinch
Copy link
Member

rwinch commented Mar 1, 2018

We will look into this for 5.1. In the meantime, you can handle this by handling the exception (you should be doing this anyways).

Handle the Exception

Spring Boot

For example, with Spring Boot add ErrorPageRegistrar:

@Bean
public static ErrorPageRegistrar securityErrorPageRegistrar() {
    return registry -> registry.addErrorPages(new ErrorPage(RequestRejectedException.class, "/errors/400"));
}
@Controller
public class SecurityErrorController {
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	@RequestMapping("/errors/400")
	String error(RequestRejectedException e) {
		return "errors/400";
	}
}

src/main/java/resources/templates/errors/400.html

<html>
<head>
<title>Bad Request</title>
</head>
<body>
<h1>Bad Request</h1>
</body>
</html>

NOTE: Ensure the error page is public (i.e. permitAll) so that unauthenticated users are able to view the error page.

Servlet Container

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
  ...
  
  <error-page>
  <exception-type>org.springframework.security.web.firewall.RequestRejectedException</exception-type>
  <location>/errors/400</location>
  </error-page>
</web-app>

Ensure you add a URL mapping for /errors/400 which sets the status and renders the response you want.

NOTE: Ensure the error page is public (i.e. permitAll) so that unauthenticated users are able to view the error page.

Customizing HttpFirewall

You can also revert to the previous HttpFirewall or customize the StrictHttpFirewall by exposing a bean. Of course this is not recommended as it can lead to exploits.

@rwinch rwinch added the status: waiting-for-triage An issue we've not yet triaged label Mar 1, 2018
@hth
Copy link

hth commented Mar 1, 2018

I would recommend including #5044 with this issue

@rwinch rwinch changed the title Access to non-normalized URIs causes HTTP 500 Provide a way to handle RequestRejectedException Mar 19, 2018
@Glamdring
Copy link

Glamdring commented Mar 21, 2018

@rwinch I tried those but they still result in an unwanted stacktrace in the log, because the exception is happening outside the normal exception handling flow. Looking at the stacktrace, it is not caught anywhere and so can't be processed by exception or error page handlers

@Glamdring
Copy link

Glamdring commented Mar 21, 2018

For anyone trying to get rid of the stacktrace and is running undertow:

@Bean
public UndertowServletWebServerFactory embeddedServletContainerFactory() {
    UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
    factory.addDeploymentInfoCustomizers(new UndertowDeploymentInfoCustomizer() {
        @Override
        public void customize(DeploymentInfo deploymentInfo) {
            deploymentInfo.setExceptionHandler(new ExceptionHandler() {
                @Override
                public boolean handleThrowable(HttpServerExchange exchange, ServletRequest request,
                                               ServletResponse response, Throwable throwable) {
                    if (throwable instanceof RequestRejectedException) {
                        ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_BAD_REQUEST);
                        logger.warn("Request rejected due to: {} for URI {}", throwable.getMessage(),
                                ((HttpServletRequest) request).getRequestURI());
                        return true;
                    }
                    return false;
                }
            });
        }
    });
}

@gionn
Copy link

gionn commented Apr 18, 2018

Thanks to this new feature I discovered that Jetty was returning HTML errors for exception thrown outside of spring MVC.

To handle this RequestRejectedException I did:

        contextHandler.setErrorHandler( new JsonErrorHandler() );

and in JsonErrorHandler

public class JsonErrorHandler extends ErrorHandler
{
    private final ObjectMapper mapper = new ObjectMapper();

    @Override
    protected void generateAcceptableResponse( Request baseRequest, HttpServletRequest request,
            HttpServletResponse response, int code, String message, String mimeType ) throws IOException
    {
        response.setStatus( 400 );
        baseRequest.setHandled( true );
        Writer writer = getAcceptableWriter( baseRequest, request, response );
        if ( writer != null )
        {
            response.setContentType( MimeTypes.Type.APPLICATION_JSON.asString() );
            Throwable th = (Throwable) request.getAttribute( RequestDispatcher.ERROR_EXCEPTION );
            writer.write( getPayload( th ) );
        }
    }

getPayload should return a JSON string describing the error.

@rwinch rwinch modified the milestones: 5.1.0.M1, 5.1.0.M2 May 11, 2018
@maneeshbhunwal123
Copy link

maneeshbhunwal123 commented Jul 3, 2018

@rwinch
I tried your solution, adding

@Bean
public static ErrorPageRegistrar securityErrorPageRegistrar() {
    return registry -> registry.addErrorPages(new ErrorPage(RequestRejectedException.class, "/errors/400"));
}

but it is not working for me. do you have any pointer? spring is not forwarding the request to this endpoint. Checked and saw that this bean is initialised/picked up during startup.

after some debugging and searching I came accross
https://stackoverflow.com/questions/27825148/spring-boot-errorpage-not-working-with-standalone-tomcat
and
http://automateddeveloper.blogspot.com/2015/03/spring-boot-tomcat-error-handling.html
which says it wont work when deploying on standalone tomcat.

@hth
Copy link

hth commented Jul 6, 2018

Hope this one does not get pushed to another milestone.

@axelo
Copy link

axelo commented Jul 17, 2018

Another solution to skip the logging is using janino togheter with logback and filtering out the exception.

For example:

  1. Add compile group: 'org.codehaus.janino', name: 'janino', version: '3.0.7' as a new dependency.

  2. Edit / create logback-spring.xml.

And for the appropriate appender add a EvaluatorFilter using JaninoEventEvaluator ,

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> 
            <evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
                <expression>return throwable instanceof org.springframework.security.web.firewall.RequestRejectedException;</expression>
            </evaluator>
            <OnMismatch>NEUTRAL</OnMismatch>
            <OnMatch>DENY</OnMatch>
        </filter>

        <encoder>
            <Pattern>
                ${FILE_LOG_PATTERN}
            </Pattern>
        </encoder>
        <file>${LOG_FILE}</file>
    </appender>

Now all RequestRejectedException logging should be filtered out by logback.

Some references:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-logging.html
https://stackify.com/logging-logback/

@rwinch rwinch modified the milestones: 5.1.0.M2, 5.1.0.RC1 Jul 26, 2018
@ct-parker
Copy link

ct-parker commented Aug 12, 2018

I implemented a workaround for this bug that is fairly flexible (see How to intercept a RequestRejectedException in Spring?). I also posted a second solution for those who simply want to suppress or control the logging. Essentially, I extended HttpStrictFirewall to add an attribute to the request that gets picked up by a downstream HandlerInterceptor. At this point, you can handle these like any other request while still utilizing the information from the firewall. It turned out that nearly all of the blocked requests were from clients with cookies disabled (;jsessionid= was in the URL). This included the Google indexer. I would not mind at all if the Spring Security developers used this as a model for making the HttpFirewall more flexible for developers.

@hth
Copy link

hth commented Aug 25, 2018

Is this going to be resolved or its gone in oblivion?

@rwinch
Copy link
Member

rwinch commented Aug 27, 2018

@hth Thanks for the nudge.

With a reasonable workaround and lots to do we haven't had time to do it. If someone is interested in submitting a PR I'd be glad to help coach them through it.

@GerogeLeon
Copy link

the same to me

ERROR o.a.c.c.C.[.[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL was not normalized.
at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:248)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:194)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.ufoto.gateway.ratelimit.RateLimitTemplate.rateLimit(RateLimitTemplate.java:47)
at com.ufoto.gateway.ratelimit.uri.UriRateLimitServletFilter.doFilterInternal(UriRateLimitServletFilter.java:40)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.ufoto.gateway.ratelimit.RateLimitTemplate.rateLimit(RateLimitTemplate.java:47)
at com.ufoto.gateway.ratelimit.ip.IpRateLimitServletFilter.doFilterInternal(IpRateLimitServletFilter.java:42)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.ufoto.gateway.cryption.DecryptRequestServletFilter.doFilterInternal(DecryptRequestServletFilter.java:96)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.ufoto.gateway.cryption.EncryptResponseServletFilter.doFilterInternal(EncryptResponseServletFilter.java:103)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:155)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:123)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
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:493)
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.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
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)

@rwinch rwinch added status: ideal-for-contribution An issue that we actively are looking for someone to help us with and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 7, 2019
@stuffToDo
Copy link

stuffToDo commented Jan 24, 2019

"reasonable workaround" ... does not work. It does respond with the configured 400 JSP but still logs the exception with stacktrace. The exception is logged here (StandardWrapperValve)...

} catch (Throwable e) { ExceptionUtils.handleThrowable(e); container.getLogger().error(sm.getString( "standardWrapper.serviceException", wrapper.getName(), context.getName()), e); throwable = e; exception(request, response, e); }

This exception should be logged but there is no need for the stacktrace. During a security scan this exception overwhelms the logs. There are other Spring Security exceptions that are logged as WARN without the stacktrace.

@rwinch
Copy link
Member

rwinch commented Jan 25, 2019

This is likely something you want to be drawn to your attention since it may be someone trying to bypass the firewall. If you are not interested in that log, you can also create a Filter that catches the exception and handles it.

@piyushsoni21
Copy link

piyushsoni21 commented Feb 6, 2019

One way to handle by implement GenericFilterBean and more important defined the order if you have multiple filters in your application "//" in url

@component
@order(Ordered.HIGHEST_PRECEDENCE)
public class RequestRejectedExceptionFilter extends GenericFilterBean {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletResponse res = (HttpServletResponse) response;
    HttpServletRequest req = (HttpServletRequest) request;
    try {
        chain.doFilter(request, response);
    } catch (RequestRejectedException ex) {
        ObjectMapper om = new ObjectMapper();
        JsonError errorDetails = new JsonError(req.getHeader(REQUEST_ID), HttpStatus.BAD_REQUEST.value(),
                "some message", "error message");
        JsonResponse errorResponse = new JsonResponse(errorDetails);
        String jsonErrorResponse = om.writeValueAsString(errorResponse);

        res.setStatus(HttpStatus.BAD_REQUEST.value());
        res.setContentType("application/json");
        res.getWriter().append(jsonErrorResponse);
        
    }
}

}

@venkatraman96
Copy link

Check my answer in below thread:

https://stackoverflow.com/a/55383661/8174613

@a-sayyed
Copy link
Contributor

a-sayyed commented Mar 29, 2019

A much easier and simpler way to handle it could be by using Spring AOP. We can wrap the default FilterChainProxy execution with a try-catch block and translate the exception to a 400 response.

@Aspect
@Component
public class FilterChainProxyAdvice {

    @Around("execution(public void org.springframework.security.web.FilterChainProxy.doFilter(..))")
    public void handleRequestRejectedException (ProceedingJoinPoint pjp) throws Throwable {
        try {
            pjp.proceed();
        } catch (RequestRejectedException exception) {
            HttpServletResponse response = (HttpServletResponse) pjp.getArgs()[1]);
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
        }
    }
}

see: https://stackoverflow.com/a/55420714

@leonard84
Copy link
Contributor

@rwinch if you'd like a contribution, then it would be quite helpful if you provide some hints as to how you'd like it to be solved, so that it fits well into the architecture.

@rwinch rwinch added in: web An issue in web modules (web, webmvc) type: improvement and removed status: ideal-for-contribution An issue that we actively are looking for someone to help us with labels Jun 13, 2019
@rwinch rwinch self-assigned this Jun 13, 2019
@rwinch
Copy link
Member

rwinch commented Jun 13, 2019

Thanks for volunteering @leonard84 The issue is yours. I think we do the following

  • add a CsrfExceptionHandler interface
  • Update FilterChainProxy to allow a CsrfExceptionHandler to be injected. The default implementation just throws the CsrfException as is.
  • Update the ExceptionHandlingConfigurer to allow the CsrfExceptionHandler to be configured

Let me know if you have any questions or otherwise need any help contributing.

@leonard84
Copy link
Contributor

leonard84 commented Jun 25, 2019

@rwinch the RequestRejectedException does not have any direct connection to CsrfException. Should I just treat it as s/Csrf/RequestRejected/g, i.e. RequestRejectedExceptionHandler...?

@leonard84
Copy link
Contributor

I've started with the implementation leonard84@44bf916 (still WIP), and I'd like some feedback.

However, there are some issues:

  • FilterChainProxy is configured in WebSecurity, but the ExceptionHandlingConfigurer lives in a different package, so the getRequestRejectedHandler() would have to be public. Would it make more sense to make it a property of WebSecurity?
  • Travis seems to have removed Oracle Java 8 support
$ ~/bin/install-jdk.sh --target "/home/travis/oraclejdk8" --workspace "/home/travis/.cache/install-jdk" --feature "8" --license "BCL"
install-jdk.sh 2019-06-25
Expected feature release number in range of 9 to 14, but got: 8
The command "~/bin/install-jdk.sh --target "/home/travis/oraclejdk8" --workspace "/home/travis/.cache/install-jdk" --feature "8" --license "BCL"" failed and exited with 3 during .

@rwinch
Copy link
Member

rwinch commented Jun 27, 2019

Thanks @leonard84! Could you submit a draft pr so we can discuss more easily.

@leonard84
Copy link
Contributor

Done #7052

@jzheaux jzheaux added type: enhancement A general enhancement and removed type: improvement labels Jul 9, 2019
@rwinch rwinch removed their assignment Jul 29, 2019
@leogong
Copy link

leogong commented Jan 16, 2020

i found this code in the StrictHttpFirewall class.

if (path.indexOf("//") > -1) {
    return false;
}

Why add this code? i think browsers support this .

Thx

@rwinch rwinch closed this as completed in b826c79 May 1, 2020
rwinch added a commit that referenced this issue May 1, 2020
rwinch added a commit that referenced this issue May 1, 2020
@rwinch rwinch self-assigned this May 1, 2020
@rwinch rwinch added the status: duplicate A duplicate of another issue label May 1, 2020
@joshymon
Copy link

joshymon commented Jul 7, 2020

Any idea when is this version available for consumption?

@rwinch
Copy link
Member

rwinch commented Jul 8, 2020

The linked pull request #7052 indicates it was made available in 5.4.0-M1. You can look at the milestones to find out when it will be in a GA release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web An issue in web modules (web, webmvc) status: duplicate A duplicate of another issue type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.