资讯动态

前端安全实战指南:从XSS到供应链攻击的防御体系

发布时间:2026/10/18 4:54:56 来源:尧图企业网站定制
1. 前端安全不是“后端的事”一次数据泄露排查给我的教训很多前端开发者都有一个根深蒂固的错觉安全是后端的事前端只管把页面画出来、把接口调通就行。我早期也这么想直到有一次参与排查一个用户数据异常泄露的问题最后定位到的根因竟然是一个前端渲染环节的疏忽——用户提交的昵称里带了可执行的脚本片段被直接塞进页面后在另一个用户的浏览器里执行了把本地存储的登录凭证读走并外发。那一刻我才真正意识到前端是离用户最近的一层也是攻击面最广的一层前端不设防后端再坚固也白搭。这篇内容我想系统地把前端会遇到的安全问题捋一遍。不是那种列个名词就完事的清单而是把每一类问题的触发原理、真实攻击路径、代码层面的复现方式、以及可落地的防御手段都讲清楚。适合有一定前端基础、想把自己负责的项目安全水位提上去的开发者也适合刚入行、还没建立安全意识的新人。我会尽量用生活化的类比把原理讲透再给出可以直接抄的代码和配置。需要先明确一个前提前端安全的核心矛盾在于——浏览器默认信任页面里的一切代码而用户输入和第三方资源都是不可信的。所有的防御手段本质上都是在“不可信的数据”和“可信的执行环境”之间建立隔离。理解了这条主线后面所有的具体问题你都能自己推导出防御思路。下面我按攻击面从大到小、从常见到冷门逐个拆解。每一类我都会给出“为什么会中招”和“怎么防”两部分中间穿插我自己踩过的坑。2. XSS前端安全里绕不开的头号问题2.1 XSS的本质是“数据被当成了代码”XSS跨站脚本攻击这个词听起来很专业但它的本质特别朴素你本来想把一段用户输入当成普通文字显示结果浏览器把它当成了代码去执行。打个比方你在餐厅点菜写了一张纸条“我要一份番茄炒蛋”结果后厨把“番茄炒蛋”四个字当成了菜谱指令真的去炒了一盘——这就是XSS。数据在错误的上下文里被赋予了执行能力。XSS通常分三类这个分类不是学术游戏而是决定了你该在哪里设防存储型XSS恶意内容被存进了数据库比如评论、昵称、个人简介每次有人访问这个页面都会触发。危害最大因为它是持久化的一个用户中招所有看到这条内容的用户都中招。反射型XSS恶意内容藏在URL参数里服务器把它原样反射回页面。需要诱导用户点击特定链接才能触发常见于搜索框、错误提示页。DOM型XSS整个攻击过程完全不经过服务器纯粹是前端JavaScript把不可信数据写进了危险的位置。这类最容易被前端开发者忽略因为它看起来“后端没返回任何异常数据”。2.2 一个真实的DOM型XSS复现我拿一个最常见的场景举例。假设有个页面要根据URL里的name参数显示欢迎语代码写成这样// 危险写法 const params new URLSearchParams(location.search); const name params.get(name); document.getElementById(welcome).innerHTML 欢迎你 name;看起来没问题对吧但如果用户访问的链接是https://example.com/page?nameimg srcx onerroralert(document.cookie)那么innerHTML会把这段字符串解析成真正的HTMLimg标签加载失败触发onerror里面的脚本就执行了。整个过程服务器完全没参与后端日志里干干净净但攻击已经完成了。这里的关键点是innerHTML、outerHTML、document.write、insertAdjacentHTML这些API都会把字符串当HTML解析。只要数据来源不可信用它们就等于开门揖盗。2.3 防御XSS的三层防线防御不是靠单一手段而是要建立多层防线任何一层被绕过还有下一层兜底。第一层输出编码按上下文选择编码方式。这是最根本的。数据放在HTML文本里、放在属性里、放在script里、放在URL里需要的编码方式完全不同。比如放进HTML文本要转义 放进URL要用encodeURIComponent。很多人只做了一种转义就以为万事大吉结果换个上下文就失效了。第二层优先用安全的API。能用textContent就别用innerHTML能用setAttribute就别拼字符串。现代框架React、Vue默认会对插值做转义这是它们最大的安全价值之一。但要注意框架提供的“绕过转义”的口子React的dangerouslySetInnerHTML、Vue的v-html一旦用了就等于自己把防线拆了。第三层CSP内容安全策略。这是一道“即使你写错了代码也能兜底”的防线。通过HTTP响应头告诉浏览器只允许加载哪些来源的脚本、禁止内联脚本执行。配置得当的话即使页面里被注入了script浏览器也会拒绝执行。Content-Security-Policy: default-src self; script-src self; object-src none; base-uri self注意CSP里千万不要图省事写script-src unsafe-inline那等于把内联脚本的禁令完全废掉XSS防御效果大打折扣。如果确实有内联脚本需求用nonce或hash机制。2.4 我踩过的坑以为转义了就安全早期我做过一个富文本展示功能用户可以在后台编辑器里写带格式的内容。我当时想反正内容都要经过后端后端会过滤前端直接innerHTML渲染就行。结果后端只过滤了script标签攻击者用img onerror就绕过了。后来我改成用成熟的白名单过滤库比如DOMPurify在前端也过一遍只允许安全的标签和属性通过问题才解决。这个教训是永远不要自己写过滤规则。XSS的绕过手法有几百种编码、大小写、标签嵌套、SVG、MathML……你想到的绕过方式攻击者早就想到了。用经过大量实战检验的库比自己拍脑袋写正则靠谱得多。3. CSRF与点击劫持借用户之手完成攻击3.1 CSRF的核心是“浏览器自动带上了凭证”CSRF跨站请求伪造的原理和XSS完全不同。XSS是攻击者让代码在受害者浏览器里执行CSRF是攻击者借用受害者已经登录的身份在受害者不知情的情况下发起请求。关键在于浏览器的Cookie机制你登录了某网站浏览器存了会话Cookie之后你访问这个网站的任何请求浏览器都会自动带上这个Cookie。攻击者要做的就是诱导你在登录状态下访问一个恶意页面这个页面里藏着一个指向目标网站的请求浏览器自动带上Cookie服务器以为是你的正常操作于是执行了。举个具体例子。假设某网站有个转账接口POST /transfer参数是to和amount。攻击者做一个页面里面放一个自动提交的表单form actionhttps://target.com/transfer methodPOST idf input nameto valueattacker_account input nameamount value10000 /form scriptdocument.getElementById(f).submit();/script你只要在登录状态下打开这个页面表单自动提交浏览器带上你的Cookie钱就转走了。整个过程你什么都没点。3.2 防御CSRF让请求“无法被伪造”防御思路是让攻击者无法构造出一个“服务器认可的请求”。主流手段有几种CSRF Token服务器在页面里埋一个随机token提交请求时必须带上这个token服务器校验。攻击者的页面拿不到这个token受同源策略保护所以伪造的请求会被拒绝。这是最经典也最可靠的方式。SameSite Cookie属性给Cookie设置SameSiteLax或Strict浏览器在跨站请求时就不会带上这个Cookie。Lax是现在的默认值能挡住大部分CSRF。这是成本最低的防御建议所有会话Cookie都加上。校验Origin/Referer头服务器检查请求来源是不是自己的域名。简单有效但Referer可能被隐私设置屏蔽所以只能作为辅助。Set-Cookie: sessionidxxx; SameSiteLax; Secure; HttpOnly注意HttpOnly这个属性很重要它让JavaScript无法读取这个Cookie能有效缓解XSS窃取Cookie的问题。会话Cookie一定要加。3.3 点击劫持你以为点的是A其实点的是B点击劫持Clickjacking是另一种“借用户之手”的攻击。攻击者用一个透明的iframe把目标网站盖在你的页面上你以为自己在点某个按钮实际上点的是iframe里目标网站的按钮。防御手段主要是响应头X-Frame-Options: DENY或者用CSP的frame-ancestors指令更灵活Content-Security-Policy: frame-ancestors none这两个头告诉浏览器我的页面不允许被任何其他页面用iframe嵌套。这样攻击者就没法把你盖在他的页面上。3.4 一个容易忽略的点GET请求不该有副作用我见过不少项目把“删除”“修改”这种操作做成GET请求比如GET /delete?id123。这是CSRF的重灾区因为GET请求可以轻易通过img src触发连表单都不用。任何有副作用的操作都必须用POST/PUT/DELETE并且加CSRF防护。这是设计层面的纪律不是技术问题。4. 第三方依赖与供应链你引入的每一个包都是潜在入口4.1 前端项目最大的攻击面其实是node_modules现代前端项目动辄几百上千个依赖包直接依赖几十个间接依赖可能上千。这些包来自全球各地你根本不知道某个包的维护者是谁、代码里有没有恶意逻辑。供应链攻击就是针对这个环节攻击者要么污染某个流行包要么劫持某个维护者的账号发布带毒版本。我印象很深的一次是某个流行的工具库被曝出在某个版本里偷偷加了窃取环境变量的代码。这种攻击特别隐蔽因为包的功能看起来完全正常恶意逻辑只在特定条件下触发。4.2 依赖安全的实操检查清单防御供应链攻击核心是“知道自己用了什么并且持续监控”。锁定版本用package-lock.json或yarn.lock锁定所有依赖的精确版本避免自动升级到带毒版本。提交lock文件到版本库团队统一。定期审计npm audit或yarn audit能扫描已知漏洞。虽然它报的很多是间接依赖的低危问题但高危的一定要处理。减少依赖每引入一个包前问自己这个功能我自己写要多少行如果只有几十行可能自己写更安全。依赖越少攻击面越小。关注依赖的维护状态长期不更新、维护者单一的包要警惕。可以用工具检查依赖的健康度。# 查看依赖树找出重复和可疑的包 npm ls --all # 审计并自动修复可修复的漏洞 npm audit fix4.3 第三方脚本CDN引入的代码你控制不了比npm包更危险的是直接在页面里用script srchttps://some-cdn.com/lib.js引入的第三方脚本。这些脚本运行在你的页面上下文里能访问你的DOM、Cookie、localStorage权限和你的自有代码完全一样。一旦这个CDN被入侵或者这个库本身有问题你的用户数据就全暴露了。我的建议是能用npm包本地打包的就别用CDN至少版本可控。必须用CDN的加integrity属性做子资源完整性校验确保加载的代码没被篡改script srchttps://cdn.example.com/lib.js integritysha384-xxxxx crossoriginanonymous/script给第三方脚本加CSP限制只允许特定来源。注意integrity属性要求CDN支持CORS否则校验会失败。上线前一定要测试。5. 数据存储与传输前端能碰到的敏感信息边界5.1 localStorage不是保险箱很多前端开发者习惯把token、用户信息往localStorage里一塞就完事。但localStorage有几个致命问题它没有过期机制数据会一直留着它能被同源的任何JavaScript读取一旦有XSStoken直接被偷它不随请求自动发送需要手动加到请求头里容易漏。相比之下HttpOnly的Cookie在防XSS窃取上更安全因为JS读不到。但Cookie又有CSRF问题需要配合SameSite和CSRF Token。所以没有银弹要根据场景权衡。我的实践是会话凭证优先用HttpOnly Secure SameSite的Cookie前端拿不到token反而更安全。如果因为跨域等原因必须用localStorage存token那一定要把XSS防御做到极致并且给token设置较短的过期时间。5.2 敏感信息不该出现在前端代码里这是个老生常谈但总有人犯的问题把API密钥、数据库连接串、内部接口地址硬编码在前端代码里。前端代码是完全公开的任何人打开开发者工具就能看到打包压缩也挡不住。我见过有人在React代码里写第三方服务的secret key等于把钥匙挂在门上。正确的做法是所有需要保密的操作都放到后端前端只调用自己的后端接口由后端去持有和使用密钥。前端代码里出现的任何字符串都要假设它是公开的。5.3 传输层HTTPS是底线不是选项现在还有项目在开发环境用HTTP、上线才切HTTPS甚至有的内部系统一直用HTTP。这是非常危险的因为HTTP传输的数据可以被中间人任意读取和篡改。前端能做的所有资源用HTTPS加载避免混合内容HTTPS页面里加载HTTP资源浏览器会警告甚至阻止。配置HSTS响应头强制浏览器后续都用HTTPS访问Strict-Transport-Security: max-age31536000; includeSubDomains敏感表单提交前确认是HTTPS不要在不安全的环境下输入密码。6. 把防御落到工程里一份可执行的前端安全清单6.1 开发阶段就该做的事安全不是上线前才补的而是贯穿开发全程的。我在项目里会强制这几件事代码审查时专门看安全任何用到innerHTML、eval、new Function、document.write的地方都要重点审查问清楚数据来源是否可信。ESLint安全规则配置eslint-plugin-security等插件自动标记危险写法。统一封装危险操作把DOM操作、请求发送封装成统一的工具函数在函数内部做转义和校验避免每个开发者各写各的。输入校验前置前端做基础校验格式、长度、类型虽然不能替代后端校验但能挡住大量低级问题也提升用户体验。6.2 上线前的安全检查表检查项具体动作优先级XSS防护确认无危险innerHTML用法CSP已配置高CSRF防护会话Cookie加SameSite关键操作有Token高点击劫持配置X-Frame-Options或frame-ancestors中依赖安全跑一遍audit处理高危漏洞高敏感信息全局搜索密钥、内网地址确认无泄露高传输安全全站HTTPS配置HSTS高第三方脚本加integrityCSP限制来源中6.3 一个我常用的CSP配置模板CSP配置是最容易配错也最容易配松的。下面这个模板我用了很多项目在安全性和可用性之间比较平衡Content-Security-Policy: default-src self; script-src self nonce-随机值; style-src self unsafe-inline; img-src self data: https:; connect-src self https://api.example.com; frame-ancestors none; base-uri self; form-action self解释几个关键点script-src用nonce而不是unsafe-inline这样内联脚本必须带正确的nonce才能执行XSS注入的脚本拿不到nonce就被拦了。style-src允许unsafe-inline是因为很多UI库需要动态样式风险相对可控。connect-src限制AJAX能请求的域名防止数据被外发到攻击者服务器。注意CSP上线前一定要用Content-Security-Policy-Report-Only模式先跑一段时间收集违规报告确认没有误伤正常功能再切正式模式。直接上正式模式很容易把页面搞挂。6.4 安全是持续过程不是一次性任务最后分享一个我自己的体会前端安全没有“做完”的那一天。新的攻击手法、新的依赖漏洞、新的浏览器特性都在不断变化。我现在的习惯是每个月花半天时间做一次安全巡检跑一遍依赖审计、看看CSP报告、回顾一下最近新增的代码里有没有危险写法。这个投入不大但能避免绝大多数问题。还有一点安全防御要“纵深”不要指望单一手段。XSS靠转义CSPHttpOnly三层CSRF靠SameSiteTokenOrigin校验三层。任何一层都可能因为各种原因失效但三层同时被绕过的概率就极低了。这就像家里的门锁你不会只装一道锁就安心而是门锁、防盗门、监控一起上。如果你负责的项目现在还没做过系统的安全梳理我建议从XSS和依赖审计这两个最容易出问题的地方入手先把最明显的口子堵上再逐步完善其他防线。安全这件事早做一天就少一分风险。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