FlawOpen

flawopen.com/Teardowns/cve-2022-31150-undici-crlf-injection

● CVE-2022-31150 · CVSS 5.3 · 보통
보안 연구 · FlawOpen

CVE-2022-31150: Undici CRLF 인젝션

CVE-2022-31150에서는 신뢰할 수 없는 CR/LF가 Undici 요청 직렬화에 들어갈 수 있었습니다. 상류 수정과 업데이트 방법을 설명합니다.

💡 알기 쉬운 설명 (ELI5)

HTTP 요청은 줄마다 정해진 항목이 있는 양식과 같습니다. 확인하지 않은 문자를 복사하면 숨은 줄바꿈이 뒤의 내용을 새 항목처럼 보이게 할 수 있습니다. Undici 수정은 쓰기 전에 헤더 값, 메서드, 경로를 검증합니다.

핵심 개념 및 용어

Undici
Node.js용 HTTP/1.1 클라이언트 라이브러리이며, Node.js는 내장 fetch를 위해 Undici 구현도 포함합니다.
CRLF 인젝션
신뢰할 수 없는 캐리지 리턴 및 줄바꿈 바이트가 프로토콜 필드에 들어가 줄 경계를 바꿀 수 있는 문제입니다.
HTTP 토큰
헤더 이름이나 메서드 같은 프로토콜 요소에 허용되는 제한된 문자 집합입니다.
요청 대상
HTTP 요청에서 보내는 경로와 쿼리이며, 제어 문자가 구조를 바꾸지 못하게 해야 합니다.

프레임워크 매핑

  • 이 가이드에서 설명하는 약점의 CWE 참조: CWE-93

근본 원인 분석 (Root Cause)

수정 전 Undici는 신뢰할 수 없는 입력에서 온 잘못된 제어 문자가 포함된 헤더 값을 HTTP 요청으로 직렬화할 수 있었습니다. CR/LF는 줄 구분자이므로 이를 허용하면 헤더 줄을 삽입하거나 손상시킬 수 있습니다. 상류 패치는 헤더 이름을 HTTP 토큰으로 검증하고, 값의 잘못된 문자를 거부하며, 메서드 토큰을 검사하고, 경로의 제어 문자를 차단합니다. GitHub 권고는 패키지 버전 5.8.0 미만을, NVD 설명은 5.7.1 미만을 영향 범위로 적습니다. 둘 다 5.8.0을 수정 버전으로 지정합니다. 이는 클라이언트 요청 인젝션이며 SSRF나 자격 증명 노출이 자동으로 발생하지는 않습니다. 실제 영향은 애플리케이션과 네트워크 경로에 달려 있습니다.

단계별 공격 실행 흐름

단계 1

1. 신뢰할 수 없는 값

애플리케이션 데이터가 Undici 헤더, 메서드 또는 경로에 전달됩니다.

단계 2

2. 검증 누락

영향받는 버전에서는 잘못된 제어 문자가 요청 구성 단계까지 들어갈 수 있었습니다.

단계 3

3. 프로토콜 경계

HTTP 직렬화는 줄 경계를 사용합니다. 제어 문자를 허용하면 필드가 삽입되거나 손상될 수 있습니다.

단계 4

4. 수정과 제한

Undici 5.8.0은 직렬화 전에 헤더 바이트, 메서드 토큰, 경로 문자를 검증합니다. 영향은 환경에 따라 다릅니다.

소스 코드 비교: 취약한 구현 vs 보안 패치

취약한 구현
// a29a151 이전 lib/core/request.js의 관련 직렬화 지점
function serializeRequest(request, method, path, headers) {
  request.method = method;
  request.path = path;
  for (const [key, value] of Object.entries(headers)) {
    // 직렬화 전에 프로토콜 문자를 검증하지 않습니다.
    request.headers += `${key}: ${value}\r\n`;
  }
}
보안 강화 패치
// 업스트림 요청 파서의 강화된 검증 패턴
const token = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
const invalidFieldValue = /[^\t\x20-\x7e\x80-\xff]/;
const invalidPath = /[^\u0021-\u00ff]/;

function validateRequestTarget(method, path) {
  if (!token.test(method)) throw new InvalidArgumentError('invalid request method');
  if (invalidPath.test(path)) throw new InvalidArgumentError('invalid request path');
}

function processHeader(request, key, value) {
  if (!token.test(key)) throw new InvalidArgumentError('invalid header key');
  if (invalidFieldValue.test(value)) throw new InvalidArgumentError(`invalid ${key} header`);
  request.headers += `${key}: ${value}\r\n`;
}

엔지니어링 및 시스템 보안 강화 체크리스트

출처