소프트웨어 개발보안 가이드 분석(2021) : HTTP 응답 분할
HTTP 요청에 들어 있는 파라미터가 HTTP 응답헤더에 포함되어 사용자에게 다시 전달될 때, 입력값에 CR(Carriage Return)이나 LF(Line Feed)와 같은 개행문자가 존재하면 HTTP 응답이 2개 이상으로 분리되는 보안약점
소프트웨어 개발보안가이드(2021), 한국인터넷진흥원
HTTP 응답 분할이란?
HTTP 응답 분할 공격은 공격자가 웹 서버로 하여금 예상치 못한 응답을 생성하도록 조작하는 기법입니다. 이 공격은 특히 웹 애플리케이션에서 사용자의 입력을 검증 없이 HTTP 헤더에 반영할 때 발생합니다. 공격자는 HTTP 응답의 헤더를 조작하여, 의도한 대로 추가적인 HTTP 응답을 삽입할 수 있게 됩니다. 이를 통해 쿠키 탈취, 크로스사이트 스크립팅(XSS), Web 캐시 포이즈닝 공격 등 다양한 공격을 수행할 수 있습니다.
공격 매커니즘
HTTP 응답 분할 공격은 주로 CR(캐리지 리턴)과 LF(라인 피드) 문자를 포함하는 사용자 입력을 웹 애플리케이션에서 제대로 처리하지 못할 때 발생합니다. 이러한 취약점을 이용하여 공격자는 HTTP 응답을 의도적으로 분할하여, 추가적인 헤더나 바디를 삽입할 수 있습니다. 예를 들어, 공격자가 사용자의 입력을 통해 삽입한 개행 문자를 통해 새로운 HTTP 응답을 시작하게 만들 수 있습니다.
취약한 웹 어플리케이션의 예
다음은 취약한 웹 애플리케이션에서의 HTTP 응답 분할 공격을 보여주는 간단한 예시입니다.
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter("name");
response.addHeader("X-Welcome", "Welcome " + name);
}
위 예시에서 name 파라미터는 사용자로부터 받은 입력을 검증 없이 HTTP 응답 헤더에 추가합니다. 만약 name 파라미터로 "Tom\r\nSet-Cookie: sessionId=123456"와 같은 입력이 제공된다면, 이는 두 개의 헤더(X-Welcome와 Set-Cookie)를 생성하게 되며, 이는 HTTP 응답 분할 공격으로 이어질 수 있습니다.
시큐어코딩 적용 방법
HTTP 응답 분할 공격을 방지하기 위한 일반적인 대응 방안으로는 다음과 같은 내용들이 있습니다.
- 입력 검증: 모든 사용자 입력에 대해 적절한 검증을 수행하여, CR과 LF와 같은 제어 문자를 포함하지 않도록 합니다.
- 출력 인코딩: 사용자 입력을 HTTP 응답 헤더에 반영하기 전에 적절한 출력 인코딩을 적용하여, 해석될 수 있는 제어 문자를 방지합니다.
- 최신 보안 패치 적용: 웹 서버 및 애플리케이션 서버에 대한 최신 보안 패치를 정기적으로 적용하여, 알려진 취약점을 해결합니다.
- 보안 강화된 프레임워크 사용: 보안 강화된 개발 프레임워크와 라이브러리를 사용하여, 일반적인 보안 취약점으로부터 보호받을 수 있습니다.
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SafeResponseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String redirectUrl = request.getParameter("url");
// 개행 문자를 필터링하여 HTTP 응답 분할 방지
String safeRedirectUrl = removeNewlineCharacters(redirectUrl);
response.setHeader("Location", safeRedirectUrl);
response.setStatus(HttpServletResponse.SC_FOUND);
}
// 개행 문자 제거 메서드
private String removeNewlineCharacters(String input) {
return input.replaceAll("\r", "").replaceAll("\n", "");
}
}
이 코드에서 removeNewlineCharacters 메서드는 주어진 문자열에서 모든 캐리지 리턴(\r)과 라인 피드(\n) 문자를 제거하여 반환합니다. 이렇게 처리된 safeRedirectUrl은 개행 문자를 포함하지 않기 때문에, HTTP 응답 분할을 일으킬 수 있는 요소가 제거됩니다. 따라서 사용자 입력을 통한 의도치 않은 HTTP 헤더나 응답의 삽입을 방지할 수 있습니다.
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String redirectUrl = request.getParameter("url");
// 안전한 방법: 사용자 입력을 검증
if (isValidUrl(redirectUrl)) {
response.setHeader("Location", redirectUrl);
response.setStatus(HttpServletResponse.SC_FOUND);
} else {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
}
// URL 검증을 위한 메서드 (간단한 예시)
private boolean isValidUrl(String url) {
try {
new URL(url);
// 추가적인 검증 로직 구현 가능
return true;
} catch (MalformedURLException e) {
return false;
}
}
위 코드에서는 isValidUrl 메서드를 통해 사용자 입력이 유효한 URL 형식인지 검증합니다. 이러한 검증 과정은 개행 문자와 같이 응답 분할을 유발할 수 있는 입력을 차단합니다. 개행 문자 자체를 필터링 하는 방식은 아니지만, 정상적인 운영 서비스 범위 내에 메타 기호에 대한 필터링 처리가 직접적으로 곤란한 경우라면, 위와 같이 URL 구성형태의 유효성 검증 단계를 적용하는 방법으로도 해당 공격을 대응할 수 있을 것으로 생각됩니다.