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

[CVE-2020-25633] JAX-RS RESTEasy and MP Client

    XMLWordPrintable

Details

    • Bug
    • Status: Resolved (View Workflow)
    • Critical
    • Resolution: Done
    • 3.11.2.Final
    • 3.11.3.Final
    • None

    Description

      Potential CVE in JAX-RS RESTEasy and MP Client

      RESTEasy and MP-REST clients have a proxy client system that can leak information to the end user. The JAX-RS client API has the same issue.

      Suppose a client C makes a REST call to Server A, which is implemented in RESTEasy or Quarkus (which uses RESTEasy), and that REST endpoint makes a REST call to Server B (for whatever reason) using JAX-RS RESTEasy client or MP REST client.

      Now suppose that Server B answers with an HTTP status >= 300. When that happens, the JAX-RS client API, JAX-RS RESTEasy proxy client or MP REST client will turn that status into an exception of type WebApplicationException containing the HTTP response from Server B, including status code, headers and body.

      The Server A endpoint (RESTEasy or Quarkus) will catch this exception and send the contained Response (from server B) directly to the client C, potentially leaking security information in headers, cookies and body.

      The attached application illustrate how trivial it is to trigger with both clients. This is the code on Server A:

       

       

      @Path("/server")
      public class ExampleResource {
          @Inject
          @RestClient
          ExternalService externalService;
          
          @ConfigProperty(name = "external-auth")
          String externalAuth;
      
          @GET
          @Produces(MediaType.TEXT_PLAIN)
          public String hello() {
              externalService.ping(externalAuth);
              return "hello";
          }
      
          @Path("resteasy")
          @GET
          @Produces(MediaType.TEXT_PLAIN)
          public String resteasyHello() {
              Client client = ClientBuilder.newClient();
              WebTarget target = client.target("http://localhost:8080");
              ResteasyWebTarget rtarget = (ResteasyWebTarget)target;         
              ExternalService simple = rtarget.proxy(ExternalService.class);
              simple.ping(externalAuth);
              return "hello";
          }
      
          @Path("jaxrs")
          @GET
          @Produces(MediaType.TEXT_PLAIN)
          public String jaxrsHello() {
              Client client = ClientBuilder.newClient();
              WebTarget target = client.target("http://localhost:8080").path("client");
              target.request().cookie("auth", externalAuth).get(String.class);
              return "hello";
          }
      }
      

      Here in all cases, the call to `ping()` (or `get(String.class`) will trigger an exception and cause the Server B's response to be forwarded directly to the client C.

       

      Here is an example implementation of the Server B code, which may include security information in headers or cookies:

       

       

      @Path("/client")
      public class PingResource {
          @GET
          @Produces(MediaType.TEXT_PLAIN)
          public String ping(@CookieParam("auth") String auth) {
              // validate and renew the auth cookie
              NewCookie authCookie = new NewCookie("auth", auth);
              throw new WebApplicationException(Response.status(Status.BAD_REQUEST)
                                                .cookie(authCookie)
                                                .header("Secret", "leak")
                                                .entity("could not find it, sorry").build());
          }
      }
      

       

      If you start the application with `mvn quarkus:dev` you can query Server A at http://localhost:8080/server (for the MP-REST client) or http://localhost:8080/server/resteasy (for the RESTEasy proxy client) or http://localhost:8080/server/jaxrs (for the JAX-RS client) and you will get this response back:

       

       

      HTTP/1.1 400 Bad Request
      Secret: leak
      Set-Cookie: auth=SECRET;Version=1
      Content-Length: 24
      Content-Type: text/plain;charset=UTF-8
       
      could not find it, sorry
      

      Which is the response from Server B.

       

      I believe it's pretty easy to make RESTEasy/Quarkus/JAX-RS servers leak information about external services if you know they use either REST client, just by passing them payloads that will trigger non-2XX status codes from the external services.

      For example, if a RESTEasy/Quarkus/JAX-RS server forwards part of the payload to an external service, which will validate the payload (let's suppose it's a user name or book ISBN and only that external service can validate that the user/book exists) and will return a non-2XX response, which the RESTEasy/Quarkus/JAX-RS server will leak to the client.

       

      It's possible that this is exploitable in another way by crafting a payload that an attacker will know will cause the external service to return a non-2XX response with a specific header/body/content-type/cookie that the RESTEasy/Quarkus handles specially in a ContainerResponseFilter, ExceptionMapper or MessageBodyWriter, which would force the RESTEasy/Quarkus/JAX-RS server to execute that handler which may have unwanted effects. This is probably harder to exploit, but not impossible.

       

      The cause of the issue is in https://github.com/resteasy/Resteasy/blob/master/resteasy-client/src/main/java/org/jboss/resteasy/client/jaxrs/internal/ClientInvocation.java#L231 and https://github.com/resteasy/Resteasy/blob/master/resteasy-client/src/main/java/org/jboss/resteasy/client/jaxrs/internal/ClientInvocation.java#L252 for the RESTEasy proxy client and its implementation of the JAX-RS cliehnt, and in https://github.com/resteasy/Resteasy/blob/master/resteasy-client-microprofile-base/src/main/java/org/jboss/resteasy/microprofile/client/DefaultResponseExceptionMapper.java for the MP REST client.

      This is because we wrap client exceptions in WebApplicationException exceptions, and JAX-RS servers propagate those exceptions directly to clients, because they're supposed to be created by the endpoints.

      The fact that they're created by the client calls is wrong and dangerous.

      This possibly impacts the MP REST spec, but also JAX-RS because its spec implies this should be done:

      4.5.2 Client Runtime

      Note that the client runtime will only throw an instance of WebApplicationException (or any of its subclasses) as a result of a response from the server with status codes 3xx, 4xx or 5xx.

      Based on that, I think the RESTEasy and MP REST clients should never throw any subtype of WebApplicationException, which can contain Response objects. Making a separate exception type should be enough to avoid the issue.

      I don't think non-WebApplicationException exceptions thrown by the client code could trigger a security issue on the server, although it's possible those exceptions will be indavertantly handled by a user's ExceptionMapper, this won't be the fault of the framework which won't turn those exceptions into a response that leaks information.

      https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2020-25633

      Third Party: https://vuldb.com/?id.161620

      Third Party: https://nvd.nist.gov/vuln/detail/CVE-2020-25633

       

      Attachments

        Issue Links

          Activity

            People

              rsigal@redhat.com Ronald Sigal
              adamevin andy damevin
              Alessio Soldano, andy damevin, Bud Lefkof (Inactive), Chess Hazlett, Dimitrios Andreadis, Emmanuel Bernard, Guillaume Smet, James Perkins, Jason Greene, Ronald Sigal, Scott Marlow, Stephane Epardaud, Stuart Douglas (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: