cool_index

import express from "express";
import jwt from "jsonwebtoken";
import cookieParser from "cookie-parser";
import crypto from "crypto";
const FLAG = process.env.DASFLAG || "DASCTF{fake_flag}";
const app = express();
app.use(express.json());
app.use(cookieParser());
app.use(express.static("static"));
app.set("view engine", "ejs");

const JWT_SECRET = crypto.randomBytes(64).toString("hex");

const articles = [
    {
        line1: "我还是在这里 我还是",
        line2: "如约而至地出现了"
    },
    {
        line1: "你们有成为更好的自己吗",
        line2: "真的吗 那可太好了"
    },
    {
        line1: "你知道吗 我经常说",
        line2: "把更多的时间花在 CTF 上(?)"
    },
    {
        line1: "这是一种信念感",
        line2: "就像我出来那给你们"
    },
    {
        line1: "我也希望你们能把更多时间花在热爱的事情上",
        line2: "我是一个特别固执的人"
    },
    {
        line1: "我从来不会在意别人跟我说什么",
        line2: "让我去做以及怎么做 我不管"
    },
    {
        line1: "如果 你也可以像我一样",
        line2: "那我觉得 这件事情"
    },
    {
        line1: "欢迎参加 DASCTF x GFCTF 2024!",
        line2: FLAG,
    },
];

app.get("/", (req, res) => {
    const token = req.cookies.token;
    if (token) {
        try {
            const decoded = jwt.verify(token, JWT_SECRET);
            res.render("home", {
                username: decoded.username,
                subscription: decoded.subscription,
                articles: articles,
            });
        } catch (error) {
            res.clearCookie("token");
            res.redirect("/register");
        }
    } else {
        res.redirect("/register");
    }
});

app.get("/register", (req, res) => {
    res.render("register");
});

app.post("/register", (req, res) => {
    const { username, voucher } = req.body;
    if (typeof username === "string" && (!voucher || typeof voucher === "string")) {
        const subscription = (voucher === FLAG + JWT_SECRET ? "premium" : "guest");
        if (voucher && subscription === "guest") {
            return res.status(400).json({ message: "邀请码无效" });
        }
        const userToken = jwt.sign({ username, subscription }, JWT_SECRET, {
            expiresIn: "1d",
        });
        res.cookie("token", userToken, { httpOnly: true });
        return res.json({ message: "注册成功", subscription });
    }

    return res.status(400).json({ message: "用户名或邀请码无效" });
});

app.post("/article", (req, res) => {
    const token = req.cookies.token;
    if (token) {
        try {
            const decoded = jwt.verify(token, JWT_SECRET);
            let index = req.body.index;
            if (req.body.index < 0) {
                return res.status(400).json({ message: "你知道我要说什么" });
            }
            if (decoded7subscription !== "premium" && index >= 7) {
                return res
                    .status(403)
                    .json({ message: "订阅高级会员以解锁" });
            }
            index = parseInt(index);
            if (Number.isNaN(index) || index > articles.length - 1) {
                return res.status(400).json({ message: "你知道我要说什么" });
            }

            return res.json(articles[index]);
        } catch (error) {
            res.clearCookie("token");
            return res.status(403).json({ message: "重新登录罢" });
        }
    } else {
        return res.status(403).json({ message: "未登录" });
    }
});

app.listen(3000, () => {
    console.log("3000");
});

直接给出payload:{"index":[7,2]}
主要是parseInt对数组进行解析的时候只取了数组的第一个值,从而绕过判断
2024-04-20T07:30:08.png

EasySignin

注册之后就可以任意更改用户密码
2024-04-20T03:18:29.png
可以ssrf,但是过滤了file和etc,测试发现3306端口开启了
2024-04-20T08:52:11.png
参考博客,直接复制payload写文件,但是说没有权限,于是尝试读文件
2024-04-20T08:55:55.png
这里用cyberchef把工具得到的%替换成%25
2024-04-20T08:57:09.png
解码得到flag
2024-04-20T08:58:51.png