XSSの対策
ここまで説明してきたとおり、XSSが発生する原因は以下の3種類が大部分を占めています。
- テキストノードに出力する際の「<」「>」のエスケープ漏れ
- 属性値に出力する際の「"」「'」のエスケープ漏れ
- URLをリンクとして取り扱う際のプロトコルスキームの確認漏れ
1.および2.はエスケープ漏れがXSSの原因であることは自明なので、HTMLとして出力する際にこれらをエスケープします。エスケープすべき対象は以下の5文字です。
< > " ' &
「&」は直接XSSの原因になるわけではありませんが、この文字をエスケープしなければユーザーから入力された「&」を正しく表示できませんので、エスケープが必要です。
また、2.についてはエスケープに加えて、属性値を必ず「"」または「'」で囲むようにします。そうでなければ、"や'をエスケープしていたとしてもXSSの発生につながることがあるからです。
多くのプログラミング言語やWebアプリケーション作成用のフレームワークでは、HTMLに出力するために、これらの文字を適切にエスケープする関数があらかじめ用意されていることが多いでしょう。
PHPであればhtmlspecialchars関数がそれにあたります。
<?php // GETで与えられたitemパラメータを取得 $item = $_GET[ 'item' ]; // さまざまな処理 …… ?> <body> <!-- itemをエスケープしてHTML内のテキストノードに出力 --> <span><?php echo htmlspecialchars( $item, ENT_QUOTES, 'UTF-8' ); ?></span> …… <!-- itemをエスケープしてHTML内の属性値に出力 --> 検索文字列: <input type="text" name="item" value="<?php echo htmlspecialchars( $item, ENT_QUOTES, 'UTF-8' ); ?>"> </body>
エスケープするのは、HTMLとして出力する直前です。外部から入力された文字列を受け取った直後にエスケープしてしまうと、その後その文字列を用いてさまざまな処理、例えば文字列の文字数に依存しているなどの処理を行う際に問題が発生しやすくなるからです。必ず、「HTMLとして出力する直前にエスケープ」を原則としましょう。
3.については、URLをリンクとして出力する際にはスキームがhttpあるいはhttpsであること、すなわちURLが「http://」または「https://」で始まることを確認するようにします。場合によっては「/foo」のように「/」で始まるURLを許容してもよいでしょう(注2)。
決して、javascript:スキームであればNG、のような確認を行わないようにしてください。XSSが発生するのはjavascript:スキームだけでなくvbscript:スキームを始め複数の種類があるため、「httpやhttpsのように安全なものだけを許可する」という方針のほうが堅実だからです。
例えば、PHPであれば以下のように正規表現を用いて文字列の先頭が「http://」「https://」あるいは「/」であることを確認し、そうである場合にのみa要素のhref属性にURLを設定することで、リンクによるXSSを防ぐことができます。
<?php function checkUrl( $url ){ if( preg_match( "/\Ahttps?:\/\//", $url ) || preg_match( "/\A\//", $url ) ){ return $url; }else{ return ""; } } ?> …… <div> <a href="<?php echo htmlspecialchars( checkUrl( $url ), ENT_QUOTES, 'UTF-8' ); ?>"> <?php echo htmlspecialchars( $url, ENT_QUOTES, 'UTF-8' ); ?></a> </div>
ここでもa要素のhref属性の属性値として出力していますので、先ほど同様にhtmlspecialcharsによるエスケープを忘れてはいけません。
注2
攻撃者の指定したURLをリダイレクト先として用いる場合には、リダイレクト先が許可されたドメインであるかを確認し、オープンリダイレクタと呼ばれる脆弱性を防ぐ必要があります。オープンリダイレクタについての説明は本稿の範囲を超えますので今回は割愛します。
まとめ
ここまで解説したとおり、XSSの対策として有効な基本的なルールは以下のようなものになります。
- テキストノードや属性値としてHTMLを出力する時点で「<」「>」「"」「'」「&」の5文字をエスケープする
- 属性値は引用符で囲む
- URLをリンクとして取り扱う場合にはhttpおよびhttpsスキームに限定する
これですべてのXSSが防げるわけではありませんが、現実に発生しているXSSの多くはこのルールを徹底することで防げるものが大半です。
本稿では、XSSの原理と対策の基本について解説を行いました。
一方、XSSは攻撃の種類や発生箇所が多岐にわたるため、本稿だけではカバーできていない内容も多数あります。それらについては先に紹介したXSS (Cross Site Scripting) Prevention Cheat Sheetなどを活用してみてください。また、近年はJavaScriptによるクライアント側でのコード量が増えたことによって、JavaScript上で発生するDOM Based XSSと呼ばれる種類のXSSも増加しています。これについてはDOM based XSS Prevention Cheat Sheetなどを参考にしてみてください。
本稿がセキュアなWebアプリケーション構築の一助になれば幸いです。