Uploaded image for project: 'RESTEasy'
  1. RESTEasy
  2. RESTEASY-2812

MediaTypeHeaderDelegate cache can be poisoned by erroroneous semi-colons

XMLWordPrintable

    • Hide

      This is relatively easy to replicate locally in a test of this class. Simply initialise the class and validate that parse and toString are symmetric for "application/json". Then call parse with "application/json;". Repeat the symmetric test and it will fail.

      We're creating a PR to address this.

      Show
      This is relatively easy to replicate locally in a test of this class. Simply initialise the class and validate that parse and toString are symmetric for "application/json". Then call parse with "application/json;". Repeat the symmetric test and it will fail. We're creating a PR to address this.
    • Undefined

      The org.jboss.resteasy.plugins.delegates.MediaTypeHeaderDelegate class had a MediaType cache introduced to it back in March 2019. The cache is implemented with two maps: Map<String, MediaType> map and Map<MediaType, String> reverseMap.

      Generally speaking, these maps contain a collection of common media types. So, for instance there are matching entries in both maps for application/json:

      map:       "application/json" -> MediaType.APPLICATION_JSON
      reverseMap: MediaType.APPLICATION_JSON -> "application/json"
      

      However, if this code ever encounters a malformed Content Type string from another server in the response, then it can be poisoned. The specific example we have encountered of this is:

      HTTP/1.1 200 OK
      Date: Thu, 14 Jan 2021 00:44:56 GMT
      Content-Type: application/json;
      Content-Length: 2
      

      [ Note the semi-colon at the end of the content type header. It should be noted that this is not a valid value, but that doesn't stop a downstream server pushing it. ]

      A Resteasy client receiving this response will call MediaTypeHeaderDelegate.parse("application/json;"):

         public static MediaType parse(String type)
         {
            MediaType result = map.get(type);
            if (result == null) {
                result = internalParse(type);
                final int size = map.size();
                if (size >= MAX_MT_CACHE_SIZE) {
                    map.clear();
                    reverseMap.clear();
                }
                map.put(type, result);
                reverseMap.put(result, type);
            }
            return result;
         }
      

      The "get" on line 1 of the method will not return a value since type includes the semi-colon. Hence, it will be parsed into a MediaType = "application/json" and the two maps will look like this:

      map:       "application/json" -> MediaType.APPLICATION_JSON
      map:       "application/json;" -> MediaType.APPLICATION_JSON
      reverseMap: MediaType.APPLICATION_JSON -> "application/json;"
      

      From this point on, any calls to toString on this class passing the MediaType "application/json" will get the String "application/json;".

      Now, this is where this gets interesting if you are (like us) using both Resteasy clients and Resteasy services in the same machine. This poisoned cache is also used by the server side of the JAX-RS code. So, anywhere where you have used the MediaType "application/json" in the @Produces section of a resource will exhibit the same behaviour.

      @GET
      @Produces(MediaType.APPLICATION_JSON)
      Optional<SomeResult> doSomthing();
      

      So now that server is returning non-compliant Content-Type headers in responses, and will continue to do so until the server is restarted. Then, any server using Resteasy client that talks to the newly poisoned server will also have its cache poisoned.

      For us this exhibited as a server that would start up fine and respond with the correct Content-Type until shortly after startup when it reached out to another server. At which point it would then start responding erroneously.

              rsigal@redhat.com Ronald Sigal
              stevenewson Steve Newson (Inactive)
              Votes:
              3 Vote for this issue
              Watchers:
              6 Start watching this issue

                Created:
                Updated:
                Resolved: