Challenge

でもguestから始めよう!

(抜粋)

function auth(req, res, next) {
  const username = req.cookies.username;
  if (!username) return res.redirect("/login");

  const user = users.get(username);
  if (!user) return res.redirect("/login");

  req.user = user;
  next();
}

~~

app.post("/register", (req, res) => {
  const user_data = req.body;
  if (
    !user_data ||
    !user_data.username ||
    !user_data.password ||
    !/^[a-z0-9]+$/.test(user_data.username) ||
    (user_data.nickname && !/^[a-z0-9]+$/.test(user_data.nickname))
  )
    return res.send("invalid input");

  if (users.has(user_data.username)) {
    return res.send("user already exists");
  }

  users.set(user_data.username, {
    role: "guest",
    ...user_data,
  });

  res.cookie("username", user_data.username);
  res.redirect("/");
});

~~

app.post("/login", (req, res) => {
  const { username, password } = req.body;
  const user = users.get(username);
  if (!user || user.password !== password) {
    return res.send("invalid credentials");
  }

  res.cookie("username", username);
  res.redirect("/");
});

app.get("/", auth, (req, res) => {
  const { username, role, nickname } = req.user;

  res.send(`
    <h1>Hello ${username} (${nickname ?? "no nickname"})</h1>
    <p>role: ${role}</p>
    ${role === "admin" ? `<p><b>${FLAG}</b></p>` : ""}
    <a href="/logout">Logout</a>
  `);
});

Solution

register

まずは次の入力検証をします。

その後、ユーザを生成します

users.set(user_data.username, {
  role: "guest",
  ...user_data,
});

ここの処理に脆弱性があります。

フォームからの正規の入力では user_datausernamepasswordで構成されます。

しかし、ここに role を仕込むことで role: "guest" 部分を上書きできます。(後勝ちする仕様のため)

よって、 role=adminを含むPOSTを投げ、 登録ユーザ名のクッキーをセットし /を開けばフラグが表示されます