4.2 Ajax 安全性


4.2 Ajax 安全性

本节摘要:Ajax 把浏览器与服务器之间的数据通道打开得更大,攻击面也随之扩大。本节攻两大主题:跨域——同源策略在防什么、CORS 预检的完整往返过程、服务端该怎么配;注入与伪造——XSS 如何借数据流进入页面、输出编码与白名单净化怎么设防、CSRF 为什么能借你的 Cookie 干坏事、Token 与 SameSite 如何堵住它。

两个攻击现场

现场一(跨域误会):本地开发时页面跑在 5173 端口、接口在 8000 端口,控制台一片红:跨域请求被同源策略拦截。新手的第一反应是"浏览器多管闲事",想找开关关掉它。这个反应方向完全错了——同源策略不是障碍,是你正受其保护的证据。

现场二(XSS 注入):评论区有用户提交了内容为一个图片标签加报错事件属性的留言。列表页用 innerHTML 直接渲染所有评论——每个浏览评论的用户,会话凭证就被这段脚本发走了。存储型 XSS 的完整链条:恶意输入 → 存进数据库 → 借 Ajax 响应回到页面 → 以页面身份执行。

理解这两个现场需要的背景知识是同一块:浏览器给页面划的安全边界,以及Ajax 数据流如何与这条边界交互

CORS 预检的完整往返

跨域请求的预检协商流程

跨域请求的预检协商流程

一、同源策略与 CORS:受控的开放

同源的定义三件套:协议、域名、端口全相同。https与http 不同源,a站与b站 不同源,8000与8001 端口不同源。同源策略规定:一个源里的脚本,默认不能读取另一个源的响应。这条策略防的是现场一背后真正的威胁:你登录了银行网站,此时访问了恶意页面,那个页面的脚本向银行接口发请求——没有同源策略,浏览器会自动带上你的银行 Cookie,响应也能被恶意脚本读到,你的余额与转账能力就归它了。

CORS(跨域资源共享)是这条铁律的受控开口:由服务端通过响应头声明"我信任哪个源"。关键认知有三条。

**其一,拦的是读不是发。**跨域请求实际发出去了、服务器也可能处理了,浏览器只是把响应藏起来不给脚本。这解释了一个经典困惑:"日志里怎么有这个跨域请求,前端不是说被拦了吗"——被拦的是读取,不是到达。

其二,预检是先问后做。"简单请求"(GET、POST 表单等少数方法加安全头)直接发,响应里带允许源即可;带自定义头(Authorization)、非简单方法(PUT、DELETE)、或 JSON 体的请求会先触发 OPTIONS 预检——浏览器先问"这个源、这些方法与头,行不行",服务端答"行"才发正式请求。预检结果可以缓存(服务端声明缓存时长),别让它每次都来一轮。

其三,凭证是另一档信任。默认跨域不带 Cookie。要带就必须双端声明:前端 credentials: 'include'(Fetch)或 withCredentials = true(XHR),服务端回显具体源并声明允许凭证——通配符星号与凭证互斥。服务端配置示例:

// 服务端 CORS 配置示意 const allowedOrigins = ['https://app.example.com', 'https://admin.example.com']; app.use((req, res, next) => { const origin = req.headers.origin; if (allowedOrigins.includes(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); // 回显具体源 res.setHeader('Access-Control-Allow-Credentials', 'true'); // 允许凭证 res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization'); res.setHeader('Access-Control-Max-Age', '600'); // 预检缓存十分钟 } if (req.method === 'OPTIONS') return res.sendStatus(204); // 预检快速放行 next(); });

⚠️ 生产环境两个绝对禁止:通配符源加允许凭证同开(等于向所有网站开放带 Cookie 的访问,配置形同虚设);把 Origin 原样回显不做白名单校验(等于"谁来都欢迎")。排障提示:报"读了不存在的响应头"时,检查服务端是否声明了 Access-Control-Expose-Headers(2.2 节的坑)。

