😎

サークルでTsukuCTF2023に参加してきました💪

2023/12/12に公開

TsukuCTF2023に参加してきました!

2023/12/9 ~ 2023/12/10 に開催されたTsukuCTF2023に「Lumos」として参加し、 5位 という好成績を出すことができました!!

 私たちLumosは横浜国立大学の公認プログラミングサークルです。普段はみんなDiscord上で自由に活動して互いに情報共有をしています。これまでサークルとしてCTFに出たことはなかったのですが、XでCTFの存在を知り、急遽サークル内で募集を掛けたところ8名が集まり、今回出場することになりました。

今回のチームの大半はCTF初出場!! 私自身(@Shion1305)もOSINTの経験が無いどころか、CTFの経験自体浅く... チームで出場した経験もこれまで無かったので非常に新鮮な経験でした。
 TsukuCTFはOSINTの問題が多めのCTF。OSINTだと特にプログラミングなどの専門知識が無くてもチャレンジしやすく、CTF初めてという人でも楽しめると思います!
 分からない問題をメンバーと話し合って、一緒に考えたり発見を共有したりしながら解き進めて、やっと解けた時の達成感は半端なかったです。
(今回の機会を通じてサークルにCTFを布教していきたい!)

せっかくの機会! ということでメンバーからWriteupを募ってまとめてみました!

WEB

WEBはEXECpyのみ解くことができませんでした... 悔しい..!

MEMOwow [496 pts: web]

author: @Shion1305

今回はメモアプリがテーマ。以下のように、メモ情報の書き込みと読み込みの2つのエンドポイントが用意されていて、どのように /memo/flag に入っているデータを取り出すか、という問題でした。

@app.route("/write", methods=["POST"])
def write_post():
    if not "memo" in session:
        return redirect(url_for("index"))
    memo = urllib.parse.unquote_to_bytes(request.get_data()[8:256])
    if len(memo) < 8:
        return abort(403, "これくらいの長さは記憶してください。👻")
    try:
        session["memo"].append(memo)
        if len(session["memo"]) > 5:
            session["memo"].pop(0)
        session.modified = True
        filename = base64.b64encode(memo).decode()
        with open(f"./memo/{filename}", "wb+") as f:
            f.write(memo)
    except:
        return abort(403, "エラーが発生しました。👻")
    return render_template("write_post.html", id=filename)


@app.route("/read", methods=["POST"])
def read_post():
    if not "memo" in session:
        return redirect(url_for("index"))
    filename = urllib.parse.unquote_to_bytes(request.get_data()[7:]).replace(b"=", b"")
    filename = filename + b"=" * (-len(filename) % 4)
    if (
        (b"." in filename.lower())
        or (b"flag" in filename.lower())
        or (len(filename) < 8 * 1.33)
    ):
        return abort(403, "不正なメモIDです。👻")
    try:
        filename = base64.b64decode(filename)
        if filename not in session["memo"]:
            return abort(403, "メモが見つかりません。👻")
        filename = base64.b64encode(filename).decode()
        with open(f"./memo/{filename}", "rb") as f:
            memo = f.read()
    except:
        return abort(403, "エラーが発生しました。👻")
    return render_template("read_post.html", id=filename, memo=memo.decode())


if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=31415)
解法

プログラムの流れを整理します。

読み出し(/read)の流れ

  1. リクエストIDが以下の条件を満たすか確認。
    • . flag が含まれていない。
    • 12文字より長い。
  2. リクエストIDをbase64でdecode。
  3. セッションにdecodeしたIDが含まれていれば、再びencode。
  4. encodeされた値のファイル名に含まれているデータを返す。

書き込み(/write)の流れ

  1. リクエスト内容が8文字以上であるか確認。
  2. リクエスト内容を受け取り、セッションに追加。
  3. リクエスト内容をencodeしたものをファイル名として、リクエスト内容を保存。

Flagを得るまで

