页面被别的网站嵌进 iframe 展示时,拦截写在哪个响应头
页面被别的网站用 iframe 嵌走时,拦截写在响应头上,而不是写在页面里的一段脚本里。可用的是两条:X-Frame-Options 与内容安全策略中的 frame-ancestors 指令。前者只剩 DENY 和 SAMEORIGIN 两个可靠取值,后者才能表达「允许哪几个来源嵌」。如果需要的只是「谁都不能嵌」或「只允许同源嵌」,写前者就够;一旦要放行特定合作方,就必须用 frame-ancestors。
嵌走页面造成的不是显示问题
被嵌套的风险点在于:用户看到的界面是本站的,操作却是别人页面里布置的。合作方的推广页、聚合站、甚至伪装成正常站点的页面,都可以把登录框、留言表单放进一个看不见的外框里,把用户的点击引到本站的真实功能上。这类问题的通用叫法是点击劫持,它的成立条件很简单——页面允许被嵌入,且页面里有可交互的功能。
所以判断要不要拦,不看有没有人嵌,而看这一页有没有可交互的动作。
两条响应头各自的可用范围
X-Frame-Options 的两个可靠取值:DENY 表示文档不能在任何框架里加载,无论同源还是跨源;SAMEORIGIN 表示只有所有上级框架与本页同源时才允许嵌入。ALLOW-FROM 指定来源的写法已被废弃,规范文档里明确写着现代浏览器遇到带这个指令的响应头会完全忽略它——也就是说,一旦写了 ALLOW-FROM,整条拦截等于没加,而不是降级为只允许那个来源。
要表达「允许某些来源嵌」,需要使用内容安全策略头里的 frame-ancestors 指令,它的定义就是「哪些父页面可以用 frame、iframe、object 或 embed 嵌入本页」。文档同时提示:需要比 X-Frame-Options 更完整的选择时,应改用 frame-ancestors。
按页面类型分档的嵌入策略表
| 页面类型 | 建议档位 | 理由 |
|---|---|---|
| 后台与登录相关页面 | DENY | 只允许直接访问,任何嵌套都不合理 |
| 留言、评论等可交互表单页 | DENY 或 SAMEORIGIN | 表单在嵌套状态下最容易被借用 |
| 商品与业务办理页 | SAMEORIGIN | 允许本站内部框架,拒绝外部站点 |
| 需要对外分发的内容页 | frame-ancestors 指定来源 | 允许合作方嵌入,同时收紧来源范围 |
写策略时的常见失误是「一刀切 DENY」把合法的分发场景也关掉,或者反过来为省事写了 ALLOW-FROM 却以为已经放行白名单。
站内已有的注入防护管不到这一层
内容站通常已经把注入类风险当作基本项:AnQiCMS 内置 JWT 认证、内容敏感词过滤与防采集干扰码,用于防御 SQL 注入与 XSS 攻击;留言与评论进入发布前还有内容审核与敏感词过滤这一道。这两层处理的是「提交进来的内容能不能执行、能不能发布」。
被嵌套是相反方向的问题:内容本身是干净的,页面却出现在别人的框架里,用户与上下文都不再可控。因此它不能靠敏感词过滤或内容审核解决,只能靠响应头声明来源策略。反过来,如果站点在反向代理或服务器层统一加头,就要确认三类页面(后台、表单页、分发页)是否用了同一份配置——按路径分档比全站一份更省事故。
常见问题
问:只在页面顶部加一段禁止嵌套的脚本可行吗? 答:不可行作为防线。脚本要被执行才生效,而嵌套页面可以先把外层的样式与遮挡布置好;声明必须在响应到达浏览器时就生效,也就是写在响应头里。
问:两条响应头能同时写吗? 答:可以。过渡期常见做法是同时下发,用 X-Frame-Options 兜住不支持细粒度指令的旧环境,用 frame-ancestors 表达真实策略;但要确认两者语义不冲突,避免一处 DENY、一处放行。
问:加了拦截之后自己的嵌入功能失效了怎么判断? 答:先看失效的是哪一层:本站页面嵌本站页面应当被 SAMEORIGIN 放过;若是第三方组件把内容放进框架展示,属于分发场景,应改为按来源收紧而不是整体关掉。