はじめに
下記のTweetで出題させていただいた、Flatt Security Developers' Quiz #4にご参加いただきありがとうございました!
⚡️ Flatt Security Developers' Quiz #4 開催! ⚡️
— 株式会社Flatt Security (@flatt_security) 2022年9月15日
<p>タグの内容をalertで表示できますか?皆さんの回答お待ちしております!
4-1デモ環境: https://t.co/YsSVU8PMId
4-2デモ環境: https://t.co/ZlMqtuDqoY
回答提出フォーム: https://t.co/Eo07H2Gc5c pic.twitter.com/TI3jRw7MhV
景品の獲得条件を満たした方には追ってメールでご連絡を差し上げますので、ご確認いただけますと幸いです。なお、景品獲得条件を満たさなかった方にはご連絡いたしません。ご了承ください。
今回のクイズでFlatt Securityに興味を持ってくださった方は是非下記のバナーよりサービス詳細をご覧ください。
今回のクイズの確認
ソースコード
4-1
<html> <head> <meta charset="utf-8"> <title>リダイレクトページ</title> </head> <body> <form> <label for="url">リダイレクト先のURLを入力してください:</label> <input type="text" name="url" placeholder="https://example.com" id="url"> <input type="submit"> </form> <p id="flag">FLATT{CAN_YOU_GET_ME?}</p> <script> const params = new URLSearchParams(location.search); const url = params.get("url"); if (url) { location.href = url; } </script> </body> </html>
4-2
<html> <head> <meta charset="utf-8"> <title>リダイレクトページ 2</title> </head> <body> <form> <label for="url">リダイレクト先を入力してください (static.twitter-quiz.flatt.training内のURLを指定してください!): </label> <input type="text" name="url" placeholder="https://static.twitter-quiz.flatt.training/" id="url"> <input type="submit"> </form> <p id="flag">FLATT{YOU_CANT_GET_ME!}</p> <script> const params = new URLSearchParams(location.search); const url = params.get("url"); if (url) { try { const parsed = new URL(url); if (parsed.origin === window.origin) { location.href = url; } } catch (_) { } } </script> </body> </html>
デモ環境
- 4-1: https://static.twitter-quiz.flatt.training/2208leopald-7b9260bb-1327-45b0-baa5-c67570436525/quiz1.html
- 4-2: https://static.twitter-quiz.flatt.training/2208leopald-7b9260bb-1327-45b0-baa5-c67570436525/quiz2.html
出題形式
HTMLの
タグの内容をalertで表示する方法を解答する。
難易度
4-1は開発者にとって「かんたん」、4-2は「難しい」という想定で設定しました。
解答例
4-1
javascript:alert(window.flag.innerText)
という値をurl
パラメーターに対して渡す、もしくはフォームから送信することで alert(window.flag.innerText)
を実行し、ページ上からフラグを取得することが出来ます。
4-2
以下のようなHTMLを作成し、開いた際に表示されるClick me!
というリンクをクリックすることで、alert(window.flag.innerText)
をstatic.twitter-quiz.flatt.training
上で実行することが出来ます。
<html> <head> <meta charset="utf-8"> <title>4-2 回答</title> </head> <body> <script> const iframe = document.createElement("iframe"); iframe.sandbox = "allow-modals allow-scripts allow-popups"; iframe.srcdoc = '<a href="https://static.twitter-quiz.flatt.training/2208leopald-7b9260bb-1327-45b0-baa5-c67570436525/quiz2.html?url=javascript:alert(window.flag.innerText)" target="_blank">Click me!</a>'; document.body.appendChild(iframe); </script> </body> </html>
解説
4-1
この問題は、任意の値をlocation.href
に代入できるという状態で、XSSを行うという問題です。
解法としては、javascript:...
といったようなjavascript
スキームを使用し、alert(window.flag.innerText)
を実行するといった形のものになります。
HTML Living Standardで定められているように、現在主に使用されているブラウザは、ナビゲーションが発生した際にナビゲート先のスキームがjavascript
であれば、JavaScriptを実行するという仕様となっています。
これにより、location.href
やa
タグのhref
属性等に任意の値が指定できる場合、XSSを行うことが可能となります。
4-2
こちらの問題は4-1と非常に似ているのですが、window.origin
とurl
パラメーターの値をパースした際のorigin
が同一である場合にのみ、location.href
に対する代入が発生するという条件が追加されています。
この問題の解法は少し複雑で、sandbox
属性を付与したiframeの中から、問題のURLをurl
パラメーターにjavascript:alert(window.flag.innerText)
という値を指定した状態で開かせることにより、alert(window.flag.innerText)
を実行することが出来ます。
内部的には以下のような理由でXSSが可能となっています。
sandbox
属性が付与されたiframe内のドキュメントはopaque originと呼ばれるオリジンを持つようになり、これはシリアライズされた際に"null"
となります。
この仕様により、sandbox
属性を指定したiframe内に問題のページを読み込ませることで、window.origin
を"null"
とすることができます。
そして、new URL
でjavascript:alert(window.flag.innerText)
をパースした際のorigin
も同様に"null"
となるため、parsed.origin === window.origin
がtrue
になり、location.href
に対してjavascript:alert(window.flag.innerText)
を渡すことができるようになります。
しかしながら、サンドボックス状態のiframe内で発生したjavascript:...
に対する遷移においてはJavaScriptを正常に実行することができません。1
ただし、allow-popups-to-escape-sandbox
をsandbox
属性で指定しなければ、サンドボックス状態のiframe内から開かれたページもsandbox
の制限を受けるという仕様となっており、かつGoogle Chromeにおいてはサンドボックス状態のウィンドウであったとしても、サンドボックス状態のiframeから新しいウィンドウを開くことにより、javascript:...
に対するページ遷移によるJavaScriptの実行が可能であるという挙動があるため、サンドボックス化されたiframe内から新しいウィンドウを開き、そこで問題のページを読み込ませることによりalert(window.flag.innerText)
を実行することができるようになります。
また、MDN web docsに記載のある通り、この状態のオリジンは同一オリジンポリシーの検証に常に失敗するようになるため、ここで実行されているJavaScriptから直接Cookieやローカルストレージへのアクセス等を行うことはできません。 (ただし、ページへ遷移した際にCookieが送信されているため、潜在的に漏洩してはいけない情報等を取得できる可能性があります。)
なお、この仕様はWHATWGにおいても過去に議論の対象となっています: https://github.com/whatwg/html/issues/3585
今回のような脆弱性への対策方法
今回の問題で取り扱った脆弱性は、モダンなフレームワークを使用していたとしても作り込んでしまう可能性があるものとなっています。
ユーザーの入力を受け取ってリダイレクトを行う必要がある場合は、リダイレクト先のURLのスキームが安全なものかを検証することで、XSSの発生を防ぐことが出来ます。
終わりに
Quizにご参加いただいた皆様、ありがとうございました。不定期ではありますがよりみなさまに楽しみながらセキュリティを学んでいただけるよう今後も発信を続けてまいります。
我々株式会社Flatt Securityはセキュリティ診断サービスを提供しています。
Webアプリケーションやスマートフォンアプリケーションを対象に、セキュリティエンジニアによる手動診断によって高い精度で脆弱性を洗い出すことが可能です。 ツールによる診断しか過去実施しておらず認証や決済といった重要な機能のセキュリティに不安があったり、既存のベンダーとは違う会社に依頼したいと考えていたりする方はお気軽にご相談ください。
数百万円からスタートの大掛かりなものばかりを想像されるかもしれませんが、上記のデータが示すように、診断は幅広いご予算帯に応じて実施が可能です。ご興味のある方向けに下記バナーより料金に関する資料もダウンロード可能です。
また、Flatt Securityはセキュリティに関する様々な発信を行っています。 最新情報を見逃さないよう、公式Twitterのフォローをぜひお願いします!
ここまでお読みいただきありがとうございました。
- どの仕様に影響されてこの挙動となっているか作問者は確認できなかったため、ご存知の方がいましたらご一報いただけると幸いです!↩