news 2026/6/30 3:44:45

Web安全双雄:XSS与CSRF攻击原理与立体防御实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Web安全双雄:XSS与CSRF攻击原理与立体防御实战

1. 项目概述:从“小把戏”到“大麻烦”的Web安全双雄

在Web应用开发与安全防护的日常工作中,有两个名字总是如影随形,它们不像SQL注入那样直接“掏空”数据库,也不像DDoS那样声势浩大,却像潜伏在阴影中的刺客,利用用户对浏览器的信任,悄无声息地完成攻击。这就是XSS(跨站脚本攻击)和CSRF(跨站请求伪造)。我见过太多项目,前端做得炫酷,后端逻辑严谨,却在部署上线后,因为对这两种攻击的防御疏忽,导致用户数据泄露、账户被恶意操作,甚至整个站点的信誉崩塌。今天,我们就来彻底拆解这对“黄金搭档”,不光是讲原理,更要结合我踩过的坑和实战中的案例,把防御方案讲透,让你下次在代码评审或渗透测试报告里看到它们时,能一眼看穿本质,并知道如何根治。

简单来说,XSS是“往别人的页面里插自己的脚本”,而CSRF是“借用别人的身份发自己的请求”。一个核心在于“脚本执行”,一个核心在于“请求伪造”。理解它们,不仅是安全工程师的必修课,更是每一位前后端开发者在设计功能、编写代码时必须绷紧的一根弦。我们会从攻击者的视角出发,看看他们是如何利用这些漏洞的,然后再切换到防御者的视角,构建起从开发到部署的立体防御体系。无论你是刚入门的安全爱好者,还是有一定经验的开发者,这篇文章都能帮你建立起清晰、可落地的认知与实践框架。

2. 核心攻击原理深度拆解:信任是如何被背叛的?

要有效防御,必须先深入理解攻击是如何发生的。XSS和CSRF虽然经常被并列提及,但它们的攻击面、利用条件和最终目标截然不同。我们将它们拆开来看,你会发现,它们攻击的是Web安全模型中两个不同维度的“信任”。

2.1 XSS攻击:当浏览器执行了不该执行的代码

XSS的本质是攻击者将恶意脚本注入到可信的网页中,当其他用户浏览该网页时,其浏览器会执行这些恶意脚本。关键在于“注入”与“执行”。根据脚本注入和执行的持久性位置,XSS主要分为三类:反射型、存储型和DOM型。

反射型XSS是最常见也最“经典”的一种。攻击过程通常是这样:攻击者构造一个包含恶意脚本的URL,然后通过邮件、社交网站等方式诱骗用户点击。服务器接收到这个请求后,未经过滤或转义,直接将恶意脚本作为响应的一部分返回给用户的浏览器,浏览器将其当作页面正常内容执行。举个例子,一个搜索功能,URL可能是https://example.com/search?q=用户输入。如果后端直接拼接:<p>您搜索的关键词是:+用户输入+</p>,那么当攻击者输入<script>alert('XSS')</script>并诱使用户访问https://example.com/search?q=<script>alert('XSS')</script>时,这个脚本就会在用户的浏览器里弹窗。它的数据流向是:用户浏览器 -> 服务器 -> 用户浏览器。恶意脚本并不存储在服务器上。

存储型XSS的危害性更大,因为它具有持久性。攻击者将恶意脚本提交到网站的后端数据库(如论坛发帖、用户评论、个人资料昵称),之后任何浏览到包含该恶意内容的页面的用户,其浏览器都会执行该脚本。比如,一个博客评论系统,如果不对用户输入的评论内容进行过滤,攻击者提交一条包含<script>stealCookie()</script>的评论。此后,所有访问这篇博客文章的用户,在加载评论时都会执行这个窃取Cookie的脚本。它的数据流向是:攻击者 -> 服务器数据库 -> 所有受害用户浏览器。

