iframe介绍
HTML 内联框架元素 (
<iframe>
) 表示嵌套的browsing context。它能够将另一个 HTML 页面嵌入到当前页面中。
<iframe src="./test.html" frameborder="0"></iframe>
如果 src
所在的资源位置与当前文档不同源,或者禁止嵌套iframe,则显示不出期待的内容
跨域的情况后面再谈。
iframe的特性
iframe最基本的特性,就是能自由操作iframe和父框架的内容(DOM). 但前提条件是同域. 如果跨域顶多只能实现页面跳转window.location.href
。
我们首先看简单的同源情况。
W3C标准
属性 | 描述 |
---|---|
align | 根据周围的文字排列 iframe。 |
contentDocument | 容纳框架的内容的文档。返回 frame/iframe 生成的文档对象。 |
contentWindow | 返回 frame/iframe 生成的 window 对象。 |
frameBorder | 设置或返回是否显示框架周围的边框。 |
height | 设置或返回 iframe 的高度。 |
longDesc | 设置或返回指向包含框架内容描述文档的 URL。 |
marginHeight | 设置或返回 iframe 的顶部和底部的页空白。 |
marginWidth | 设置或返回 frame/iframe 的左侧和右侧的页空白。 |
name | 设置或返回 frame/iframe 的名称。 |
noResize | 设置或返回框架是否可调整大小。 |
scrolling | 设置或返回框架是否可拥有滚动条。 |
src | 设置或返回应被加载到框架中的文档的 URL。 |
width | 设置或返回 iframe 的宽度。 |
sandbox
iframe有一个html5新特性sandbox,是出于iframe的不安全而提出的。
<iframe src="./test.html" sandbox="allow-forms"></iframe>
- allow-same-origin:允许被视为同源,即可操作父级DOM或cookie等
- allow-top-navigation:允许当前iframe的引用网页通过url跳转链接或加载
- allow-forms:允许表单提交
- allow-scripts:允许执行脚本文件
- allow-popups:允许浏览器打开新窗口进行跳转
- “”:设置为空时上面所有允许全部禁止
获取父级内容
在同域下,父页面可以获取子iframe的内容,那么子iframe同样也能操作父页面内容。在iframe中,可以通过在window上挂载的几个API进行获取。
window.parent 获取上一级的window对象,如果还是iframe则是该iframe的window对象
window.top 获取最顶级容器的window对象,即,就是你打开页面的文档
window.self 返回自身window的引用。可以理解 window===window.self
同页面一样,iframe具有onload事件,会阻塞主页面的onload
iframe优缺点
优点:
- 页面和程序分离,几乎不会受到外界任何js或者css的影响, 便于使用
- 可以通过iframe嵌套通用的页面, 提高代码的重用率, 比如页面的头部样式和底部版权信息
- 重新加载页面时, 不需要重载iframe框架页的内容, 增加页面重载速度.
- iframe可以解决第三方内容加载缓慢的问题.
缺点:
- 会产生很多页面,不容易管理
- iframe框架的内容无法被搜索引擎捕获, 所以iframe不适用于首页
- iframe兼容性较差
- iframe有一定的安全风险
- iframe会阻塞主页面的Onload事件
iframe跨域通信
嵌入的iframe如果和当前页面不同源,那么iframe无法读取主页面的cookie、localStorage、indexDB,无法获得DOM以及ajax请求无法发送等跨域问题 实现跨域的方式有:1)
window.domain
;2)window.name
;3)location.hash
;4)window.postmessage
。
1. window.domain
document.domain作用是获取/设置当前文档的原始域部分,同源策略会判断两个文档的原始域是否相同来判断是否跨域。这意味着只要把这个值设置成一样就可以解决跨域问题了。
该方式只能用于二级域名相同的情况下,比如a.test.com和b.test.com适用于该方式。 只需要给页面添加document.domain ='test.com'表示二级域名都相同就可以实现跨域。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
2. window.name
window.name是个特殊的值,无论是iframe内嵌的页面还是普通的页面都存在这个变量。只要设置了这个值之后无论如何修改页面的地址(哪怕是跨域的地址),这个值都会一直存在。(跟着页面窗口存在而不是跟着地址存在) name可以很长(2MB),因此可以将数据放在name里,利用url改变而name不变的特性传递数据 具体如下: 在a页面嵌入b页面,实现a,b的跨域通信,c与a同源
// a页面html的body
<iframe src="c页面地址" name="iframeInA" id="iframeInA" frameborder="0"></iframe>
<script>
var iframeInA = document.querySelector('#iframeInA')
iframeInA.onload = function() {
iframeInA.contentWindow.name = '要传递的数据'
iframeInA.src = 'b页面的地址'
}
</script>
在a页面中先插入c,a和c同源,所以可以先在a页面中操作c页面的window.name,然后再把iframe的src指向b页面,b和c处于一个iframe窗口,这时window.name的值就可以在b页面中获取到!
var data = ''
var iframe = document.createElement('iframe')
iframe.src = 'b页面地址'
iframe.style.display = 'none'
document.body.appendChild(iframe)
iframe.onload = function() {
iframe.onload = function() {
data = iframe.contentWindow.name
console.log('收到数据', data);
}
iframe.src = 'c页面地址'
}
过程正好反过来,先插入b,b页面中修改window.name,然后再把iframe页面替换成c,因为a和c同源,所以a页面就能拿到window.name的值。
3. location.hash
location.hash改变hash地址但不会触发页面刷新
a父页面可以将信息放到子页面url的hash值中,然后在子页面的内部监听hash值的变化(a->b)
。
<iframe src="b页面地址" name="iframeInA" id="iframeInA" frameborder="0"></iframe>
<script>
var data = '父向子(a->b)传递的数据'
var url = 'b页面地址' + '#' + data
document.getElementById('iframeInA').src = url + '#' + data
</script>
(b->a)
b页面改变a页面地址hash值,a页面监听地址栏的变化获取相应的数据,但是a、b页面不同源,b页面不能直接操作改变a页面地址的hash值。
于是b可以通过创建c页面(图6所示),让c和a同源,把值传给c,c来改变a的地址hash,从而达到a、b的通信。
// b页面
<script>
var data = '父向子(a->b)传递的数据'
var ifrproxy = document.createElement('iframe')
ifrproxy.style.display = 'none'
ifrproxy.src = 'a页面地址' + data
document.body.appendChild(ifrproxy)
</script>
// c页面
window.onload = function() {
parent.parent.location.hash = window.location.hash.substring(1) // a的hash为b传的data
}
4. window.postMessage
HTML5引入了跨文档通信 API,使用targetWidw.postMessage发送消息,window.onmessage监听接收消息,需要考虑兼容性问题。
// a页面
<iframe src="b页面地址" name="iframeInA" id="iframeInA" frameborder="0"></iframe>
<script>
var data = 'a->b的数据'
window.onload = function() {
document.getElementById('iframeInA').contentWindow.postMessage(data, 'b页面地址')
}
</script>
// b页面
<script>
window.onload = function(message) {
window.onmessage = function(e) {
console.log('收到a页面的数据', e.data)
}
}
</script>
iframe安全
是 Web 开发中最古老、最简单的内容嵌入技术之一,时至今日仍被使用。然而,在实践中使用 iframe 可能会带来一些安全隐患,向攻击者敞开大门。
iframe 注入
是一个非常常见的跨站脚本攻击(XSS)。 iframe 使用多个标签在网页上展示 HTML 文档并将用户重定向到其他的网站。此行为允许第三方将恶意的可执行程序、病毒或蠕虫植入你的 web 程序中,并在用户的设备上运行。
跨框架脚本攻击
跨框架脚本攻击(XFS)结合 iframe 和 JavaScript 恶意脚本,用于窃取用户的资料。 XFS 攻击者说服用户访问由他所控制的网页,并通过 iframe 引用一个结合了恶意脚本的合法站点。当用户在向 iframe 中的合法网站输入凭据时,JavaScript 恶意脚本将记录他们的输入。
点击劫持
点击劫持攻击能诱骗用户点击隐藏的网页元素。由此一来,用户可能会因此在无意中下载恶意程序,浏览恶意网站,提供密码或敏感信息、转账或进行网络购物 攻击者通常会通过 iframe 在网站上覆盖一个不可见的 HTML 元素来执行点击劫持。
iframe 网络钓鱼
试考虑一个社交平台,它允许用户和开发人员使用 iframe 将第三方网页合并到他们的粉丝页面或其他的应用程序中。
攻击者经常滥用这个功能来将 iframe 用于网络钓鱼攻击。 试考虑一个社交平台,它允许用户和开发人员使用 iframe 将第三方网页合并到他们的粉丝页面或其他的应用程序中。攻击者经常滥用这个功能来将 iframe 用于网络钓鱼攻击。
在预设情况下,iframe 中的内容能重定向顶级窗口。因此,攻击者可能会利用跨站脚本(XSS)漏洞来将网络钓鱼的代码当作 iframe 植入到 Web 应用程序中,引导用户进入钓鱼网站。
iframe 网络钓鱼攻击者不能伪造网址栏,但他们能触发重定向,操纵用户之后所接收的内容。 这个问题可以通过替换
sandbox
中的allow-top-navigation
属性值来避免。
iframe 能提高用户的互动性。但是,当你使用 iframe 的时候,你处理的内容是来自于你无法控制的第三方。因此,iframe 经常会对你的应用程序造成威胁。
然而,我们不能因为安全顾虑就停止使用 iframe。我们需要意识到这些问题并采取防范措施来保护我们的应用程序。
禁止嵌套iframe
有的网站可能会禁止被嵌套其他网站 也就是嵌入后出现下面情况
1. 添加X-Frame-Options
响应头
这个响应头是用来是否允许网页通过iframe元素嵌套的,它有三个值
-
deny:禁止,不允许任何网页嵌套,包括是同源的域名也不可以。
-
sameorigin:只允许同源的域名访问
- 比如
https://test.aaa.com/abc
嵌套https://test.aaa.com/test
的网页,同源, 允许嵌套 - 比如
https://test.aaa.com/abc
嵌套https://cp3.abc.com/test
的网页,不同源,不可以嵌套
- 比如
-
allow-from url:允许url的域名可以嵌套,多个可以逗号隔开,比如
allow-from http://aaa.com
, 允许http://aaa.com
嵌套本网页。
谷歌浏览器不支持该设置
allow-from
,firefox可以
const http = require('http')
http.createServer((req, res) => {
res.writeHead(200, {
"Content-Type": 'text/html; charset=UTF-8',
"X-Frame-Options": 'sameorigin'
})
res.end('hello set csp') }).listen(8081)
console.log('服务已开启,请打开http://127.0.0.1:8081')
新建一个html页面测试<iframe src="http://127.0.0.1:8081"></iframe>
,结果:127.0.0.1
拒绝请求
设置Content-Security-Policy
Content-Security-Policy, 叫做内容安全策略,简称CSP
,限定网页允许加载的资源策略,一定程度上防范外部的xss等攻击。
它可以设置很多限定策略,这里我们是要限定iframe的嵌套,所以用"Content-Security-Policy": "frame-ancestors 'self'"
。
const http = require('http')
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html; charset=UTF-8',
"Content-Security-Policy": "frame-ancestors 'self'"
})
res.end('hello set csp') }).listen(8081)
console.log('服务已开启,请打开http://127.0.0.1:8081')
通过窗口判断
根据当前的页面的顶级窗口window.top
和自身窗口window.self
是否相等
如果不相等,则是因为嵌入了iframe,因为iframe的自身窗口和顶级的窗口是不相等的。\
if (window.top != window.self) {
window.top.location = window.self.location; // 替换顶级窗口的地址
}