クイズです:あなたはXSS脆弱性を防御できますか?

2 年前 作成者 David Grudl  

このクイズであなたの知識とセキュリティスキルを試してみてください!攻撃者がHTMLページを制御するのを防ぐことができますか?

すべてのタスクで、同じ問題に取り組みます:XSS脆弱性 を作成せずに、HTMLページで変数$str を適切に表示する方法です。防御の基本は「エスケープ」、つまり特殊な意味を持つ文字を対応するシーケンスに置き換えることです。例えば、文字列をHTMLテキストに出力する際、< という文字が特別な意味(タグの先頭を示す)を持つ場合、これをHTMLエンティティ&lt; に置き換えると、ブラウザは< という記号を正しく表示する。

XSSの脆弱性は非常に深刻なので、警戒してください。攻撃者にページやユーザーアカウントまで支配される可能性があります。幸運と、HTMLページの安全性を保つことに成功しますように!

最初の3つの質問

第1、第2、第3の例で、どの文字をどのように処理する必要があるかを指定する:

1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>

出力が何らかの方法で処理されなければ、表示されたページの一部となる。攻撃者が文字列'foo" onclick="evilCode()' を変数に挿入することに成功し、出力が処理されなかった場合、その要素をクリックするとそのコードが実行されることになります:

$str = 'foo" onclick="evilCode()'
❌ not treated: <input value="foo" onclick="evilCode()">
✅ treated:     <input value="foo&quot; onclick=&quot;evilCode()">

各例に対する解答:

&lt; 1) 文字<& は、HTML のタグとエンティティの先頭を表す。&amp; &quot; 2) 文字"& は,属性値の終わりと HTML エンティティの始まりを表します。&amp; &apos; 3)'& は,属性値の末尾と HTML エンティティの先頭を表す文字です。&amp;

正解するごとに1点ずつもらえます。もちろん、3つのケースとも、他の文字を実体に置き換えることも可能です。害はありませんが、必要ではありません。

質問 No.4

続いて、この文脈で変数を表示する場合、どの文字を置き換える必要があるのでしょうか?

<input value=<?= $str ?>>

解答です:ご覧のように、ここでは引用符が欠けています。最も簡単な方法は、前の質問と同じように、単に引用符を追加してからエスケープすることです。空白や、>,/,=,some others など、タグの中で特別な意味を持つ文字をすべてHTMLエンティティに置き換えるという第二の解決策もあるのですが、これはどうでしょうか?

質問 No.5

さあ、面白くなってきましたね。どのキャラクターをこの文脈で扱う必要があるのか:

<script>
	let foo = '<?= $str ?>';
</script>

ソリューションの中です。 <script>タグの場合、エスケープのルールはJavaScriptによって決定されます。ここではHTMLエンティティは使用しませんが、1つだけ特別なルールがあります。では、どの文字をエスケープすればよいのでしょうか。JavaScriptの文字列の中では、文字列を区切る' をバックスラッシュでエスケープし、\' に置き換えるのが自然です。JavaScriptは複数行の文字列をサポートしていないため(テンプレート・リテラル を除く)、改行文字もエスケープする必要があります。ただし、通常の\n\r に加えて、JavaScript は Unicode 文字\u2028\u2029 も改行文字と見なすので、これもエスケープする必要があることに注意しましょう。最後に、特別なルールとして、文字列には</script を含んではいけないということがあります。これは、例えば、<\/script に置き換えることで防ぐことができます。

これを知っていた人は、おめでとうございます。

質問 No.6

次の文脈は、前回のバリエーションに過ぎないような気がします。扱いは変わるのでしょうか?

<p onclick="foo('<?= $str ?>')"></p>