DOM型XSS是一种比较特殊的类型,它的恶意代码执行完全发生在客户端的DOM解析环境,不经过服务器。攻击利用的是前端JavaScript对DOM的操作。例如,页面有一段JS代码:document.getElementById('content').innerHTML = window.location.hash.substring(1);,它把URL的hash部分(#后面的内容)直接写入了页面的innerHTML。如果攻击者构造一个URL:https://example.com/page#<img src=1 onerror=alert('XSS')>,那么当用户访问时,onerror事件就会被触发。这种攻击更难被传统的服务端WAF(Web应用防火墙)检测到,因为恶意负载根本不会发送到服务器。

注意:很多人认为用了前端框架(如React, Vue)就天然免疫XSS,这是误区。框架确实在默认情况下提供了很好的转义保护(例如React对{}内的变量进行转义),但如果你使用了dangerouslySetInnerHTML(React)或v-html(Vue)这类故意绕过安全机制的方法,或者将未经验证的数据传递给eval()setTimeout()等函数,XSS漏洞依然会产生。

2.2 CSRF攻击:借刀杀人的艺术

如果说XSS是利用用户对网站的信任,在网站上“种木马”,那么CSRF就是利用网站对用户浏览器的信任,让浏览器在用户不知情的情况下,“代替”用户向网站发起一个恶意请求。它的核心在于“伪造”。

想象一个场景:你登录了网上银行A(bank-a.com),并且会话Cookie还在有效期内。此时,你不小心访问了一个恶意网站B(evil.com)。网站B的页面上隐藏着一个自动提交的表单,或者一张自动加载的图片,其src指向银行A的转账接口:<img src="https://bank-a.com/transfer?to=attacker&amount=10000" width="0" height="0">。你的浏览器在加载这个图片时,会自动携带你登录银行A的Cookie,向银行A发起一个GET请求。银行A的服务器看到这个带有合法Cookie的请求,就会认为是你本人操作的,从而执行转账。这就是一次典型的CSRF攻击。

CSRF攻击成功的必要条件通常被称为“三个确认”:

  1. 登录状态确认:用户已经登录了目标网站(如银行),并且会话尚未过期。
  2. 请求可预测确认:攻击者能够推测出目标网站某个敏感操作(如修改密码、转账)的请求参数格式(URL、方法、参数名)。
  3. 浏览器自动携带凭证确认:目标网站依赖Cookie等浏览器自动携带的机制进行身份验证,且没有其他不可预测的令牌(如CSRF Token)进行二次校验。

与XSS不同,CSRF攻击中,恶意网站B无法直接读取银行A的Cookie(受同源策略保护),但它可以“借用”这个Cookie发起请求。攻击者的目标不是获取用户数据,而是以用户身份执行某个操作。

3. 防御体系构建:从编码到架构的层层设防

理解了攻击原理,防御就有了清晰的靶子。防御XSS和CSRF不是单一措施,而是一个从开发习惯到架构设计的系统工程。下面我将分层次、分场景地给出具体、可操作的防御方案。

3.1 XSS防御:关键在于“不信任”与“转义”

防御XSS的核心思想是:永远不要信任用户输入,对所有输出到页面的动态内容进行适当的处理。这需要前后端协同。

3.1.1 输入验证与过滤这是第一道防线,但绝不是唯一防线。原则是“白名单”优于“黑名单”。即,只允许符合明确规则的输入通过,而不是试图拦截所有已知的恶意模式。

  • 场景:用户注册时的“用户名”字段。
  • 操作:后端使用正则表达式进行白名单验证,例如只允许中英文、数字和下划线:/^[\\u4e00-\\u9fa5a-zA-Z0-9_]+$/。对于富文本编辑器(如评论、文章内容),完全过滤HTML是不现实的,此时应使用严格的白名单标签和属性过滤库(如Java的Jsoup,Python的bleach)。
  • 心得:过滤要在服务端做。前端验证是为了用户体验(即时反馈),后端验证是为了安全。攻击者可以完全绕过前端,直接构造请求发给后端。

3.1.2 输出编码/转义这是防御XSS最根本、最有效的手段。根据数据将要放置的上下文环境,进行不同的编码。

  • HTML上下文:将数据放入HTML标签内部(如<div>内容)或普通属性(如alt,value)时,需要对&,<,>,",'等字符进行HTML实体编码。例如,<转成&lt;。大多数现代Web框架的模板引擎(如Thymeleaf, Freemarker, Django Templates)在默认情况下都会自动进行HTML转义。
    <!-- 错误示例:直接输出 --> <div>${userInput}</div> <!-- 如果userInput是 `<script>alert(1)</script>`,就会执行 --> <!-- 正确示例:框架自动转义或手动转义后 --> <div>&lt;script&gt;alert(1)&lt;/script&gt;</div> <!-- 浏览器会将其显示为文本,而非执行 -->
  • JavaScript上下文:将数据放入<script>标签内或事件处理器(如onclick)时,需要进行JavaScript编码。通常使用\xXX\uXXXX形式的Unicode转义。
    // 错误示例 var userData = "${userInput}"; // 如果userInput是 `";alert(1);//`,就会闭合字符串并执行新代码 // 正确做法:使用JSON.stringify(它会自动处理引号和转义) var userData = ${jsonStringifiedUserInput}; // 注意:这里jsonStringifiedUserInput已经是JSON字符串,无需外加引号
  • URL上下文:将数据作为URL的一部分(如href,src)时,需要进行URL编码。
    <!-- 错误示例 --> <a href="https://example.com?redirect=${userInput}">点击</a> <!-- 如果userInput是 `javascript:alert(1)`,就会形成XSS --> <!-- 正确做法:严格验证协议头,只允许http/https,并对参数值进行URL编码 -->
  • CSS上下文:较少见,但也需注意。应对放入CSS的值进行编码。

3.1.3 利用内容安全策略(CSP)CSP是一个强大的后端HTTP头,它告诉浏览器只允许加载和执行来自哪些源的资源(脚本、样式、图片等),从根本上减少了XSS的攻击面。

  • 如何设置:在服务器的HTTP响应头中添加Content-Security-Policy
    Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src *; font-src 'self'
    • default-src 'self': 默认只允许加载同源资源。
    • script-src 'self' https://trusted.cdn.com: 脚本只允许来自同源和指定的CDN。
    • style-src 'self' 'unsafe-inline': 样式允许同源和内联样式(某些UI框架需要)。
    • img-src *: 图片可以从任何地方加载。
    • font-src 'self': 字体只允许同源。
  • 实操心得:部署CSP建议分两步走。第一步,先使用Content-Security-Policy-Report-Only头,只报告违规行为而不拦截,观察日志,调整策略。第二步,确认策略无误后,切换到强制的Content-Security-Policy头。这能避免因策略过严导致网站功能异常。

3.1.4 设置安全的Cookie属性对于通过XSS窃取的Cookie,我们可以通过设置其属性来增加攻击者利用的难度。

  • HttpOnly: 这是最重要的属性。设置后,JavaScript(document.cookie)无法读取该Cookie,只能由浏览器在HTTP请求中自动携带。这能有效防止XSS攻击者直接窃取会话标识。
    // 在Java Servlet中设置HttpOnly Cookie Cookie sessionCookie = new Cookie("JSESSIONID", sessionId); sessionCookie.setHttpOnly(true); response.addCookie(sessionCookie);
  • Secure: 仅允许Cookie通过HTTPS协议传输,防止在明文HTTP中被窃听。
  • SameSite: 这个属性是防御CSRF的利器,但对限制Cookie在跨站请求中的发送也有帮助。StrictLax模式可以阻止第三方上下文发起的请求自动携带Cookie。

3.2 CSRF防御:关键在于“不可预测性”与“同源验证”

防御CSRF的核心思想是:确保敏感请求是由源自本网站的、可信的页面发起的,而不是来自第三方网站。

3.2.1 使用CSRF Token(同步器令牌模式)这是目前最主流、最有效的防御方案。原理是为每个用户会话生成一个随机、不可预测的Token,在渲染表单(或任何可能触发状态变更的请求)时,将这个Token作为一个隐藏字段(对于表单)或自定义HTTP头(对于AJAX)嵌入。服务器在处理请求时,校验这个Token的有效性。

  • 服务端实现
    1. 用户登录或访问站点时,在服务器端(Session中)为其生成一个唯一的CSRF Token。
    2. 在渲染任何包含表单的页面时,将该Token输出到一个隐藏的<input>字段中,例如<input type="hidden" name="_csrf" value="生成的随机Token">
    3. 对于AJAX请求,可以将Token放在页面的<meta>标签里,由前端JavaScript读取并设置为自定义请求头(如X-CSRF-TOKEN)。
    4. 服务器接收到POST/PUT/DELETE等非幂等请求时,从请求参数或头部取出Token,与Session中存储的Token进行比对。一致则通过,不一致或缺失则拒绝。
  • 为什么有效:恶意网站无法预先得知或获取到这个Token(受同源策略保护),因此它构造的伪造请求中必然缺少有效的Token,服务器校验会失败。
  • 注意事项
    • Token必须足够随机(使用密码学安全的随机数生成器),且与用户会话绑定。
    • Token应是一次性的,或具有较短的有效期,并在使用后更新,以防止重放攻击。
    • 确保Token只通过HTTPS传输,防止被中间人窃取。

3.2.2 校验请求来源(Origin/Referer Header)服务器可以检查HTTP请求头中的OriginReferer字段,判断请求是否来自合法的源(即自己的网站)。

  • Origin头:对于跨域请求,浏览器会自动添加,指示请求的来源(协议+域名+端口)。对于同源请求,某些浏览器可能不发送。
  • Referer头:表示前一个页面的地址。但注意,Referer可能被用户浏览器设置或代理服务器过滤掉,存在为空的情况。
  • 实现:在服务器端拦截器或中间件中,对于敏感操作,检查OriginReferer是否以自己网站的域开头。
    // 伪代码示例 String origin = request.getHeader("Origin"); String referer = request.getHeader("Referer"); if (origin != null && !origin.startsWith("https://your-domain.com")) { throw new CsrfException("Invalid origin"); } // 或者检查Referer
  • 局限性:这不是一个完美的方案。Referer可能缺失或被篡改(虽然浏览器通常不允许JS修改Referer,但某些浏览器扩展或非浏览器客户端可以)。它通常作为CSRF Token方案的补充。

3.2.3 利用SameSite Cookie属性这是浏览器提供的一种从源头限制Cookie发送范围的机制,对防御CSRF有奇效。

  • SameSite=Strict: 最严格。Cookie仅在同站请求(即当前网站域下)中发送。这意味着如果用户从其他网站(如邮件链接)点击过来,即使已登录,首次请求也不会携带Cookie,可能导致需要重新登录。适用于极高安全要求的操作。
  • SameSite=Lax(默认值): 宽松模式。在跨站请求中,仅对安全(HTTPS)的顶级导航(如链接点击)发送Cookie,而对子请求(如图片、iframe、AJAX)不发送。这平衡了安全性和用户体验。大多数情况下,Lax是推荐设置。
  • SameSite=None: Cookie在所有上下文中发送,但必须同时设置Secure属性(即仅限HTTPS)。这是为了兼容一些需要跨站Cookie的第三方服务。
  • 如何设置:在设置Cookie的响应头中指定。
    Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
  • 实操心得:将关键会话Cookie设置为SameSite=LaxStrict,能极大地缓解CSRF攻击。对于现代浏览器,这几乎可以防御绝大多数传统的CSRF攻击。但是,它不能防御同源下的XSS攻击发起的请求(因为同源请求会携带Cookie),也不能防御某些特定的攻击场景(如“登录CSRF”)。因此,它应与CSRF Token结合使用。

3.2.4 要求用户进行二次验证对于特别敏感的操作(如转账、修改密码、修改邮箱),强制要求用户进行二次验证,例如输入登录密码、短信验证码、或使用U盾等。这虽然不是纯粹的CSRF防御技术,但能从业务逻辑层面增加攻击门槛。

4. 实战场景与工具链:在靶场和真实代码中演练

理论讲得再多,不如亲手实践。下面我将带你搭建一个简单的靶场环境,并分析真实框架中的防御机制,让你有更直观的感受。

4.1 使用DVWA/Pikachu靶场进行手动测试

DVWA(Damn Vulnerable Web Application)和Pikachu是两款非常经典的Web漏洞学习靶场,内置了XSS和CSRF的漏洞场景。

环境搭建(以DVWA为例)

  1. 最方便的方式是使用Docker。确保你已安装Docker和Docker Compose。
  2. 创建一个docker-compose.yml文件:
    version: '3' services: dvwa: image: vulnerables/web-dvwa ports: - "8080:80" environment: - PHPIDS=off # 关闭PHPIDS以方便测试 volumes: - ./dvwa_data:/app
  3. 在终端运行docker-compose up -d
  4. 浏览器访问http://localhost:8080,按照页面提示完成安装(数据库设置等),默认登录账号/密码是admin/password
  5. 在DVWA首页左侧,将安全级别设置为Low,这样防护最弱,便于我们理解漏洞原理。

反射型XSS(Low级别)测试

  1. 进入XSS (Reflected)模块。
  2. 在输入框尝试输入<script>alert(document.domain)</script>,点击提交。你会看到一个弹窗,显示当前域名。这说明脚本被执行了。
  3. 尝试绕过:切换到MediumHigh级别,DVWA引入了简单的过滤(如将<script>替换为空)。你可以尝试使用大小写混合、双写、或利用事件处理器(如<img src=1 onerror=alert(1)>)进行绕过。这个过程能让你深刻理解黑名单过滤的局限性。

存储型XSS测试

  1. 进入XSS (Stored)模块。
  2. 在留言板输入恶意脚本并提交。
  3. 刷新页面或让其他“用户”(你可以新开一个浏览器无痕窗口访问)查看该页面,脚本会自动执行。这模拟了攻击持久化的效果。

CSRF(Low级别)测试

  1. 进入CSRF模块。你会看到一个修改密码的简单表单。
  2. 观察URL,例如http://localhost:8080/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#。攻击者可以构造一个类似的URL,诱使你点击。
  3. 你可以自己写一个简单的HTML页面evil.html,放在本地,内容如下:
    <img src="http://localhost:8080/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change#" width="0" height="0"> <p>你刚刚可能被CSRF攻击了(如果已登录DVWA)</p>
  4. 在已登录DVWA的同一个浏览器中,打开这个evil.html文件。观察DVWA的密码是否被修改(可能需要重新登录验证)。这个实验直观展示了CSRF的威力。

4.2 现代框架中的内置防御机制分析

了解手动防御后,我们看看主流框架是如何帮我们自动化这些安全措施的。

Spring Security (Java) 中的CSRF防护: 在Spring Boot项目中,只要引入了spring-boot-starter-security依赖,CSRF防护默认是开启的。它使用同步器令牌模式。

  • 原理:Spring Security会为每个会话生成一个CSRF Token,并期望在除GET,HEAD,TRACE,OPTIONS之外的所有请求中(通常是状态修改请求),携带这个Token。Token可以放在_csrf请求参数中,也可以放在X-CSRF-TOKENX-XSRF-TOKEN请求头中。
  • Thymeleaf模板自动集成:如果你使用Thymeleaf,在表单中添加th:action属性后,Thymeleaf会自动为你添加一个名为_csrf的隐藏字段。
    <form method="post" th:action="@{/change-password}"> <!-- Thymeleaf会自动插入:<input type="hidden" name="_csrf" value="..."/> --> <input type="password" name="newPassword"> <button type="submit">修改密码</button> </form>
  • AJAX请求:你需要从meta标签或Cookie中获取Token,并手动设置到请求头中。Spring Security默认会将Token放在名为XSRF-TOKEN的Cookie中,你可以这样处理:
    // 使用jQuery示例 var csrfToken = $("meta[name='_csrf']").attr("content"); var csrfHeader = $("meta[name='_csrf_header']").attr("content"); $.ajax({ url: '/api/data', type: 'POST', beforeSend: function(xhr) { xhr.setRequestHeader(csrfHeader, csrfToken); // 例如 X-CSRF-TOKEN }, // ... });
  • 禁用CSRF:对于纯API服务(无状态,使用JWT等),你可能需要禁用CSRF。可以在安全配置中关闭:
    @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // 禁用CSRF保护 .authorizeRequests() // ... 其他配置 } }

    重要提示:除非你非常确定你的API调用场景不会受到CSRF攻击(例如,所有客户端都是你控制的移动App,且认证不依赖Cookie),否则不要轻易禁用。

Django (Python) 中的CSRF防护: Django的CSRF中间件(django.middleware.csrf.CsrfViewMiddleware)同样默认启用,使用同步器令牌模式。

  • 模板中使用:在模板的表单标签内使用{% csrf_token %}模板标签。
    <form method="post"> {% csrf_token %} <!-- 其他表单字段 --> <input type="submit" value="提交"> </form>
    这个标签会被渲染成一个隐藏的input字段:<input type="hidden" name="csrfmiddlewaretoken" value="令牌值">
  • AJAX请求:你需要从Cookie中获取名为csrftoken的Cookie值,并将其作为X-CSRFTOKEN请求头发送。Django贴心地提供了获取该Cookie的JS函数示例。
  • 豁免CSRF:对于某些不需要CSRF保护的视图(如接收第三方Webhook的接口),可以使用装饰器@csrf_exempt

React/Vue (前端) 与XSS: 现代前端框架在默认情况下,通过数据绑定机制自动对动态内容进行HTML转义,这是防御XSS的第一道强大防线。

  • React:在JSX中使用花括号{}插入变量时,React会自动将其转义为字符串。只有使用dangerouslySetInnerHTML属性时,你才需要格外小心,确保其内容是安全的。
    // 安全:userContent会被转义 <div>{userContent}</div> // 危险:需要确保htmlString绝对安全 <div dangerouslySetInnerHTML={{__html: htmlString}} />
  • Vue:模板中的双花括号{{ }}v-bind指令(缩写:)在默认情况下也会进行转义。只有使用v-html指令时,才需要你自行确保安全。
    <!-- 安全:content会被转义 --> <p>{{ content }}</p> <!-- 危险:需要确保rawHtml绝对安全 --> <p v-html="rawHtml"></p>
    心得:在React/Vue项目中,XSS漏洞往往出现在错误使用上述“危险”方法、或直接将不可信数据传递给eval()setTimeout()innerHTML等场景。建立严格的代码审查流程,禁止随意使用这些特性,是至关重要的。

5. 高级话题与疑难排查:当基础防御失效时

即使我们做好了所有基础防御,在复杂的现实环境中,仍然可能遇到一些棘手的场景和高级攻击手法。了解它们,能让我们在安全设计上考虑得更周全。

5.1 XSS的进阶绕过与防御

攻击者不会止步于简单的<script>标签。他们会尝试各种奇技淫巧来绕过过滤。

基于字符编码的绕过: 过滤器可能只寻找<script>字符串,但攻击者可以使用HTML实体编码、JS Unicode编码等方式进行混淆。

  • 攻击载荷<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>。这里的&#97;等是alert(1)的HTML十进制实体编码。浏览器在解析HTML属性时会对其进行解码。
  • 防御:进行输出编码时,必须根据最终的输出上下文进行编码。在HTML属性上下文中,即使输入看起来是编码过的,只要浏览器会解码,我们就必须在输出前,确保将&等字符转义为&amp;。同时,输入过滤应采用规范化和解码后再检查的策略。

利用SVG/HTML5新特性: SVG文件内可以包含JavaScript,某些对<script>过滤严格的系统,可能允许上传SVG图片。

  • 攻击载荷:一个恶意的SVG文件内容。
    <svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)">
  • 防御:对用户上传的文件进行严格的类型检查(不仅看扩展名,更要看魔数或解析文件头),并将上传的文件存储在独立的、不可执行脚本的域名下(使用CDN或静态资源服务器),并设置正确的Content-Type。对于图片,可以进行二次渲染(压缩、缩放)以破坏内嵌的脚本。

