原稿:语雀 · Web 安全 · 原目录:Web & 浏览器 › Web 安全
同源策略
参考链接:
同源策略限制从一个源(origin)加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制
同源判定
如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”
同源的例子
1. 协议相同, 主机名相同
http://example.com/app1/index.html
http://example.com/app2/index.html
2. http默认端口80
http://Example.com:80
http://example.com
不同源例子
1. 协议不同
http://example.com/app1
https://example.com/app2
2. 主机不同:包括只是子域不同的情况
http://example.com
http://www.example.com
http://myapp.example.com
3. 端口不同
http://example.com
http://example.com:8080
3. 域名不同,IP相同
http://www.demo.com/a.js
http://127.0.0.1/b.js
跨源访问限制
同源策略控制不同源之间的交互,这些交互通常分为三类,还有对于数据存取的限制:
- 跨域写操作(Cross-origin writes)一般是被允许的,如 <a> 链接、重定向,表单提交
- 跨域资源嵌入(Cross-origin embedding)一般是被允许(后面会举例说明),如 <script> 嵌入脚本, <link> 嵌入 css、 <img> 嵌入图片
- 跨域读操作(Cross-origin reads)一般是不被允许的,dom 无法获取、ajax 无法发送
- 跨源数据存取:不允许跨域
- 存储在浏览器中的数据,如 localStorage 和 IndexedDB,是以源进行分割。每个源都拥有自己单独的存储空间,一个源中的 JavaScript 脚本不能对属于其它源的数据进行读写操作。
- cookie 的 Domain 和 path 规定了允许 cookie 发送给哪些URL:Domain 指定了哪些主机可以接受 cookie。如果不指定,默认为 origin,不包含子域名。如果指定了Domain,则一般包含子域名。而 Path 则指定了主机下的哪些路径可以接受 cookie
跨域通信
同源策略默认阻止“跨域”获取资源,有下面几种方法实现跨域通信
jsonp
同源策略允许跨域资源嵌入,可以通过使用 <script> 进行跨域请求: <script> 本身的功能就是下载 js 并解析执行,响应中返回的脚本也会被执行,其中可以直接使用 JSON 传递 js 对象,当前文档中的回调可以接收 JSON 对象。这种跨域的通讯方式称为 jsonp
- 优点:服务器与客户端跨源通信的常用方法。简单实用,老式浏览器全部支持,服务器改造非常小。
- 缺点:只能实现 get 一种请求、不安全、容易遭到 xss 攻击
下面是一个 jsonp 的简单实现:
请求返回的 js 脚本是 jsonp({ “pid”:12345, “price”:25 });
<script>
const jsonp = function (res) {
alert("product_id="+res.pid+" price="+res.price);
};
</script>
<script type="text/javascript" src="www.test.com/getProduct?pid=12345&callback=jsonp">
</script>
jsonp 安全性问题
- 接口受到攻击:伪造对 jsonp 接口的请求,从响应内容中获取数据,解决方案:设置 csrf token、服务器通过 refer 验证调用方
- jsonp 可能导致 XSS 攻击:http://youdomain.com?callback=<script>alert(1)</script> 可能返回 <script>alert(1)</script>({ data }) ,解决方案:限制 callback 的长度,过滤函数名中的尖括号;定义响应内容 Content-Type: application/json,避免作为 html 解析
CORS
参考链接:MDN-跨域资源共享 阮一峰:跨域资源共享 CORS 详解
CORS 是一个W3C标准,允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了AJAX只能同源使用的限制
(1)简单请求
(1)简单请求定义:
请求类型:get/post/head
HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:application/x-www-form-urlencoded、multipart/form-data、text/plain
(2)客户端行为:
浏览器会自动为跨域 ajax 请求添加 origin 字段
(3)服务端行为:
需要为响应 header 设置 Access-Control-Allow-Origin
(4)cookie:
CORS 请求默认不发送 cookie,即便本地有目标站点的 cookie,若要携带 cookie 请求,需要:
前端 XMLHttpRequest 对象指定 withCredentials = true
服务端响应头中 Access-Control-Allow-Credentials: true
服务端响应头中 Access-Control-Allow-Origin 不能为 *
cookie 依然遵循同源政策,只有用服务器域名设置的 cookie 才会上传,其他域名的 cookie 并不会上传,且原网页代码中的 document.cookie 也只能获取和当前文档同源的 cookie(即便这样,仍有 XSS 风险)
(2)非简单请求
非简单请求通常是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型、或自定义了头部信息
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
复杂请求的跨域,在正式请求前都会有预检请求,即 OPTIONS 请求(也叫 preflight),用于向服务器请求权限信息,除了 origin 字段,还有
Access-Control-Request-Method:必须字段,用来列出 CORS 请求会用到哪些 HTTP 方法
Access-Control-Request-Headers:逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段,上例是 X-Custom-Header
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
- 如果服务端支持 CORS 时,会返回相应的 access-control-allow-origin
- 如果服务器否定 “预检” 请求,会返回一个正常的 HTTP 回应,但是没有任何 CORS 相关的头信息字段,浏览器就会认定服务器不同意预检请求,因此触发一个错误,可被 XMLHttpRequest 对象的 onerror 回调捕获
当服务器通过预检请求后,之后客户端的每次请求都会设置 origin,而服务端的响应每次也都带有 access-control-allow-origin
XSS
参考链接:前端安全系列(一):如何防止XSS攻击?
XSS (Cross Site Scripting),即跨站脚本攻击,攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。
攻击分类
根据攻击的来源,XSS 攻击可分为存储型、反射型和 DOM 型三种。
类型 | 存储区(恶意代码存放的位置) | 插入点(由谁取得恶意代码,并插入到网页上) |
存储型 XSS | 后端数据库 | HTML |
反射型 XSS | URL | HTML |
DOM 型 XSS | 后端数据库/前端存储/URL | 前端 JavaScript |
反射型 XSS 举例
假设前端的搜索页面如下,根据 url 的关键词获取参数
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜索</button>
<div>
您搜索的关键词是:<%= getParameter("keyword") %>
</div>
当 url 是 http://xxx/search?keyword="><script>alert(‘XSS’);</script>",确认后页面会弹出两个 alert,因为服务端会解析出请求参数 keyword,得到 ><script>alert(‘XSS’);</script>,拼接到 HTML 中返回给浏览器。形成了如下的 HTML:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜索</button>
<div>
您搜索的关键词是:"><script>alert('XSS');</script>
</div>
浏览器无法分辨出 <script>alert(‘XSS’);</script> 是恶意代码,因而将其执行。这是反射型 XSS 的例子,存储型与之类似,不过恶意代码已经提前存在于后端数据库中
DOM 型 XSS 举例