ifram标签,跨域通信以及存在的安全问题

2,159 阅读9分钟

iframe介绍

HTML 内联框架元素 (<iframe>)  表示嵌套的browsing context。它能够将另一个 HTML 页面嵌入到当前页面中。

<iframe src="./test.html" frameborder="0"></iframe>

如果 src 所在的资源位置与当前文档不同源,或者禁止嵌套iframe,则显示不出期待的内容

image.png

跨域的情况后面再谈。

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优缺点

优点:

  1. 页面和程序分离,几乎不会受到外界任何js或者css的影响, 便于使用
  2. 可以通过iframe嵌套通用的页面, 提高代码的重用率, 比如页面的头部样式和底部版权信息
  3. 重新加载页面时, 不需要重载iframe框架页的内容, 增加页面重载速度.
  4. iframe可以解决第三方内容加载缓慢的问题.

缺点:

  1. 会产生很多页面,不容易管理
  2. iframe框架的内容无法被搜索引擎捕获, 所以iframe不适用于首页
  3. iframe兼容性较差
  4. iframe有一定的安全风险
  5. 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

有的网站可能会禁止被嵌套其他网站 也就是嵌入后出现下面情况
image.png

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; // 替换顶级窗口的地址 
}