DOM型XSS与前端框架的盲区: 即使后端做了完美转义,如果前端JavaScript不当地使用了innerHTMLouterHTMLdocument.write(),或者将不可信数据传递给eval()setTimeout()new Function()等,依然会导致DOM型XSS。

  • 案例:一个从URL获取参数并动态更新页面内容的功能。
    // 危险代码 const productId = new URLSearchParams(window.location.search).get('id'); document.getElementById('product-info').innerHTML = loadProductInfo(productId); // 如果loadProductInfo返回了HTML字符串且包含恶意脚本...
  • 防御
    1. 首选:使用安全的API。用textContent代替innerHTML来设置纯文本。如果必须设置HTML,使用经过严格净化的库(如DOMPurify)进行处理。
    2. 避免:绝对不要将不可信数据拼接字符串后传给eval()setTimeoutsetInterval的第一个字符串参数,或new Function的构造函数。如果必须动态执行代码,请使用其他架构。
    3. 框架最佳实践:在React/Vue中,严格遵守数据驱动视图的原则,避免直接操作DOM。

5.2 CSRF防御的边界情况与对策

“登录CSRF”攻击: 传统CSRF攻击针对的是已登录用户。但“登录CSRF”攻击的是登录过程本身。攻击者伪造一个登录请求,让受害者在不知情的情况下,使用攻击者控制的账号密码登录了目标网站。此后,受害者在该网站上的所有操作(如发帖、购物)都会记录在攻击者的账号下,可能导致隐私泄露或为攻击者“刷单”。

  • 防御:在登录表单中也加入CSRF Token。因为登录请求通常也是状态变更操作(创建会话)。确保登录接口同样受到CSRF保护。

