1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

初ブログです、自分用に整理のため。

お偉いさんが
「成長したいなら書きなさいっ!」
って言ってたんだもん。。。

DiceCTF 2024

一人で参加、主にwebに取り掛かりました。
もう少し解けるようになりたいな。。。

  1. dicedicegoose
  2. funnylogin

dicedicegoose [web 445 solves]

image.png

image.png

鬼ごっこ形式
wasdでさいころが上下する。黒マスに到達したら勝ちだが、黒マスはランダムに移動する。
最短回数(9回)で到達したらflagを獲得できる。

ソースコードにスクリプトの詳細あり

// 省略

do {
      nxt = [goose[0], goose[1]];
      switch (Math.floor(4 * Math.random())) {
        case 0:
          nxt[0]--;
          break;
        case 1:
          nxt[1]--;
          break;
        case 2:
          nxt[0]++;
          break;
        case 3:
          nxt[1]++;
          break;
      }
    } while (!isValid(nxt));

    goose = nxt;

    history.push([player, goose]);

    redraw();
  };

サイコロ(player)と黒枠(goose)を行動ごとにhistoryにpushしているようだ。

function win(history) {
    const code = encode(history) + ";" + prompt("Name?");

    const saveURL = location.origin + "?code=" + code;
    displaywrapper.classList.remove("hidden");

    const score = history.length;

    display.children[1].innerHTML = "Your score was: <b>" + score + "</b>";
    display.children[2].href =
      "https://twitter.com/intent/tweet?text=" +
      encodeURIComponent(
        "Can you beat my score of " + score + " in Dice Dice Goose?",
      ) +
      "&url=" +
      encodeURIComponent(saveURL);

    if (score === 9) log("flag: dice{pr0_duck_gam3r_" + encode(history) + "}");
  }

最短回数、すなわち score=9 のときのみ行動履歴であるhistoryをencodeしたのちflagとして表示する。

思いついた考え方は2つ。

  1. Math.floor(4 * Math.random()) を変えて、黒枠が左に移動するように固定して実行
    開発ツールの使い方を把握しきれていない...
    開発ツール上でスクリプト書き換えて、書き換えたものをもとに実行する方法ってあるんですかね(あるとは思うんですが ; ;)

  2. encode(history) をごり押し作成
    最短ケースのhistoryは次の通りになるはず

history =[
    [[0, 1], [9, 9]], 
    [[1, 1], [9, 8]], 
    [[2, 1], [9, 7]], 
    [[3, 1], [9, 6]], 
    [[4, 1], [9, 5]], 
    [[5, 1], [9, 4]], 
    [[6, 1], [9, 3]], 
    [[7, 1], [9, 2]], 
    [[8, 1], [9, 1]]
]

これを用いてencode(history)にあてはめたら、flagを獲得できる。

function encode(history) {
    const data = new Uint8Array(history.length * 4);

    let idx = 0;
    for (const part of history) {
      data[idx++] = part[0][0];
      data[idx++] = part[0][1];
      data[idx++] = part[1][0];
      data[idx++] = part[1][1];
    }

    let prev = String.fromCharCode.apply(null, data);
    let ret = btoa(prev);
    return ret;
  }

やり方はいろいろあるっぽいが、コンソールを用いて実行。

pythonで書き換えた場合は以下の通りで同様の結果。

python
import base64
history = []

for i in range(9):
    history.append([[i, 1], [9, 9-i]])

def encode(history):
    data = bytearray(len(history) * 4)

    idx = 0
    for part in history:
        data[idx] = part[0][0]
        data[idx + 1] = part[0][1]
        data[idx + 2] = part[1][0]
        data[idx + 3] = part[1][1]
        idx += 4

    ret = base64.b64encode(data).decode()
    return ret

print(("flag: dice{pr0_duck_gam3r_" + encode(history) + "}"))

実行結果

flag: dice{pr0_duck_gam3r_AAEJCQEBCQgCAQkHAwEJBgQBCQUFAQkEBgEJAwcBCQIIAQkB}

funnylogin [web 269 solves]

image.png
image.png

ソースコードは次の通り。

const express = require('express');
const crypto = require('crypto');

const app = express();

const db = require('better-sqlite3')('db.sqlite3');
db.exec(`DROP TABLE IF EXISTS users;`);
db.exec(`CREATE TABLE users(
    id INTEGER PRIMARY KEY,
    username TEXT,
    password TEXT
);`);

const FLAG = process.env.FLAG || "dice{test_flag}";
const PORT = process.env.PORT || 3000;

const users = [...Array(100_000)].map(() => ({ user: `user-${crypto.randomUUID()}`, pass: crypto.randomBytes(8).toString("hex") }));
db.exec(`INSERT INTO users (id, username, password) VALUES ${users.map((u,i) => `(${i}, '${u.user}', '${u.pass}')`).join(", ")}`);

const isAdmin = {};
const newAdmin = users[Math.floor(Math.random() * users.length)];
isAdmin[newAdmin.user] = true;

app.use(express.urlencoded({ extended: false }));
app.use(express.static("public"));

app.post("/api/login", (req, res) => {
    const { user, pass } = req.body;

    const query = `SELECT id FROM users WHERE username = '${user}' AND password = '${pass}';`;
    try {
        const id = db.prepare(query).get()?.id;
        if (!id) {
            return res.redirect("/?message=Incorrect username or password");
        }

        if (users[id] && isAdmin[user]) {
            return res.redirect("/?flag=" + encodeURIComponent(FLAG));
        }
        return res.redirect("/?message=This system is currently only available to admins...");
    }
    catch {
        return res.redirect("/?message=Nice try...");
    }
});

app.listen(PORT, () => console.log(`web/funnylogin listening on port ${PORT}`));

順々なIDに加え、ランダムなユーザー&パスワードを100000個もつデータベースを作成する。また、1つのユーザーをadminとして登録する。

usernameとpasswordの2つが入力として必要であり、users[id] && isAdmin[user]が満たさればflagが獲得できる。

user[id]はidに1~100000のどれかが入ればいいのでSQLインジェクションでなんとかなりそう。すなわちpasswordの方でSQLインジェクションを実施。
usernameの方でisAdmin[user]の存在さえ証明できればいいが...?

終了後にJavascriptではプロトタイプというものがあることを理解。

プロトタイプオブジェクト

空のオブジェクトを定義したとしても、いくつかのメソッドが自動的に定義されるというものである。

つまり、const isAdmin = {};の部分でtoStringメソッドを含めた複数のメソッドが自動生成される。生成されたメソッドを用いることで、trueの判定を得られる。

以上より、次のように入力を行うことでflagを獲得できる。

Username: toString
Password: 1' or id=1; --

実行結果

image.png

dice{i_l0ve_java5cript!}
1
1
0

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
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?