green_bar.png Save The Earth! Save The Earth! - 地球環境を守ろう! Save The Earth! green_bar.png

URL: https://2022.angstromctf.com/challenges

OSCP の勉強をしていて、自分のWeb関連のスキルが弱いと感じたので、

まだ OSCP の勉強の真っ最中ですが、CTFのWeb問題だけは時々トライしていこうと思います。


いちおう、210点を獲得し、順位は814位でした。
angstromctf_2022_score.png

以下は解いたチャレンジです。(相変わらず、ほとんど解けてないです。。。)
angstromctf_2022_solves.png


Xtra Salty Sardines でやったことは、自分で今後も参照することもありそうなので、writeup残しておきます。



[Web]: Xtra Salty Sardines (70 points)


Challenge

Clam was intensely brainstorming new challenge ideas, when his stomach growled! He opened his favorite tin of salty sardines, took a bite out of them, and then got a revolutionary new challenge idea. What if he wrote a site with an extremely suggestive acronym?

Admin Bot

Attachment:

  • index.js

中身:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
const express = require("express");
const path = require("path");
const fs = require("fs");
const cookieParser = require("cookie-parser");

const app = express();
const port = Number(process.env.PORT) || 8080;
const sardines = {};

const alpha = "abcdefghijklmnopqrstuvwxyz";

const secret = process.env.ADMIN_SECRET || "secretpw";
const flag = process.env.FLAG || "actf{placeholder_flag}";

function genId() {
    let ret = "";
    for (let i = 0; i < 10; i++) {
        ret += alpha[Math.floor(Math.random() * alpha.length)];
    }
    return ret;
}

app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());

// the admin bot will be able to access this
app.get("/flag", (req, res) => {
    if (req.cookies.secret === secret) {
        res.send(flag);
    } else {
        res.send("you can't view this >:(");
    }
});

app.post("/mksardine", (req, res) => {
    if (!req.body.name) {
        res.status(400).type("text/plain").send("please include a name");
        return;
    }
    // no pesky chars allowed
    const name = req.body.name
        .replace("&", "&amp;")
        .replace('"', "&quot;")
        .replace("'", "&apos;")
        .replace("<", "&lt;")
        .replace(">", "&gt;");
    if (name.length === 0 || name.length > 2048) {
        res.status(400)
            .type("text/plain")
            .send("sardine name must be 1-2048 chars");
        return;
    }
    const id = genId();
    sardines[id] = name;
    res.redirect("/sardines/" + id);
});

app.get("/", (req, res) => {
    res.sendFile(path.join(__dirname, "index.html"));
});


app.get("/sardines/:sardine", (req, res) => {
    const name = sardines[req.params.sardine];
    if (!name) {
        res.status(404).type("text/plain").send("sardine not found :(");
        return;
    }
    const sardine = fs
        .readFileSync(path.join(__dirname, "sardine.html"), "utf8")
        .replaceAll("$NAME", name.replaceAll("$", "$$$$"));
    res.type("text/html").send(sardine);
});

app.listen(port, () => {
    console.log(`Server listening on port ${port}.`);
});


Solution

イワシの缶に名前が付けられるようになっています。かなり塩っぱそう(extra salty)。

XSSですね。

angstromctf_2022_mksardine.png


以下はAdmin Botです。URLを指定すると、アクセスしてくれるみたいです。

angstromctf_2022_adminbot.png


ソースコードを見ると、いくつかの特殊文字はエスケープされるみたいです。

1
2
3
4
5
        .replace("&", "&amp;")
        .replace('"', "&quot;")
        .replace("'", "&apos;")
        .replace("<", "&lt;")
        .replace(">", "&gt;");

とりあえず、"<h2>aa</h2>” を入れてみたところ、どうやらフィルターは同じ文字に対して一回だけかかるみたいです。

angstromctf_2022_XSS_filter.png

フィルターは簡単に回避できそう。


フラグは、Admin Bot のみアクセスできるようになっています。

1
2
3
4
5
6
7
8
// the admin bot will be able to access this
app.get("/flag", (req, res) => {
    if (req.cookies.secret === secret) {
        res.send(flag);
    } else {
        res.send("you can't view this >:(");
    }
});


iframeを2つ用意して、iframe1でフラグへのアクセス、iframe2ではiframe1の値を取り出して外部に送信するXSSを用意します。

イメージとしては、こんな感じです。https://beeceptor.com/ で書いたルールです。

(結果として、これじゃダメなんですが)

angstromctf_2022_XSS_beeceptor_rule.png


XSSは、以下の通りです。最初の3文字は、フィルター回避用です。

これを、イワシの缶の名前としてセットして、URLを生成します。その後、そのURLをiframe2にセットします。

angstromctf_2022_XSS.png


で、このiframe1とiframe2を含むページをどこでホストするかなんですが、https://beeceptor.com/ でやってしまうと Cookie が iframe1 に適用されずに Admin Botでもフラグが取れなくなってしまいます。


なので、そのページ自体も、イワシの缶の名前を使って生成します。

angstromctf_2022_XSS2.png


ブラウザでアクセスしてみると、こんな感じです。

angstromctf_2022_XSS_browser.png


これを、Admin Bot にアクセスさせると、フラグが得られます。

angstromctf_2022_XSS_flag.png


Flag: actf{those_sardines_are_yummy_yummy_in_my_tummy}