JSON API的CSRF防护: 对于接收JSON格式数据的API,传统的在表单中加隐藏字段的方式不适用。攻击者仍然可以构造一个Content-Typetext/plainapplication/x-www-form-urlencoded的请求来提交恶意数据,如果服务器端没有严格校验Content-Type,可能会被绕过。

  • 防御策略
    1. 校验Content-Type头:服务器端严格检查请求的Content-Type头是否为application/json。但这并非绝对安全,因为某些场景下(如CORS预检请求)可以伪造。
    2. 使用自定义请求头:这是更推荐的方式。让前端在发送AJAX请求时,添加一个自定义头(如X-Requested-With: XMLHttpRequest)。由于浏览器同源策略的限制,普通HTML表单(<form>提交)或<img>标签发起的请求无法添加自定义头。服务器端校验该头是否存在即可。
      // 前端AJAX设置 fetch('/api/endpoint', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' // 自定义头 }, body: JSON.stringify(data) });
      // 后端校验(Spring Security 伪代码) // 可以在安全配置中要求特定路径的请求必须包含此头 http.csrf().requireCsrfProtectionMatcher(request -> { // 排除某些不需要CSRF的请求,如登录、公开API // 对于需要保护的API,检查是否有自定义头 return !request.getHeader("X-Requested-With").equals("XMLHttpRequest"); });
    3. 将Token放入JSON Body:虽然不常见,但也可以将CSRF Token作为JSON Body中的一个字段发送,服务器端从Body中解析并校验。这要求攻击者必须能预测Token,难度等同于传统表单方式。

