설명
포맷 문자열 취약점은 String.format, System.out.printf, Formatter.format 등에서 포맷 문자열 자리에 신뢰할 수 없는 외부 입력을 그대로 사용할 때 발생합니다. 공격자는 추가 포맷 지정자(예: %s, %d, 위치 지정자 %1$... 등)를 삽입하여 인자 개수/타입 불일치를 유도해 예외를 발생시키거나, 위치 지정자를 악용해 해당 호출에 전달된 다른 인자의 내용을 의도하지 않게 노출시키는 등의 공격을 할 수 있습니다.
잠재적 영향
- 서비스 장애: 잘못된 포맷 지정으로 MissingFormatArgumentException, IllegalFormatConversionException 등이 발생해 요청 처리 실패 또는 장애 유발
- 정보 노출: 위치 지정자(%1$..., %2$...) 등을 악용해 해당 호출에 전달된 다른 인자의 값이 노출될 수 있음
- 로그 오염/우회: %n 등 제어 문자를 삽입해 로그 포맷을 깨뜨리거나 다중 라인을 유도해 모니터링을 우회
- 자원 고갈형 DoS: 매우 큰 폭/정밀도(%1000000s 등)를 사용해 과도한 메모리/CPU 사용을 유발할 가능성
해결 방법
- 포맷 문자열은 항상 상수(고정 문자열)로 사용하고, 사용자 입력은 포맷 인자로만 전달하기
- 입력을 그대로 출력해야 한다면 포맷 문자열로 "%s"를 사용하기
- 포맷 패턴을 외부에서 받아야 한다면, 허용 목록(whitelist) 기반으로 제한된 소수 패턴만 허용하고 나머지는 기본 패턴으로 대체
- 로그 출력에는 SLF4J의 매개변수 바인딩(예:
logger.info("User: {}", user))을 사용하세요. 로그 줄바꿈 등 제어 문자 처리는 별도로 적용해야 합니다.
예시
변경 전
java
import javax.servlet.http.HttpServletRequest;
public class BadFormatExample {
public void printUser(HttpServletRequest req) {
String pattern = req.getParameter("pattern"); // 외부 입력
String user = req.getParameter("user");
// BAD: 포맷 문자열에 외부 입력 사용
System.out.printf(pattern, user);
}
}
변경 후
java
import javax.servlet.http.HttpServletRequest;
import java.util.Set;
public class GoodFormatExample {
private static final Set<String> ALLOWED_PATTERNS = Set.of("User: %s", "Hello, %s!");
public void printUser(HttpServletRequest req) {
String pattern = req.getParameter("pattern");
String user = req.getParameter("user");
// 기본은 고정 포맷 사용
String safePattern = "User: %s";
// 포맷을 외부에서 받아야 한다면 허용 목록으로 제한
if (pattern != null && ALLOWED_PATTERNS.contains(pattern)) {
safePattern = pattern;
}
System.out.printf(safePattern, user);
}
public void echoRaw(HttpServletRequest req) {
String text = req.getParameter("text");
// 입력을 그대로 보여주려면 포맷 문자열은 항상 고정("%s")
System.out.printf("%s", text);
}
}
설명:
- 변경 전: 외부 입력값이 포맷 문자열로 그대로 사용됩니다. 이 예시에는 인자가 하나뿐이므로
%2$s는 인자 부족 예외를 일으키며,%d는 문자열 인자와 타입이 맞지 않습니다. 위치 지정자는 호출에 전달하지 않은 메모리를 읽는 기능이 아닙니다. - 변경 후: 포맷 문자열을 상수나 제한된 허용 목록으로 고정합니다. 사용자 입력은 포맷 인자로 전달하므로 입력 안의 포맷 지정자가 해석되지 않습니다. 출력 위치에 필요한 인코딩과 로그 제어 문자 처리는 별도로 적용하세요.