二、XSS:数据与代码的分界

XSS(跨站脚本)的原理一句话:攻击者让他的数据被你的页面当代码执行。Ajax 数据流是重灾区,因为接口数据最终要进 DOM。

三个注入入口对应三种堵法。

入口一:innerHTML 拼接。3.3 节的事故现场。堵法是默认 textContent——内容当纯文本显示,标签原样可见不执行。需要富文本时用白名单净化(只放行安全标签与属性),不用黑名单(列不全)。

**入口二:属性注入。**把用户数据拼进 HTML 属性(title、自定义属性)时,引号逃逸可以闭合原属性、注入事件属性。堵法是用 DOM API 的 setAttribute——浏览器负责属性的转义边界,不要手工拼属性字符串。

**入口三:URL 注入。**链接的 href 来自用户数据时,javascript: 伪协议是脚本载体。堵法是校验协议白名单(只允许 http、https、mailto 等),或干脆不渲染用户提供的链接。

纵深防御的最后两层。其一 CSP 响应头(内容安全策略):声明"本页面只允许从这些源加载脚本、禁止内联脚本"——即使注入成功,脚本也加载不了。它是"假设前面的防线都破了"之后的保险丝:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'

其二 HttpOnly Cookie:会话 Cookie 加上此标记后 JavaScript 完全读不到——就算脚本注入成功,偷凭证这条路也断了。配置在服务端 Set-Cookie 上,是一行配置换一层保险的划算买卖。

判断你的渲染层是否安全,一个快速自查:全库搜索 innerHTML、insertAdjacentHTML、document.write 与拼属性,逐处确认内容来源是可信的或净化过的。3.3 节的策略表(textContent 默认、模板加净化处理富文本)就是执行层。

三、CSRF:借你的身份干活

XSS 是在你的页面里执行代码,CSRF(跨站请求伪造)更阴险——一行你的代码都不用执行

攻击链条:你登录了银行站,会话 Cookie 还在浏览器里;你顺手点了个"中奖"页面;那个页面里有一个自动提交的表单,目标直指银行的转账接口。浏览器发这个请求时自动带上了银行站的 Cookie——在银行看来,这是"已登录的你"亲手转的账。

Ajax 时代 CSRF 的面有了变化:接口若改用 Authorization 头携带 Token(不自动附带),经典 CSRF 大部分失效——恶意页面无法读取你存在另一站的 Token,也设置不了自定义头(会触发预检且预检失败)。这正是 2.4 节推荐 Token 方案的深层理由。但仍要设防的场景:继续依赖 Cookie 鉴权的接口(老系统、需要浏览器自动携带的场合)。防线三选一或叠加:

SameSite Cookie 属性:声明 Cookie 只在同站请求携带。设为 Lax 或 Strict 后,来自别的站点的跨站请求不再自动带 Cookie——一行属性解决大半问题,现代浏览器已默认 Lax。

CSRF Token:服务端给表单或页面下发一次性随机令牌,提交时校验。恶意页面猜不到令牌,伪造失败。

校验 Origin 与 Referer:服务端检查请求来源是不是自己的站。作为兜底层与其他手段叠加。

三层的关系:SameSite 是默认开机的门锁,CSRF Token 是保险柜,Origin 校验是门口的监控——叠加使用,别单点依赖

四、把防线整理成清单

威胁 利用什么 核心防线 辅助防线
数据窃取(跨域读取) 同源策略边界 CORS 白名单由服务端配置 不开放通配符加凭证
XSS 数据被当代码执行 textContent 默认、富文本白名单净化 CSP 响应头、HttpOnly Cookie
CSRF 浏览器自动带 Cookie Token 鉴权或 SameSite 属性 CSRF Token、Origin 校验
接口滥用 接口裸奔可被脚本直调 服务端鉴权与限流 频控、验证码、行为风控