SameSite Cookie的局限性SameSite=Lax是当前浏览器的默认值,极大地缓解了CSRF。但它并非万能:

  • GET请求的CSRFLax模式允许在跨站顶级导航(如点击链接)的GET请求中发送Cookie。如果某个敏感操作(如删除文章)错误地使用了GET方法,仍然可能受到CSRF攻击。因此,严格遵守RESTful规范,状态修改操作必须使用POST、PUT、DELETE等非GET方法,是防御CSRF的重要前提。
  • 浏览器兼容性:虽然现代浏览器都已支持,但仍需考虑少量旧版本浏览器的用户。

5.3 渗透测试中的常见问题排查清单

当你负责一个项目的安全审计或收到一份渗透测试报告时,如何快速定位XSS和CSRF问题?以下是一个速查清单:

XSS漏洞排查点

  1. 输入点:所有用户可控的输入(URL参数、表单字段、HTTP头、上传文件、WebSocket消息)。
  2. 输出点:这些输入被输出到的所有上下文(HTML正文、HTML属性、JavaScript代码、CSS、URL)。
  3. 过滤与编码
    • 后端是否对输入进行了白名单过滤或净化?
    • 前端/模板引擎在输出时,是否根据上下文进行了正确的编码?(检查是否滥用v-html,dangerouslySetInnerHTML,是否直接拼接字符串生成HTML或JS)。
  4. CSP:是否部署了Content-Security-Policy头?策略是否过于宽松(如存在unsafe-inline,unsafe-eval)?
  5. Cookie安全:关键Cookie(如会话ID)是否设置了HttpOnlySecure属性?

