标签

CSRF

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种网络攻击方式,它允许攻击者利用用户已经认证的身份,在不知情的情况下,以该用户的名义执行恶意操作。这些操作可能包括提交表单、更改用户设置或进行金钱交易等。CSRF攻击适用于基于cookie的认证系统,因为浏览器会自动附带当前域下的cookie信息。

CSRF
查看更多相关内容
服务端5月30日 22:17
什么是 CSRF 攻击?它如何工作,又该怎么防护?CSRF(跨站请求伪造)不是偷 Cookie,而是**借浏览器自动带 Cookie 的机制,冒充用户发起操作**。典型流程是:用户已经登录 `bank.example`,浏览器里有登录 Cookie;随后用户打开攻击者页面,页面自动提交一个转账、改邮箱或删除数据的请求;请求发到 `bank.example` 时,浏览器会按域名自动带上 Cookie。服务器如果只看 Cookie 判断“用户已登录”,却不校验这次操作是不是从本站页面主动发起,就会把伪造请求当成真实操作。 CSRF 成立通常要同时满足几个条件:用户处于登录状态;目标站使用 Cookie、Session 这类浏览器会自动携带的凭证;接口会改变状态;服务端没有校验 CSRF Token、Origin、Referer 或 SameSite 等额外信号。少了其中任何一环,攻击难度都会明显上升。 ## 追问 ### CSRF 和普通跨域请求有什么关系? 很多人以为浏览器有同源策略,所以 CSRF 发不出去,这是误解。同源策略主要限制攻击者读取跨站响应,不能阻止浏览器发送表单、图片、脚本、跳转这类请求。CSRF 往往不需要读响应,只要服务器执行了“改状态”的动作,攻击就已经成功。 CORS 也是类似道理:它控制前端脚本能不能读取响应,不等于所有跨站请求都被拦截。尤其是普通表单提交、图片加载、顶级导航,本来就不依赖 CORS 成功读取响应。 ### GET、POST、JSON API 都会被 CSRF 打中吗? GET 如果只做查询,风险相对小;但如果 GET 做删除、改状态、触发任务,就非常危险,因为 `<img>`、`<script>`、`<a>` 都能轻易触发 GET。POST 表单同样能被恶意页面自动提交,所以“改成 POST”不是完整防护。 JSON API 的攻击门槛高一点,因为 `application/json` 和自定义请求头通常会触发 CORS 预检。但如果服务端错误放开 CORS,或者接口同时兼容表单格式,仍然可能被利用。接口设计上应坚持:读操作和写操作分离,写操作必须带额外校验。 ### CSRF Token 为什么有效? CSRF Token 的作用是证明“请求来自服务端渲染或授权过的本站页面”。服务端生成一个不可预测的随机值,放在页面、meta 标签或接口返回里,前端提交写请求时把它放进隐藏字段或 `X-CSRF-Token` 请求头。攻击者页面不能读取目标站页面内容,也就拿不到这个随机值。 Token 不能随便设计:不要放 URL 里,避免进入日志和 Referer;要绑定用户会话或登录态;过期策略要合理;高风险操作可以使用一次性 Token。Token 校验失败时,应该返回明确的 403,并记录来源、用户、接口和 request id,方便排查误杀。 ### SameSite、Origin、Referer 应该怎么配合? 现代防护通常是组合拳。Cookie 设置 `SameSite=Lax` 可以挡住大量跨站 POST 和 iframe 场景;`SameSite=Strict` 更安全,但会影响外部链接跳转后的登录体验;必须跨站携带 Cookie 时才用 `SameSite=None; Secure`。 服务端还可以校验 `Origin`,没有 Origin 时再看 `Referer`。这两者适合做来源判断和日志审计,但不建议作为唯一防线,因为隐私策略、代理、旧浏览器或特殊跳转可能让头部缺失。更稳的策略是:SameSite 降低默认风险,Token 验证操作意图,Origin/Referer 做辅助拦截。 ### XSS 会不会绕过 CSRF 防护? 会。只要攻击者能在你的页面里执行脚本,就可能读取非 HttpOnly 的 CSRF Token,或者直接在同源上下文里调用接口。也就是说,CSRF Token 不能替代 XSS 防护。 所以安全设计要分层:Cookie 使用 `HttpOnly`、`Secure`、`SameSite`;页面输出要做转义和 CSP;写接口校验 Token 和来源;高风险操作再加二次确认、幂等号或重新输入密码。不要指望一个机制解决所有问题。 ## 示例 下面是一个最简单的 CSRF 攻击示例。用户只要打开攻击页面,浏览器就会向目标站发起请求: ```html <form action="https://bank.example/transfer" method="POST"> <input name="to" value="attacker"> <input name="amount" value="1000"> </form> <script>document.forms[0].submit()</script> ``` 服务端防护可以这样做: ```js app.post('/transfer', requireLogin, csrfCheck, async (req, res) => { await transfer(req.user.id, req.body.to, req.body.amount); res.json({ ok: true }); }); function csrfCheck(req, res, next) { const token = req.get('x-csrf-token') || req.body.csrf_token; if (!token || token !== req.session.csrfToken) return res.sendStatus(403); next(); } ``` 这段逻辑的重点不是代码长短,而是把“用户已登录”和“用户确实从本站页面发起操作”分开验证。CSRF 正是利用了很多系统只验证前者、忽略后者的漏洞。
服务端5月30日 21:29
什么是 CSRF 攻击?它需要满足哪些攻击条件?CSRF 是跨站请求伪造,核心不是偷走用户信息,而是借用用户已经登录的身份去发请求。只要目标站点依赖 Cookie 判断登录态,浏览器又会在请求目标域名时自动带上 Cookie,攻击者就可能诱导用户打开恶意页面,让目标站误以为这次修改密码、转账、删除数据是用户本人操作。它通常需要:用户已登录、目标操作靠 Cookie 认证、接口缺少额外意图校验、攻击者能构造可触发请求。 ## 追问 ### CSRF 为什么能利用 Cookie? 浏览器发送 Cookie 看的是请求目标域名,不看请求是不是用户主动点的。恶意站不能读取 Cookie,但能诱导浏览器带 Cookie 发请求。 ### 哪些接口最容易被打中? 所有会改变状态的接口都要重点看,比如转账、改邮箱、改密码、删除数据、绑定账号。GET 接口如果偷偷做写操作也很危险。 ### CORS 拦住跨域响应还有 CSRF 风险吗? 有。CORS 主要限制攻击者读取响应,CSRF 很多时候只需要请求被执行,不需要看到结果。 ### 实际项目怎么防? 常见组合是 CSRF Token + SameSite Cookie + Origin/Referer 校验。Token 证明页面来自本站,SameSite 降低自动带 Cookie 概率。 ## 示例 ```html <form action="https://bank.example/transfer" method="POST"> <input name="to" value="attacker"> </form> <script>document.forms[0].submit()</script> ```
服务端5月30日 21:29
双重提交 Cookie 如何防护 CSRF?实现时要注意什么?双重提交 Cookie 防护 CSRF 的核心是:服务端生成随机 token,同时让浏览器把它放在 Cookie 里,并要求前端在表单字段或请求头里再提交一份。真正的同站页面能读到这份非 HttpOnly 的 CSRF Cookie,所以能把 token 放进请求;恶意站点虽然能诱导浏览器自动带上 Cookie,却读不到目标站 Cookie,也就很难提交同一份 token。服务端比较两边是否一致,就能拦住大多数跨站伪造请求。 ## 追问 ### 它和传统 CSRF Token 有什么区别? 传统 Token 通常保存在服务端 Session 里,请求来了拿提交值和 Session 值比对。双重提交 Cookie 不存服务端状态,更适合分布式和无状态接口,但要更小心 XSS、子域名和 Cookie 覆盖问题。 ### Cookie 能不能设置 HttpOnly? 普通双重提交模式下不能,因为前端需要读取 Cookie 后放进请求头或表单字段。更安全的变体是签名双重提交,避免攻击者伪造结构正确的 token。 ### 为什么不建议把 token 放 URL? URL 会进入浏览器历史、代理日志、服务端访问日志和第三方统计系统。Token 更适合放请求头或 POST body。 ### 最容易踩什么坑? Domain 配太宽,任意子域问题都可能影响主站;只判断字符串相等,没有处理缺失、长度不等和异常输入,也会留下边界问题。 ## 写段代码 ```js res.cookie('csrf_token', token, { httpOnly:false, secure:true, sameSite:'lax', path:'/' }); if (req.cookies.csrf_token !== req.get('x-csrf-token')) return res.sendStatus(403); ```
服务端5月28日 06:58
REST API 的 CSRF 防护怎么做?认证方式不同策略完全不同REST API 的 CSRF 防护和传统 Web 应用有明显差异。核心问题在于:REST API 可能被浏览器、移动应用、第三方服务等多种客户端调用,认证方式也不统一,防护策略必须因地制宜。 ## REST API 为什么仍需关注 CSRF 很多人认为 REST API 只用 JSON 就安全了,但事实并非如此。只要你的 API 满足以下条件,CSRF 风险就存在: - 使用 Cookie 进行身份认证(浏览器会自动携带) - 允许跨域请求(CORS 配置宽松) - 接受 `application/json` 以外的 Content-Type 攻击者的思路很直接:构造一个恶意页面,让已登录用户在不知情的情况下向你的 API 发起请求。如果认证靠 Cookie,浏览器会自动带上,服务端无法区分请求来源。 ## 关键判断:你的认证方式决定防护策略 ### Token 认证(JWT / OAuth)— 天然免疫 Token 放在 `Authorization` 头中,浏览器不会自动发送,攻击者无法在跨站请求中携带。这是 REST API 最推荐的认证方式: ```javascript // 服务端验证 function authenticateJWT(req, res, next) { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) return res.status(401).json({ error: "未提供认证令牌" }); try { req.user = jwt.verify(token, JWT_SECRET); next(); } catch { res.status(401).json({ error: "令牌无效或已过期" }); } } ``` 需要注意:Token 存储在 localStorage 有 XSS 风险,存 HttpOnly Cookie 又会回到 CSRF 问题。生产中推荐短期 Token + 内存存储,刷新 Token 放 HttpOnly Cookie 并配合 CSRF 防护。 ### Cookie 认证 — 必须防护 Cookie 认证在前后端分离架构中仍然常见,尤其是需要与旧系统兼容时。这种场景下 CSRF 防护不可省略。 ## Cookie 认证下的四种防护手段 ### 手段一:SameSite Cookie 属性 最简单的第一道防线,设置 `SameSite` 限制跨站 Cookie 发送: ```javascript app.use(session({ secret: process.env.SESSION_SECRET, cookie: { httpOnly: true, secure: true, sameSite: "strict" // 严格模式,完全禁止跨站发送 } })); ``` `strict` 最安全但会影响从外部链接进入的体验,`lax` 是更常见的折中选择——允许 GET 请求的顶级导航携带 Cookie,阻止跨站 POST 请求。 **局限**:并非所有浏览器都完整支持 SameSite(虽然主流浏览器已跟上),且移动端 WebView 行为不一致。不能作为唯一防线。 ### 手段二:CSRF Token + 自定义请求头 这是最经典的防护方案,双重验证确保请求来源可信: ```javascript // 生成并下发 CSRF Token app.get("/api/csrf-token", (req, res) => { const token = crypto.randomBytes(32).toString("hex"); req.session.csrfToken = token; res.json({ csrfToken: token }); }); // 验证:通过自定义请求头传递 Token function csrfGuard(req, res, next) { // 安全方法跳过 if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next(); const headerToken = req.headers["x-csrf-token"]; if (!headerToken || headerToken !== req.session.csrfToken) { return res.status(403).json({ error: "CSRF 验证失败" }); } next(); } ``` 前端配合: ```javascript // 页面初始化时获取 Token,后续请求放在自定义头中 const csrfToken = await fetch("/api/csrf-token").then(r => r.json()); await fetch("/api/transfer", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken.csrfToken }, body: JSON.stringify({ to: "user123", amount: 100 }) }); ``` **为什么用自定义头而非表单字段**:跨域请求中浏览器不允许自定义头,攻击者无法通过表单或 iframe 伪造带 `X-CSRF-Token` 头的请求。这比把 Token 放在请求体中更可靠。 ### 手段三:Origin / Referer 头验证 作为辅助手段,验证请求来源是否合法: ```javascript function originGuard(req, res, next) { if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next(); const origin = req.headers.origin || req.headers.referer; const allowed = ["https://example.com", "https://app.example.com"]; if (!origin || !allowed.some(o => origin.startsWith(o))) { return res.status(403).json({ error: "请求来源非法" }); } next(); } ``` **注意**:Origin 头在某些旧浏览器中可能缺失,Referer 可能被隐私策略截断。只能作为补充,不能作为唯一防线。 ### 手段四:CORS 严格配置 CORS 配置不当会直接暴露 API: ```javascript app.use(cors({ origin: (origin, callback) => { const allowed = ["https://example.com", "https://app.example.com"]; // 移动端请求可能无 origin,需要其他认证方式保障 if (!origin || allowed.includes(origin)) { callback(null, true); } else { callback(new Error("CORS 拒绝")); } }, credentials: true, // 允许携带 Cookie methods: ["GET", "POST", "PUT", "DELETE"], allowedHeaders: ["Content-Type", "Authorization", "X-CSRF-Token"] })); ``` **关键点**:`credentials: true` 时 `origin` 不能用 `*`,必须明确指定。`Access-Control-Allow-Headers` 必须包含 `X-CSRF-Token`,否则浏览器会阻止预检请求通过。 ## 混合客户端场景的分层防护 实际项目中,API 往往同时服务浏览器和移动端,认证方式混合: ```javascript function layeredAuth(req, res, next) { const authHeader = req.headers.authorization; if (authHeader?.startsWith("Bearer ")) { // JWT 认证 — 移动端/第三方,无需 CSRF 防护 try { req.user = jwt.verify(authHeader.slice(7), JWT_SECRET); return next(); } catch { return res.status(401).json({ error: "令牌无效" }); } } // Cookie 认证 — 浏览器端,需要 CSRF 防护 if (req.session?.userId) { if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next(); const csrfToken = req.headers["x-csrf-token"]; if (!csrfToken || csrfToken !== req.session.csrfToken) { return res.status(403).json({ error: "CSRF 验证失败" }); } req.user = { id: req.session.userId }; return next(); } res.status(401).json({ error: "需要身份认证" }); } ``` 核心原则:**根据认证方式决定是否启用 CSRF 防护,Token 认证跳过,Cookie 认证必检。** ## 面试回答要点 面试中回答这个问题,抓住三个层次: 1. **先说判断依据**:REST API 是否需要 CSRF 防护,取决于认证方式。Token 认证天然免疫,Cookie 认证必须防护。 2. **再说防护手段**:SameSite Cookie 是基线,CSRF Token + 自定义请求头是核心,Origin 验证和 CORS 配置是补充,多层组合最可靠。 3. **最后说实践**:混合客户端场景下,按认证方式分层处理,Token 走 JWT 验证、Cookie 走 CSRF 校验,不要一刀切。
服务端5月28日 06:53
CSRF 防护的性能影响有哪些,如何进行优化?CSRF 防护在生产环境中确实会引入性能开销,但合理的架构设计可以在安全与性能之间取得平衡。理解开销来源并采用分层优化策略,是高并发场景下的关键能力。 ## CSRF 防护的性能开销来源 ### Token 生成与验证 CSRF Token 的生成依赖加密安全随机数生成器(CSPRNG)。以 Node.js 为例,`crypto.randomBytes(32)` 单次调用约 0.02ms,万次批量生成耗时约 234ms,即每秒可生成约 4 万个 Token。单次开销极低,但在 QPS 超过 5000 的高并发场景下,Token 生成会成为不可忽视的 CPU 消耗点。 Token 验证的开销取决于存储方式。纯字符串比对耗时微乎其微,但涉及数据库查询时,每次验证需要 10-50ms 的 I/O 延迟。在请求量大的写接口上,这意味着数据库连接池容易被占满。 ### 会话加载与存储访问 传统 CSRF 防护要求在每个状态变更请求中加载会话,验证 Token 是否匹配。Spring Security 5 及更早版本默认在每次请求时加载 CsrfToken,即使该请求不需要 CSRF 校验(如 GET 请求)。Spring Security 6 已改为延迟加载(Deferred CsrfToken),仅在需要时才访问会话存储,显著降低了不必要的开销。 ### 页面缓存失效 CSRF Token 是用户级别且动态生成的,包含 Token 的页面无法被 CDN 或反向缓存。这是常被忽视的性能影响——一个本可以命中缓存的页面,因为嵌入了 CSRF Token 而必须回源渲染。Cloudflare 的技术分析指出,CSRF Token 是页面级缓存最大的阻碍之一,尤其对于包含表单的页面。 ## 核心优化策略 ### 策略一:选择合适的 Token 存储方案 | 存储方式 | 读延迟 | 写延迟 | 扩展性 | 适用场景 | |---------|--------|--------|--------|---------| | 内存 | <1ms | <1ms | 低 | 单实例、低流量 | | Redis | 1-5ms | 1-5ms | 高 | 分布式系统、高流量 | | 数据库 | 10-50ms | 10-50ms | 中 | 简单应用、低流量 | 生产环境推荐 Redis + 本地内存二级缓存。本地缓存命中时延迟 <0.1ms,未命中时回退到 Redis,兼顾性能与分布式一致性: ```javascript class CachedTokenService { constructor(redisClient) { this.redis = redisClient; this.localCache = new Map(); this.localTTL = 300000; // 5 分钟本地缓存 this.maxLocalSize = 10000; } async getToken(userId) { // L1: 本地内存 const cached = this.localCache.get(userId); if (cached && Date.now() - cached.ts < this.localTTL) { return cached.token; } // L2: Redis const redisToken = await this.redis.get(`csrf:${userId}`); if (redisToken) { this._setLocal(userId, redisToken); return redisToken; } // L3: 生成新 Token const token = crypto.randomBytes(32).toString('hex'); await this.redis.setex(`csrf:${userId}`, 3600, token); this._setLocal(userId, token); return token; } _setLocal(userId, token) { if (this.localCache.size >= this.maxLocalSize) { const oldest = this.localCache.keys().next().value; this.localCache.delete(oldest); } this.localCache.set(userId, { token, ts: Date.now() }); } } ``` ### 策略二:使用 HMAC 无状态 Token 消除存储开销 传统 Token 需要服务端存储,每次验证都访问存储层。HMAC-based Token 将签名嵌入 Token 本身,验证时只需重新计算签名比对,无需任何存储访问: ```javascript const crypto = require('crypto'); class HMACTokenService { constructor(secret) { this.secret = secret; } // 生成:sessionId + 时间戳 + HMAC签名 generate(sessionId) { const timestamp = Math.floor(Date.now() / 3600000); // 按小时粒度 const payload = `${sessionId}:${timestamp}`; const signature = crypto .createHmac('sha256', this.secret) .update(payload) .digest('hex'); return `${payload}:${signature}`; } // 验证:重新计算签名比对,无需存储 validate(token, sessionId) { const [sid, timestamp, signature] = token.split(':'); if (sid !== sessionId) return false; const payload = `${sid}:${timestamp}`; const expected = crypto .createHmac('sha256', this.secret) .update(payload) .digest('hex'); // 检查当前小时和上一个小时的签名,容忍 Token 在小时边界附近生成 const currentHour = Math.floor(Date.now() / 3600000); if (parseInt(timestamp) < currentHour - 1) return false; return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } } ``` HMAC Token 的优势在于验证延迟从 1-50ms(存储访问)降到 <0.1ms(纯计算),且无需维护 Token 存储的过期清理。Spring Security 的 `CookieCsrfTokenRepository` 和 Django 的 `django.middleware.csrf` 都支持类似的签名验证模式。缺点是 Token 无法主动撤销,需依赖较短的有效期。 ### 策略三:避免不必要的 Token 生成 关键原则:安全请求(GET、HEAD、OPTIONS)不需要 CSRF Token 验证。确保框架配置仅对状态变更请求启用校验。 Spring Security 6 的 `CsrfToken` 延迟加载机制值得借鉴——Token 对象在请求处理链中懒初始化,仅当实际读取或校验时才触发生成和存储访问。Django 的 `{% csrf_token %}` 模板标签也有类似优化——仅在模板实际渲染该标签时才从会话中读取或生成 Token,GET 请求访问不含表单的页面时完全不触发 Token 逻辑。 ### 策略四:解耦页面缓存与 Token 渲染 将 CSRF Token 从缓存的 HTML 中剥离,通过独立接口按需获取: ```html <!-- 可被 CDN 缓存的 HTML 模板 --> <form action="/transfer" method="POST"> <input type="hidden" name="_csrf" value="" id="csrfInput"> <!-- 其他表单字段 --> </form> <script> // 页面加载后异步获取 Token fetch('/api/csrf-token', { credentials: 'same-origin' }) .then(r => r.json()) .then(data => { document.getElementById('csrfInput').value = data.token; }); </script> ``` 这样页面主体可以被 CDN 缓存,Token 通过轻量 API 单独获取。另一种方案是使用 ESI(Edge Side Includes),在 CDN 边缘节点将 Token 片段注入缓存的页面,Nginx 和 Varnish 均支持此特性。 ### 策略五:优先采用免 Token 方案 现代浏览器提供了不依赖 Token 的 CSRF 防护手段,可以显著降低服务端开销: **SameSite Cookie 属性**:将 Cookie 设置为 `SameSite=Strict` 或 `SameSite=Lax`,浏览器自动阻止跨站请求携带 Cookie。Chrome 80+ 默认 `SameSite=Lax`,覆盖了大部分 CSRF 攻击场景。这是目前成本最低的防护方式,OWASP 将其列为推荐的 CSRF 防御手段之一。 **Fetch Metadata 请求头**:Chrome 和 Firefox 支持的 `Sec-Fetch-Site`、`Sec-Fetch-Mode` 等头,服务端可据此判断请求来源: ```javascript function isSafeRequest(req) { const site = req.headers['sec-fetch-site']; const mode = req.headers['sec-fetch-mode']; if (site === 'same-origin') return true; if (site === 'none' && mode === 'navigate') return true; return false; } ``` **Origin / Referer 头校验**:对于有 Origin 头的请求,验证其值是否在白名单内。这种方法无需 Token 存储,开销几乎为零。 **组合策略实践**:生产环境推荐 SameSite Cookie 作为基础防护层,对安全等级更高的操作(如支付、转账)叠加 Token 验证。这样大部分普通请求的 CSRF 防护零开销,仅关键路径承担 Token 成本。 ## 性能监控指标 生产环境需关注以下 CSRF 相关指标: - **Token 生成耗时**:P99 应 <5ms,超出则检查 CSPRNG 实现 - **Token 验证耗时**:含存储访问时 P99 应 <10ms,HMAC 模式应 <0.5ms - **缓存命中率**:本地缓存命中率 >90% 为健康,低于 70% 需扩大缓存容量 - **会话加载频率**:对比总请求数与 CSRF 校验请求数,比值过高说明延迟加载未生效 ## Token 长度与安全性的平衡 | 配置 | 长度 | 熵 | 性能 | 适用场景 | |-----|------|-----|------|---------| | minimal | 16 字节 | 64 bit | 最优 | 性能敏感、已有其他防护层 | | balanced | 32 字节 | 128 bit | 良好 | 通用场景(推荐) | | secure | 64 字节 | 256 bit | 可接受 | 安全等级最高的场景 | 128 bit 熵(32 字节 hex)是绝大多数场景的最佳选择——碰撞概率可忽略,性能影响极小。 ## 追问:CSRF 防护和 CORS 是什么关系? CSRF 和 CORS 解决的是不同层面的跨域问题。CORS 控制的是浏览器是否允许读取跨域响应,CSRF 控制的是浏览器是否自动携带凭据发起跨域请求。一个请求可能被 CORS 阻止但仍然构成 CSRF 风险(如 form 表单提交不受 CORS 约束)。SameSite Cookie 同时减少了 CSRF 和 CORS 的攻击面,但不互为替代。正确做法是同时配置 CORS 白名单和 CSRF 防护,两者互补而非互斥。
服务端5月27日 20:31
如何检测和记录 CSRF 攻击,有哪些监控策略?CSRF 攻击的检测依赖对请求来源和行为的异常识别,核心监控策略包括 Referer/Origin 校验、Token 失效记录、请求频率异常检测和日志溯源分析。 ## 检测 CSRF 攻击的三个关键信号 **1. 请求头异常**——非 GET 请求缺失 Referer 或 Origin 与目标域名不匹配,是最直接的攻击特征。Referer Check 不能作为唯一防御手段,但作为检测指标非常有效。 **2. Token 校验失败**——服务端验证 CSRF Token 不通过时,记录该请求的来源 IP、User-Agent、时间戳和目标路径。高频 Token 失败通常意味着定向攻击。 **3. 行为模式异常**——同一用户短时间内发起大量状态变更请求,或请求来源 IP 与历史地理位置不匹配(如1小时内跨1000公里),需要结合业务场景判断。 ## 监控策略怎么落地 - **中间件层拦截+记录**:在 CSRF Token 校验中间件中,校验失败时同步写入结构化日志,字段包括 eventType(detected/blocked)、confidence(置信度)、patterns(匹配的异常类型) - **Referer/Origin 白名单监控**:维护合法来源域名列表,非白名单来源的写操作请求触发告警 - **频率阈值告警**:对同一 IP 或用户 ID,10秒内超过20次写请求触发异常标记,配合 Prometheus + Alertmanager 推送 - **日志聚合分析**:按时间窗口统计攻击趋势、Top 攻击 IP、高频异常模式,生成日报辅助策略调优 ## 面试追问 **Q: 为什么 Referer Check 不能单独做防御?** Referer 可被用户隐私设置或浏览器策略省略,正常请求也可能缺失 Referer,单独依赖会误拦合法请求。但它作为检测信号仍然有效——有 Referer 但域名不匹配,基本可以判定可疑。 **Q: SameSite Cookie 能完全替代 CSRF Token 吗?** 不能。SameSite=Lax 只保护跨站顶级导航的 POST 请求,同站内的 XSS 配合 CSRF 仍可绕过。此外老旧浏览器不支持 SameSite 属性。两者应该组合使用。
服务端5月27日 20:29
CSRF Token 是如何工作的?CSRF Token 是服务端生成并下发给客户端的随机值,客户端提交请求时必须携带,服务端校验通过才放行。攻击者因受同源策略限制无法读取页面中的 Token,因此无法伪造合法请求。 ## CSRF Token 如何防御攻击 浏览器同源策略阻止恶意页面读取目标站点的响应内容,攻击者拿不到页面里嵌入的 Token 值,构造的请求自然无法通过服务端校验。Token 防御的本质就是利用同源策略制造信息不对称——你有 Cookie 我也有,但我有你没有的 Token。 ## 完整工作流程 服务端在会话中生成至少 128 位的加密安全随机 Token,通过表单隐藏字段或自定义请求头(如 `X-CSRF-Token`)下发给客户端。请求提交时客户端携带 Token,服务端比对该值与会话中存储的是否一致,一致则放行,否则拒绝。 ## 两种主流实现模式 **同步器 Token 模式**——Token 存在服务端会话中,客户端通过表单隐藏字段提交。Django 的 `{% csrf_token %}` 和 Express 的 csurf 中间件都用这种模式。 **双重提交 Cookie 模式**——Token 同时写入 Cookie 和请求参数/请求头,服务端比对两者是否一致。好处是服务端不用存 Token,适合无状态架构。但必须给 Cookie 加 `SameSite` 属性,防止被恶意页面读取。 ## 追问:SameSite Cookie 能否替代 CSRF Token? 不能完全替代。`SameSite=Strict` 会阻止所有跨站请求,从外部链接点进来直接 403,体验差;`SameSite=Lax` 允许顶级导航的 GET 请求,攻击面仍在。加上旧浏览器和移动端 WebView 对 SameSite 支持不一致,实际部署中推荐两者配合:SameSite 做基础防护,Token 做关键操作的强校验。 ## 追问:SPA 中如何处理 CSRF Token? 前端通过接口获取 Token 后缓存,在 axios 拦截器里自动注入请求头。注意两个坑:会话过期或服务端返回 403 时必须重新获取 Token;不要把 Token 存 localStorage,XSS 一炸就全完了,建议用 HttpOnly Cookie 配合自定义请求头的双重提交方案。 ## 追问:微服务架构下 Token 如何共享? 两条路:一是统一网关集中生成和校验 Token,会话状态存 Redis,各微服务从缓存读;二是用 HMAC 签名的加密 Token,服务端不存状态,直接验签就行,更适合无状态服务。选哪种取决于你的架构是否已经依赖分布式缓存。
服务端5月27日 20:27
CSRF 攻击有哪些绕过手法?如何逐一防范?CSRF 绕过的核心思路是:让服务端"误以为"伪造请求合法。常见手法分五类——窃取/预测 Token、利用 SameSite 配置失误、伪造或缺失 Referer、子域名攻击双重 Cookie、以及利用 JSONP/DNS Rebinding 等协议特性。防御关键是**多层叠加**:SameSite=Strict + 服务端 Token 校验 + Origin 白名单,任一层被绕过仍有兜底。 ## 绕过 CSRF Token **Token 泄露**:站点若存在 XSS 漏洞,攻击者可通过 `document.querySelector` 直接读取页面中的 Token 并外发。这是 Token 防护最大的短板——XSS 一旦存在,CSRF Token 形同虚设。 **Token 可预测**:部分框架用时间戳或弱随机数生成 Token,攻击者拿到一两个样本就能推算规律。必须使用密码学安全的随机生成器(如 `crypto.randomBytes`),Token 长度不少于 128 位。 **Token 删除测试**:实际渗透中,直接删掉请求中的 Token 参数,约 40% 的应用仍会放行。服务端必须校验 Token 存在且匹配,而非仅校验"若存在则匹配"。 > 追问:如果站点同时有 XSS 和 CSRF Token,Token 还有用吗?——没用。XSS 能窃取 Token,此时应优先修复 XSS,同时用 SameSite Cookie 作为第二道防线。 ## 绕过 SameSite Cookie **SameSite=None 配置失误**:`SameSite=None` 必须配合 `Secure` 属性,否则现代浏览器会拒绝设置。不少开发者只写了 `SameSite=None` 却漏掉 `Secure`,等于白设。 **SameSite=Lax 的 GET 绕过**:Lax 模式允许顶级导航的 GET 请求携带 Cookie。若接口接受 GET 方法修改数据(如 `GET /api/delete?id=1`),攻击者用 `<img>` 或 `<a>` 标签即可触发。 **子域名与站内跳转**:同站(Same-Site)的判定基于注册域名,子域名间的请求属于同站。攻击者若控制了子域名的 XSS,仍可在同站上下文中发起请求绕过 SameSite。 ## 绕过 Referer/Origin 校验 **Referer 为空**:HTTPS→HTTP 降级、隐私插件、书签访问等场景下 Referer 为空。若服务端逻辑是"Referer 为空则放行",等于开了后门。正确做法是拒绝空 Referer 的状态变更请求。 **Referer 校验不严**:只判断 `referer.includes("example.com")` 时,`evil-example.com` 也能通过。必须校验完整 Origin,且用白名单而非黑名单。 ## 绕过双重提交 Cookie 双重提交的原理是 Cookie 和请求参数中各放一份 Token,服务端比对两者一致。但若 Cookie 设在父域名(`domain: .example.com`),子域名的 XSS 可以写入伪造值,使 Cookie 和参数中的 Token 都由攻击者控制。 ## 协议级绕过 **JSONP 端点**:JSONP 的 `callback` 参数允许攻击者指定函数名,脚本加载时浏览器自动带上 Cookie,天然绕过 Token 校验。应废弃 JSONP,改用 CORS。 **DNS Rebinding**:攻击者控制 DNS 使域名先解析到恶意 IP(满足同源策略),再解析到目标 IP 发起请求。防御手段是校验 Host 头、启用 DNSSEC。 ## 防护 Checklist | 防护层 | 要点 | |---|---| | Token | 密码学随机生成、服务端强制校验存在性、绑定会话 | | Cookie | `SameSite=Strict`、`HttpOnly`、`Secure`、精确域名 | | Origin | 白名单校验、拒绝空 Origin | | 架构 | 废弃 JSONP、状态变更接口仅接受 POST、子域名隔离 | 多层防护是唯一可靠策略,任一层被突破时其他层仍能拦截。安全没有银弹,但叠加防御能显著提高攻击成本。