初心者の初心者による初心者のためのWaniCTF2021 writeup

CTF

こんばんは。先日開催されたWaniCTF2021に参戦したので、記録を残します。

CTF初参戦の初心者がどんな感じに問題を解いていったのかを書くことで、これから始めようという方の参考になればと思います。

前提

  • CTF初参戦
  • セキュリティの仕事をしているわけではない
  • 情報処理安全確保支援士の試験勉強を通じてセキュリティに興味がでた

参戦前の学習状況は以下の通りです。

  • picoCTFのpicoGymを10問程度解いた。時間換算だと3時間程度

結果

以下の内訳で合計11問解き、99位 2045ptでした。(1問以上解いた人が330人くらい)

  • beginner 7問
  • easy 4問
  • normal 1問

1問も解けなかったらどうしようと思っていたんですが、やさしい問題や誘導も多く、たくさん解けて楽しかったです。

参戦期間

開催期間は

2021/11/5(金) 20:00 ~ 2021/11/7(日) 20:00

でした。

私は大体以下のような時間で参戦しました。

  • 金曜の夜に3~4時間ほど
  • 土曜は風邪で1日ダウン
  • 日曜はちょっと元気な時間を使って4時間ほど
  • 合計8時間くらい

このタイミングで風邪をひくというやらかしがあったのはとても残念です。。。

寒くなってきたのでみなさんも体調には気をつけてください。

問題ごとの解法 & 思考

分類順で書きますが、基本的に低難易度から順番に解いていきました。

解けた問題と、挑戦したけど解けなかった問題だけ記載します。

Web

sourcemap

問題文の誘導通りに対象サイトを開いて、chromeの開発ツールでjavascriptのソースコードを確認します。

submit関数の中に文字列の判定をしている行があったので、そこにブレークポイントを設置し、適当なパスワードを入力してcheckを押します。

条件判定直前で一時停止させた後、

e(426, 438, 431, 439) + e(440, 447, 438, 437) + t(622, 614, 627, 628) + t(626, 634, 618, 617) + “_50urc3m4p}” という右式をコンソールに直接入力するとFlagが得られました。

POST challenge

これはシンプルにWebの教育としていい問題だなと思いました。

ファイルをダウンロードしてapp.jsを確認すると、各エンドポイントごとに必要なPOSTデータが確認できます。

各問題に適切なBodyやHeaderを用意してリクエストをなげると、Flagの断片が得られたので、最後に結合して問題クリアです!

URL = "https://post.web.wanictf.org/"


def q1():
    res = requests.post(URL + "chal/1", data={"data": "hoge"})
    print(res.text)


def q2():
    headers = {'User-Agent': 'Mozilla/5.0'}
    res = requests.post(url=URL + "chal/2", data={"data": "hoge"}, headers=headers)
    print(res.text)


def q3():
    res = requests.post(url=URL + "chal/3", json={"data": {"hoge": "fuga"}})
    print(res.text)


def q4():
    res = requests.post(url=URL + "chal/4", json={"hoge": 1, "fuga": None})
    print(res.text)


def q5():
    dir = pathlib.Path(__file__).parent
    res = requests.post(url=URL + "chal/5", files={"data": open(dir / "wani.png", "rb")})
    print(res.text)

NoSQL

ちょっとだけ背伸びして1問だけNormal難易度にも挑戦してみました。

サイトが用意されているので、どうにかログインしなさいという問題です。

まず、ファイルをダウンロードして、中身を色々確認します。

するとmongo/1-create-collection.jsに初期ユーザーの設定情報(ユーザー名とパスワード)が書かれています。

勝利を確信して、ログインしようとしますが、なんと入れません。(そんなバカな!)

10分ほど考えて、もしかしてこれはひっかけなのか?という気持ちになり、さらに探します。

今度はusers.jsonにusername: admin, password: passという情報を見つけます。

今度こそ勝ったと思って入力しますがこれまた入れません。(ちくしょう!!)

ここで一旦落ち着きます。問題文はNoSQLです。ということはNoSQL特有のセキュリティ問題であるはずです。

“NoSQL 脆弱性”でググります。すると、NoSQL特有のインジェクションの脆弱性があることがわかりました。なんとNoSQLの便利な機能である演算子による抽出を逆手にとって、色々悪さができるようです。

ググって見つけた記事とほぼ同じ条件でPOSTデータを使うと、無事ログインできてFlagが得られました。

NoSQL特有の脆弱性は初めてしったのでとても勉強になりました。こういった教育的な問題に触れられるのもCTFのいいところかもしれないですね。

import requests


if __name__ == "__main__":
    data = {
        "username": {"$ne": "  "},
        "password": {"$ne": "  "}
    }
    res = requests.post("https://nosql.web.wanictf.org/login", json=data)
    print(res.text)

Crypto

fox

ファイルをダウンロードするとPythonのソースコードが入っていました。

ソースコードを読むと、どうやらバイト列を整数に変換しているようです。つまり整数をバイト列にもどせば良さそうとわかります。

与えられたソースコードだとスクラッチで変換処理を書いていましたが、Pythonにはそれ用の関数(int.to_bytes)があるのでそちらを使って書きます。

ただし、int.to_bytesは引数にバイト長を与える必要があります。何文字の文字列になるかわからなかったので、強引にループを使って総当たりしてみました。

import pathlib