CSRF漏洞排查点

  1. 状态变更端点:所有非GET的、会导致状态变化的API端点(POST, PUT, DELETE, PATCH)。
  2. 防护机制
    • 这些端点是否要求CSRF Token?Token是否随机、与会话绑定、一次性或有时效?
    • 框架的CSRF中间件是否全局启用?是否有特定接口被错误豁免(@csrf_exempt)?
  3. Cookie设置:会话Cookie是否设置了SameSite属性(至少为Lax)?
  4. 请求方法:是否有状态变更操作错误地使用了GET方法?
  5. JSON API:对于JSON API,是否校验了Content-Type或使用了自定义请求头防护?

工具辅助

  • Burp Suite / OWASP ZAP:使用这些代理工具进行自动化的主动和被动扫描,可以快速发现常见的XSS和CSRF问题。
  • 浏览器开发者工具:检查网络请求,查看Cookie属性(Application -> Storage -> Cookies),检查响应头中是否有安全相关的Header(如CSP,Set-Cookie: HttpOnly; Secure; SameSite)。
  • 代码审计工具:对于源代码,可以使用类似SemgrepCodeQL等工具,编写或使用现成的规则来查找可能存在漏洞的代码模式(如查找innerHTML,eval()的调用)。

安全是一个持续的过程,而非一劳永逸的状态。XSS和CSRF作为OWASP Top 10的常客,其原理相对经典,但攻击者的手法也在不断演化。作为开发者,最有效的防御是建立起“安全左移”的意识,在需求评审、架构设计、编码实现、代码审查、测试部署的每一个环节,都将这些安全考量融入其中。从写好每一行对用户输入进行编码的代码开始,从为每一个表单添加CSRF Token开始,你的应用就会变得坚固得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/30 3:42:52

