HTTPレスポンス分割

HTTPヘッダーのCR/LFを検証してレスポンス分割を防ぐ

説明

入力に含まれるキャリッジリターン(\r、CR)や改行(\n、LF)がレスポンスヘッダーに入り、サーバー、プロキシ、受信側がメッセージの境界と解釈すると、レスポンス分割が起こり得ます。追加のヘッダーやレスポンスを注入されると、キャッシュ汚染やXSSにつながる可能性があります。

不正なヘッダー値を拒否・修正するサーバーもありますが、配備経路全体が同じ処理をするとは限りません。コンテナーの検証を有効にし、アプリケーションでも値の構文を確認してください。

想定される影響

  • レスポンスを改ざんしたブラウザー動作の操作
  • キャッシュ汚染による悪意あるコンテンツの配信
  • 注入した本文が実行可能なマークアップとして解釈される場合のXSS

対処方法

  1. 信頼できない入力を直接ヘッダーに入れず、定義済みの値を優先してください。
  2. CR、LFや、そのヘッダーで許可されない制御文字を含む値を拒否してください。
  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()で制御文字を拒否し、検証した値だけを設定します。Java文字列のエスケープはHTTPヘッダーの対策にはなりません。

参考資料