こんにちは!作者のActiveTKです。
今回は、HTTPヘッダー・インジェクションについて解説しようと思います!
1. HTTPの仕組み
HTTPのレスポンスは、以下のような形式です。
HTTP/{バージョン} {ステータスコード} {ステータス}\n\r
{ヘッダー1}: {内容}\n\r
{ヘッダー2}: {内容}\n\r
..(省略)
{ヘッダーX}: {内容}\n\r
\n\r
{本文}
最初の行に状況が書かれ、その次の行からヘッダーが書かれます。
そして、最初の「\n\r\n\r」からを本文として認識されます。
2. HTTPヘッダー・インジェクション
PHPでは、header関数を使う事でヘッダーを追加する事が出来ます。
例えば、
<?php
header("Location: https://blog.activetk.jp/");
と実行すれば、ヘッダー内にLocationという項目が追加され、
その効果でhttps://blog.activetk.jp/にリダイレクトされます。
ついでに、header関数は行の最後の「\n\r」を自動で付けてくれます。
HTTPの仕様で、\n\rがあると別のヘッダーとして認識されます。
なので、
<?php
header("Test1: hogehoge\n\rTest2: hogehoge");
と実行すれば、「Test1」と「Test2」の両方のヘッダーが追加されます。
では、$_GET[“href”]で指定されたサイトへリダイレクトするサービスを作ったとします。
すると、
<?php
if (isset($_GET["href"]))
{
header("Location: " . $_GET["href"]);
exit("redirecting..");
}
else exit("Error.");
というコードになります。
ユーザーが「?href=http://example.com/」と入力したとすると、
HTTP/1.1 301 Moved Permanently\n\r
Connection: Close\n\r
Content-Length: 13\n\r
Location: http://example.com/\n\r
\n\r
redirecting..
というレスポンスになり、正常にリダイレクトされます。
しかし、ユーザーが「?href=http://example.com/%0AHogehoge: This%20is%20test」と入力したとすると、
HTTP/1.1 301 Moved Permanently\n\r
Connection: Close\n\r
Content-Length: 13\n\r
Location: http://example.com/\n\r
Hogehoge: This is test\n\r
\n\r
redirecting..
というレスポンスが生成されます。
よって、「Hogehoge」というヘッダーを送れてしまいます。
ユーザー由来のデータをheader関数で送信すると、関係の無い別のヘッダーまで追加出来てしまうという事です。
しかし、ヘッダーを追加するだけではCookie情報を抜き取ったりする事は出来ません。
悪意のあるページに飛ばす事は可能ですが。
問題はここからです。
ユーザーが「?href=http://example.com/%0A%0A<script>alert(“document.cookie”);</script>」と入力したとすると、
HTTP/1.1 301 Moved Permanently\n\r
Connection: Close\n\r
Content-Length: 13\n\r
Location: http://example.com/\n\r
\n\r
<script>alert("document.cookie");</script>
redirecting..
というレスポンスが生成されます。
この例ではリダイレクトサービスなので、JavaScriptが実行されずにリダイレクトされる可能性が高いですが、このようにJavaScriptを埋め込めてしまいます。
JavaScriptを使用して、「document.cookie」などの情報を送信されてしまうと、セッションハイジャックなどをされてしまう可能性があります。
3. 対策
・ヘッダー内の改行コードを削除する
<?php
if (isset($_GET["href"]))
{
$url = str_replace("\n\r","", $_GET["href"]);
header("Location: " . $url);
exit("redirecting..");
}
else exit("Error.");
・ホワイトリスト方式 (※コードは省略)
・サーバーで、「Content-Length」を指定するように設定する
・nonceを指定して、不正なJavaScriptを実行させない
<?php
$nonce = substr(str_shuffle('1234567890abcdefghijklmnopqrstuvwxyz'), 0, 30);
header("Content-Security-Policy: script-src 'nonce-{$nonce}' 'self' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval'");
?>
<script>alert("ここは実行されない!");</script>
<script nonce="<?=$nonce?>">alert("ここは実行される!");</script>
・Cookieに「HTTP Only」属性をつける
<?php
setcookie("Cookie名", "中身", time()+60*60*24*365, "/", "{サイトのドメイン}", {SSL対応の場合はtrue}, true);
・ユーザーの入力値に頼らない
<?php
if (isset($_GET["href"]))
{
$url = "";
if ($_GET["href"] == "type1") $url = "test1.example.com";
else if ($_GET["href"] == "type2") $url = "test2.example.com";
else $url = "www.example.com";
header("Location: http://" . $url);
exit("redirecting..");
}
else exit("Error.");
最後まで目を通して頂き、誠にありがとうございました。