内容简介:准确的说,同源策略是指,浏览器内部在发起如下请求时,该来源必须是当前同源的HTTP资源:注意:两个URI同源当且仅当它们的协议://host:port相同。
*本文作者:x565178035,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。
同源策略
准确的说,同源策略是指,浏览器内部在发起如下请求时,该来源必须是当前同源的HTTP资源:
1. 以跨站点的方式调用XMLHttpRequest或者Fetch API。 2. Web字体(用于CSS中@ font-face的跨域字体使用) 3. WebGL textures 4. 使用drawImage绘制到canvas的图像/视频帧。 5. 样式表(用于CSSOM访问)
注意:两个URI同源当且仅当它们的协议://host:port相同。
从第一点可以看到,浏览器限制从脚本内部发起跨域的HTTP请求——更准确的说,同源策略有的限制有两种表现:(1)限制发起AJAX请求(XMLHttpRequest,Fetch);(2)拦截其他跨站请求的返回结果;这取决于请求是否为简单请求。
CORS
跨域资源共享(Cross-Origin Resource Sharing, CORS)是一种解决跨域请求的方案,其机制是使用一组额外响应头(Access-Control-Allow-Origin)和预检请求(OPTIONS)来使浏览器有权使用非同源资源。大部分的现代浏览器符合该标准。
简单请求
若请求满足所有下述条件,则该请求可视为“简单请求”:
使用下列方法之一:
GET HEAD POST
并且Content-Type的值仅限于下列三者之一:
text/plain multipart/form-data application/x-www-form-urlencoded
Fetch 规范定义了对 CORS 安全的首部字段集合,也就是说,不得手动设置除以下集合之外的字段(否则不为简单请求)。该集合为:
Accept Accept-Language Content-Language Content-Type DPR Downlink Save-Data Viewport-Width Width
并且请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
并且请求中没有使用 ReadableStream 对象。
简单请求会直接发送请求而不会触发预请求,但是不一定能拿到结果,这取决于请求的服务器Response的Access-Control-Allow-Origin内容。注意以上条件只要有一条不满足则不为简单请求。
简单请求跨域表现
发起请求服务 http://127.0.0.1:8000/ajax.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>AJAX</title> </head> <script> function submitRequest() { var xhr = new XMLHttpRequest(); xhr.open("GET", "http://127.0.0.1:8888/get", true); xhr.withCredentials = true; xhr.send(); xhr.onreadystatechange = function(){ if(xhr.readyState === 4 && xhr.status === 200){ alert(xhr.responseText); } } } </script> <button onclick="submitRequest()">AJAX</button> </html>
非同源服务 http://127.0.0.1:8888/:
from flask import Flask, request, render_template_string, session app = Flask(__name__) app.secret_key='random_secret_key' @app.route('/get', methods=['GET']) def get(): if session.get('user','')=='admin': return "Admin do something!" else: return "No Privilege..." @app.route('/login', methods=['GET']) def login(): user=request.args.get("user", "Null") session["user"]=user template=""" <h3> Login as {{ user }}... </h3> """ return render_template_string(template, user=user) if __name__ == '__main__': app.run(host='127.0.0.1', port=8888, debug=True)
发请求,可以看到请求确实已发送,并且可以带cookie(withCredentials),但是js没有拿到结果:
AJAX请求结果(请求成功,回传失败,所以这也是GET型CSRF无法很好防范的原因):
综上,对于简单跨域请求,若未正确配置则请求正常发送,不能获取返回结果(浏览器拦截)。
Origin和Access-Control-Allow-Origin
可以看到在请求中存在Origin字段,它标记了来源,对应的Access-Control-Allow-Origin为回应包头携带字段,它表示那些来源可以访问本域,*表示所有来源(注意它不能与credentials一起使用)。
使用CORS实现的支持跨域的非同源服务 http://127.0.0.1:8888/:
@app.route('/get', methods=['GET']) def get(): if session.get('user','')=='admin': ret = "Admin do something!" else: ret = "No Privilege..." resp=make_response(ret) resp.headers['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000" resp.headers['Access-Control-Allow-Credentials'] = 'true' resp.headers['Access-Control-Allow-Methods'] = "POST, GET, OPTIONS, PUT, DELETE, PATCH" return resp
其中还有几个header:
Access-Control-Allow-Credentials:如果请求需要带cookie,该header必须为true,同时Access-Control-Allow-Origin不能为*,否则同样拿不到结果; Access-Control-Allow-Methods:允许的请求方式 Origin和Access-Control-Allow-Origin一个为请求携带的字段,一个为回应携带的字段,浏览器以此来判断js是否可以接收回应。
改造后前端终于能够拿到结果:
预检请求
若请求不为简单请求,那么在发起该请求前必须使用OPTIONS发送预验请求,服务器允许后才能发送实际请求(可以猜想这是为了防止CSRF)。
当请求满足一下任一条件时,该请求为非简单请求:
使用了下面任一 HTTP 方法:
PUT DELETE CONNECT OPTIONS TRACE PATCH
人为设置了对 CORS 安全的首部字段集合 之外的其他首部字段。
Content-Type的值不属于下列之一:
application/x-www-form-urlencoded multipart/form-data text/plain
请求中的 XMLHttpRequestUpload 对象注册了任意多个事件监听器。
请求中使用了 ReadableStream 对象。
预检请求跨域表现
假设有服务器 http://127.0.0.1:8888/json:
@app.route('/json', methods=['GET','POST']) def json(): if request.method == 'GET': return render_template('json.html', Evil="Benign") else: if session.get('user','')=='admin': print("session:",session) data=request.json ret='Admin do '+data["action"] else: ret="No Privilege2..." print(ret) return jsonify({'result': ret})
‘templates/json.html’内容为:
<html> <title>{{ Evil }}</title> <center> <h1> Reset Password </h1> <head> <script type="text/javascript"> function submitRequest() { var xhr = new XMLHttpRequest(); xhr.open("POST", "http://127.0.0.1:8888/json", true); xhr.setRequestHeader("Accept", "*/*"); xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"); xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); xhr.withCredentials = true; xhr.send(JSON.stringify({"action":"change passwd..."})); xhr.onreadystatechange = function(){ if(xhr.readyState === 4 && xhr.status === 200){ alert(xhr.responseText); } } } </script> </head> <body> <button onclick="submitRequest()">Conform</button> </body> </html>
同域不存在预检请求:
跨域出现OPTIONS请求,默认情况下跨域被阻止:
Access-Control-Request-Method:字段说明请求的操作。
允许跨域请求
在OPTIONS和POST报头加入Access-Control-Allow-Origin等字段
@app.route('/json', methods=['GET','POST','OPTIONS']) def json(): if request.method == 'GET': return render_template('json.html', Evil="Benign") else: if session.get('user','')=='admin': print("session:",session) data=request.json ret='Admin do '+data["action"] else: ret="No Privilege2..." resp=make_response(jsonify({'result': ret})) resp.headers['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000" resp.headers['Access-Control-Allow-Credentials'] = 'true' resp.headers['Access-Control-Allow-Methods'] = "POST, GET, OPTIONS, PUT, DELETE, PATCH" resp.headers['Access-Control-Allow-Headers'] = "origin, content-type, accept, x-requested-with" return resp
跨站成功,先发送OPTIONS,再发送POST,注意这两个报头必须都存在CORS字段。
与CORS有关的HTTP头
请求
Origin:<origin>:表示实际请求的源站 Access-Control-Request-Method: <method>:用于预检请求,表示真实的请求方法。 Access-Control-Request-Headers: <field-name>[, <field-name>]*:用于预检请求,表示真实请求所携带的首部字段(从抓包上来看chrome没有按要求来啊Orz)
响应
Access-Control-Allow-Origin: <origin> | *:允许外域URI Access-Control-Allow-Credentials:false:是否允许浏览器读取response内容(如cookie) Access-Control-Allow-Methods:用于预检请求响应,表示允许使用的HTTP方法 Access-Control-Allow-Headers:用于预检请求响应,表示允许携带的头部 Access-Control-Expose-Headers:允许响应时能获取的其他头部(在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头) Access-Control-Max-Age:preflight请求的最大响应时间
参考链接
Cross-Origin Resource Sharing(CORS)详解,CORS详解,CORS原理分析, https://www.cnblogs.com/demingblog/p/8393511.html
HTTP访问控制(CORS), https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
*本文作者:x565178035,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 干货|110G python学习资源共享
- 掌握并理解 CORS (跨域资源共享)
- CORS(跨域资源共享)错误配置漏洞的高级利用
- 全栈资源共享 一起成长,努力成为你想成为的样子
- Angular、.NET Web Api 验证(Authentication)与跨来源资源共享(Cors)
- 构建共享服务消息中心,避免资源浪费
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
大规模Web服务开发技术
伊藤直也、田中慎司 / 李剑 / 电子工业出版社 / 2011-7 / 59.00元
Hatena是日本最大的Web服务提供商之一,它提供的服务包括关键字(类似于维基百科)、博客、相册等。《大规模Web服务开发技术》由伊藤直也、田中慎司所著,内容主要来自Hatena为学生们举行的暑期实习的课程,内容涵盖广泛,介绍了性能优化、分布式、算法、系统架构等各个方面,甚至还介绍了硬件的经济成本,是运维工程师们必不可少的参考书。书中还包括几个算法实习课题,介绍了压缩算法、全文搜索等算法的实现方......一起来看看 《大规模Web服务开发技术》 这本书的介绍吧!