Jimmy's blog
Difficulty
Hard
Points
400
Description
The technology is always evolving, so why do we still stick with password-based authentication? That makes no sense! That’s why I designed my own password-less login system. I even open-sourced it for everyone interested, how nice of me!
Quick Analysis
From the attached source code.
Content of index.js
...
const utils = require("./utils");
...
app.get("/article", (req, res) => {
const id = parseInt(req.query.id).toString();
const article_path = path.join("articles", id);
try {
const contents = fs.readFileSync(article_path).toString().split("\n\n");
const article = {
id: article_path,
date: contents[0],
title: contents[1],
summary: contents[2],
content: contents[3]
}
res.render("article", { article: article, session: req.session, flag: process.env.FLAG });
} catch {
res.sendStatus(404);
}
})
...
app.post("/register", (req, res) => {
const username = req.body.username;
const result = utils.register(username);
if (result.success) res.download(result.data, username + ".key");
else res.render("register", { error: result.data, session: req.session });
})
app.post("/login", upload.single('key'), (req, res) => {
const username = req.body.username;
const key = req.file;
const result = utils.login(username, key.buffer);
if (result.success) {
req.session.username = result.data.username;
req.session.admin = result.data.admin;
res.redirect("/");
}
else res.render("login", { error: result.data, session: req.session });
})
app.get("/logout", (req, res) => {
req.session.destroy();
res.redirect("/");
})
app.get("/edit", (req, res) => {
if (!req.session.admin) return res.sendStatus(401);
const id = parseInt(req.query.id).toString();
const article_path = path.join("articles", id);
try {
const article = fs.readFileSync(article_path).toString();
res.render("edit", { article: article, session: req.session, flag: process.env.FLAG });
} catch {
res.sendStatus(404);
}
})
app.post("/edit", (req, res) => {
if (!req.session.admin) return res.sendStatus(401);
try {
fs.writeFileSync(path.join("articles", req.query.id), req.body.article.replace(/\r/g, ""));
res.redirect("/");
} catch {
res.sendStatus(404);
}
})Content of article.ejs
Content of edit.ejs
Content of utils.js
NGINX configuration
Analyzing index.js
index.jsThe
index.jsrequiresutil.jsfile.The endpoint
/registerrequires a username only and returns a key for authentication.The endpoint
/loginrequires a username and akeyfile.The flag passed to
article.ejsviewres.render("article", { article: article, session: req.session, flag: process.env.FLAG });.The flag is not rendered via
/articleendpoint based on the content of thearticle.ejs.The flag passed to
edit.jsviewres.render("edit", { article: article, session: req.session, flag: process.env.FLAG });.The flag is rendered via
GET/edit?id=<INTEGER>endpoint based on the content of theedit.ejs:<p class="mb-5 text-center"><%= flag %></p>The endpoint
/editrequires an admin sessionif (!req.session.admin) return res.sendStatus(401);for both methodsGETandPOST.The
POSTendpoint/edit?id=<string>is vulnerable, where you could path traverse via theidparameter./articles/<id>and write content to the traversal path via the articlePOSTparameter.fs.writeFileSync(path.join("articles", req.query.id), req.body.article.replace(/\r/g, ""));
Analyzing utils.js
utils.jsthe utils file registered an administrator user with
jimmy_jammyas a username and a random key with 1024 bytesregister("jimmy_jammy", 1);.the register function is vulnerable to account takeover, where you could traversal and overwrite an existing user's key.
Analyzing NGINX configuration [1]
the flag is replaced with
oof, that was close, glad i was here to save the dayvia NGINXsub_filter. The ngx_http_sub_module module is a filter that modifies a response by replacing one specified string by another. This module is not built by default, it should be enabled with the --with-http_sub_module configuration parameter.
Exploitation
Register
./jimmy_jammyto overwrite the actualjimmy_jammykey.Login with
jimmy_jammyand use the key that we obtained via the registration function.Edit the
edit.jsview to<%= btoa(flag) %>which encodes the flag to base64 viaPOST/edit?id=../views/edit.jsarticle=<%25%3d+btoa(flag)+%25>to bypass the NGINX sub_filter.
The flag
Navigate to the endpoint /edit?id=1 to get the base64 flag
References
http://nginx.org/en/docs/http/ngx_http_sub_module.html#sub_filter
Last updated
Was this helpful?