Feb 15, 2026·7 min read·160 visits
BusyBox `wget` (<= 1.37.0) doesn't strip control characters from URLs. If an attacker controls the URL passed to `wget`, they can inject `\r\n` sequences to manipulate the HTTP request headers. This allows for HTTP Request Splitting, potentially poisoning caches or injecting malicious headers like `Authorization` or `X-Forwarded-For`.
BusyBox, the undisputed 'Swiss Army Knife of Embedded Linux,' has a jagged edge in its `wget` implementation. Through version 1.37.0, the utility fails to properly sanitize URL inputs, specifically allowing Carriage Return (CR) and Line Feed (LF) characters to pass through to the raw HTTP request line. This vulnerability, discovered by researcher Takeuchi Yuma, opens the door to HTTP Request Splitting and CRLF Injection. In environments where `wget` processes untrusted URLs—such as webhooks, CI/CD pipelines, or containerized automated tasks—attackers can inject arbitrary HTTP headers, potentially leading to cache poisoning, request smuggling, or internal security control bypasses.
If you have ever touched a router, an IoT toaster, or a minimal Docker container, you have used BusyBox. It is the minimalist's dream: a single binary that masquerades as hundreds of standard Unix utilities. It is efficient, it is everywhere, and it is usually running as root in places you cannot easily patch. One of its most critical applets is wget, a tool used universally to fetch files, health-check services, and download initialization scripts.
The problem with minimalism is that sometimes safety checks are viewed as 'bloat.' In the world of high-level languages like Python or Go, HTTP libraries protect you from yourself. They refuse to put a newline character in a header because they know it violates the protocol. BusyBox, however, is written in C. It talks to the network using raw sockets and optimism. It assumes that if you give it a URL, that URL is a valid string of characters that won't break the HTTP protocol.
CVE-2025-60876 serves as a harsh reminder that the HTTP protocol is, at its core, just text sent over a wire. If you can control that text, you control the conversation. By failing to scrub 'control characters'—specifically the Carriage Return (\r, 0x0D) and Line Feed (\n, 0x0A)—BusyBox wget allows an attacker to break out of the URL path and start writing their own HTTP headers. This isn't a memory corruption bug; it's a logic flaw where the program blindly trusts that a string is just a string, not a command delimiter.
To understand why this is dangerous, you have to look at how a raw HTTP request is constructed. When you run wget http://example.com/file, the program opens a socket to port 80 and sends a formatted string. It looks something like this:
GET /file HTTP/1.1
Host: example.com
User-Agent: Wget
Connection: close
The structure relies entirely on newlines (\r\n) to separate the request line from the headers, and the headers from the body. The vulnerability lies in networking/wget.c. The code takes the path component of the URL and sprintfs it directly into that first line. It does not check if the path contains the very characters used to delimit the protocol.
If an attacker provides a URL like http://example.com/file%0d%0aX-Evil: true, and the shell or a calling script decodes that before passing it to wget (or if wget processes a redirect containing it), the resulting raw request sent to the server becomes:
GET /file
X-Evil: true HTTP/1.1
Host: example.com
...The server parses this. It sees a GET request for /file. Then, crucially, it sees a header named X-Evil with a value of true HTTP/1.1. Wait, that looks broken, doesn't it? The server might ignore the garbage trailing the header, or worse, if the attacker aligns the injection perfectly, they can completely fabricate a second request (Request Splitting) or inject critical headers that the backend application uses for authentication.
Let's look at the smoking gun. In the vulnerable versions of BusyBox, the code responsible for parsing the URL and preparing the request didn't care about the content of the string. It was a simple string manipulation operation. The fix, introduced in response to this CVE, is telling. It adds a dedicated sanitization function.
Here is the essence of the fix introduced in networking/wget.c. The developers had to explicitly teach wget what a 'bad' byte looks like:
static void die_on_bad_http_bytes(const char *s, int reject_space, const char *what)
{
const unsigned char *p = (const unsigned char*)s;
while (*p) {
unsigned char c = *p++;
// The Blocklist: CR, LF, Control Chars, DEL, and optionally Space
if (c == '\r' || c == '\n' || c < 0x20 || c == 0x7f || (reject_space && c == ' '))
bb_error_msg_and_die("bad %s", what);
}
}Before this function existed, wget would happily process a URL containing 0x0D (\r) and 0x0A (\n). Now, it iterates through the target.path and target.host byte by byte. If it sees a control character (anything below 0x20), it calls bb_error_msg_and_die.
Notice the reject_space argument? The vulnerability wasn't just CRLF. It was also raw spaces. In HTTP/1.1, the request line is METHOD URI VERSION. If you inject a space into the URI, you can prematurely terminate the URI field and confuse the server into thinking the protocol version comes next. This patch slams the door on both techniques.
How do we weaponize this? We need a scenario where we control the URL. Imagine a microservice that downloads images based on a user-supplied URL. It runs a command like busybox wget $USER_INPUT -O -. If the application doesn't strictly validate the URL format before shelling out (and let's be honest, they rarely do), we are in business.
We can verify this with a simple Python server that checks for a secret header, X-Secret. If the header is present, it gives us the flag. If not, it gives us a 403.
# The Victim Server Logic
if self.headers.get("X-Secret") == "yes":
return "Here are the nuclear codes"
else:
return "Access Denied"The attack payload looks like this:
http://127.0.0.1:9000/funny_cat.jpg HTTP/1.1 X-Secret: yes a:
When wget receives this (assuming the shell passes the raw bytes), it constructs the request. The injection splits the request line. The a: at the end is a classic trick to consume the legitimate HTTP/1.1 that wget automatically appends, turning it into a garbage header a: HTTP/1.1.
The Resulting Wire Traffic:
GET /funny_cat.jpg HTTP/1.1
X-Secret: yes
a: HTTP/1.1
Host: 127.0.0.1:9000
User-Agent: Wget
...The server sees the X-Secret header, validates it, and hands over the goods. We have successfully bypassed the application logic by manipulating the transport layer.
You might be thinking, 'So I can inject headers, big deal.' But in the modern cloud landscape, headers are identity.
1. SSRF on Steroids: If you can inject headers, you can often bypass internal authentication. Many internal APIs trust requests coming from localhost or specific subnets, relying on headers like X-User-ID or X-Role: Admin. If you can force wget to send those headers, you become the admin.
2. Cache Poisoning: This is the most dangerous vector. If wget is being used to fetch a resource that is then cached (e.g., a reverse proxy or CDN), injecting a split response could poison the cache for everyone. You could force the server to respond with a malicious JavaScript file instead of the expected jQuery library, and that malicious file gets cached and served to every subsequent user.
3. Request Smuggling: By manipulating the Content-Length or Transfer-Encoding headers via injection, you can desynchronize the connection between wget and the server (or a proxy in between). This is advanced territory, but it allows for stealing responses meant for other requests sharing the same TCP connection.
The remediation is straightforward but requires a binary update. You cannot configure your way out of this within wget itself.
1. Upgrade BusyBox: The patch was applied in late August 2025. You need a version of BusyBox newer than 1.37.0 (likely 1.37.1 or 1.38.0). If you are on a distro like Alpine or Debian, check specifically for CVE-2025-60876 in the changelogs.
2. Sanitize Inputs Upstream: Do not rely on the tool to be safe. If your application calls wget with user input, validate the URL in your application code first. Use a strict regex. Reject any input containing \r, \n, or raw spaces. If you are passing a URL to a shell command, ensure it is properly quoted, although quoting won't save you if the tool itself (wget) is vulnerable to the content of the string.
3. Switch Tools (If Possible): If you can, use curl (which has had strict protocol validation for years) or native HTTP libraries in your programming language. Using a CLI tool via system() or subprocess calls is historically a recipe for disaster.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
BusyBox wget BusyBox | <= 1.37.0 | Post-1.37.0 commits |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-113 (CRLF Injection) |
| Attack Vector | Network (Input Dependent) |
| CVSS v3.1 | 6.5 (Medium) |
| Impact | Header Injection, Request Splitting |
| EPSS Score | 0.05% |
| Exploit Status | PoC Available |
The software includes user-supplied data in an HTTP response header without validating that the data does not contain CR and LF characters.
Cloudreve is vulnerable to an incorrect authorization bypass. When listing files, Cloudreve returns a context_hint (represented as a UUID) to the client. If this context hint is replayed on the /file/url or /file/thumb routes, Cloudreve's database file system caches the shareNavigatorState containing the loaded share root. Within the cache lifetime (TTL of 300 seconds), if the user re-requests the same file with the cached hint, the system restores the state and completely bypasses the root security checks (which validate share expiration, remaining download limits, owner status, and passwords). This allows unauthorized users to continue generating signed file URLs and downloading files even after a share has been deleted, has expired, or has reached its download limit.
A path traversal vulnerability exists in Cloudreve's remote download workflow, where improper sanitization of file paths returned by configured remote downloaders (such as aria2) allows authenticated users to write files outside the designated target folder.
An integer overflow vulnerability exists in the HTTP/1.x chunked encoding parser of the vibeio-http library. The flaw is caused by unchecked integer addition when calculating the total buffer size required for processing parsed chunk lengths. By sending a maliciously crafted HTTP request containing an extremely large chunk size, an unauthenticated remote attacker can trigger a runtime panic, leading to complete denial of service.
netfoil, an allowlist-based DNS proxy, failed to sanitize ALPN fields parsed from untrusted DNS-over-HTTPS (DoH) HTTPS Resource Records. This allowed attackers to inject ANSI escape sequences into log files or trigger Denial of Service (DoS) via uncontrolled memory allocations.
An issue was discovered in the tokio-postgres library for Rust prior to version 0.7.18. A trust assumption mismatch between the PostgreSQL protocol messages sent by a server and how they are parsed and indexed by the client-side library allows a rogue or compromised database server to trigger a Denial of Service (DoS) crash via an unhandled out-of-bounds slice indexing panic.
CVE-2026-14669 is a critical heap-based buffer overflow vulnerability in PostgreSQL's date/time formatting function to_char(timestamptz). The flaw arises from unsafe copying of user-controlled timezone abbreviations into a fixed-size internal buffer. An authenticated database user can trigger this issue by setting a long POSIX timezone abbreviation containing custom formatting, allowing them to overwrite adjacent heap structures and hijack execution control to achieve remote code execution (RCE) with the privileges of the 'postgres' operating system user.