/read を用いて /memo/flag を取得するには、encodeして flag となる値がセッションに含まれている必要がある。
encodeして flag になる値は...
011111 011111 011111 011111 つまり 0x7E 0x56 0xA0
 しかし、 /write では8文字以上という制約がついているため、このままではリクエストを実行できない。
 そこで、0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x7E 0x56 0xA0 としてみる。(これをencodeすると ////////flag となる)
 この値をSessionに追加するには、このバイト列を /write にPOSTリクエストで送信すればよい。

 今度は /read でこのSessionに引っかかるようなmemoidを提供すればflagを取得できる。
 今回 flag を連続して含めることができないため、base64において無効となるような文字を挟ませることでこの制約を回避できる。
////////fl!!ag
 先ほど得たCookieを用いて次のリクエストを出すとフラグが得られた。

TsukuCTF23{b45364_50m371m35_3xh1b175_my573r10u5_b3h4v10r}

base64とバイト列の関係をこれまでで一番意識した問題でした... 凄く悩みました..w

OSINT

airport [100 pts: osint]

author: @kurikin
airport

つくしくんは、旅の思い出を振り返っていましたが、この写真はどこの空港かわからなくなりました。
ここはどこの空港か教えてくれませんか?
Flagフォーマットは TsukuCTF23{空港の3レターコード(IATA)} です。

解法

自動車の右に見える滑走路番号が見えます

「滑走路 32 14」と調べてみると、、、

大阪国際空港のトリビア | 大阪国際空港(伊丹空港) (osaka-airport.co.jp)

このサイトがヒット。

❗ Point
滑走路の端に記されている数字やアルファベット。これは、滑走路の向きを表した「滑走路番号」で、世界的ルールに基づいてつけられています。 大阪国際空港は、磁北から時計回りに320°を向く「滑走路32 (Runway Three Two) 」と140°を向く「滑走路14(Runway One Four)」があります。通常時は滑走路32を使用する北向きの進行方向ですが、まれに逆向きのコース、滑走路14を使用する場合があり、通称「ワンフォー」と呼ばれています。

との記述があり、写真の空港が伊丹空港だとわかるので、伊丹空港の3レターコード「ITM」を回答すればOKです

TsukuCTF23{ITM}

castle [100 pts: osint]

author: @ck9
castle

この前、お城に行ってこの写真を取ってきたんだ!
どこにあるかわかるかい?
フラグのフォーマットは、TsukuCTF23{緯度_経度} です。 小数点は第三桁まで有効とします。

解法

Googleで画像検索をかけると、兵庫県姫路市の「太陽公園」がヒットしました。
次のブログを見ると、写真は太陽公園内の「白鳥城」であると分かります。

https://ameblo.jp/kagikou2002/entry-12764593580.html

後はGoogle Mapのストリートビューで似た景色を探し、緯度と経度を得れば終了です

TsukuCTF23{34.886_134.630}

eruption [100 pts: osint]

author: @ck9
eruption

つくしくんは旅行に行ったときに噴火を見ました。噴火の瞬間を実際に見たのは初めてでしたが、見た日付を覚えていません。
つくしくんが噴火を見た日付を写真の撮影日から特定して教えてください。
撮影場所が日本なのでタイムゾーンはJSTです。フラグの形式は TsukuCTF23{YYYY/MM/DD} です。

解法

画像検索にかけると、以下のnote内にかなり近い写真を発見しました。
JANOG49 in 鹿児島 に現地参加しました!|株式会社ブロードバンドタワー (note.com)

JANOG開催期間中に一度だけ大きく黒煙を吐いた桜島を激写することに成功!

上記の記述があることから、写真の噴火は桜島のもので、かつJANOG49開催期間内の2022年1月26日から28日の間に撮影されたものであると推測できました。
気象庁の公式サイトから桜島の火山活動情報を調べ、1月28日であることが確定できました。

桜島 有史以降の火山活動 | 気象庁

TsukuCTF23{2022/01/28}

location_for_what [100 pts: osint]

author: @ck9
location_for_what

とある場所を友達と探索していると、「ここ、何かの映画の聖地だった気がするけど、名前忘れちゃった......」とのこと。
シュッと特定して教えてあげよう!
Flagの形式は TsukuCTF23{映画のタイトル} です。

解法

画像検索をかけ、写真の場所が新宿御苑であることが確認できました。

そのまま「新宿御苑 映画 聖地」で検索し、映画『言の葉の庭』の舞台であることがわかりました。

TsukuCTF23{言の葉の庭}

green_bridge [180 pts: osint]

author: @ck9

この写真が撮影されたのはどこですか...?
Flagフォーマットは TsukuCTF23{緯度_経度} です。
端数は少数第4位を四捨五入して小数点以下第3位の精度で回答してください。

解法

緑色の橋が特徴的だったため橋の部分のみを選択して画像検索したところ、栃木県那須塩原市にある「森林の駅/もみじ谷大吊橋」であることが確認できました。

あとはストリートビューと写真を見比べながら撮影地点の座標を特定して完了です。

TsukuCTF23{36.956_139.880}

perfume [198 pts: osint]

とある施設でいろいろな香水を見かけたが、施設の場所が思い出せない。
この施設の場所を調べ、教えてほしい。
フラグはTsukuCTF23{緯度_経度}であり、小数点第三桁まで有効である。

解法

右側の香水部分のみを選択し画像検索したところ、「大分香りの博物館」の展示であることが特定できたため、Googleマップで座標を確認し回答しました。

TsukuCTF23{33.312_131.488}

mab [228 pts: osint]

author: @ck9

mab.main.jpが使用しているレンタルサーバサービスを特定し、そのWebサイトのドメイン名を答えてください。Flagフォーマットは TsukuCTF23{ドメイン名}です。

解法

恐らくレンタルサーバーの契約者向け無料ドメインであると推測し、main.jpでwhois検索をかけました。

$ whois main.jp

登録者名やネームサーバーの情報から、GMOペパボ株式会社が運営するロリポップ!の無料ドメインであることがわかりました。
以下のヘルプページからも、main.jpのサブドメインが契約者向けに用意されていることが確認できます。

ロリポップ!のドメインと独自ドメインの違いはなんですか - ヘルプセンター|ロリポップ!レンタルサーバー

TsukuCTF23{lolipop.jp}

tsukushi_estate [232 pts: osint]

author: @ck9

つくし君が写真に写っているビルにオフィスを構えたいらしいのだけど、築年数が少し心配......
つくし君の代わりに調査してください!

Flagの形式は TsukuCTF23{築年_月} です。
例えば、2022年3月に出来たビルであれば、 TsukuCTF23{2022_03} になります。

解法

合同会社つくし不動産のホームページから貸オフィスの賃貸物件(下記ページ)を検索したところ、3件のみヒットしました。

三重県の貸事務所(貸オフィス)を地域から探す | 合同会社つくし不動産

各物件のページを確認した所、物件情報の写真から伊勢SIビル(築1983年3月)であることが特定できました。

(貸事務所(貸オフィス)) 伊勢SIビルの物件情報 | 合同会社つくし不動産

TsukuCTF23{1983_03}

travel_with_tsukushi [281 pts: osint]

author: @ck9

旅が好きなつくしくんは、空港の写真からそれがどこの空港かすぐにわかります。
つくしくんからの挑戦状!
これがどこの空港かわかるかな?
Flagフォーマットは TsukuCTF23{空港の3レターコード(IATA)} です。

解法

3台の飛行機が並んでいたため各飛行機のロゴ部分を選択して画像検索をしたところ、手前から順に

  • エア・アラビア
  • バティック・エア・マレーシア
  • マレーシア航空

での飛行機であることがわかりました。

マレーシアの航空会社が2つあること、エア・アラビアが就航してるマレーシアの空港はクアラルンプール国際空港だけであることの2点からクアラルンプール国際空港であると推測し、3レターコードである「KUL」を回答したところ正解でした。

TsukuCTF23{KUL}

kiZOU [321 pts: osint]

author: @ck9

ここは日本で一番のリゾート地!少し歩くと目の前に素敵な像が見えたから写真を撮ったつもりだったんだけど、見返したら端っこしか写ってない!困ったなぁ、この像についてもっと知りたかったんだけどなぁ。僕の代わりにこの像について調べてくれないか?
フラグ形式は TsukuCTF23{像を寄贈した人物の名前} です。

解法

写真の右側をよく見ると、少し見づらいですが「au Style NAHA」の文字が確認できます。

auの店舗情報から沖縄県那覇市の商業施設「パレット久茂地」内にあることが判明したため、「パレット久茂地 シーサー 寄贈」で検索すると以下のツイート(ポスト)がヒットし、右下の像が1991年那覇市政70周年に上原清善氏が寄贈したシーサーであることが確認できました。

https://twitter.com/kintaro11111/status/1082234341361504256

TsukuCTF23{上原清善}

big_statue [354 pts: osint]

author: @kurikin

大きなドリアンだ!どこにあるんだろう?? フラグの形式は TsukuCTF23{緯度_経度} です。例えば、この像が東京の渋谷駅にある場合、フラグは TsukuCTF23{35.6580_139.7016} となります。

解法

中国語らしきものが像の上端に見えるため、画像検索で中国語を調べます。すると、「利陞榴莲王」 という果物の名前だということが分かります。

この名前で検索するとシンガポールにあるお店の情報が多数ヒットするので、この後はストリートビューで順に探していけば答えにたどり着きます。

TsukuCTF23{1.3623_103.8872}

TrainWindow [394 pts: osint]

author: @satake231

夏、騒音、車窓にて。
フラグのフォーマットは、TsukuCTF23{緯度_経度}です。
緯度経度は小数第五位を切り捨てとします。

解法

画面中央にある「TTC」の文字に注目します。

この名前で検索すると、熱海の上多賀にある会社のようです。画面右奥に見える島が「初島」と考えれば、この結果は整合的です。

車窓から撮っているとのことなので、写真手前に見える特徴的な屋根の建物を線路沿いを中心に航空写真で探していけば、解答に至ることができます。

TsukuCTF23{35.0642_139.0664}

CtrlAltPrtSc [427 pts: osint]

author: @ck9

仕事中にCtrl + Alt + PrtScでウィンドウのスクリーンショットを撮ったよ。
つくし君がサボって使用していたサービスの名前を答えよ。
フラグはTsukuCTF23{サービスの名前}の形式です。

解法


スクリーンショットの左上をよく見るとバックグラウンドにYouTubeのロゴが写っていることがわかります。

スクリーンショットの写り込みには注意しましょう。

TsukuCTF23{YouTube}

3636 [444 pts: osint]

author: @kurikin

ここはどこ...?
Flagフォーマットは TsukuCTF23{緯度_経度} です。
端数は少数第四位を四捨五入して小数点以下第三位の精度で回答してください。

解法

5-3636 o.ed.jp で検索すると、「とうみょうこども園」がヒットしました。矢印が右を向いているので、ストリートビュー で「とうみょうこども園」前の道をずっと左側に辿って行ったら見つけました。

TsukuCTF23{37.502_139.929}

Yuki [446 pts: osint]

author: @ck9

雪、無音、窓辺にて。
フラグのフォーマットは、TsukuCTF23{緯度_経度}です。
緯度経度は小数第四位を切り捨てとします(精度に注意)。

解法

橋の部分のみを選択して画像検索したところ以下の旅行記事がヒットし、北海道の定山渓温泉 定山渓ビューホテルのラウンジからの景色であることが特定できました。

青く美しい氷像に感動『2023千歳・支笏湖氷濤まつり』と定山渓ビューホテル宿泊記 | 4travel.jp

ブログ内の写真から特徴的な椅子の形状も一致していることが確認できたため、Googleマップで座標を特定して回答しました。

TsukuCTF23{42.969_141.167}

tsukushi_no_kuni [451 pts: osint]

author: @ck9

かつて、筑紫国を統治していた国造の一人が乱を起こした。
その子孫の一人が、ある天皇と同一人物である説が提唱されている。
その子孫の名前を TsukuCTF23{} で囲んで答えよ。

解法

「筑紫国 乱」で検索したところ「磐井の乱」がヒットしたため、その首謀者である「筑紫君磐井」の子孫について更に調べました。
その結果、磐井の子孫である「筑紫薩夜麻」について、高市皇子=薩夜麻=天皇であるとする言説を主張するブログを発見したため、これをフラグとして回答しました。

https://anisakuayataka.blog.fc2.com/blog-entry-463.html

TsukuCTF23{筑紫薩夜麻}

free_rider [463 pts: osint]

author: @kurikin

https://www.fnn.jp/articles/-/608001
私はこのユーチューバーが本当に許せません!
この動画を見たいので、元のYouTubeのURLを教えてください。
また、一番上の画像(「非難が殺到」を含む)の再生位置で指定してください。
フラグフォーマットは、TsukuCTF23{https://www.youtube.com/watch?v=REDACTED&t=REDACTEDs}

解法

YouTube上でこのYouTuberの動画を調べていきます。

https://www.youtube.com/watch?v=YzUDKBolwXY

再アップされているものが見つかりました。

TsukuCTF23{https://www.youtube.com/watch?v=Dg_TKW3sS1U&t=176s}

broken display [471pts: osint]

author: @kurikin

表示が壊れているサイネージって、写真を撮りたくなりますよね!
正しく表示されているときに書かれている施設名を見つけて提出してください!
フラグ形式: TsukuCTF23{◯◯◯◯◯◯◯◯IYA_◯◯◯◯◯◯S}

解法

画面をよく見ると、反射している文字があります。画像を反転してみると、l’occitaneではないかと推測できました。

l’occcitaneがあり、語尾に「IYA」が付くような施設を探します。

全国のL’OCCITANE(ロクシタン)の店舗一覧(97件) (dpcosme.com)

「西宮阪急百貨店」が見つかりました。この施設名は「西宮ガーデンズ」なので、これが答えになります。

TsukuCTF23{NISHINOMIYA_GARDENS}

RegexCrossword [484 pts: osint]

author: @ck9

解法

正規表現クロスワードを気合で解くだけです。
途中で縦読みっぽいな〜と感じたので左側の列を優先して埋めていったところ、1列目にメールアドレスの@以降(ドメイン)が現れてくれたので競技中はそこで一旦打ち切りました。(面白かったのでそのうち全部解きたいと思います。)

株式会社 Eyes, JAPANの公式ホームページより、郵便番号〒965-0872が特定できました。

TsukuCTF23{965-0872}

flower_bed [484 pts: osint]

author: @ck9

花壇の先にQRコードのキューブがあるようですね。友人曰く、モニュメントの近くに配置されているものらしいです。
こちらのQRコードが示すURLを教えてください! リダイレクト前のURLでお願いします!
Flagの形式は TsukuCTF23{URL} です。例えば、https://sechack365.nict.go.jp がURLなら、 TsukuCTF23{https://sechack365.nict.go.jp} が答えになります。

解法

QRコードの横の見切れた文字から、**旧福岡県公会堂貴賓館**の前のFUKUOKAモニュメントの近くに設置されているものであることがわかりました。

いくつかのツイート(ポスト)やブログからキューブの各QRコードを集めて試行したところ、貴賓館の公式ホームページが正解フラグでした。
(httpsリダイレクトに気付かず、少し戸惑いました。)

https://twitter.com/ohss43/status/1660258701671006210/photo/2

TsukuCTF23{https://www.crossroadfukuoka.jp/}

https://karafuru-style.com/2019/09/27/3254/

TsukuCTF23{http://tenjin-central-park.jp/}

正解フラグ

https://doranekoweb.com/Jealous-Guy/231024e/

TsukuCTF23{http://www.fukuokaken-kihinkan.jp}

koi [488 pts: osint]

author: @kurikin

画像フォルダを漁っていると、鯉のあらいを初めて食べた時の画像が出てきた。
当時のお店を再度訪ね、鯉の洗いを食べたいが電話番号が思い出せない。
誰か、私の代わりにお店を調べ、電話番号を教えてほしい。

記憶では、お店に行く途中で見かけたお皿が使われていた気がする。。。

Flagは電話番号となっており、ハイフンは不要である。
TsukuCTF23{電話番号}

解法

どうやら鯉のあらいという食べ物で、使われている食器が特徴的な写真です。

Google Lens で調べてみるとどうやら小石原焼という焼き物で、福岡県で有名だそうです。

ということで福岡の鯉のあらいを提供している店を頑張って探すのですが、「福岡 鯉のあらい」で検索してもなかなか同じ食器を使っている店がヒットしませんでした。

ここで画像をよく見ると、鯉のあらいだけでなくフナらしき青い魚の刺身も乗っていることに気づきました。

ということで、Google Map で「鯉のあらい フナ 福岡」と調べて出てきた店の写真を片っ端から見ていくと「川魚料理 森口屋」がすごくそれっぽいことに気づきました。

TsukuCTF23{0936176250}

めちゃくちゃお腹がすく問題でした……(というか今回の問題セット、全体的に飯テロでは?)

鯉とフナ、とても食べたい!

grass_court [488 pts: osint]

しばらく使われていないテニスコートのようだ。
この日本にあるテニスコートの場所はどこだろう。
フラグの形式は TsukuCTF23{緯度_経度}です。
小数点以下5位を切り捨てて、小数点以下4桁で答えてください。

解法

左奥にパラボラアンテナのようなものが見えます。

そこで、「パラボラアンテナ テニスコート」と検索。画像から探していくと、似ている雰囲気の画像が見つかりました。

 画像のキャプションから、これは岩手県にある天文台の1つであることが分かったので、「岩手県 天文台」と検索し、この場所は「国立天文台水沢VLBI観測所」であることが特定できました。

その後Googleマップを使用し、テニスコートは「奥州宇宙遊学館風の又三郎テニスコート」であることが確認できました。

TsukuCTF23{39.1349_141.1324}

fiction [491 pts: osint]

author: @ck9

「座標を教えてくれ」
フラグフォーマットは、TsukuCTF23{緯度_経度}です。
小数点以下5位を切り捨てて、小数点以下4桁で答えてください。

解法

画像検索したところ、VALORANTの新マップ「サンセット」内のスクリーンショットであることがわかりました。
VALORANT:新マップ「サンセット」の紹介トレーラーが公開。ロサンゼルスが舞台の2サイトマップ、EP7 ACT2で登場

ロサンゼルスが舞台である以上の情報が得られず、しばらく苦戦していました。

仕切り直して「VALORANT 緯度経度」などで検索したところ、ゲーム内の各マップのローディング画面に座標表示があることがわかり、公式YouTubeの新マップ紹介トレーラー動画からサンセットの座標を確認することができました。(4分30秒頃)
VALORANTの各マップの国・地域、Google マップにロケーションを入力して調べてみた
サンセット // マップ紹介トレーラー - VALORANT | YouTube

その後スクリーンショット内の建物のモデルとなった場所があるのかと思い付近をストリートビューで探索していましたが見つからず、最終的に元の座標をそのまま変換して入力するだけで良いことに気づき回答しました。

TsukuCTF23{34.0338_-118.2044}

hunter [498 pts: osint]

author: @Shion1305

名前をメールで聞こうとしたところ、相手のGmailの一部が分からなくなってしまいました。
大変お忙しいところ恐縮ですが、暇なときに調査してください。
qeinijo#iby#@gmail.com
#が不明な部分です。
なお、外部サービスに短期間で多くのアクセスをしないようにしてください。

解法

Googleアカウントのユーザー名の規則を調べました。

https://support.google.com/mail/answer/9211434

  • アルファベット(a~z)、数字(0~9)、ピリオド(.)を使用できる。それ以外の文字は使用できない。
  • 英数字以外の文字(ただし、ピリオド(.)を除く)をユーザー名の先頭または末尾に使用することができる。

よって、2か所に入りうる文字は、1番目が a~z 0~9 . 2番目が a~z 0~9 であることが分かります。よってパターンは高々 37 x 36

あるGoogleアカウントが存在するかどうかの確認方法は以下に載っていました。

https://stackoverflow.com/questions/43104586/how-to-check-an-email-address-for-existence-programmatically-on-gmail

次のURLにPOSTリクエストで以下のようなデータを送信すると、アカウントが存在しているかを確認できるようです。

https://accounts.google.com/InputValidator?resource=SignUp

{"input01":{"Input":"GmailAddress","GmailAddress":"{アカウント名}","FirstName":"","LastName":""},"Locale":"en-GB"}

既に使用されているメールアドレスを入力すると、エラーメッセージで以下を得ます。

You entered an email address that is already associated with an account.

よって、それぞれのメールを入力した結果、上記と同じエラーを得た時のみアカウントが存在するものとし、全てのパターンのメールアドレスについて1秒間のインターバルを入れて確認を行いました。使用したコード(関数)は以下です。

func emailExists(email string, proxyURL *url.URL) (bool, error) {
	target := "https://accounts.google.com/InputValidator?resource=SignUp"
	headers := map[string]string{
		"Content-Type": "application/json",
		"Origin":       "https://accounts.google.com",
		"Referer":      "https://accounts.google.com/SignUp?hl=en-GB",
		"User-Agent":   "Mozilla/5.0 (X11; CrOS x86_64 15359.58.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.134 Safari/537.36",
	}
	hit_message := "You entered an email address that is already associated with an account."

	payload := `{"input01":{"Input":"GmailAddress","GmailAddress":"` + email + `","FirstName":"","LastName":""},"Locale":"en-GB"}`
	req, _ := http.NewRequest("POST", target,
		strings.NewReader(payload),
	)
	for k, v := range headers {
		req.Header.Add(k, v)
	}
	transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
	client := &http.Client{Transport: transport, Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("failed to perform POST request: %v", err)
		return false, err
	}
	defer resp.Body.Close()
	b, _ := io.ReadAll(resp.Body)
	var bodyBytes interface{}
	err = json.Unmarshal(b, &bodyBytes)
	if err != nil {
		fmt.Printf("failed to unmarshal response body for email: %s, body: %s, err: %v\n", email, string(b), err)
		return false, err
	}
	if bodyBytes.(map[string]interface{})["input01"].(map[string]interface{})["Valid"] == true {
		return false, nil
	}
	message := bodyBytes.(map[string]interface{})["input01"].(map[string]interface{})["ErrorMessage"]
	if message == nil {
		fmt.Println("message is nil")
		return false, nil
	}
	if message.(string) == hit_message {
		return true, nil
	}
	return false, nil
}

全てのパターンについて確認したところ、アカウントが存在するとしてヒットしたのは以下のメールアドレスのみでした。

qeinijo.iby8@gmail.com

しかし、 TsukuCTF23{qeinijo.iby8@gmail.com} はフラグではありませんでした。

そこで、このアカウントからユーザー情報を取り出すことを試してみます。

Googleアカウントの情報を取り出す方法を調べたところ、 GHunt がヒットしました。

https://github.com/mxrch/GHunt

以下の手順でGHuntを使用すると、アカウントの情報を得ることができました。

  1. npmで GHunt をインストール
  2. GHunt companion というChrome拡張機能をインストール
  3. ghunt login を実行し、 GHunt Companion で取得したトークンを入力して認証。
  4. ghunt email qeinijo.iby8@gmail.com で情報を取得。

アカウント名に TsukuCTF23{GHun7_i5_u5efu1} を見つけてこの問題は完了です。

感想

Googleの公式サポートには、メールアドレスからアカウント名はわからない、と回答されていて最初アカウント名はないだろうと見てしまっていて、 GHunt に行きつくまでにむちゃくちゃ時間がかかりました... かかった時間計4時間。でも楽しかった。

sunset [499 pts: osint]

author: @ck9

TsukuCTF運営の1人であるshioが、今年に開催されたあるイベントが終わった後に夕日を撮影した。
この写真が撮影された日時を求めよ。
フラグフォーマットはTsukuCTF23{YYYY/MM/DD_hh:mm}である。
例えば、TsukuCTF2023の開始日時はTsukuCTF23{2023/12/09_12:20}
なお、誤差は1分まで許容され、日本標準時を用いる。

解法

写真内のテトラポットが特徴的だったため、テトラポットの部分のみを選択して画像検索したところ以下のnoteがヒットし、新潟港であることが特定できました。

新潟旅行記~日本海と坂口安吾とへぎそば編~

次に撮影日を特定するため、作問者であるshioさんのTwitter(X)(@shio_sa1t)のツイート(ポスト)をひたすら遡ったところ、以下のツイート(ポスト)から2023年9月10日のセキュリティ・ミニキャンプ in 新潟 2023に講師として参加していることがわかりました。

また、以下のページから当日の新潟市の日没時間が18:01であることが判明したため、フラグをTsukuCTF23{2023/09/10_18:01}と入力しましたが誤答でした。

https://eco.mtk.nao.ac.jp/koyomi/dni/2023/s1609.html

日の入りは完全に地平線に沈みきったときの時刻であるため、写真の時刻は恐らく日没時刻の18:01よりも少し早いのだろうと考察しましたが見当がつかず、1分の誤差許容があることから17:59と17:56で決め打ちをして入力したところ、17:56で通りました。(本問題のみ回答数が3回までという制約が課せられていたためギリギリでした。)

TsukuCTF23{2023/09/10_17:56}

udon_2023 [499 pts: osint]

author: @Rika

ここのうどん、麺だけじゃなく、鶏天も美味しい!!!
お店の場所を忘れたから、7文字のplus codeで教えて!!!
フラグフォーマットは、+を含めてTsukuCTF23{**REDACTED**+**REDACTED**}

解法

画像検索にかけたり、「鶏天 うどん」「鶏天 うどん シンプル」などと検索したりして、いろいろな鶏天うどんを見ているうちに、画像の鶏天はなんだか立派だなあ…と思い始めました。

そこで、「鶏天 大きい うどん」と検索。実は既に同じお皿が使われているお店を見つけていたものの、不正解だったため、今度はお皿ではなくテーブルに注目して、画像から探すことにしました。

すると、テーブルが似ている画像を発見。鶏天もほとんど同じで、奥にある金属の入れ物も一致していることが確認できました。

画像が掲載されているサイトを見ると、店舗が特定できたので、Googleマップでプラスコードを確認しました。

https://k1nm.tokyo/20221130archives/1080750973.html

TsukuCTF23{PP63+G6}

Rev

title_screen [487 pts: rev]

author: @kurikin

父は昔プログラマーだったらしい、
しかし、当時開発したソフトのタイトルが思い出せない。
ソフトを起動すると画面にタイトルが表示されるらしいのだが...
残っている開発データからなんとか導き出そう!

解法

ファミコンのアセンブリ(main.asm)と、ROM のメモリの設定(cfg ファイル)と文字のビットマップ(character.bmp)が与えられます

タイトル画面に表示したい文字列を特定すればフラグが入手できるようです。

アセンブリ内の data に記録されている数値列がその文字列と対応しているので、例えば 0x24 に対しては character.bmp から 0x24 番目の文字を探せばフラグが入手できます。

TsukuCTF23{Tsukushi_Quest}

たまたま 6502 について勉強していた時期があって、その知識が活かせたのがラッキーでした。普段読む x64 とは違う、変化球のようなアセンブリの問題が出るのは想定外でした。めちゃくちゃ楽しかったです!!!

Crypto

new_cipher_crypto [491 pts: crypto]

author: @kurikin

I wouldn't worry about it being decrypted because of this complicated process!

解法

RSA の (n,e,c) とランダムな 1024 bit のランダムな素数 rs := \mathrm{magic}(p,q,r) が与えられます。

ここで \mathrm{magic}(p,q,r) := (\mathrm{magic2} \circ \mathrm{magic2} \circ \mathrm{magic2})(p+q) \mod r です。

\mathrm{magic2}(x) については \mathrm{magic2} := \sum^{x-1}_{i=0} (2i+1)=x^2 なので、結局 s=(p+q)^8 \mod r となります。

rp+q と比べて十分大きいため Coppersmith’s method が使えて、これによって p+q が復元できます。実際、Coppersmith’s method を適用すると p+q の候補が複数出力され、そのうちの一つが正しいものでした。

p+q が分かれば \varphi(n):=n-(p+q)+1 より \varphi(n) がわかるので、d=e^{-1} \mod \varphi(n) とすれば秘密鍵がわかり平文が復元できます。

from Crypto.Util.number import long_to_bytes

r = 103223593878323616966427038558164830926502672938304332798494105455624811850665520007232855349275322661436610278579342219045141961390918581096853786570821153558254045159535424052709695034827346813080563034864500825268678590931984539859870234179994586959855078548304376995608256368401270715737193311910694875689
n = 90521376653923821958506872761083900256851127935359160710837379527192460953892753506429215845453212892652253605621731883413509776201057002264429057442656548984456115251090306646791059258375402999471135320210755348861434045380328105743420902906907790808856692202001235875364637226136825102255804354305482559609
e = 65537
c = 39668573485152693308506976557973651059962715190609831067529475947235507499262380684639847018675763554013384459402806860854682788726216001229367497152996318350435451964626471390994912819484930957099855090471916842543402636518804720798852670908465424119746453269056516567591615005540824659735238630769424838121
s = 47973982866708538282860872778400091099562248899259778360037347668440286307912908903978215839469425552581036546347166946878631770320447658019243172948791127890920650587484602734465156455346574205060576092006378013418405134156752490431761037178223087071698840062913540185985867322265081768252745826955229941850

F = GF(r)
PR.<x> = PolynomialRing(F)

f = x^8 - s
p_q = f.roots()
print(p_q)

for t in p_q:
  tt = int(t[0])

  phi = n - tt + 1
  d = inverse_mod(e, phi)
  m = pow(c, d, n)
  print(m)
  print(long_to_bytes(m))

TsukuCTF23{Welcome_to_crypto!}

(p+q)^8 \mod r から p+q を復元する部分がちょっと悩みました。有限体上の多項式の根ということで、第一感であった Coppersmith’s method が通って安心しました。

OSINT メインの CTF ということで初等整数論で済むような Crypto が出るかと思いましたが、思ったよりも高度な知識を使う問題が出て楽しかったです!

Discussion