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那么当攻击者输入scriptalert(XSS)/script并诱使用户访问https://example.com/search?qscriptalert(XSS)/script时这个脚本就会在用户的浏览器里弹窗。它的数据流向是用户浏览器 - 服务器 - 用户浏览器。恶意脚本并不存储在服务器上。存储型XSS的危害性更大因为它具有持久性。攻击者将恶意脚本提交到网站的后端数据库如论坛发帖、用户评论、个人资料昵称之后任何浏览到包含该恶意内容的页面的用户其浏览器都会执行该脚本。比如一个博客评论系统如果不对用户输入的评论内容进行过滤攻击者提交一条包含scriptstealCookie()/script的评论。此后所有访问这篇博客文章的用户在加载评论时都会执行这个窃取Cookie的脚本。它的数据流向是攻击者 - 服务器数据库 - 所有受害用户浏览器。DOM型XSS是一种比较特殊的类型它的恶意代码执行完全发生在客户端的DOM解析环境不经过服务器。攻击利用的是前端JavaScript对DOM的操作。例如页面有一段JS代码document.getElementById(content).innerHTML window.location.hash.substring(1);它把URL的hash部分#后面的内容直接写入了页面的innerHTML。如果攻击者构造一个URLhttps://example.com/page#img src1 onerroralert(XSS)那么当用户访问时onerror事件就会被触发。这种攻击更难被传统的服务端WAFWeb应用防火墙检测到因为恶意负载根本不会发送到服务器。注意很多人认为用了前端框架如React, Vue就天然免疫XSS这是误区。框架确实在默认情况下提供了很好的转义保护例如React对{}内的变量进行转义但如果你使用了dangerouslySetInnerHTMLReact或v-htmlVue这类故意绕过安全机制的方法或者将未经验证的数据传递给eval()、setTimeout()等函数XSS漏洞依然会产生。2.2 CSRF攻击借刀杀人的艺术如果说XSS是利用用户对网站的信任在网站上“种木马”那么CSRF就是利用网站对用户浏览器的信任让浏览器在用户不知情的情况下“代替”用户向网站发起一个恶意请求。它的核心在于“伪造”。想象一个场景你登录了网上银行Abank-a.com并且会话Cookie还在有效期内。此时你不小心访问了一个恶意网站Bevil.com。网站B的页面上隐藏着一个自动提交的表单或者一张自动加载的图片其src指向银行A的转账接口img srchttps://bank-a.com/transfer?toattackeramount10000 width0 height0。你的浏览器在加载这个图片时会自动携带你登录银行A的Cookie向银行A发起一个GET请求。银行A的服务器看到这个带有合法Cookie的请求就会认为是你本人操作的从而执行转账。这就是一次典型的CSRF攻击。CSRF攻击成功的必要条件通常被称为“三个确认”登录状态确认用户已经登录了目标网站如银行并且会话尚未过期。请求可预测确认攻击者能够推测出目标网站某个敏感操作如修改密码、转账的请求参数格式URL、方法、参数名。浏览器自动携带凭证确认目标网站依赖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的JsoupPython的bleach。心得过滤要在服务端做。前端验证是为了用户体验即时反馈后端验证是为了安全。攻击者可以完全绕过前端直接构造请求发给后端。3.1.2 输出编码/转义这是防御XSS最根本、最有效的手段。根据数据将要放置的上下文环境进行不同的编码。HTML上下文将数据放入HTML标签内部如div内容或普通属性如alt,value时需要对,,,,等字符进行HTML实体编码。例如转成lt;。大多数现代Web框架的模板引擎如Thymeleaf, Freemarker, Django Templates在默认情况下都会自动进行HTML转义。!-- 错误示例直接输出 -- div${userInput}/div !-- 如果userInput是 scriptalert(1)/script就会执行 -- !-- 正确示例框架自动转义或手动转义后 -- divlt;scriptgt;alert(1)lt;/scriptgt;/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 hrefhttps://example.com?redirect${userInput}点击/a !-- 如果userInput是 javascript:alert(1)就会形成XSS -- !-- 正确做法严格验证协议头只允许http/https并对参数值进行URL编码 --CSS上下文较少见但也需注意。应对放入CSS的值进行编码。3.1.3 利用内容安全策略CSPCSP是一个强大的后端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 selfdefault-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: 这是最重要的属性。设置后JavaScriptdocument.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在跨站请求中的发送也有帮助。Strict或Lax模式可以阻止第三方上下文发起的请求自动携带Cookie。3.2 CSRF防御关键在于“不可预测性”与“同源验证”防御CSRF的核心思想是确保敏感请求是由源自本网站的、可信的页面发起的而不是来自第三方网站。3.2.1 使用CSRF Token同步器令牌模式这是目前最主流、最有效的防御方案。原理是为每个用户会话生成一个随机、不可预测的Token在渲染表单或任何可能触发状态变更的请求时将这个Token作为一个隐藏字段对于表单或自定义HTTP头对于AJAX嵌入。服务器在处理请求时校验这个Token的有效性。服务端实现用户登录或访问站点时在服务器端Session中为其生成一个唯一的CSRF Token。在渲染任何包含表单的页面时将该Token输出到一个隐藏的input字段中例如input typehidden name_csrf value生成的随机Token。对于AJAX请求可以将Token放在页面的meta标签里由前端JavaScript读取并设置为自定义请求头如X-CSRF-TOKEN。服务器接收到POST/PUT/DELETE等非幂等请求时从请求参数或头部取出Token与Session中存储的Token进行比对。一致则通过不一致或缺失则拒绝。为什么有效恶意网站无法预先得知或获取到这个Token受同源策略保护因此它构造的伪造请求中必然缺少有效的Token服务器校验会失败。注意事项Token必须足够随机使用密码学安全的随机数生成器且与用户会话绑定。Token应是一次性的或具有较短的有效期并在使用后更新以防止重放攻击。确保Token只通过HTTPS传输防止被中间人窃取。3.2.2 校验请求来源Origin/Referer Header服务器可以检查HTTP请求头中的Origin或Referer字段判断请求是否来自合法的源即自己的网站。Origin头对于跨域请求浏览器会自动添加指示请求的来源协议域名端口。对于同源请求某些浏览器可能不发送。Referer头表示前一个页面的地址。但注意Referer可能被用户浏览器设置或代理服务器过滤掉存在为空的情况。实现在服务器端拦截器或中间件中对于敏感操作检查Origin或Referer是否以自己网站的域开头。// 伪代码示例 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有奇效。SameSiteStrict: 最严格。Cookie仅在同站请求即当前网站域下中发送。这意味着如果用户从其他网站如邮件链接点击过来即使已登录首次请求也不会携带Cookie可能导致需要重新登录。适用于极高安全要求的操作。SameSiteLax默认值: 宽松模式。在跨站请求中仅对安全HTTPS的顶级导航如链接点击发送Cookie而对子请求如图片、iframe、AJAX不发送。这平衡了安全性和用户体验。大多数情况下Lax是推荐设置。SameSiteNone: Cookie在所有上下文中发送但必须同时设置Secure属性即仅限HTTPS。这是为了兼容一些需要跨站Cookie的第三方服务。如何设置在设置Cookie的响应头中指定。Set-Cookie: sessionidabc123; Path/; HttpOnly; Secure; SameSiteLax实操心得将关键会话Cookie设置为SameSiteLax或Strict能极大地缓解CSRF攻击。对于现代浏览器这几乎可以防御绝大多数传统的CSRF攻击。但是它不能防御同源下的XSS攻击发起的请求因为同源请求会携带Cookie也不能防御某些特定的攻击场景如“登录CSRF”。因此它应与CSRF Token结合使用。3.2.4 要求用户进行二次验证对于特别敏感的操作如转账、修改密码、修改邮箱强制要求用户进行二次验证例如输入登录密码、短信验证码、或使用U盾等。这虽然不是纯粹的CSRF防御技术但能从业务逻辑层面增加攻击门槛。4. 实战场景与工具链在靶场和真实代码中演练理论讲得再多不如亲手实践。下面我将带你搭建一个简单的靶场环境并分析真实框架中的防御机制让你有更直观的感受。4.1 使用DVWA/Pikachu靶场进行手动测试DVWADamn Vulnerable Web Application和Pikachu是两款非常经典的Web漏洞学习靶场内置了XSS和CSRF的漏洞场景。环境搭建以DVWA为例最方便的方式是使用Docker。确保你已安装Docker和Docker Compose。创建一个docker-compose.yml文件version: 3 services: dvwa: image: vulnerables/web-dvwa ports: - 8080:80 environment: - PHPIDSoff # 关闭PHPIDS以方便测试 volumes: - ./dvwa_data:/app在终端运行docker-compose up -d。浏览器访问http://localhost:8080按照页面提示完成安装数据库设置等默认登录账号/密码是admin/password。在DVWA首页左侧将安全级别设置为Low这样防护最弱便于我们理解漏洞原理。反射型XSSLow级别测试进入XSS (Reflected)模块。在输入框尝试输入scriptalert(document.domain)/script点击提交。你会看到一个弹窗显示当前域名。这说明脚本被执行了。尝试绕过切换到Medium或High级别DVWA引入了简单的过滤如将script替换为空。你可以尝试使用大小写混合、双写、或利用事件处理器如img src1 onerroralert(1)进行绕过。这个过程能让你深刻理解黑名单过滤的局限性。存储型XSS测试进入XSS (Stored)模块。在留言板输入恶意脚本并提交。刷新页面或让其他“用户”你可以新开一个浏览器无痕窗口访问查看该页面脚本会自动执行。这模拟了攻击持久化的效果。CSRFLow级别测试进入CSRF模块。你会看到一个修改密码的简单表单。观察URL例如http://localhost:8080/vulnerabilities/csrf/?password_new123password_conf123ChangeChange#。攻击者可以构造一个类似的URL诱使你点击。你可以自己写一个简单的HTML页面evil.html放在本地内容如下img srchttp://localhost:8080/vulnerabilities/csrf/?password_newhackedpassword_confhackedChangeChange# width0 height0 p你刚刚可能被CSRF攻击了如果已登录DVWA/p在已登录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-TOKEN或X-XSRF-TOKEN请求头中。Thymeleaf模板自动集成如果你使用Thymeleaf在表单中添加th:action属性后Thymeleaf会自动为你添加一个名为_csrf的隐藏字段。form methodpost th:action{/change-password} !-- Thymeleaf会自动插入input typehidden name_csrf value.../ -- input typepassword namenewPassword button typesubmit修改密码/button /formAJAX请求你需要从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 methodpost {% csrf_token %} !-- 其他表单字段 -- input typesubmit value提交 /form这个标签会被渲染成一个隐藏的input字段input typehidden namecsrfmiddlewaretoken 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-htmlrawHtml/p心得在React/Vue项目中XSS漏洞往往出现在错误使用上述“危险”方法、或直接将不可信数据传递给eval()、setTimeout()、innerHTML等场景。建立严格的代码审查流程禁止随意使用这些特性是至关重要的。5. 高级话题与疑难排查当基础防御失效时即使我们做好了所有基础防御在复杂的现实环境中仍然可能遇到一些棘手的场景和高级攻击手法。了解它们能让我们在安全设计上考虑得更周全。5.1 XSS的进阶绕过与防御攻击者不会止步于简单的script标签。他们会尝试各种奇技淫巧来绕过过滤。基于字符编码的绕过 过滤器可能只寻找script字符串但攻击者可以使用HTML实体编码、JS Unicode编码等方式进行混淆。攻击载荷img srcx onerror#97;#108;#101;#114;#116;#40;#49;#41;。这里的#97;等是alert(1)的HTML十进制实体编码。浏览器在解析HTML属性时会对其进行解码。防御进行输出编码时必须根据最终的输出上下文进行编码。在HTML属性上下文中即使输入看起来是编码过的只要浏览器会解码我们就必须在输出前确保将等字符转义为amp;。同时输入过滤应采用规范化和解码后再检查的策略。利用SVG/HTML5新特性 SVG文件内可以包含JavaScript某些对script过滤严格的系统可能允许上传SVG图片。攻击载荷一个恶意的SVG文件内容。svg xmlnshttp://www.w3.org/2000/svg onloadalert(1)防御对用户上传的文件进行严格的类型检查不仅看扩展名更要看魔数或解析文件头并将上传的文件存储在独立的、不可执行脚本的域名下使用CDN或静态资源服务器并设置正确的Content-Type。对于图片可以进行二次渲染压缩、缩放以破坏内嵌的脚本。DOM型XSS与前端框架的盲区 即使后端做了完美转义如果前端JavaScript不当地使用了innerHTML、outerHTML、document.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字符串且包含恶意脚本...防御首选使用安全的API。用textContent代替innerHTML来设置纯文本。如果必须设置HTML使用经过严格净化的库如DOMPurify进行处理。避免绝对不要将不可信数据拼接字符串后传给eval()、setTimeout、setInterval的第一个字符串参数或new Function的构造函数。如果必须动态执行代码请使用其他架构。框架最佳实践在React/Vue中严格遵守数据驱动视图的原则避免直接操作DOM。5.2 CSRF防御的边界情况与对策“登录CSRF”攻击 传统CSRF攻击针对的是已登录用户。但“登录CSRF”攻击的是登录过程本身。攻击者伪造一个登录请求让受害者在不知情的情况下使用攻击者控制的账号密码登录了目标网站。此后受害者在该网站上的所有操作如发帖、购物都会记录在攻击者的账号下可能导致隐私泄露或为攻击者“刷单”。防御在登录表单中也加入CSRF Token。因为登录请求通常也是状态变更操作创建会话。确保登录接口同样受到CSRF保护。JSON API的CSRF防护 对于接收JSON格式数据的API传统的在表单中加隐藏字段的方式不适用。攻击者仍然可以构造一个Content-Type为text/plain或application/x-www-form-urlencoded的请求来提交恶意数据如果服务器端没有严格校验Content-Type可能会被绕过。防御策略校验Content-Type头服务器端严格检查请求的Content-Type头是否为application/json。但这并非绝对安全因为某些场景下如CORS预检请求可以伪造。使用自定义请求头这是更推荐的方式。让前端在发送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); });将Token放入JSON Body虽然不常见但也可以将CSRF Token作为JSON Body中的一个字段发送服务器端从Body中解析并校验。这要求攻击者必须能预测Token难度等同于传统表单方式。SameSite Cookie的局限性SameSiteLax是当前浏览器的默认值极大地缓解了CSRF。但它并非万能GET请求的CSRFLax模式允许在跨站顶级导航如点击链接的GET请求中发送Cookie。如果某个敏感操作如删除文章错误地使用了GET方法仍然可能受到CSRF攻击。因此严格遵守RESTful规范状态修改操作必须使用POST、PUT、DELETE等非GET方法是防御CSRF的重要前提。浏览器兼容性虽然现代浏览器都已支持但仍需考虑少量旧版本浏览器的用户。5.3 渗透测试中的常见问题排查清单当你负责一个项目的安全审计或收到一份渗透测试报告时如何快速定位XSS和CSRF问题以下是一个速查清单XSS漏洞排查点输入点所有用户可控的输入URL参数、表单字段、HTTP头、上传文件、WebSocket消息。输出点这些输入被输出到的所有上下文HTML正文、HTML属性、JavaScript代码、CSS、URL。过滤与编码后端是否对输入进行了白名单过滤或净化前端/模板引擎在输出时是否根据上下文进行了正确的编码检查是否滥用v-html,dangerouslySetInnerHTML是否直接拼接字符串生成HTML或JS。CSP是否部署了Content-Security-Policy头策略是否过于宽松如存在unsafe-inline,unsafe-evalCookie安全关键Cookie如会话ID是否设置了HttpOnly和Secure属性CSRF漏洞排查点状态变更端点所有非GET的、会导致状态变化的API端点POST, PUT, DELETE, PATCH。防护机制这些端点是否要求CSRF TokenToken是否随机、与会话绑定、一次性或有时效框架的CSRF中间件是否全局启用是否有特定接口被错误豁免csrf_exemptCookie设置会话Cookie是否设置了SameSite属性至少为Lax请求方法是否有状态变更操作错误地使用了GET方法JSON API对于JSON API是否校验了Content-Type或使用了自定义请求头防护工具辅助Burp Suite / OWASP ZAP使用这些代理工具进行自动化的主动和被动扫描可以快速发现常见的XSS和CSRF问题。浏览器开发者工具检查网络请求查看Cookie属性Application - Storage - Cookies检查响应头中是否有安全相关的Header如CSP,Set-Cookie: HttpOnly; Secure; SameSite。代码审计工具对于源代码可以使用类似Semgrep、CodeQL等工具编写或使用现成的规则来查找可能存在漏洞的代码模式如查找innerHTML,eval()的调用。安全是一个持续的过程而非一劳永逸的状态。XSS和CSRF作为OWASP Top 10的常客其原理相对经典但攻击者的手法也在不断演化。作为开发者最有效的防御是建立起“安全左移”的意识在需求评审、架构设计、编码实现、代码审查、测试部署的每一个环节都将这些安全考量融入其中。从写好每一行对用户输入进行编码的代码开始从为每一个表单添加CSRF Token开始你的应用就会变得坚固得多。