点击劫持防护中如何正确检测页面是否被嵌入iframe?

萌新.辽源 阅读 13

我在做点击劫持防护,看到可以用 top !== self 来判断是否被嵌套,但实际测试时发现有些情况下这个判断不生效,比如在同域 iframe 里。我试了下面这段代码,但好像还是会被绕过?

<script>
if (top !== self) {
  top.location = self.location;
}
</script>

有没有更可靠的 Parent 检测方式?或者是不是应该配合 X-Frame-Options 头一起用?

我来解答 赞 6 收藏
二维码
手机扫码查看
1 条解答
迷人的楚萓
你的代码思路是对的,但 top !== self 这个判断在某些场景下确实有坑,咱们来捋一捋。

先说为什么你原来的代码可能不生效:

top !== self 本身逻辑没问题,如果页面被嵌入iframe,top 应该是外层window对象,self 是当前window,它们肯定不相等。但问题可能出在执行时机上——如果你的脚本在DOM还没完全加载时就执行了,或者父页面在脚本执行后通过某些方式修改了top的引用,那确实可能出现判断失效的情况。

另外,同域iframe里这个判断是有效的,你感觉不生效可能是别的原因,比如父页面用了 document.domain 做了域提升,或者干脆就没正确加载你的防护脚本。

一个更可靠的检测方式:

(function() {
// 基础检测:top 和 self 不相等说明被嵌入了
var isFramed = top !== self;

// 额外检测:通过 frameElement 判断
// 如果当前窗口是 frame/iframe,frameElement 会有值
// 顶层窗口的 frameElement 是 null
try {
if (window.frameElement && window.frameElement.tagName) {
isFramed = true;
}
} catch (e) {
// 跨域情况下访问 frameElement 会抛异常
// 这种情况下我们认为可能是被嵌入了
isFramed = true;
}

// 循环检测:遍历所有父窗口
// 理论上可以检测多重嵌套,但跨域会报错
try {
var parent = top;
while (parent !== parent.parent) {
parent = parent.parent;
}
// 如果能安全到达最顶层,说明没有被异常嵌套
// 但这个方法在跨域时 parent.parent 可能访问不到
} catch (e) {
// 跨域访问 parent.parent 报错,说明被嵌入了
isFramed = true;
}

if (isFramed) {
// 防护措施:尝试跳出iframe
try {
top.location = self.location;
} catch (e) {
// 跨域情况下无法修改 top.location
// 可以用其他方式提示,比如弹窗、显示警告等
document.body.innerHTML = '<h1>请勿将本站点嵌入其他网站!</h1>';
}
}
})();


但是!我必须说但是——

靠JavaScript做点击劫持检测本身就是防君子不防小人。为什么这么说:

1. 父页面可以通过设置 X-Frame-Options: DENYSAMEORIGIN 来阻止被嵌入,但这是在服务器端设置
2. 父页面可以完全屏蔽你的JS执行,比如用