CVE-2026-61500 is an unauthenticated session-forgery vulnerability in Rejetto HFS 3.0.0 through 3.2.0. HFS generated its Koa session-cookie signing key with JavaScript Math.random() and exposed outputs from the same V8 PRNG in the unauthenticated SRP login handshake. An attacker can reconstruct the PRNG state, recover the signing key, forge an administrator session, and use the documented server_code configuration feature to execute server-side JavaScript.
This repository contains a Python proof of concept and a disposable Docker comparison using the official HFS 3.2.0 and 3.2.1 images. The publication build is intentionally restricted to HTTP targets on the local loopback interface.
Send six unauthenticated loginSrp1 API requests for the known admin account. Each vulnerable response places a numeric loggingIn.sid and its signed session cookie in Set-Cookie headers.
Convert five consecutive doubles to their 53 visible PRNG bits and brute force the eleven omitted low bits. Reverse and advance V8's xorshift128+ recurrence until one internal state satisfies every observed value.
Reproduce the three base-36 chunks used by HFS randomId(30) . The PoC accounts for V8 shortest-string rounding and checks candidates against an observed hfs_http.sig HMAC, which also identifies the startup offset.
Sign a synthetic session containing username: admin , then call get_config to prove that the forged cookie has administrator access.
Call set_config with a small server_code module. The default payload writes a benign marker in /data ; --command is available only for the loopback Docker lab.
This is a black-box HTTP chain: the PoC does not read files, memory, environment variables, or process state from the target. Source knowledge is used to model the vulnerable algorithm.
Affected and fixed versions
Affected: HFS 3.0.0 through 3.2.0.
First fixed release: HFS 3.2.1 .
Fix: 59472e534bf7e056d708382d02935c2eaf956927 .
The fix replaces the signing key with 32 bytes from Node.js randomBytes() and replaces the exposed numeric login identifier with randomUUID() . Supplying an explicit strong COOKIE_SIGN_KEYS value mitigates signing-key prediction, but upgrading remains the recommended remediation.
Validated environment
Host: Linux 6.18.33.2-microsoft-standard-WSL2, x86_64.
Docker 29.7.2; Docker Compose v5.5.0; Python 3.14.4.
Vulnerable image: rejetto/hfs:v3.2.0 , digest sha256:d6765e93b68de222583be7788afad699695fd08aa2f56377337f5139779e0746 .
Fixed image: rejetto/hfs:v3.2.1 , digest sha256:61db4da1f494df254aa7f48889c676b424b413e45b7b92cf4276f4b8e632aaec .
Demo callback image: python:3.13-alpine , digest sha256:1a63a53928ce53d2b0baf08092a703f4840ac5dfbd61fd48802dbf48e08c801e .
Test date: 2026-09-26.
Both HFS services bind only to host loopback; the callback publishes no port. The lab creates a synthetic administrator because loginSrp1 must be invoked for an existing username; the password is neither known nor used by the exploit.
Run the disposable comparison
Requirements are Docker with Compose, Python 3.10 or newer, and curl .
The verifier removes only lab/runtime/vulnerable and lab/runtime/fixed , starts both digest-pinned images, runs the positive and negative controls, and stops the containers by default. Use KEEP_LAB=1 ./verify.sh to leave the lab running for inspection.
With the lab retained, the direct benign-marker invocation is:
To demonstrate command execution inside the owned lab container:
Any non-loopback hostname, HTTPS target, or remote IP is rejected by argument validation. The exploit modifies HFS server_code ; use only the disposable lab or a system for which you have explicit authorization.
The recorded demo goes one step further: demo.sh starts the unexposed callback service on the Compose network and uses --command to connect a Bash reverse shell to it. The callback sends only id , uname -a , pwd , and exit , records the transcript under ignored lab/runtime/ , and closes. No callback port is bound to the host.
The real 2026-09-26 run recovered one PRNG state and its signing key, received HTTP 200 for a forged administrative get_config , installed the marker payload, and observed CVE_2026_61500_RCE_CONFIRMED in the vulnerable container. A separate --command 'id > /data/cve-command-output.txt' control produced uid=0(root) gid=0(root) groups=0(root) inside that official container. The recorded Docker-only reverse shell independently returned the same root identity and /data working directory. Against 3.2.1, the first login response contained an opaque UUID and the PoC exited with status 3 before attempting session forgery. See docs/example-output.txt and docs/e2e-results.json .
Public-exploit
On 2026-09-26, exact CVE and exploit/PoC searches were run against SearchSploit (local Exploit-DB index), GitHub-indexed web results, Packet Storm, Exploit-DB, and the general web. No working public exploit was identified at that point; results found CVE/advisory metadata and exploit-tracking pages only. This is a dated, best-effort result, not a claim that no exploit can exist elsewhere or appear later.
Exploit: A. Ramos (Twitter: @aramosf ).
Vulnerability discovery: Zach Hanley ( @hacks_zach ) of Horizon3.ai, in collaboration with Claude and Anthropic Research.
Fix: Massimo Melina / Rejetto.
Upstream fixing commit
CVE-2026-61500 record
For authorized security research, defensive validation, and education only. You are responsible for obtaining permission and complying with applicable law.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
