はまやんはまやんはまやん

hamayanhamayan's blog

XSS Challenge 2020-06 Writeupまとめ

かの有名なキヌガワマサトさんのXSSチャレンジ。
XSS Challenge 2020-06
なんかのきっかけで見つけたけれど、さっぱりわからんくて放置してた。
けど、以下のツイートを見つけて、復習できた。
(8) おまどんさんはTwitterを使っています 「結局解けなかったやつ。解法めちゃ納得した ・同一オリジンであれば親ページ下のiframeにdocument.writeで書き込める ・長い文字列はiframeのnameに入れて文字数制限を回避 ・邪魔な "Invalid code:" は /**/ でコメントアウト」 / Twitter

参考Writeups

攻撃の流れ

  1. Frame1, Frame2を用意して、Frame1には普通の攻撃先サイトを読み込んで、Frame2は空にしておく
  2. Frame2に対してcodeパラメタを使って、その先のJSONPっぽいやつを呼び出す
  3. 呼び出した結果、Frame1に文字列が書き込まれる
  4. 全部書き込み切ったらFrame1でスクリプト発動。Frame2のnameに入っているJSコードを実行して、Alert読み出し達成

テク抽出と疑問解消(できてないのもあるけど)

XFS: Cross-Frame Scripting

同じオリジンであれば、親サイトが別オリジンであってもフレーム間通信ができる。
同じようなテクを使う問題をこの前見た。
Chaining No impact(N/A) Bugs to get High impact
このテクの名前ってXFSで正しいんかな?

公式解説のHTMLでは、以下のようにフレームを2つ用意して、フレームYでXSSXSSではないのかも)を起こして、その結果としてフレームXに書き込みをしている。

<iframe src="https://vulnerabledoma.in/xss_2020-06/" name="x" onload="go()"></iframe>
<iframe id="y" name="alert(document.domain)"></iframe>

JSONPっぽいURLの生成方法に弱点

ソースコードでは以下のようにURLを作成している。

s.src = `/xss_2020-06/check_code.php?callback=callback&code=${encodeURI(code)}`;

公式解説では、これのcodeに[payload]&callback=top.x.document.writeをInjectionしている。
2つ弱点を利用している。

  • encodeURIはエスケープとして不適切
  • GET parameterは複数回指定することで上書き可能
    • なるほど。今回は最後に入力したものが採用されるみたいだけど、確か実装依存。前調べたときのメモには以下のように書いてあった
    • ?username=abc&username=efg
      • $_GET["username"] -> efg
      • new URL(location).searchParams.get("username") -> abc

document.writeを使ったコード追記

これは初めて見た。
公式解説では、↑のテクを使って、フレームXに対して以下をdocument.writeしている。

await loadIframe("<script>/*");
await loadIframe("*/eval(/*");
await loadIframe("*/top[1]/*");
await loadIframe("*/.name)//");
await loadIframe("<\/script>");

これの結果、フレームXには以下のように書き込まれる。

<body>Invalid code: '<script>/*'Invalid code: '*/eval(/*'Invalid code: '*/top[1]/*'Invalid code: '*/.name)//'Invalid code: '</script>'</body>

コメントが色々挟まってるのはcheck_code.phpからの応答にInvalid codeが入るから。
仮にこれが完成して、実行されれば、XSSが成立しそうな感じがする。

XFSでは同じオリジンであれば、フレームのnameを取得可能

個人的にはここが一番直感に反するのだが、XFSでフレームの中身を同じオリジンなら相互参照可能なのは、まあ、分かる。
しかし、そのフレームを定義している別オリジンのサイトのフレームのnameは参照可能であるようだ。
非直感といえど、フレーム間参照時にtop.x.document.writeみたいに、別オリジンの構造を思いっきり使ってるんだけれど、
どういう風に分別しているんだろう…

最終的にはalert(document.domain)を実行したいので、これをnameに入れておいて、
<script>eval(top[1].name)</script>を実行する。

  • nameから引っ張ってこなくても、document.writeを使ったコード追記で直接alert書き込めばいいのでは?
    • alert(document/**/.domain);のようにちょうどいい所にコメントが入るのはちゃんと動くけど
    • alert(docum/**/ent.domain);のようにキーワードをぶった切る場合は動かない
    • documentが最も長いキーワードであるが、文字数制限があり、これを一回では書けないので直接は無理