解答です:ここでもJavaScript文字列のエスケープルールが適用されますが、HTMLエンティティがエスケープされなかった前の文脈とは異なり、ここではエスケープされます。そこでまず、バックスラッシュを使ってJavaScript文字列をエスケープし、次に特殊文字("& )をHTMLエンティティに置き換えます。正しい順序が重要なので、注意してください。

ご覧のように、同じJavaScriptのリテラルでも、エンコードが異なれば <script>要素で、属性で異なる!

質問 No.7

JavaScriptからHTMLに戻りましょう。コメント内のどの文字をどのように置き換えればいいのでしょうか?

<!-- <?= $str ?> -->

解決方法HTML(およびXML)のコメント内では、<&"' などの従来の特殊文字をすべて表示することができます。しかし、禁止されているのは、意外かもしれませんが、-- という2つの文字です。この文字列のエスケープは指定されていませんので、どのように置き換えるかはあなた次第です。空白を挟むこともできます。あるいは、例えば、== に置き換えることもできます。

質問 No.8

終わりに近づいてきましたので、問題を変化させてみましょう。この文脈で変数を印刷するときに気をつけなければならないことは何か、考えてみてください:

<a href="<?= $str ?>">...</a>

解決策エスケープだけでなく、javascript: のような危険なスキームが含まれていないことを確認することも重要です。このように構成されたURLは、クリックされると攻撃者のコードが実行されることになります。

質問番号9

最後に、本当の通の方へのご褒美です。これは、モダンなJavaScriptフレームワーク、特にVueを使用したアプリケーションの例です。#app 要素の中で変数を表示するときに気をつけるべきことがわかるかどうか、見てみましょう:

<div id="app">
    <?= $str ?>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
    el: '#app',
    ...
})
</script>

このコードでは、#app 要素にレンダリングされる Vue アプリケーションを作成します。Vueは、この要素の内容をテンプレートとして解釈します。そして、テンプレート内では、二重中括弧を解釈 します。これは、変数の出力やJavaScriptコードの呼び出し(例、 {{ foo }}).

そのため、#app 要素内では、<& の文字の他に、{{ のペアも特別な意味を持っており、Vue がこれを独自のタグとして解釈しないように、他の適切な配列に置き換える必要があります。HTMLエンティティに置き換えても、この場合は役に立ちません。どう対処すればいいのでしょうか。次のように、中括弧の間に空のHTMLコメントを挿入する方法があります。 {<!-- -->{となり、Vueはこのシーケンスを無視する。

クイズ結果

クイズはどうだった?あなたは何問正解していますか?4問以上正解された方は、上位8%の解答者です – おめでとうございます!

しかし、Webサイトのセキュリティを確保するためには、あらゆる場面で出力を適切に処理することが必要です。

もしあなたが、典型的なHTMLページで、どれだけの異なる文脈が出現するかに驚かれたのであれば、私たちはまだそれら全てに言及していないことをご理解ください。そうすると、クイズが長くなってしまうからです。とはいえ、テンプレート・システムがそれを処理できるのであれば、すべてのコンテキストでエスケープする専門家である必要はないでしょう。

では、テストしてみましょう。

テンプレート化システムの性能は?

最近のテンプレートシステムは、出力された変数を自動的にエスケープするautoescaping機能を誇っています。これが正しく行われれば、あなたのウェブサイトは安全です。もし、それがうまくいかなければ、サイトはXSS脆弱性のリスクにさらされ、その深刻な結果を招くことになります。

このクイズの問題から、人気のあるテンプレートシステムをテストし、その自動エスケープの効果を検証します。それでは、PHPのテンプレートシステムのレビューを始めましょう。

Twig ❌

最初に紹介するのは、Symfonyフレームワークと組み合わせて最もよく使われるテンプレートシステムTwig (バージョン3.5)です。Twigにクイズに答える仕事をさせます。変数$str には常にトリッキーな文字列が入りますが、その出力をどう処理するか見てみましょう。右側に結果を見ることができます。また、プレイグラウンドで その答えと行動を調べることもできます。

   {% set str = "<'\"&" %}
1) <p>{{ str }}</p>
2) <input value="{{ str }}">
3) <input value='{{ str }}'>

   {% set str = "foo onclick=evilCode()" %}
4) <input value={{ str }}>

   {% set str = "'\"\n\u{2028}" %}
5) <script>	let foo = '{{ str }}'; </script>
6) <p onclick="foo('{{ str }}')"></p>

   {% set str = "-- ---" %}