还有一条贯穿性的纪律值得单列:前端的一切校验都不是安全边界。客户端校验(3.2 节)是给用户的体验提示,攻击者绕过页面直调接口毫无门槛——每个接口必须在服务端重新校验权限与输入。同样,"接口地址保密"也不是防线,网络面板里一切裸奔。

常见疑问解答

开发环境的跨域报错怎么解决

正确姿势是配开发代理——开发服务器把接口请求转发到后端,浏览器看到的是同源请求。错误姿势是关浏览器安全策略或后端开全通配符,前者伤自己机器,后者容易带着上生产。

HTTPS 能防 CSRF 或 XSS 吗

不能。HTTPS 防的是链路上的窃听与篡改,XSS 与 CSRF 利用的分别是"代码注入"与"身份借用",都发生在加密信道正常工作的情况下。三者的防线互不替代。

Token 存在哪里比较安全

没有绝对安全的浏览器存储:localStorage 可被 XSS 读取(所以 XSS 防线必须扎实),内存变量最短命但刷新即失,HttpOnly Cookie 读不到但自动携带又回到了 CSRF 面。常见折中:短时效访问令牌放内存、长效刷新令牌放 HttpOnly Cookie——把两边的弱点错开。

本节要点回顾

  • 同源策略拦读取不拦发出,CORS 是服务端声明信任谁;预检先问后做、结果可缓存;凭证与通配符互斥。
  • XSS 的本质是数据变代码:textContent 默认、富文本白名单净化、属性走 setAttribute、URL 校验协议;CSP 与 HttpOnly 是纵深保险丝。
  • CSRF 借的是自动附带的 Cookie:Token 鉴权釜底抽薪,SameSite 属性一行解决大半,多层叠加别单点依赖。
  • 前端校验永远不是安全边界,服务端逐接口重新校验;"接口保密"不是防线。
  • 四类威胁四套防线:跨域白名单、输出净化、令牌与 Cookie 属性、服务端鉴权限流——各自防各的,不能互相顶替。

一次安全自查的执行清单

把本章内容转成一份可以在自己项目上直接执行的安检清单,建议按序走,前紧后松。第一步,渲染面扫描:全库搜索所有把数据写进 HTML 的调用点,逐处确认内容来源——可信静态片段放行、纯文本字段改 textContent、富文本路径检查净化库调用与白名单配置。第二步,头与 Cookie 配置核查:响应头里 CSP 是否声明、脚本源是否收紧;会话 Cookie 是否带 HttpOnly 与 SameSite;这两项是"一行配置一层防线"的高性价比检查。第三步,跨域配置审计:拉出所有 CORS 相关配置,确认白名单制、无通配符加凭证组合、预检缓存已配置、暴露的自定义头逐项列明。第四步,鉴权与校验抽查:随机抽五个写操作接口,用工具直调(绕过页面),验证未登录与越权场景是否被服务端拒绝——任何"前端挡住了就行"的接口都是裸的。第五步,依赖与净化库:净化库与安全相关依赖保持更新,历史上这类库的绕过漏洞出现过多次,版本停留是隐性风险。

清单执行的一个常见坑要提醒:自查容易做成"自我表扬"——检查项在脑子里过一遍就打勾。有效的做法是留下证据:每步截图或记录(搜索命中的行号清单、配置的原文、直调接口的响应),汇总成一页报告。有证据的自查才经得起复盘,也方便下次对照"上次的问题修了没"。安全工作没有"完成态",只有"最近一次检查是什么时候"。给安检定个节律——季度做一遍全清单、依赖更新时做对应单项、新功能上线前做渲染面抽查——让它像还房贷一样规律。规律感本身就是防御:攻击者找的是没人看的那扇窗,而你的目光定期扫过每一扇。安检节律之外,给每个高危改动(跨域配置、净化白名单、鉴权中间件)设置双人评审的硬门槛——这类配置一行笔误就是全站洞开,多一双眼睛的成本,远低于一次事故的学费。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U