dir = pathlib.Path(__file__).parent
with open(dir / "output.txt") as f:
    text = f.read()

for i in range(1, 100):
    try:
        print(int(text).to_bytes(i, "big"))
    except:
        print("skip", i)
        continue

dango

foxと同じように、ファイルをダウンロードして中のPythonのコードを読みます。

なにやらKeyを3つ作成して、XORでごちゃごちゃやっているらしいことが読み取れました。

XORの性質はAtCoderでも頻出なので、割とラッキーでした。暗号文に対してKeyのXORを取っているということは、再度同じKeyをXORしてやれば、Keyが打ち消されて暗号文が取れるだろうと予想がつきます。

ただし、Key0が欲しいにもかかわらず、与えられているのはXOR(key0, 1, 2)だったり、XOR(key1, 2)なので、こちらも同様にKey0だけ取得できるように適切にXORを取る必要があります。

def solve():
    import pathlib

    dir = pathlib.Path(__file__).parent
    with open(dir / "output.txt") as f:
        cifer = f.readline().strip().split(" : ")[1]
        A = f.readline().strip().split(" : ")[1]
        B = f.readline().strip().split(" : ")[1]
        C = f.readline().strip().split(" : ")[1]

    key012 = bytes.fromhex(A)
    key12 = bytes.fromhex(C)
    key0 = XOR(key012, key12)
    flag_byte = XOR(bytes.fromhex(cifer), key0)

    print(flag_byte)

Forensics

propaganda

ファイルをダウンロードすると、mp4が入っています。よくわからないまま再生すると、再生の途中で突然Flagが一瞬映りました。該当箇所で一時停止して、Flagを打ち込むと無事クリアでした。

こんな問題もあるんですね。今回は20秒くらいでしたが、例えば1時間の動画でこれやられると辛いなとか考えていました。

partition01

これは解けませんでした。

ファイルをダウンロードするとイメージファイルが入っています。問題文にはパーティションを作ったと書いてあるので、その辺りのワードでググります。

とりあえずわかったことはイメージファイルの中身をパーティションで区切る的なことができるらしいということです。

その後はfdiskでイメージファイルのパーティションを確認して、個別のパーティションをマウントするということをやっていました。ただマウントするところでErrorがでて最後まで解決できず、諦めました。

Misc

binary

1行ずつbitが記載されているcsvが与えられます。

1行ずつ読み込んで1byteごとに文字に変換してやればいいんだろうということがわかったので、コードを書きます。

import csv
import pathlib

if __name__ == "__main__":
    dir = pathlib.Path(__file__).parent
    with open(dir / "binary.csv") as f:
        rows = csv.reader(f)
        next(rows)
        word = []
        byte = ""
        for i, row in enumerate(rows):
            bit = row[1]
            byte += bit
            if i % 8 == 7:
                word.append(int(byte, 2).to_bytes(1, "big").decode())
                byte = ""

    print("".join(word))

docker_dive

Dockerイメージを起動して実行するとFlagが得られるというシンプルな問題でした。

私はPCがmacなので、virtualboxでubuntuを立ち上げて、その中にdockerを入れるという方法で環境構築しています。

一応再現性?のために、Vagrantfileにconfig.vm.provision “shell”, path: “provision.sh”を追加した上で、以下のshellスクリプトでdockerを入れています。

CTF用にいろいろカスタマイズしていきたいなと思います。

# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|

  config.vm.box = "bento/ubuntu-18.04"
  config.vm.provision "shell", path: "provision.sh"

end
#!/bin/sh

sudo apt update -y
sudo apt install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"

sudo apt update -y
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

nealest

ダウンロードするとjpegが入っていて、最寄駅を探せとのことです。

この時点で勝利を確信して、これは知ってるぞexifでしょ!GPSの情報があるんでしょ!ということで意気揚々とexifを確認します。

。。。

なんと情報がありません。

ググりながら色々検討するも結局最後まで解けませんでした。

Pwn

nc

問題文にあるncコマンドを打ち込むと、どこかのサーバーに接続?します。

lsでファイルを見るとflag.txtがあったので、catで中身を見ると無事Flagゲットです。

BOF

ncと同様にコマンドを打ち込むと、今度はふっかつのじゅもん(パスワード)を求められます。

問題文がBOF(buffer overflow)なので、とりあえず長い文字列を適当に打ってみたら、正解だったようでFlagゲットです。

got rewriter

正直なにをやっているか、さっぱりわかっていないのですが、こちらのwriteupをそのままトレースしてクリアしました。(https://tech.kusuwada.com/entry/2020/11/23/223204)

どうもアドレスを書き換えて、無理やり対象の関数を呼び出しているっぽいので、しっかり勉強しておこうと思います。

Reversing

ltrace

何をどうやって解いたのかさっぱり忘れてしまいました。

ltraceのコマンドを対象ファイルに実行すると、Flagらしい文字列が表示されるが、途中で文字が切れていたので、オプションをごちゃごちゃして全部表示させるようなことをしたと思います。

最後に

初参戦でしたが、とても楽しめました。

学びになる部分もたくさんありました。セキュリティの基本的な知識がえられることに加えて、Linuxを触れる機会にもなるので、自分の弱いところを強化できるという意味でもすごく満足度が高かったです。

今回の問題の復習をしつつ、また初心者向けの大会があったら参加してみようと思います。

コメント

タイトルとURLをコピーしました