Jan 24, 2026·6 min read·38 visits
If you're using `miniserve` with file uploads enabled, you might be serving up your own system files on a silver platter. CVE-2025-67124 is a race condition that lets an attacker trick the server into following a symbolic link during the upload finalization process. By winning the race, they can overwrite files outside the upload directory (like `/etc/shadow` or `~/.ssh/authorized_keys`), leading to Denial of Service or potential Remote Code Execution. The fix? Upgrade immediately or turn off uploads.
A classic Time-of-Check to Time-of-Use (TOCTOU) vulnerability in miniserve v0.32.0 allows attackers to overwrite arbitrary files via symbolic link racing during file uploads.
miniserve is one of those tools that developers love. It's a statically compiled, zero-dependency, "just works" HTTP server written in Rust. Need to share a directory? miniserve .. Done. It's the Swiss Army knife of quick-and-dirty file sharing.
But things get complicated when you stop just serving files and start accepting them. The --upload-files flag transforms this simple file server into a writable drop box. And as any seasoned security researcher knows, allowing users to write to the disk is like inviting a vampire into your house—you have to set very specific ground rules, or you're going to have a bad time.
In version 0.32.0, those ground rules were strictly enforced... but they were enforced with a stopwatch that was just a little too slow. We're looking at a classic concurrency bug in a modern language: a Time-of-Check to Time-of-Use (TOCTOU) vulnerability that proves even Rust's borrow checker can't save you from logic errors.
The vulnerability lies in how miniserve handles the finalization of uploaded files. When you upload a file, the server needs to decide where to put it and ensure it's not overwriting something it shouldn't—or at least, that was the intention.
The logic follows a familiar, fatal pattern:
This is the "Time-of-Check" and the "Time-of-Use". In a single-threaded world, this is fine. But filesystems are shared state. Between step 1 and step 2, there is a microsecond-sized window of opportunity. It's like checking if the bridge is down, looking away to tie your shoe, and then driving the car forward assuming the bridge is still there.
An attacker with write access to the upload directory (common in shared hosting or container volumes) can exploit this gap. They wait for the server to validate the filename payload.txt. Right after the check passes, but before the server opens the file descriptor for writing, the attacker swaps payload.txt with a symbolic link pointing to /etc/passwd. The server, blind to the switch, happily opens the link and overwrites the system password file.
While Rust provides memory safety, it doesn't automatically enforce atomic filesystem operations. The vulnerable code likely looked something like this (pseudocode representation of the logic flaw):
// The Vulnerable Pattern
let dest_path = upload_dir.join(filename);
// 1. The Check
if !dest_path.exists() {
// <--- ATTACKER SWAPS FILE FOR SYMLINK HERE
// 2. The Use
let mut file = File::create(dest_path)?;
file.write_all(&data)?;
}The issue is that path.exists() and File::create(path) are two separate system calls. The operating system kernel schedules them independently. If the attacker is fast enough (or the server is slow enough), the state of the filesystem changes between those two lines.
The Fix usually involves using low-level file flags to ensure atomicity. In POSIX systems, this means using O_CREAT | O_EXCL. This flag tells the kernel: "Create this file, but fail instantly if it already exists." This combines the Check and the Use into a single atomic instruction, leaving no gap for the attacker to slip in a symlink.
Exploiting a race condition is usually about brute force and timing. We need to continuously toggle a file between being a "safe" file and a "malicious" symlink, hoping the server catches the symlink at the exact wrong moment.
The Setup:
miniserve instance with --upload-files.The Attack Loop:
# The target we want to overwrite
TARGET="/root/.ssh/authorized_keys"
# The bait file
BAIT="payload.txt"
while true; do
# State A: Normal file (passes the check)
touch $BAIT
# State B: Symlink to target (hijacks the write)
rm $BAIT
ln -s $TARGET $BAIT
doneWhile this loop runs, the attacker spams upload requests to miniserve. Most requests will fail (either the file exists, or the write fails permissions). But eventually, the stars align: miniserve checks the path when it's State A (safe), context switches, the loop swaps it to State B (symlink), and then miniserve writes to the target. Bingo. You've just overwritten the root SSH keys with your uploaded content.
Why should you panic? Because arbitrary file overwrites are rarely just about defacement. They are the gateway to Remote Code Execution (RCE).
If an attacker can overwrite specific files, they own the system:
~/.ssh/authorized_keys with their own public key. Result: SSH access as the user running miniserve./etc/cron.d/ or /etc/periodic/. Result: The system executes the attacker's script automatically.miniserve's own config or binary (if permissions allow). Result: Persistent backdoor.Even in a restricted container, overwriting /etc/shadow or /etc/passwd can cause a Denial of Service or allow privilege escalation if the container is running as root (which, let's be honest, half the containers on the internet are).
The mitigation is straightforward but requires code changes. You cannot "config" your way out of a race condition in the binary logic, other than disabling the affected feature.
For Users:
--upload-files, turn it off. It's the safest way to operate.For Developers:
open with flags that enforce your constraints.O_TMPFILE (on Linux) to write data to an unnamed temporary file first, then linkat it into place. This is atomic and avoids partial file uploads appearing in the directory.rename operations, which are atomic on POSIX filesystems.CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
miniserve svenstaro | = 0.32.0 | 0.33.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-367 (TOCTOU) |
| Attack Vector | Local / Network (Uploads) |
| CVSS | 6.8 (Medium) |
| Impact | Arbitrary File Overwrite |
| Exploit Status | PoC Available |
| Architecture | x86, ARM, etc. (Rust generic) |
The software checks the state of a resource before using it, but the resource's state can change between the check and the use in a way that invalidates the results of the check.
A high-severity access control vulnerability in ToolHive CLI before v0.30.1 and ToolHive Studio before v0.38.0 allows local containerized MCP servers to bypass network isolation. This enables malicious workloads to establish TCP/IP connections to administrative and control plane endpoints exposed on the host loopback interface.
AnyCable is a real-time communication server. Prior to version 1.6.15, its Pusher-compatible REST API suffered from an authentication bypass vulnerability because it failed to verify that the request body matched the signature-validated body_md5 parameter. This allows attackers to perform replay attacks with modified body contents.
A denial-of-service vulnerability exists in AnyIO prior to version 4.14.2. Standard error streams of process-pool workers are connected to an operating system pipe that is never drained by the parent process. This allows a worker to fill the pipe buffer and deadlock indefinitely.
CVE-2026-63349 is a critical privilege-dropping bypass vulnerability in the AnyIO asynchronous framework (versions 4.14.0 and 4.14.1) on POSIX platforms. Due to a variable assignment typo, supplementary groups specified by the developer are not correctly propagated to the execution backend, resulting in subprocesses retaining the parent process's elevated supplementary group permissions.
CVE-2026-63406 is an information disclosure vulnerability in AnyCable-go prior to version 1.6.15. The built-in telemetry client is enabled by default with a hardcoded public authentication token ('secret'). This client digests highly sensitive configuration parameters and command-line arguments, including JWT secrets and RPC secrets, into a stable SHA-256 fingerprint. This fingerprint is sent over public networks, exposing those administrative secrets to offline dictionary and brute-force attacks if intercepted.
CVE-2026-84992 is a Cross-Site Scripting (XSS) vulnerability affecting md-editor-v3 before version 6.5.4. It occurs because the fenced-code block language parser directly interpolates unescaped language metadata into unquoted HTML attributes inside the custom rendering callback. This bypasses the built-in XSSPlugin which runs during the parsing phase, before rendering.