近400家美国报纸状告OpenAI和微软,AI版权纠纷成结构性产业冲突

【近400家美国报纸状告OpenAI和微软&#xff1a;偷内容不付钱】近日&#xff0c;曼哈顿联邦法院收到一份长达55页的诉状&#xff0c;代表近400家美国报纸的出版商联盟将OpenAI和微软告上法庭。原告名单涵盖《纽约每日新闻》等知名报纸&#xff0c;以及众多美国本地小报。出版商…

作者头像 李华
网站建设 2026/6/30 3:41:36

3步掌握小说下载器:你的个人数字图书馆终极指南

3步掌握小说下载器&#xff1a;你的个人数字图书馆终极指南 【免费下载链接】novel-downloader 一个可扩展的通用型小说下载器。 项目地址: https://gitcode.com/gh_mirrors/no/novel-downloader 你是否曾遇到过这样的情况&#xff1a;追更的小说突然下架&#xff0c;付…

作者头像 李华
网站建设 2026/6/30 3:40:54

Sand.ai曹越:每代模型押注非共识,视频模型是通往世界模型重要路径

曹越与Sand.ai的创业历程“每一代模型&#xff0c;我们都在押注一个非共识。” Sand.ai创始人曹越不太关心自己站在共识的哪一边。Sand.ai是一家视频生成模型和产品公司&#xff0c;成立于2024年1月。在上一段创业“光年之外”结束后&#xff0c;曹越投入到Sand.ai的创业中&…

