このセクションでは、クロスサイトスクリプティングの脆弱性を防止するための一般的な原則と、XSS攻撃から保護するためのさまざまな一般的な技術の使用方法について説明します。
クロスサイトスクリプティングの防止は、一般的に二層の防御によって達成することができます:
Burp Scannerを使用して、XSSを含む多数のセキュリティ脆弱性をwebサイトでスキャンすることができます。 Burpの最先端のスキャンロジックは、熟練した攻撃者の行動を複製し、それに応じてXSS脆弱性の高いカバレッジを達成することができます。 Burp Scannerを使用すると、XSS攻撃に対する防御が効果的に機能していることを保証できます。
出力データのエンコード
エンコードは、ユーザーが制御可能なデータがページに書き込まれる前に直接適用する必要があります。 たとえば、JavaScript文字列内の値は、HTMLコンテキスト内の値とは異なるタイプのエスケープを必要とします。
HTMLコンテキストでは、ホワイトリストに登録されていない値をHTMLエンティティに変換する必要があります:
-
<
に変換する:<
-
>
に変換する:>
JavaScriptの文字列コンテキストでは、英数字以外の値はUnicodeエスケープする必要があります:
-
<
に変換する:\u003c
-
>
に変換する:\u003e
時には、正しい順序で、エンコーディングの複数のレイヤーを適用する必要があります。 たとえば、イベントハンドラ内にユーザー入力を安全に埋め込むには、JavaScriptコンテキストとHTMLコンテキストの両方を処理する必要があります。 したがって、最初に入力をUnicode-escapeしてからHTML-encodeする必要があります:
<a href="#" onclick="x='This string needs two layers of escaping'">test</a>
到着時に入力を検証する
エンコーディングはおそらくXSS防御の最も重要な行ですが、すべてのコンテキストでXSS脆弱性を防ぐだけでは不十分です。 また、入力がユーザーから最初に受信された時点で、入力をできるだけ厳密に検証する必要があります。
入力検証の例には次のものがあります:
- ユーザーが応答で返されるURLを送信した場合、HTTPやHTTPSなどの安全なプロトコルで開始されていることを検証します。 そうしないと、誰かが
javascript
やdata
のような有害なプロトコルでサイトを悪用する可能性があります。 - ユーザーが数値であると予想される値を指定した場合、その値に実際に整数が含まれていることを検証します。
- 入力に期待される文字セットのみが含まれていることを検証します。
入力検証は、無効な入力をブロックすることによって理想的に動作するはずです。 無効な入力を有効にするためにクリーンにしようとする別のアプローチは、よりエラーが発生しやすく、可能な限り避けるべきです。
ホワイトリストとブラックリスト
入力検証は、一般的にブラックリストではなくホワイトリストを使用する必要があります。 たとえば、すべての有害なプロトコル(javascript
、data
など)のリストを作成しようとする代わりに、javascript
、data
などを使用します。)、単に安全なプロトコル(HTTP、HTTPS)のリストを作成し、リストにないものを禁止します。 これにより、新しい有害なプロトコルが表示されたときに防御が中断されず、ブラックリストを回避するために無効な値を難読化しようとする攻撃の影響を受けにくくなります。
“安全な”HTMLを許可する
ユーザーがHTMLマークアップを投稿できるようにすることは可能な限り避けるべきですが、時にはそれがビジネス要件です。 たとえば、ブログサイトでは、制限されたHTMLマークアップを含むコメントの投稿が許可される場合があります。
古典的なアプローチは、潜在的に有害なタグとJavaScriptを除外しようとすることです。 安全なタグと属性のホワイトリストを使用してこれを実装しようとすることができますが、ブラウザの解析エンジンの不一致や突然変異XSSのような癖のため、このアプローチは安全に実装することは非常に困難です。
最も悪いオプションは、DOMPurifyのように、ユーザーのブラウザでフィルタリングとエンコードを実行するJavaScriptライブラリを使用することです。 他のライブラリでは、ユーザーがmarkdown形式でコンテンツを提供し、markdownをHTMLに変換することができます。 残念ながら、これらのライブラリはすべてXSSの脆弱性を時々持っているので、これは完璧な解決策ではありません。 使用する場合は、セキュリティ更新プログラムを注意深く監視する必要があります。
注
JavaScriptに加えて、CSSや通常のHTMLなどの他のコンテンツは、いくつかの状況で有害である可能性があります。
悪意のあるCSSを使用した攻撃
テンプレートエンジンを使用してXSSを防ぐ方法
多くの現代のウェブサイトは、TwigやFreemarkerなどのサーバー側のテンプレートエ これらは通常、独自のエスケープシステムを定義します。 たとえば、Twigでは、コンテキストを定義する引数を指定してe()
フィルタを使用できます:
{{ user.firstname | e('html') }}
JinjaやReactなどの他のテンプレートエンジンは、デフォルトで動的コンテンツをエスケープし、XSSのほとんどの発生を効果的に防止します。
特定のテンプレートエンジンまたはフレームワークを使用するかどうかを評価するときは、エスケープ機能をよく確認することをお勧めします。
注
ユーザー入力をテンプレート文字列に直接連結すると、XSSよりも深刻なサーバー側のテンプレート注入に対して脆弱になります。
PHPでXSSを防ぐ方法
PHPにはhtmlentities
というエンティティをエンコードする組み込み関数があります。 HTMLコンテキスト内で入力をエスケープするには、この関数を呼び出す必要があります。 関数は3つの引数で呼び出される必要があります:
- あなたの入力文字列。
-
ENT_QUOTES
, これは、すべての引用符をエンコードする必要があることを指定するフラグです。 - ほとんどの場合、UTF-8でなければならない文字セット。 たとえば、
:
<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>
JavaScriptの文字列コンテキストでは、すでに説明したように入力をUnicodeエスケープする必要があります。 残念ながら、PHPは文字列をUnicodeエスケープするAPIを提供していません。 これがPHPでそれを行うためのいくつかのコードです:
<?php
function jsEscape($str) {
$output = '';
$str = str_split($str);
for($i=0;$i<count($str);$i++) {
$chrNum = ord($str);
$chr = $str;
if($chrNum === 226) {
if(isset($str) && ord($str) === 128) {
if(isset($str) && ord($str) === 168) {
$output .= '\u2028';
$i += 2;
continue;
}
if(isset($str) && ord($str) === 169) {
$output .= '\u2029';
$i += 2;
continue;
}
}
}
switch($chr) {
case "'":
case '"':
case "\n";
case "\r";
case "&";
case "\";
case "<":
case ">":
$output .= sprintf("\u%04x", $chrNum);
break;
default:
$output .= $str;
break;
}
}
return $output;
}
?>
PHPでjsEscape
関数を使用する方法は次のとおりです:
<script>x = '<?php echo jsEscape($_GET)?>';</script>
または、テンプレートエンジンを使用することもできます。
JavaScriptでXSSクライアント側を防ぐ方法
JavaScriptでHTMLコンテキストでユーザー入力をエスケープするには、JavaScriptがHTMLをエンコードするAPIを提供していないため、独自のHTMLエン 文字列をHTMLエンティティに変換するJavaScriptコードの例を次に示します:
function htmlEncode(str){
return String(str).replace(//gi, function(c){
return '&#'+c.charCodeAt(0)+';';
});
}
次に、この関数を次のように使用します:
<script>document.body.innerHTML = htmlEncode(untrustedValue)</script>
入力がJavaScript文字列内にある場合は、Unicodeエスケープを実行するエンコーダーが必要です。 ここにサンプルUnicode-encoderがあります:
function jsEscape(str){
return String(str).replace(//gi, function(c){
return '\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
});
}
次に、この関数を次のように使用します:
<script>document.write('<script>x="'+jsEscape(untrustedValue)+'";<\/script>')</script>
jQueryでXSSを防止する方法
jQueryのXSSの最も一般的な形式は、ユーザー入力をjQueryセレクターに渡すときです。 Web開発者は多くの場合、location.hash
を使用してセレクタに渡し、jQueryがHTMLをレンダリングするときにXSSを引き起こすことになります。 jQueryはこの問題を認識し、入力がハッシュで始まるかどうかをチェックするためにセレクターロジックにパッチを適用しました。 これで、jQueryは最初の文字が<
の場合にのみHTMLをレンダリングします。 信頼されていないデータをjQueryセレクタに渡す場合は、上記のjsEscape
関数を使用して値を正しくエスケープしてください。
コンテンツセキュリティポリシー(CSP)を使用したXSSの緩和
コンテンツセキュリティポリシー(CSP)は、クロスサイトスクリプティングに対する最後の防 XSS防止に失敗した場合は、攻撃者ができることを制限することで、CSPを使用してXSSを軽減できます。
CSPを使用すると、外部スクリプトをロードできるかどうか、インラインスクリプトを実行するかどうかなど、さまざまなことを制御できます。 CSPを展開するには、ポリシーを含む値を持つContent-Security-Policy
というHTTP応答ヘッダーを含める必要があります。
CSPの例は次のとおりです:
default-src 'self'; script-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none';
このポリシーでは、画像やスクリプトなどのリソースをメインページと同じオリジンからのみロードできるように指定します。 したがって、攻撃者がXSSペイロードを正常に注入することができたとしても、現在のオリジンからのリソースのみをロードすることができます。 これにより、攻撃者がXSSの脆弱性を悪用する可能性が大幅に減少します。
外部リソースの読み込みが必要な場合は、攻撃者がサイトを悪用するのを助けないスクリプトのみを許可するようにしてください。 たとえば、特定のドメインをホワイトリストに登録した場合、攻撃者はそれらのドメインから任意のスクリプトをロードする可能性があります。 可能であれば、自分のドメインでリソースをホストしてみてください。
それが不可能な場合は、ハッシュベースまたはnonceベースのポリシーを使用して、異なるドメイン上のスクリプトを許可できます。 Nonceは、スクリプトまたはリソースの属性として追加されるランダムな文字列で、ランダムな文字列がサーバー生成の文字列と一致する場合にのみ実行され 攻撃者はランダム化された文字列を推測できないため、有効なnonceを持つスクリプトまたはリソースを呼び出すことができないため、リソースは実行され