400 bad request returned in spring boot even before the request reaches filter

Viewed 5965

I have a spring boot application with a GET service.

@RequestMapping(value = "/abc/track/{type}", method = RequestMethod.GET)

    public void DummFunc(                            
          @RequestParam(value="subs", required = false) String sub,
,  HttpServletResponse response) {}

value for subs is an encoded value.

If I pass following as value to parameter subs

{%22endpoint%22:%22https://def.abc.com/tyu/send/eD3vpGNQW28:APA91bHOo8rYrV0xccdQz3okjZJG-QGrJX8LG6ahJnEUpMNfGedqi3hJxQsJx_8BMbH6oDjaSPPEXqzNWchWrGSkhuTkSdemikkys1U22Ipd7MsRw0owWbw89V2fslIVAJ6G5lkyvYuQ%22,%22expirationTime%22:null,%22keys%22:{%22p256dh%22:%22BK0pktn50CMsTQtqwdPlKlJtdFs0LFeXX14T1zgoz6QWnvSTp5dxtChnUP5P1JX0TsjcopbdPKyN31HfABMUyic%22,%22auth%22:%22qbO_z0vGao9wD-E5g8VU-A%22}}

It fails to capture the request and control does not come inside of the function.

If we instead pass as value to parameter subs:

%7B%22endpoint%22:%22https://def.abc.com/tyu/send/dX5q5eV7hFQ:APA91bHib-0QXrMzjatcvTR_uaIeeJK8lf6GmXUC9Jxv0Oxth-BzD4GmWnd4-YpDZv8qSFZ0eSg9mB2YkRvkc5ezdXW5KeaHjuQZfdyDxyBXjJgE-25Xbtlk37pdm8vfLk20k0k_VxW9%22,%22expirationTime%22:null,%22keys%22:%7B%22p256dh%22:%22BCCvcBLpRqp4u0auP688_MUJLsAiwWlQSn5kpyl8YVsEo_J-KpSdnhCmVIE_BhDXBcFYflPK52hqhYf3EaOCyuY%22,%22auth%22:%22iKuW_ESkCZnubWcQu_JK8w%22%7D%7D

It works fine.

Basically, the request that has issue getting through has { and } instead of %7Band %7D.

My question is instead of application failing with 400 bad request ,how do I capture such requests in my app, encode them properly and then process them or may be atleast handle this error

Also, I tried adding Filter and RestControllerAdvice.

Findings so far:

  1. What I realised was that probably request does not even reach the application because If I remove DummFunc from my code that is mapped to request /abc/track/{type}, It still does not return 404 but 400 only.
  2. CommonsLoggingFilter is not logging this particular request with 400 bad response code but is able to log rest of the requests (Another hint that request does not hit the application - I guess)
  3. While debugging, I realised that if I hit this API from chrome app of Postman and browser, it returns 400 Bad request but if I hit this API from desktop Postman app, it returns 200.
  4. I also tried adding exception-handler in the controller as well through a new class, none of them captured the request

// @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(Throwable.class)
        public void handle(HttpMessageNotReadableException e) {
            System.out.println("EXCEPTION HAPPENED");
            System.out.println(e);
        }

In my application.properties, relevant props are as follows:

spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true

#security.user.name=user
#security.basic.enabled=true


logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG,stdout
log4j.logger.httpclient.wire=DEBUG,stdout
management.endpoints.web.exposure.include=*
management.endpoint.beans.cache.time-to-live=10s
server.tomcat.accesslog.enabled = true
log4j.logger.org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod=DEBUG,stdout
logging.level.org.springframework.web=DEBUG,stdout
server.tomcat.uri-encoding=UTF-8

So, what is it that can be done to handle it properly?

I am using IntelliJ and it's embedded Tomcat server.

2 Answers

Basically , The problem was Tomcat was not letting the request through.

This behavior was introduced in Tomcat 7.0.76, 8.0.42 and 8.5.12, to conform RFC 7231.

This enforced check can be revert using the property requestTargetAllow in catalina.properties to allow forbiden characters:

tomcat.util.http.parser.HttpParser.requestTargetAllow=|{}

Since I am using embedded tomcat in Intellij and could not edit cataline.properties directly, I added

-Dtomcat.util.http.parser.HttpParser.requestTargetAllow=|{}

to VM Options in Run -> Edit Configuration.

I would strongly recommend you to use a RequestMethod.POST instead RequestMethod.GET.

"{", "}", "|", "\", "^", "~", "[", "]", and ""` are all "not safe characters", you were so clever using %7B and %7D, this should have worked, just make sure that you are using %7B and %7D in correct order.

On Postman they work because Postman interprets curly brackets as environment variables. Correct me if I'm wrong, but I guess Postman returns you 200 but your application still does not receive any data. (right?)

There's another possible solution... 400 means that the error suppose to be on client side, just try send your encrypted data as a simple string and then treat this string on server side.

Good Luck!

Related