作者头像 李华
网站建设 2026/6/30 3:40:41

THS7530EVM评估板实战指南:全差分放大器高速电路设计与调试

1. 项目概述&#xff1a;从评估板到实战设计在模拟电路设计&#xff0c;尤其是高速信号链的构建中&#xff0c;全差分放大器&#xff08;FDA&#xff09;是一个绕不开的核心器件。它不像普通的单端运放那样“接地气”&#xff0c;其差分输入和输出的架构&#xff0c;天生就是为…

作者头像 李华
网站建设 2026/6/30 3:39:29

IP2336H至为芯支持C口双向快充的18W多串锂电池充放电芯片

英集芯IP2336H是一款用于蓝牙音箱、电动工具、移动电源等充电方案的锂电池充放电管理SOC芯片。升压充电兼容5V/3A、9V/2.2A&#xff1b;反向降压放电同样支持 5V/3A、9V/2.2A 输出&#xff0c;充放电功率高达18W。硬件可灵活定制&#xff0c;适配2~6串锂电池配置。集成PD3.0、Q…

作者头像 李华
网站建设 2026/6/30 3:36:13

Kubernetes StatefulSet 容器存储架构

Kubernetes StatefulSet 容器存储架构解析 在云原生应用部署中&#xff0c;有状态服务&#xff08;如数据库、消息队列&#xff09;的稳定运行依赖持久化存储和拓扑顺序。Kubernetes StatefulSet 正是为此设计的控制器&#xff0c;它通过独特的存储架构解决了有状态应用的部署…

作者头像 李华