# 前言
前面我们谈过了很多关于页面与应用之间的交互,实现方法也有不少。这次我们主要谈谈zoom关于页面与应用之间交互的那些事
# zoom的魔法
使用过zoom的视频会议的人都经历过这种情况,有时候会议发起者发一个网页地址给你,你点击后却是拉起本地的zoom视频通话客户端(事先安装过或卸载过),没有ajax去请求,没有websocket去连接,没有插件来扩展。然而通话客户端就是被拉起了。相信不少人也能想到了这个实现原理:安装过的客户端起了一个一直在后台运行的本地微服务端,然后通过url调用微服务端接口,即可拉起视频客户端。
# 网页里头的秘密
1.假设我们打开网页便马上发送一个ajax请求本地服务器,本地服务器设置好cors,这个方法一般可行,但是不兼容IE9及以下。zoom却可以支持低版本IE。有没有不通过ajax就能够发送请求给本地服务器呢?可以,那就是img,iframe之类的标签。给一个透明的或者宽高设置为0的img或者iframe标签的src设置请求地址即可。不需要返回信息,不存在跨域,不存在兼容性的完美解决方案。
2.有一个问题,zoom的网页地址的是 https,我们没见过经过CA授权的https的本地服务吧?自签名的https肯定是不行的,http和https之间的通讯也肯定是不行的。敲黑板,奇淫巧技来了。利用了一个dns域名解析指向了127.0.0.1或者localhost,本地服务就可接收来自https网页的请求。所以在https那个页面发起的https://xxxx 请求其实就是在给本地微服务器发消息呢。
# 存在的漏洞
首先来了解一下csrf攻击:攻击者盗用了你的身份,以你的名义向第三方网站发送恶意请求。
我们知道 zoom 主打的是视频会议,所以它的客户端必然会调用摄像头。于是有人利用zoom的微服务端偷偷调用用户的摄像头,这简直不敢想象会泄露多少用户的隐私。黑客们会利用某种手段(如:csrf),给你的网页或者诱导你点击进去的某一个页面里添加一个给微服务端发请求的img(或者iframe)标签,由于微服务端没有其他校验,攻击者甚至不需要获取用户的任何信任信息。然后偷偷调用你的微服务端,接着拉起摄像头。由于不需要返回数据,并且使用的标签发送请求,同时zoom的微服务段采用的是GET请求,所以cors跨域影响不到后台调用摄像头来侵犯用户的隐私。
第二种可能的漏洞,如果微服务端设置了CORS为通配符,黑客可以在你访问的某个页面的某一个点击事件中发起拉起微服务的请求。所以需要限制CORS。
# 解决的办法
好像zoom已经修复了这个漏洞,具体怎么修复的我也不清楚。我们这边提供了一个思路。限制csrf攻击一般有几个方法:
1.采用post,这里我们需要用到GET,淘汰。 2.增加token验证,可行性不错,但是会新增一个获取token的请求,可能新增危险。 3.自定义http请求头,由于使用标签发送请求所以pass。 4.Referer
根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。我们可以通过校验referer来判断发送请求的地址是否为指定地址来阻止攻击。这种方法的显而易见的好处就是简单易行,网站的普通开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。
下面用nodejs 模拟一下。
var express = require("express");
var app = express();
var hostName = '127.0.0.1';
var port = 12521;
var fs = require('fs');
var https = require('https');
var allowUrls = 'https://xxxxxx.com'; // 指定的请求网址
var options = {
key: fs.readFileSync('./key.key'),
cert: fs.readFileSync('./pem.pem')
};
app.all('*', function (req, res, next) {
res.header("Access-Control-Allow-Origin", allowUrls); // 设置跨域
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.get("/get", function (req, res) {
if (req.headers.referer.slice(0, allowUrls.length) == allowUrls) { // 判定referer地址是否来自指定网站
// 执行相关操作
}
else {
res.status(500).send('Allow-Control-Allow-Origin Error!')
}
})
https.createServer(options, app).listen(port, function () {
console.log(`服务器运行在https://${hostName}:${port}`);
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cors和referer双重保障。