説明
String.format、System.out.printf、Formatter.format などの書式文字列に信頼できない入力を使うと、攻撃者が %s、%d、%1$... などの書式指定子を挿入できます。引数の数や型が合わず例外が発生したり、その呼び出しに渡された別の引数が意図せず表示されたりするおそれがあります。
想定される影響
- 処理の失敗:
MissingFormatArgumentExceptionやIllegalFormatConversionExceptionにより、リクエスト処理が中断されるおそれがあります。 - 情報漏えい:
%1$...、%2$...などの位置指定子により、同じ呼び出しに渡された別の引数が露出する場合があります。 - ログの乱れ:
%nで改行を挿入され、ログの構造や監視に影響するおそれがあります。 - リソースの枯渇:
%1000000sのように幅や精度を極端に大きくすると、メモリやCPUを過剰に消費する可能性があります。
対処方法
- 書式文字列は定数にし、ユーザー入力は引数の値としてのみ渡してください。
- 入力をそのまま文字列として表示する場合は、固定の
"%s"を使ってください。 - 外部からパターンを選択する必要がある場合は、少数のパターンを許可リストで限定し、それ以外は既定のパターンに置き換えてください。
- ログには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は文字列の型と一致しません。位置指定子は、呼び出しに渡していない任意のメモリを読む機能ではありません。 - 変更後: 書式を定数または限定された許可リストに固定しています。入力は引数として渡すため、その中の書式指定子は解釈されません。出力先に応じたエンコーディングやログ制御文字の処理は、別途適用してください。