
CVE-2025-60876 BusyBox wget의 HTTP 헤더 인젝션에 대한 동작 보존 수정, 개념 증명, 퍼센트 인코딩 패치 및 업스트림 제출 자료 포함.
CVE-2025-60876에 대한 동작을 유지하는 수정 사항입니다. 이는 BusyBox wget의 HTTP 헤더 삽입 취약점이며, 이를 증명하는 개념 증명(PoC)과 수정 사항을 BusyBox 업스트림에 제출하기 위한 아티팩트가 포함되어 있습니다.
BusyBox wget은 URL 경로와 쿼리를 HTTP 요청 라인에 그대로 복사합니다. 원시 CR(0x0D), LF(0x0A) 또는 기타 제어 바이트를 포함하는 URL은 요청 라인을 분할하고 공격자가 제어하는 헤더를 주입할 수 있습니다. 공백(0x20)도 동일한 효과를 가집니다: METHOD SP request-target SP HTTP/1.1 프레이밍을 깨트립니다.
영향을 받는 버전: BusyBox 1.37.0까지 (지원되는 모든 Alpine 브랜치에 포함된 pkgver). 2025년 8월 BusyBox 메일링 리스트에 보고됨; CVE-2025-60876로 CVE 할당 (NVD CVSS 6.5, 중간).
이미 두 개의 패치가 업스트림에 제안되었습니다 (Takeuchi Yuma, 2025-08; Radoslav Kolev, 2025-11). 두 패치 모두 문제가 있는 URL을 거부하고 bb_error_msg_and_die를 호출합니다. 이는 주입을 막지만, 일반 공백도 거부하므로 이전에 작동하던 http://example.org/foo bar와 같은 URL이 이제 오류를 발생시킵니다. 이 회귀 때문에 수정 사항이 다운스트림에 채택되지 않았습니다. Alpine은 이에 대한 백포트를 보류 중입니다 (작업 항목 17872).
이 수정 사항은 경로와 호스트를 다르게 처리하며, GNU wget 및 curl의 동작과 일치합니다.
경로: networking/wget.c의 percent_encode_target() 함수는 요청 라인이 작성되기 전에 요청 대상(request-target)의 제어 바이트(0x00~0x1f), 공백(0x20), DEL(0x7f)을 퍼센트 인코딩합니다:
http://example.org/foo bar는 /foo%20bar로 전송되어 GNU wget 및 curl과 일치합니다. 회귀 없음.%는 변경되지 않으므로 이미 인코딩된 경로가 이중 인코딩되지 않습니다.호스트: 동일한 바이트가 URL 호스트에서 거부됩니다. 호스트 이름에는 제어 문자나 공백이 합법적으로 포함될 수 없으며, 퍼센트 인코딩은 권한 구성 요소(authority component)에 대해 정의되지 않습니다. 이는 프록시 모드에서 중요합니다: 호스트가 절대 형식 요청 대상(GET http://host/path)과 Host: 헤더에 배치되지만 로컬에서 확인되지 않으므로 호스트의 원시 CR이나 LF가 그렇지 않으면 주입될 수 있습니다. GNU wget (CVE-2017-6508 이후)과 curl도 호스트의 제어 문자를 거부합니다.
변경 사항은 networking/wget.c로 제한되며 하나의 헬퍼 함수를 추가합니다.
범위 외: FTP 제어 채널 (ftpcmd가 target->path와 사용자/비밀번호를 원시로 전송, 업스트림에서 인정된 TODO)은 별도의 주입 클래스이며, CVE-2025-60876의 일부가 아니며 여기서 다루지 않습니다. 별도로 추적해야 합니다.
Alpine이 사용하는 동일한 구성 (CONFIG_WERROR 및 CONFIG_TC 꺼짐)으로 BusyBox 1.37.0에 대해 빌드했습니다. 로컬 리스너가 wget이 와이어에 보내는 정확한 바이트를 캡처합니다.
전체 캡처: test/poc-output.txt. 네 개의 BusyBox testsuite/wget 테스트 모두 패치 적용된 빌드에서 통과합니다 (test/testsuite-output.txt); 거기에 기록된 다른 전체 스위트 실패는 관련이 없습니다 (mount/taskset은 루트 필요, 몇 가지 알려진 제한된 컨테이너 문제). aarch64 defconfig에서 크기 영향은 +185바이트입니다 (wget_main +142, .rodata +43, 추가되거나 제거된 심볼 없음). scripts/bloat-o-meter로 측정되었으며 cover-letter.txt에 기록되어 있습니다.
test/finalize.sh는 Docker와 함께 alpine:edge 컨테이너 내에서 실행되며 모든 것을 재현합니다 (PoC, 크기, 테스트 스위트, 패치):
docker run --rm -v "$PWD:/work" alpine:edge sh /work/test/finalize.sh
BusyBox 1.37.0을 다운로드하고 바닐라로 빌드한 후 (직접 및 프록시를 통한 주입을 보여줌), 수정 사항을 적용하고 재빌드하며 위의 요청 사례를 재실행하고, bloat-o-meter와 wget 테스트 스위트를 실행한 후 패치를 작성합니다. test/driver.sh는 더 가벼운 PoC 전용 변형입니다.
BusyBox는 메일링 리스트 프로젝트입니다. 수정 사항은 git send-email을 통해 [email protected]으로 전송되며, Radoslav Kolev의 2025년 11월 스레드에 [PATCH v3] 답장으로, 이전 보고자와 검토자에게 크레딧을 부여합니다. cover-letter.txt를 참조하세요. 수정 사항은 GPLv2이며, BusyBox와 동일한 라이선스입니다.
| 입력 URL | 바닐라 1.37.0 | 패치 적용 |
|---|
/x + CRLF + Evil: injected | Evil: injected가 실제 헤더로 도착함 (취약) | GET /x%0D%0AEvil:%20injected HTTP/1.1, 주입된 헤더 없음 |
/foo bar | 리터럴 공백 전송 | GET /foo%20bar HTTP/1.1 (회귀 없음) |
/foo%20bar (이미 인코딩됨) | 변경 없음 | GET /foo%20bar HTTP/1.1 (%2520 아님) |
/normal | 변경 없음 | GET /normal HTTP/1.1 (변경 없음) |
프록시, 호스트 = h + CRLF + PInjected: 1 | PInjected: 1이 실제 헤더로 도착함 (취약) | bad character in URL host 오류와 함께 종료, 아무것도 전송되지 않음 |
프록시, 호스트 = example.test | 정상 | GET http://example.test/p HTTP/1.1 (변경 없음) |
| 파일 | 설명 |
|---|
wget-cve-2025-60876.patch | BusyBox networking/wget.c에 대한 git format-patch 형태의 수정 사항, 전송 준비 완료 |
commit-message.txt | 커밋 메시지 (패치 헤더이기도 함) |
cover-letter.txt | 기존 스레드를 재개하는 [PATCH v3] 메일링 리스트 커버 레터 |
apply_fix.py | networking/wget.c에 변경 사항을 적용하는 생성기 (패치를 생성하는 데 사용) |
test/serve_once.py | 원시 HTTP 요청을 에코하는 단일 연결 리스너 |
test/driver.sh | 바닐라 및 패치된 BusyBox를 빌드하고 직접 + 프록시 before/after PoC 실행 |
test/finalize.sh | 전체 실행: before/after PoC, bloat-o-meter, testsuite/wget, 패치 재생성 |
test/poc-output.txt | before/after 요청 바이트 캡처 (직접 및 프록시) |
test/testsuite-output.txt | 패치된 빌드에 대한 BusyBox testsuite/wget 결과 |