全文:当协定、子搜索引擎、主搜索引擎、freenode中任一两个不全然相同时,都算是全然相同域。全然相同域间互相允诺天然资源,即使是“布吕马”。
责任编辑撷取自宏碁云街道社区《八种布吕马形式同时实现基本上原理咋回事》,译者:小弟博纳县
一、甚么是布吕马?
1.甚么是相混思路或其管制文本?
相混思路是一种签订合同,它是应用程序最核心理念也最基本上的安全可靠机能,假如缺乏了相混思路,应用程序很难受XSS、CSRF等反击。简而言之相混是指”协定+搜索引擎+路由器”二者全然相同,即使两个全然相同的搜索引擎对准同两个ip门牌号,也非相混。
相混思路管制文本有:
Cookie、LocalStorage、IndexedDB 等储存性文本DOM 结点AJAX 允诺推送后,结论被应用程序截击了但是有四个条码是容许布吕马加载天然资源:
<img src=XXX><link href=XXX><script src=XXX>2.常用布吕马情景
当协定、子搜索引擎、主搜索引擎、freenode中任一两个不全然相同时,都算是全然相同域。全然相同域间互相允诺天然资源,即使是“布吕马”。
特别表明三点:
第三:假如是协定和路由器导致的布吕马难题“后台”是束手无策的。
第三:在布吕马难题上,实际上是通过“URL的第三部”来辨识而不会根据搜索引擎相关联的IP门牌号与否全然相同来推论。“URL的第三部”能认知为“协定, 搜索引擎和路由器必须相匹配”。
这里你也许有位疑点:允诺布吕马了,那么允诺究竟接到去没?
布吕马并不是允诺发不进来,允诺能接到去,服务器端能接到允诺并恒定回到结论,只是结论被应用程序截击了。你可能会取捷伊文本,所以能发动布吕马允诺。同时也表明了布吕马并不能全然制止 CSRF,因为允诺即便是接到去了。
二、布吕马软件系统
1.jsonp
1) JSONP基本上原理
借助 <script> 条码没布吕马管制的安全可靠漏洞,网
2) JSONP和AJAX对比
的形式。但AJAX属于相混思路,JSONP属于非相混思路(布吕马允诺)
3) JSONP优缺点
JSONP优点是简单兼容性好,可用于解决主流应用程序的布吕马数据访问的难题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS反击。
4) JSONP的同时实现流程
pt>条码,把那个布吕马的API数据接口门牌号,赋值给script的src,还要在这个门牌号中向服务器传递该函数名(能通过问号传参:?callback=show)。服务器接接到允诺后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成两个字符串,例如:传递进去的函数名是show,它准备好的数据是show(我不爱你)。最后服务器把准备的数据通过HTTP协定回到给客户端,客户端再调用执行之前声明的回调函数(show),对回到的数据进行操作。在开发中可能会遇到多个 JSONP 允诺的回调函数名是全然相同的,这时候就需要自己封装两个 JSONP函数。
// index.html
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement(script)
window[callback] = function(data) {
resolve(data)
document.body.removeChild(script)
}
params = { …params, callback } // wd=b&callback=show
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join(&)}`
document.body.appendChild(script)
})
}
jsonp({
url: http://localhost:3000/say,
params: { wd: Iloveyou },
callback: show
}).then(data => {
console.log(data)
})
上面这段代码相当于向http://localhost:3000/say?wd=Iloveyou&callback=show这个门牌号允诺数据,然后后台回到show(我不爱你),最后会运行show()这个函数,打印出我不爱你
// server.js
let express = require(express)
let app = express()
app.get(/say, function(req, res) {
let { wd, callback } = req.query
console.log(wd) // Iloveyou
console.log(callback) // show
res.end(`${callback}(我不爱你)`)
})
app.listen(3000)
5) jQuery的jsonp形式
JSONP都是GET和异步允诺的,不存在其他的允诺形式和同步允诺,且jQuery默认就会给JSONP的允诺清除缓存。
$.ajax({
url:”http://crossdomain.com/jsonServerResponse”,
dataType:”jsonp”,
type:”get”,//能省略
jsonpCallback:”show”,//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略
jsonp:”callback”,//->把传递函数名的那个形参callback,可省略
success:function (data){
console.log(data);}
});
2.cors
CORS 需要应用程序和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来同时实现。
应用程序会自动进行 CORS 通信,同时实现 CORS 通信的关键是后端。只要后端同时实现了 CORS,就同时实现了布吕马。
服务器端设置 Access-Control-Allow-Origin 就能开启 CORS。 该属性表示哪些搜索引擎能访问天然资源,假如设置通配符则表示所有网站都能访问天然资源。
虽然设置 CORS 和前端没甚么关系,但是通过这种形式解决布吕马难题的话,会在推送允诺时出现两种情况,分别为简单允诺和复杂允诺。
1) 简单允诺
只要同时满足以下两大条件,就属于简单允诺
条件1:使用下列方法之一:
GETHEADPOST条件2:Content-Type 的值仅限于下列二者之一:
text/plainmultipart/form-dataapplication/x-www-form-urlencoded允诺中的任一 XMLHttpRequestUpload 对象均没注册任何事件监听器; XMLHttpRequestUpload 对象能使用 XMLHttpRequest.upload 属性访问。
2) 复杂允诺
不符合以上条件的允诺就肯定是复杂允诺了。
复杂允诺的CORS允诺,会在正式通信之前,增加一次HTTP查询允诺,称为”预检”允诺,该允诺是 option 方法的,通过该允诺来知道服务器端与否容许布吕马允诺。
我们用PUT向后台允诺时,属于复杂允诺,后台需做如下配置:
// 容许哪个方法访问我
res.setHeader(Access-Control-Allow-Methods, PUT)
// 预检的存活时间
res.setHeader(Access-Control-Max-Age, 6)
// OPTIONS允诺不做任何处理
if (req.method === OPTIONS) {
res.end()
}
// 定义后台回到的文本
app.put(/getData, function(req, res) {
console.log(req.headers)
res.end(我不爱你)
})
接下来我们看下两个完整复杂允诺的例子,并且介绍下CORS允诺相关的字段
// index.html
let xhr = new XMLHttpRequest()
document.cookie = name=xiamen // cookie不能布吕马
xhr.withCredentials = true // 前端设置与否带cookie
xhr.open(PUT, http://localhost:4000/getData, true)
xhr.setRequestHeader(name, xiamen)
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
console.log(xhr.response)
//得到响应头,后台需设置Access-Control-Expose-Headers
console.log(xhr.getResponseHeader(name))
}
}
}
xhr.send()
//server1.js
let express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
//server2.js
let express = require(express)
let app = express()
let whitList = [http://localhost:3000] //设置白名单
app.use(function(req, res, next) {
let origin = req.headers.origin
if (whitList.includes(origin)) {
// 设置哪个源能访问我
res.setHeader(Access-Control-Allow-Origin, origin)
// 容许携带哪个头访问我
res.setHeader(Access-Control-Allow-Headers, name)
// 容许哪个方法访问我
res.setHeader(Access-Control-Allow-Methods, PUT)
// 容许携带cookie
res.setHeader(Access-Control-Allow-Credentials, true)
// 预检的存活时间
res.setHeader(Access-Control-Max-Age, 6)
// 容许回到的头
res.setHeader(Access-Control-Expose-Headers, name)
if (req.method === OPTIONS) {
res.end() // OPTIONS允诺不做任何处理
}
}
next()
})
app.put(/getData, function(req, res) {
console.log(req.headers)
res.setHeader(name, jw) //回到两个响应头,后台需设置
res.end(我不爱你)
})
app.get(/getData, function(req, res) {
console.log(req.headers)
res.end(我不爱你)
})
app.use(express.static(__dirname))
app.listen(4000)
上述代码由http://localhost:3000/index.html向http://localhost:4000/布吕马允诺,正如我们上面所说的,后端是同时实现 CORS 通信的关键。
3.postMessage
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多能布吕马操作的window属性之一,它可用于解决以下方面的难题:
页面和其打开的新窗口的数据传递多窗口间消息传递页面与嵌套的iframe消息传递上面四个情景的布吕马数据传递postMessage()方法容许来自不相混的脚本采用异步形式进行有限的通信,能同时实现跨文本档、多窗口、布吕马消息传递。
otherWindow.postMessage(message, targetOrigin, [transfer]);message: 将要推送到其他 window的数据。targetOrigin:通过窗口的origin属性来指定哪些窗口能接接到消息事件,其值能是字符串”*”(表示无管制)或者两个URI。在推送消息的时候,假如目标窗口的协定、主机门牌号或路由器这二者的任一一项不相匹配targetOrigin提供的值,那么消息就不会被推送;只有二者全然相匹配,消息才会被推送。transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而推送一方将不再保有所有权。接下来我们看个例子: http://localhost:3000/a.html页面向http://localhost:4000/b.html传递“我爱你”,然后后者传回”我不爱你”。
// a.html
<iframe src=”http://localhost:4000/b.html” frameborder=”0″ id=”frame” onload=”load()”></iframe> //等它读取完触发两个事件
//内嵌在http://localhost:3000/a.html
<script>
function load() {
let frame = document.getElementById(frame)
frame.contentWindow.postMessage(我爱你, http://localhost:4000) //推送数据
window.onmessage = function(e) { //接受回到数据
console.log(e.data) //我不爱你
}
}
</script>
// b.html
window.onmessage = function(e) {
console.log(e.data) //我爱你
e.source.postMessage(我不爱你, e.origin)
}
4.websocket
Websocket是HTML5的两个持久化的协定,它同时实现了应用程序与服务器的全双工通信,同时也是布吕马的一种解决方案。WebSocket和HTTP都是应用层协定,都基于 TCP 协定。但是 WebSocket 是一种双向通信协定,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方推送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协定,连接建立好了之后 client 与 server 间的双向通信就与 HTTP 无关了。
原生WebSocket API使用起来不太方便,我们使用http://Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的应用程序提供了向下兼容。
我们先来看个例子:本地文件socket.html向localhost:3000发生数据和接受数据
// socket.html
<script>
let socket = new WebSocket(ws://localhost:3000);
socket.onopen = function () {
socket.send(我爱你);//向服务器推送数据
}
socket.onmessage = function (e) {
console.log(e.data);//接收服务器回到的数据
}
</script>
// server.js
let express = require(express);
let app = express();
let WebSocket = require(ws);//记得安装ws
let wss = new WebSocket.Server({port:3000});
wss.on(connection,function(ws) {
ws.on(message, function (data) {
console.log(data);
ws.send(我不爱你)
});
})
5. Node中间件代理(两次布吕马)
同时实现基本上原理:相混思路是应用程序需要遵循的标准,而假如是服务器向服务器允诺就无需遵循相混思路。
代理服务器,需要做以下几个步骤:接受客户端允诺 。将允诺 转发给服务器。拿到服务器 响应 数据。将 响应 转发给客户端。我们先来看个例子:本地文件index.html文件,通过代理服务器http://localhost:3000向目标服务器http://localhost:4000允诺数据。
// index.html(http://127.0.0.1:5500)
<script src=”https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js”></script>
<script>
$.ajax({
url: http://localhost:3000,
type: post,
data: { name: xiamen, password: 123456 },
contentType: application/json;charset=utf-8,
success: function(result) {
console.log(result) // {“title”:”fontend”,”password”:”123456″}
},
error: function(msg) {
console.log(msg)
}
})
</script>
// server1.js 代理服务器(http://localhost:3000)
const http = require(http)
// 第三步:接受客户端允诺
const server = http.createServer((request, response) => {
// 代理服务器,直接和应用程序直接交互,需要设置CORS 的第三部字段
response.writeHead(200, {
Access-Control-Allow-Origin: *,
Access-Control-Allow-Methods: *,
Access-Control-Allow-Headers: Content-Type
})
// 第三步:将允诺转发给服务器
const proxyRequest = http
.request(
{
host: 127.0.0.1,
port: 4000,
url: /,
method: request.method,
headers: request.headers
},
serverResponse => {
// 第三步:接到服务器的响应
var body =
serverResponse.on(data, chunk => {
body += chunk
})
serverResponse.on(end, () => {
console.log(The data is + body)
// 第四步:将响应结论转发给应用程序
response.end(body)
})
}
)
.end()
})
server.listen(3000, () => {
console.log(The proxyServer is running at http://localhost:3000)
})
// server2.js(http://localhost:4000)
const http = require(http)
const data = { title: fontend, password: 123456 }
const server = http.createServer((request, response) => {
if (request.url === /) {
response.end(JSON.stringify(data))
}
})
server.listen(4000, () => {
console.log(The server is running at http://localhost:4000)
})
上述代码经过两次布吕马,值得注意的是应用程序向代理服务器推送允诺,也遵循相混思路,最后在index.html文件打印出{“title”:”fontend”,”password”:”123456″}
6.nginx反向代理
同时实现基本上原理类似于Node中间件代理,需要你搭建两个中转nginx服务器,用于转发允诺。
使用nginx反向代理同时实现布吕马,是最简单的布吕马形式。只需要修改nginx的配置即可解决布吕马难题,支持所有应用程序,支持session,不需要修改任何代码,并且不会影响服务器性能。
同时实现思路:通过nginx配置两个代理服务器(搜索引擎与domain1全然相同,路由器全然相同)做跳板机,反向代理访问domain2接口,并且能顺便修改cookie中domain信息,方便当前域cookie写入,同时实现布吕马登录。
先下载nginx,然后将nginx目录下的nginx.conf修改如下:
// proxy服务器
server {
listen 80;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里搜索引擎
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无应用程序参与,故没相混管制,下面的布吕马配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只布吕马不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
最后通过命令行nginx -s reload启动nginx
// index.html
var xhr = new XMLHttpRequest();
// 前端开关:应用程序与否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open(get, http://www.domain1.com:81/?user=admin, true);
xhr.send();
// server.js
var http = require(http);
var server = http.createServer();
var qs = require(querystring);
server.on(request, function(req, res) {
var params = qs.parse(req.url.substring(2));
// 向后台写cookie
res.writeHead(200, {
Set-Cookie: l=a123456;Path=/;Domain=www.domain2.com;HttpOnly // HttpOnly:脚本无法读取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen(8080);
console.log(Server is running at port 8080…);
7.window.name + iframe
window.name属性的独特之处:name值在全然相同的页面(甚至全然相同搜索引擎)读取后依旧存在,并且能支持非常长的 name 值(2MB)。
其中a.html和b.html是同域的,都是http://localhost:3000;而c.html是http://localhost:4000
// a.html(http://localhost:3000/b.html)
<iframe src=”http://localhost:4000/c.html” frameborder=”0″ onload=”load()” id=”iframe”></iframe>
<script>
let first = true
// onload事件会触发2次,第1次读取布吕马页,并留存数据于window.name
function load() {
if(first){
// 第1次onload(布吕马页)成功后,切换到同域代理页面
let iframe = document.getElementById(iframe);
iframe.src = http://localhost:3000/b.html;
first = false;
}else{
// 第2次onload(同域b.html页)成功后,读取同域window.name中数据
console.log(iframe.contentWindow.name);
}
}
</script>
b.html为中间代理页,与a.html同域,文本为空。
// c.html(http://localhost:4000/c.html)
<script>
window.name = 我不爱你
</script>
总结:通过iframe的src属性由外域转向本地域,布吕马数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了应用程序的布吕马访问管制,但同时它又是安全可靠操作。
8.location.hash + iframe
同时实现基本上原理: a.html欲与c.html布吕马互相通信,通过中间页b.html来同时实现。 四个页面,全然相同域间借助iframe的location.hash传值,全然相同域间直接js访问来通信。
具体同时实现步骤:一开始a.html给c.html传两个hash值,然后c.html接到hash值后,再把hash值传递给b.html,最后b.html将结论放到a.html的hash值中。
同样的,a.html和b.html是同域的,都是http://localhost:3000;而c.html是http://localhost:4000
// a.html
<iframe src=”http://localhost:4000/c.html#iloveyou”></iframe>
<script>
window.onhashchange = function () { //检测hash的变化
console.log(location.hash);
}
</script>
// b.html
<script>
window.parent.parent.location.hash = location.hash
//b.html将结论放到a.html的hash值中,b.html可通过parent.parent访问a.html页面
</script>
// c.html
console.log(location.hash);
let iframe = document.createElement(iframe);
iframe.src = http://localhost:3000/b.html#idontloveyou;
document.body.appendChild(iframe);
9.document.domain + iframe
该形式只能用于二级搜索引擎全然相同的情况下,比如http://a.test.com 和 http://b.test.com
适用于该形式。
只需要给页面添加 document.domain =test.com 表示二级搜索引擎都全然相同就能同时实现布吕马。
同时实现基本上原理:两个页面都通过js强制设置document.domain为基础主域,就同时实现了同域。
我们看个例子:页面http://a.zf1.cn:3000/a.htmlhttp://b.zf1.cn:3000/b.html中a的值
// a.html
<body>
helloa
<iframe src=”http://b.zf1.cn:3000/b.html” frameborder=”0″ onload=”load()” id=”frame”></iframe>
<script>
document.domain = zf1.cn
function load() {
console.log(frame.contentWindow.a);
}
</script>
</body>
// b.html
<body>
hellob
<script>
document.domain = zf1.cn
var a = 100;
</script>
</body>
三、总结
CORS支持所有类型的HTTP允诺,是布吕马HTTP允诺的根本软件系统JSONP只支持GET允诺,JSONP的优势在于支持老式应用程序,以及能向不支持CORS的网站允诺数据。不管是Node中间件代理还是nginx反向代理,主要是通过相混思路对服务器不加管制。日常工作中,用得比较多的布吕马方案是cors和nginx反向代理