7) <!-- {{ str }} -->

   {% set str = "javascript:evilCode()" %}
8) <a href="{{ str }}">...</a>

   {% set str = "{{ foo }}" %}
9) <div id="app"> {{ str }} </div>

✅ <p>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script> let foo = &#039;&quot;u{2028}; </script>
❌ <p onclick="foo(&#039;&quot;u{2028})"></p>


❌ <!-- -- --- -->


❌ <a href="javascript:evilCode()">...</a>


❌ <div id="app"> {{ foo }} </div>

9つのテストのうち6つでTwigが失敗!

残念ながら、Twigの自動エスケープはHTMLのテキストと属性にしか機能せず、それも引用符で囲まれている場合にしか機能しません。引用符がなくなると、Twigはエラーを報告せず、XSSセキュリティホールを作ってしまいます。

ReactやSvelteのような一般的なライブラリでは、属性値はこのように記述されるため、これは特に不快です。TwigとReactの両方を使うプログラマは、ごく自然に引用符のことを忘れることができます。

Twigの自動エスケープは、他のすべての例で失敗しています。コンテキスト(5)と(6)では、手動でのエスケープが必要で、そのために {{ str|escape('js') }}一方、他のコンテキストでは、Twigはエスケープ機能さえ提供していません。また、悪意のあるリンクの印刷に対する保護(8)や、Vueテンプレートのサポート(9)も欠けています。

Blade ❌❌

2人目の参加者は、Laravelとそのエコシステムと緊密に統合されたテンプレートシステムBlade (バージョン10.9)です。今回も、クイズ問題でその実力を試してみます。また、その答えをプレイグラウンドで 探ることもできます。

   @php($str = "<'\"&")
1) <p>{{ $str }}</p>
2) <input value="{{ $str }}">
3) <input value='{{ $str }}'>

   @php($str = "foo onclick=evilCode()")
4) <input value={{ $str }}>

   @php($str = "'\"\n\u{2028}")
5) <script> let foo = {{ $str }}; </script>
6) <p onclick="foo({{ $str }})"></p>

   @php($str = "-- ---")
7) <!-- {{ $str }} -->

   @php($str = "javascript:evilCode()")
8) <a href="{{ $str }}">...</a>

   @php($str = "{{ foo }}")
9) <div id="app"> {{ $str }} </div>

✅ <p>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script>	let foo = &#039;&quot; ; </script>
❌ <p onclick="foo(&#039;&quot; )"></p>


❌ <!-- -- --- -->


❌ <a href="javascript:evilCode()">...</a>


❌❌ <div id="app"> &lt;?php echo e(foo); ?&gt; </div>

9回のテストのうち、6回でブレードが故障!

結果はTwigと同様です。ここでも、自動エスケープはHTMLテキストと属性においてのみ、またそれらが引用符で囲まれている場合のみ機能します。Bladeの自動エスケープは、他のすべての例でも失敗します。コンテキスト(5)と(6)では、手動エスケープが必要です。 {{ Js::from($str) }}.その他のコンテキストでは、Bladeはエスケープ機能さえ提供していません。また、悪意のあるリンクの印刷に対する保護(8)やVueテンプレートのサポート(9)もありません。

しかし、驚くべきはBladeの@php ディレクティブの失敗で、最後の行に見られるように、自作のPHPコードが直接出力されてしまうのです。

Latte ✅

このトリオは、テンプレートシステムLatte (バージョン3.0)によって結ばれています。私たちは、その自動エスケープをテストします。また、その答えと振る舞いを遊び場 で探ることができます。

   {var $str = "<'\"&"}
1) <p>{$str}</p>
2) <input value="{$str}">
3) <input value='{$str}'>

   {var $str = "foo onclick=evilCode()"}
4) <input value={$str}>

   {var $str = "'\"\n\u{2028}"}
5) <script>	let foo = {$str}; </script>
6) <p onclick="foo({$str})"></p>

   {var $str = "-- ---"}
7) <!-- {$str} -->

   {var $str = "javascript:evilCode()"}
