HTTP 응답 분할(HTTP Response Splitting) 취약점

HTTP 헤더에 포함되는 CR/LF를 검증하여 응답 분할 방지

설명

사용자 입력에 포함된 캐리지 리턴(\r, CR)이나 라인 피드(\n, LF)가 HTTP 응답 헤더에 그대로 전달되고 서버·프록시·수신자가 이를 메시지 경계로 해석하면 응답 분할이 발생할 수 있습니다. 추가 헤더나 응답을 주입하면 캐시 오염이나 XSS로 이어질 수 있습니다.

일부 서버는 잘못된 헤더 값을 거부하거나 정리하지만, 배포 환경 전체가 같은 방식으로 처리한다고 가정하지 마세요. 컨테이너의 헤더 검증을 유지하고 애플리케이션에서도 값의 문법을 확인하세요.

잠재적 영향

  • HTTP 응답 조작: 공격자가 추가적인 HTTP 응답을 삽입하여 브라우저의 동작을 조작할 수 있습니다.
  • 캐시 중독(Cache Poisoning): 악성 응답을 캐시에 저장하여 사용자에게 악성 콘텐츠를 제공할 수 있습니다.
  • Cross-Site Scripting(XSS): 공격자가 HTTP 응답 본문을 변조하여 악성 스크립트를 실행할 수 있습니다.

해결 방법

  1. HTTP 응답 헤더에 사용자 입력을 직접 포함하지 않기
    • 응답 헤더 값은 미리 정의된 값으로 제한해야 합니다.
  2. 입력값 검증 및 거부 처리
    • \r, \n 및 기타 제어 문자는 응답 헤더 값에 포함되지 않도록 요청을 거부합니다.
  3. 허용 목록 기반 헤더 값 사용
    • 헤더 값이 사용자 선택에 따라 달라져야 한다면 미리 정의한 값 집합에서만 선택하도록 제한합니다.

예시

변경 전

java
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class InsecureResponseSplitting {
    public void processRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String userInput = request.getParameter("input"); // 사용자 입력을 직접 헤더에 포함
        response.setHeader("Custom-Header", userInput); // 취약한 코드
    }
}

변경 후

이 사용자 정의 헤더는 최대 128자의 출력 가능한 ASCII만 허용합니다. 실제 헤더별 문법도 별도로 검증하세요.

java
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SecureResponseSplitting {
    public void processRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String userInput = request.getParameter("input");

        if (!isValidHeaderValue(userInput)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid input");
            return;
        }

        response.setHeader("Custom-Header", userInput); // 검증된 값만 헤더에 사용
    }

    private boolean isValidHeaderValue(String value) {
        return value != null
            && value.length() <= 128
            && value.chars().allMatch(c -> c >= 0x20 && c <= 0x7e);
    }
}

설명:

  • 변경 전:
    • 사용자 입력을 setHeader()에 직접 전달하여 수신 경로가 CR/LF를 메시지 경계로 허용하면 응답을 조작할 수 있음.
  • 변경 후:
    • isValidHeaderValue()를 사용하여 CR/LF와 제어 문자를 거부.
    • Java 문자열 이스케이프를 HTTP 헤더 보안 대책으로 사용하지 않고, 검증된 값만 setHeader()에 전달.

참조