헤더 인젝션 | 위험도: 4 (높음) | 세션 탈취, 쿠키 조작 가능
인터넷뱅킹 - 사용자 설정
언어 설정이 HTTP 쿠키로 저장됩니다
정상 요청
Set-Cookie: lang=ko-KR
CRLF: Carriage Return + Line Feed (개행 문자)
⚠️ HTTP 헤더는 CRLF로 구분됩니다. 사용자 입력에 CRLF를 삽입하면 임의의 헤더를 추가할 수 있습니다.
// ❌ 취약한 코드 - CRLF 검증 없음
@GetMapping("/setLang")
public void setLanguage(
@RequestParam String lang,
HttpServletResponse response) {
// 사용자 입력을 검증 없이 헤더에 직접 사용!
Cookie cookie = new Cookie("lang", lang);
response.addCookie(cookie);
// 또는
response.setHeader("Set-Cookie", "lang=" + lang);
}
// ✅ 안전한 코드 - CRLF 필터링 및 검증
@GetMapping("/setLang")
public void setLanguage(
@RequestParam String lang,
HttpServletResponse response)
throws SecurityException {
// 1. CRLF 문자 검증 (차단)
if (lang.contains("\r") || lang.contains("\n")) {
throw new SecurityException(
"Invalid characters in language parameter"
);
}
// 2. URL 인코딩된 CRLF 검증
String decoded = URLDecoder.decode(lang, "UTF-8");
if (decoded.contains("\r") || decoded.contains("\n")) {
throw new SecurityException(
"Encoded CRLF detected"
);
}
// 3. 화이트리스트 검증 (권장)
Set<String> allowedLanguages = Set.of(
"ko-KR", "en-US", "ja-JP", "zh-CN"
);
if (!allowedLanguages.contains(lang)) {
throw new IllegalArgumentException(
"Unsupported language: " + lang
);
}
// 4. 안전한 쿠키 설정
Cookie cookie = new Cookie("lang", lang);
cookie.setHttpOnly(true); // XSS 방어
cookie.setSecure(true); // HTTPS만
cookie.setPath("/");
cookie.setMaxAge(86400); // 24시간
response.addCookie(cookie);
}
// 추가: ResponseEntity 사용 (Spring 권장)
@GetMapping("/setLang")
public ResponseEntity<String> setLanguage(
@RequestParam String lang) {
// CRLF 검증
if (lang.matches(".*[\\r\\n].*")) {
return ResponseEntity
.badRequest()
.body("Invalid parameter");
}
// 화이트리스트 검증
if (!ALLOWED_LANGS.contains(lang)) {
return ResponseEntity
.badRequest()
.body("Unsupported language");
}
// ResponseEntity로 안전하게 반환
return ResponseEntity
.ok()
.header(HttpHeaders.SET_COOKIE,
"lang=" + lang + "; HttpOnly; Secure")
.body("Language set to: " + lang);
}
// 정규식 기반 필터링
private boolean isValidInput(String input) {
// CRLF 및 특수 문자 검증
Pattern pattern = Pattern.compile("^[a-zA-Z0-9-_]+$");
return pattern.matcher(input).matches();
}
// Apache Commons 사용
import org.apache.commons.text.StringEscapeUtils;
String sanitized = StringEscapeUtils.escapeHtml4(userInput);
response.setHeader("Custom-Header", sanitized);