Appearance
CSP、Cookie 安全属性与 HTTPS 防中间人攻击
难度等级:🟡 中级(2-4年)
分类:浏览器安全
归档日期:2026-09-03
📚 理论知识
一、CSP(Content-Security-Policy)是什么,怎么配置
定义:CSP 是一个响应头(也可以用 <meta> 标签声明),用来告诉浏览器"这个页面允许从哪些来源加载/执行哪些类型的资源"——脚本、样式、图片、字体、iframe、Ajax 请求目标等都可以分别控制。浏览器会强制执行这份策略,凡是不在白名单里的资源加载或脚本执行,直接拦截并在控制台报错。
常见核心指令:
default-src:兜底策略,没有单独指定的资源类型都用这个。script-src:允许加载和执行脚本的来源。style-src:允许加载样式的来源。img-src、font-src、connect-src(Ajax/WebSocket 请求目标):同理。frame-ancestors:控制这个页面允许被哪些来源的页面用 iframe 嵌入,是点击劫持防御的现代方案(比X-Frame-Options更灵活)。object-src 'none':禁止<object>、<embed>这类容易被用来加载 Flash/插件类恶意内容的标签。
为什么 unsafe-inline 很危险:script-src 里如果加了 'unsafe-inline',意味着页面里所有内联 <script> 标签和内联事件处理器(onclick="..." 这类)都被允许执行。一旦页面存在 XSS 漏洞,攻击者注入的往往就是一段内联脚本——如果 CSP 允许内联脚本执行,那么"阻止未授权脚本执行"这一层防线对内联注入的攻击代码完全不起作用,CSP 针对 XSS 的防御价值就被削弱了大半。
nonce / hash 机制:为了在"必须要有一些内联脚本"(很多框架的 SSR 场景)和"防止攻击者注入的内联脚本执行"之间兼顾,CSP 支持每次请求由服务端生成一个随机 nonce 值,写入响应头的 CSP 策略里,同时写在允许执行的 <script nonce="xxx"> 标签属性上,两者匹配浏览器才会执行。因为 nonce 是每次请求随机生成、攻击者猜不到,所以攻击者即使成功注入了 <script> 标签,也拿不到正确的 nonce,脚本不会被执行。hash 机制类似,是把允许执行的内联脚本内容算出哈希写进策略。
CSP 能防住什么:主要是 XSS(阻止执行未授权来源/未授权方式的脚本)、点击劫持(frame-ancestors)、以及部分数据泄露风险(限制页面能够连接、上报数据的目标域名)。
二、Cookie 的安全属性
httpOnly:禁止 JS 通过document.cookie读取这个 Cookie。核心目的是防 XSS——即使页面被注入了恶意脚本,脚本也读不到这个 Cookie 把它发送出去。secure:这个 Cookie 只允许在 HTTPS 连接下被传输,HTTP 明文连接下浏览器不会附带这个 Cookie,防止在中间人能窃听的网络环境下(比如公共 WiFi)被明文抓包获取。SameSite:核心目的是防 CSRF。Strict:完全禁止跨站请求携带这个 Cookie,包括跨站的顶层导航跳转。Lax:跨站请求默认禁止携带,但对"跨站顶层导航的 GET 请求"(比如从别的网站点链接跳转过来)放行,是目前主流浏览器的默认值,在安全和"点链接跳转后依然保持登录态"的体验之间做了折中。None:允许所有跨站请求携带,但规范要求必须同时设置secure,否则浏览器会拒绝这个 Cookie。
三者组合:httpOnly + secure + SameSite=Strict 是安全性最高的组合,通常用于像 refreshToken 这类长期敏感凭证(参见 [[jwt-token]] 里的相关讨论);而需要被前端 JS 读取用于业务逻辑的 Cookie 就没法加 httpOnly,这时候更依赖 secure 和 SameSite 兜底。
三、HTTPS 如何防止中间人攻击
TLS 握手的基本思路:先用非对称加密(公钥/私钥)协商出一个只有通信双方知道的对称密钥,之后正式的数据传输改用这个对称密钥加密——非对称加密安全性高但计算开销大,对称加密效率高但密钥必须提前安全共享,这一套握手流程正是用非对称加密解决"怎么安全地共享对称密钥"这个先有鸡还是先有蛋的问题。
证书体系怎么防冒充:服务器的公钥由受信任的 CA(证书颁发机构)签发数字证书做背书,浏览器内置了一份受信任 CA 的列表。中间人如果想冒充服务器,除非它能拿到该域名对应、由受信任 CA 签发的合法证书(正常情况下拿不到),否则浏览器验证证书链时会直接发现证书不匹配或不可信,拒绝建立连接并显示警告。这解决的是"你连接的这个公钥,确实属于它声称的那个域名"这个信任问题。
没有 HTTPS 会怎样:如果是纯 HTTP,攻击者只要处在网络链路的中间位置(公共 WiFi、被劫持的路由器等),就能直接窃听或篡改明文流量,比如把页面里的下载链接替换成恶意程序、把登录表单的提交地址改成自己的服务器。HTTPS 下,攻击者即使成功拦截了流量,没有私钥也无法解密内容,篡改也会被完整性校验(消息认证码)发现并导致连接失败。
Mixed Content(混合内容)问题:一个 HTTPS 页面里如果引入了 HTTP 协议的资源(比如 <script src="http://...">),这部分资源依然是明文传输的,等于在一个"安全的房子"里开了一扇没锁的窗——现代浏览器对这类混合内容默认会拦截(尤其是脚本类资源)或至少给出警告,因为它破坏了整个页面的安全边界。
常见误区
| 问题 | 要点 |
|---|---|
| 认为 CSP 只能防 XSS | CSP 除了防 XSS,frame-ancestors 还能防点击劫持,connect-src/img-src 限制还能一定程度限制数据被恶意上报到未授权域名。 |
认为设置了 httpOnly 就不需要再考虑 CSRF | 不对,httpOnly 只解决"读不到 Cookie",不解决"浏览器还是会自动带上这个 Cookie 发跨站请求",CSRF 防御要靠 SameSite 或 Token,二者要分开考虑。 |
| 认为 HTTPS 只是"给数据加了个密",跟身份验证没关系 | 不准确,HTTPS 除了加密传输内容,证书体系同样承担"验证对方身份"的作用,这一步和加密同样重要,纯加密没有身份验证依然可能遭遇中间人用自己的密钥冒充服务器。 |
认为 SameSite=None 就是"不安全,不该用" | 不绝对。SameSite=None 有其合理场景,比如需要跨站嵌入的第三方登录组件、支付组件,这时业务本身就需要跨站携带 Cookie,只要配合 secure 和其他鉴权手段(Token)就是可控的。 |
❌ 错误答法示范
错误示例 1:「CSP 配置了 script-src 'self' 之后,就算网站有 XSS 漏洞也完全不用担心了。」
❌ 问题:过于绝对。CSP 是纵深防御的一层,不是万能解药——如果站内本身存在允许上传/托管用户可控 JS 文件的功能,攻击者依然可以把恶意脚本作为"自己域名下的资源"绕过 'self' 限制;CSP 应该配合输入过滤、输出转义等其他手段一起用,不能替代对 XSS 漏洞本身的修复。
错误示例 2:「用了 HTTPS 就不用担心 Cookie 被窃取了,所以 httpOnly 不是必须的。」
❌ 问题:混淆了两种完全不同的威胁。HTTPS 防的是"传输过程中被中间人窃听",httpOnly 防的是"页面被 XSS 注入后,恶意脚本在浏览器本地直接读取 Cookie"——后者跟传输链路是否加密完全无关,就算全程 HTTPS,没有 httpOnly 的 Cookie 一样能被注入的脚本原地读走。
🎤 模拟面试作答
这道题我分 CSP、Cookie 安全属性、HTTPS 防中间人三部分说。
CSP 是一个响应头,声明页面允许从哪些来源加载和执行资源,比如 script-src、style-src、img-src,还有 frame-ancestors 用来控制页面能不能被别的网站用 iframe 嵌入。它主要防的是 XSS——就算代码有疏漏被注入了脚本,只要这个脚本的来源不在白名单里,浏览器也会拒绝执行。这里有个关键细节,如果 script-src 里加了 unsafe-inline,等于允许所有内联脚本执行,攻击者注入的往往就是内联脚本,这条防线基本就废了。更安全的做法是用 nonce 机制,服务端每次请求生成一个随机值,写进 CSP 头和允许执行的 script 标签里,两者匹配才执行,攻击者猜不到这个随机值,注入的脚本自然执行不了。
Cookie 的安全属性,httpOnly 让 JS 读不到这个 Cookie,防的是 XSS 场景下脚本把 Cookie 偷走;secure 让 Cookie 只能在 HTTPS 下传输,防的是明文抓包;SameSite 控制跨站请求要不要带上这个 Cookie,Strict 最严格完全不带,Lax 是现在浏览器默认值,对跨站导航的 GET 放行,防的主要是 CSRF。这三个属性防的是三类完全不同的风险,一般敏感凭证像 refreshToken 会三个一起上。
HTTPS 防中间人,核心是先用非对称加密握手协商出一个对称密钥,之后的数据传输用这个对称密钥加密,兼顾安全性和性能。而防冒充靠的是证书体系,服务器的公钥要有受信任 CA 签发的证书背书,浏览器内置了受信任的 CA 列表,如果中间人想冒充服务器,除非它能拿到这个域名对应的合法证书,否则浏览器验证证书链的时候就会直接报警。这里加密解决的是内容不被窃听篡改,证书解决的是身份不被冒充,两者缺一不可。
如果面试官感兴趣,我还可以聊聊混合内容(Mixed Content)具体是怎么被浏览器拦截的,或者展开讲讲 CSP 的 hash 机制和 nonce 机制的区别。
❓ 面试官追问预测
追问 1:CSP 是纯前端能搞定的,还是必须要后端配合?
应对思路:必须后端配合。CSP 本质是一个 HTTP 响应头(
Content-Security-Policy),虽然也能用<meta>标签在 HTML 里声明,但<meta>方式功能受限(不能设置frame-ancestors等部分指令),更完整、更安全的方式是由后端在响应头里统一下发,而且如果用 nonce 机制,nonce 值必须是服务端每次请求动态生成、同时写进响应头和 HTML 里,前端单方面做不到。
追问 2:SameSite=Lax 对跨站导航的 GET 请求放行,这个"例外"本身会不会被 CSRF 利用?
应对思路:理论上有限利用空间,但风险比
SameSite=None小很多——因为这个例外只针对"用户主动点击链接产生的顶层页面跳转"这种场景,攻击者没法用它做隐蔽的自动提交表单或 Ajax 请求(这些跨站请求依然不带 Cookie),能利用的场景局限在"诱导用户点击一个 GET 请求就能触发敏感操作"的接口设计上,所以配合"敏感操作只用 POST、不用 GET"这条原则,Lax 的风险基本可控。
追问 3:证书体系里,如果 CA 机构本身被攻破或者签发了恶意证书,HTTPS 还安全吗?
应对思路:这种情况下 HTTPS 的信任链会被破坏,是真实发生过的安全事件(历史上有 CA 被攻破签发过恶意证书的案例)。业界的应对手段包括证书透明度(Certificate Transparency,要求所有签发的证书公开可查、可审计)、HPKP/证书固定(Certificate Pinning,App 内置只信任特定证书或公钥,绕开对整个 CA 体系的信任)等,但这些更多是纵深防御,说明 HTTPS 的安全性最终还是建立在"CA 体系整体可信"这个假设之上。
追问 4:httpOnly + SameSite=Strict 已经很安全了,为什么很多项目还要额外加 CSRF Token?
应对思路:
SameSite=Strict是客户端行为,依赖浏览器版本支持和正确配置,老旧浏览器或者一些特殊场景(比如浏览器插件、部分内嵌 WebView)可能不完全遵守;而且如果站内本身存在 XSS 漏洞,攻击者的脚本运行在同源上下文里,SameSite限制的是"跨站"请求,同源发出的请求依然会正常带 Cookie,这时候 CSRF Token(如果实现为不通过 Cookie 传递)能提供额外一层独立于 Cookie 机制之外的校验。多层防御是为了不把安全性押注在单一机制上。