Infobahn CTF 2025 – writeup

最終順位は235/803位でした

beginner – pyjail

与えられたプログラムについて

  • exec(code, {})でユーザー入力を実行
  • 入力は小文字と空白のみ、最大15文字
  • 実行結果の標準出力が500文字を超えると FLAG を表示
  • グローバルに空辞書を渡しているが、通常の組み込みは利用可能
  • 記号・数字・アンダースコア不可 → __import__print('x'*500) などの書き方が使えない

解法

import this

これで len(result) > 500 が真になり FLAG が表示されます

import this について

Python の「イースターエッグ」コマンドで、実行すると 「The Zen of Python」 という短い文章(Python の設計哲学)を表示するモジュールを読み込みます
(読み込むだけで文字が表示されます)

是非一度試してみてください

実行結果

== proof-of-work: disabled ==
Enter your solution: import this
b'infobahn{Y0u_3Sc4p3D_Th3_J@1lll_4359849084894}\n'

Answer : infobahn{Y0u_3Sc4p3D_Th3_J@1lll_4359849084894}

beginner – Linearity

行列構造を使ったXOR暗号で、線形再利用を利用して復号する問題でした

1.構造の分析

  • V = [v0, v1, v2, v3, v4] は固定
  • M[i][j] = V[i] * r_ij (ただし r_ij は 0〜100 の整数)
  • C[k] = M[k//5 % 5][k % 5] ^ ord(FLAG[k])

フラグ文字 FLAG[k] は対応する行列要素 M[row][col] とのXOR

2.ポイント

row = (i // 5) % 5
col = i % 5

この部分より、フラグが 25 文字を超える場合、M の要素が再利用される
(つまり同じ M[row][col] が複数箇所に使われる)

→ これが解析のカギ

3.同じ M を共有するペアを利用

たとえば (row=0,col=0) の箇所は
i = 0, 25, 50, ...
で出現する

ここで同じ M[0][0] が使われている

それぞれに対して

C[i] ^ ord(FLAG[i]) = M[row][col]
C[j] ^ ord(FLAG[j]) = M[row][col]

なので、差分を取ると

C[i] ^ ord(FLAG[i]) = C[j] ^ ord(FLAG[j]) 

ord(FLAG[i]) ^ ord(FLAG[j]) = C[i] ^ C[j]

これで FLAG のペアの文字の XOR 関係が求まる
つまり M の実際の値を知らなくても、文字間の関係が得られる

4. フラグ形式から推測

infobahn{ は固定なので、先頭 9 文字分の ord() が確定している
そこから上の関係式を使うと、同じ M を共有するブロックの文字をすべて順に確定できる

5. M 値の復元

M[row][col] = C[i] ^ ord(FLAG[i]) なので、FLAG[i] が確定したブロックの M は直接求まる
他のブロックでも同じ M が使われていれば、そこからさらにフラグ文字を導ける
この連鎖で全体のフラグを順に解く

from hashlib import sha256

V = [14, 38, 56, 76, 51]
C = [1357, 2854, 1102, 1723, 4416, 283, 344, 4566, 5023, 1798, 477, 3833, 1839, 5416, 4017, 1066, 161, 415, 5637, 1696, 1058, 3025, 5286, 5141, 3818, 1373, 2839, 1102, 1764, 4432, 313, 322, 4545, 5012, 1835, 477, 3825]

flag = list("infobahn{" + "?" * (len(C) - 9))
M = [[None]*5 for _ in range(5)]

# 求まる M を確定
for i in range(9):
    r = (i // 5) % 5
    c = i % 5
    M[r][c] = C[i] ^ ord(flag[i])

# 既知の M で残りの文字を埋める
for i in range(9, len(C)):
    r = (i // 5) % 5
    c = i % 5
    if M[r][c] is not None:
        flag[i] = chr(C[i] ^ M[r][c])

flag = "".join(flag)
print(flag)
print(sha256(flag.encode()).hexdigest())

Answer : infobahn{You_HAVE_Aff1niTy_f0rCrypto}

misc – Sanity Check

Discordサーバーのsponsorsチャンネルのチャンネルトピックに書かれていました

Answer : infobahn{G00d_LucK_th1s_1S_Th3_54n17y_2435343!}

misc – Sponsors

https://2025.infobahnc.tf/sponsorsの下の方に書かれていました

Answer : infobahn{thanks_to_GoogleCloud_OffSec_OtterSec_RET2_Cybersharing_RapidRiskRadar_for_sponsoring!}

misc – Feedback

CTF終了1時間前に公開された問題です
フォームに回答するとフラグを入手できます

Answer : infobahn{s33_y0u_n3xt_ye4r_1n_InfobahnCTF_2026!}

web – PatchNotes CMS

シンプルなパストラバーサルの問題です

インスタンス起動→ページにアクセス後、Fileに以下の文字列を入力

../../../flag.txt

その後、Viewをクリックするとフラグが出てきます

Answer : infobahnctf{0c788146e222f9ac774da77108d8b643}

脆弱なコードについて

/app/admin/api/preview/route.ts
const fullPath = path.join(BASE_DIR, file);
と書かれている部分があります

ここで file はユーザー入力ですが、path.join()相対パスを解決してしまう ため、
../../../flag.txt のようなパスが渡されると、ディレクトリを脱出できてしまいます。

コメント

タイトルとURLをコピーしました