8) <a href="{$str}">...</a>

   {var $str = "{{ foo }}"}
9) <div id="app"> {$str} </div>

✅ <p>&lt;'"&amp;</p>
✅ <input value="&lt;&apos;&quot;&amp;">
✅ <input value='&lt;&apos;&quot;&amp;'>


✅ <input value="foo onclick=evilCode()">


✅ <script> let foo = "'\"\n\u2028"; </script>
✅ <p onclick="foo(&quot;&apos;\&quot;\n\u2028&quot;)"></p>


✅ <!--  - -  - - -  -->


✅ <a href="">...</a>


✅ <div id="app"> {<!-- -->{ foo }} </div>

ラットは9つの課題すべてで優秀な成績を収めました!

HTML属性の引用符の欠落を何とか処理し、JavaScriptを <script>要素や属性で、HTMLコメントでの禁則処理に対応しました。

さらに、攻撃者が提供する悪意のあるリンクをクリックすると、攻撃者のコードが実行されてしまうという事態を防ぐことができたのです。そして、Vueのタグのエスケープをなんとか処理することができた。

ボーナステスト.[#toc-bonus-test]

すべてのテンプレート・システムの必須機能の1つは、ブロックと関連するテンプレートの継承を扱うことです。そこで、テスト済みのすべてのテンプレート・システムに、もう1つ課題を与えることにします。description 、HTML属性に出力するブロックを作成するのです。現実の世界では、もちろんブロックの定義は子テンプレートにあり、その出力はレイアウトなどの親テンプレートにあります。これはあくまで簡略化した形ですが、ブロック出力時のオートエスケープをテストするには十分です。その性能はどうだったでしょうか?

Twig: 失敗❌ ブロックの出力時に文字が正しくエスケープされない。

{% block description %}
	rock n' roll
{% endblock %}

<meta name='description'
	content='{{ block('description') }}'>




<meta name='description'
	content=' rock n' roll '> ❌

Blade ❌ブロック出力時、文字が正しくエスケープされない。

@section('description')
	rock n' roll
@endsection

<meta name='description'
	content='@yield('description')'>




<meta name='description'
	content=' rock n' roll '> ❌

Latte ✅を渡すと、問題のある文字が正しく処理されるようになった **ラテ

{block description}
	rock n' roll
{/block}

<meta name='description'
	content='{include description}'>




<meta name='description'
	content=' rock n&apos; roll '> ✅

なぜ多くのWebサイトが脆弱なのか?

Twig、Blade、Smartyなどのシステムにおけるオートエスケープは、単に5文字<>"'& をHTMLエンティティに置き換えることで動作し、コンテキストを区別することはない。したがって、ある状況でのみ機能し、他のすべての状況では失敗します。ナイーブなオートエスケープは、誤った安心感を与えるため、危険な機能です

現在、27%以上のWebサイトがXSSを中心とした重大な脆弱性を抱えている(出典:Acunetix Web Vulnerability Report)ことは、驚くべきことではありません。この状況を打破するにはどうしたらいいのでしょうか?コンテキストを区別するテンプレートシステムを使用する。

Latteは、テンプレートを単なる文字列として認識するのではなく、HTMLを理解する唯一のPHPテンプレート作成システムです。タグや属性などが何であるかを知っています。コンテキストを区別する。そのため、HTMLのテキストでは正しくエスケープし、HTMLタグの中では異なるエスケープをし、JavaScriptの中では異なるエスケープをする、といった具合です。

Latteは、このように唯一の安全なテンプレートシステムである


*さらに、HTMLを理解しているため、ユーザーが好む素晴らしいn:attributes を提供することができます:*。

<ul n:if="$menu">
	<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>
David Grudl Programmer, blogger, and AI evangelist who created the Nette Framework powering hundreds of thousands of websites. He explores artificial intelligence on Uměligence and web development on phpFashion. Weekly, he hosts Tech Guys and teaches people to master ChatGPT and other AI tools. He's passionate about transformative technologies and excels at making them accessible to everyone.

最近の投稿