Android 組件邏輯漏洞漫談
前言
隨著社會(huì)越來(lái)越重視安全性,各種防御性編程或者漏洞緩解措施逐漸被加到了操作系統(tǒng)中,比如代碼簽名、指針簽名、地址隨機(jī)化、隔離堆等等,許多常見(jiàn)的內(nèi)存破壞漏洞在這些緩解措施之下往往很難進(jìn)行穩(wěn)定的利用。因此,攻擊者們的目光也逐漸更多地投入到邏輯漏洞上。邏輯漏洞通常具有很好的穩(wěn)定性,不用受到風(fēng)水的影響;但同時(shí)也隱藏得較深、混跡在大量業(yè)務(wù)代碼中難以發(fā)現(xiàn)。而且由于形式各異,不太具有通用性,從投入產(chǎn)出比的角度來(lái)看可能不是一個(gè)高優(yōu)先級(jí)的研究方向。但無(wú)論如何,這都始終是一個(gè)值得關(guān)注的攻擊面。因此,本文就以 Android 平臺(tái)為目標(biāo)介紹一些常見(jiàn)的邏輯漏洞。
四大組件
接觸過(guò) Android 的人應(yīng)該都聽(tīng)說(shuō)過(guò) “四大組件”,開(kāi)發(fā)應(yīng)用首先需要學(xué)習(xí)的就是各個(gè)組件的生命周期。所謂四大組件,分別是指 Activity、Service、Broadcast Receiver 和 Content Provider,關(guān)于這些組件的實(shí)現(xiàn)細(xì)節(jié)可以參考官方的文檔: Application Fundamentals[1]。
在安全研究中,四大組件值得我們特別關(guān)注,因?yàn)檫@是應(yīng)用與外界溝通的重要橋梁,甚至在應(yīng)用內(nèi)部也是通過(guò)這些組件構(gòu)建起了相互間松耦合的聯(lián)系。比如應(yīng)用本身可以不申請(qǐng)相機(jī)權(quán)限,但可以通過(guò)組件間的相互通信讓?zhuān)ㄏ到y(tǒng))相機(jī)應(yīng)用打開(kāi)攝像頭并取得拍到的照片,仿佛是自身進(jìn)行拍照的一樣。
而在組件交互的過(guò)程中,最為核心的數(shù)據(jù)結(jié)構(gòu)就是 Intent[2],這是大部分組件之間進(jìn)行通信的載體。
Intent 101
根據(jù)官方的說(shuō)法,Intent 是 “對(duì)某種要執(zhí)行的操作的抽象描述”,直譯過(guò)來(lái)也可以叫做 “意圖”,比如說(shuō)想要打開(kāi)攝像機(jī)拍照、想要打開(kāi)瀏覽器訪(fǎng)問(wèn)網(wǎng)址,想要打開(kāi)設(shè)置界面,……都可以用 Intent 來(lái)描述。
Intent 的主要形式有兩種,分別是顯式 Intent 和隱式 Intent;二者的差別主要在于前者顯式指定了 Component,后者沒(méi)有指定 Component,但是會(huì)通過(guò)足夠的信息去幫助系統(tǒng)去理解意圖,比如 ACTION、CATAGORY 等。
Intent 的最主要功能是用來(lái)啟動(dòng) Activity,因此我們以這個(gè)場(chǎng)景為例,從源碼中分析一下 Intent 的具體實(shí)現(xiàn)。啟動(dòng) Activity 的常規(guī)代碼片段如下:
Intent intent = new Intent(context, SomeActivity.class);
startActivity(intent);這里用的是顯式 Intent,但不是重點(diǎn)。一般在某個(gè) Activity 中調(diào)用,因此調(diào)用的是 Activity.startActivity,代碼在 frameworks/base/core/java/android/app/Activity.java 中,這里不復(fù)制粘貼了,總而言之調(diào)用鏈路如下:
? Activity.startActivity()
? Activity.startActivityForResult()
? Instrumentation.execStartActivity()
? ActivityTaskManager.getService().startActivity()
? IActivityTaskManager.startActivity()
最后一條調(diào)用是個(gè)接口,這是個(gè)很常見(jiàn)的 pattern 了,下一步應(yīng)該去找其實(shí)現(xiàn),不出意外的話(huà)這個(gè)實(shí)現(xiàn)應(yīng)該在另一個(gè)進(jìn)程中。事實(shí)上也正是在 system_server 中:
? ActivityTaskManagerService.startActivity()
? ActivityTaskManagerService.startActivityAsUser()
? ActivityStarter.execute()
最后一個(gè)方法通過(guò)前面?zhèn)魅氲男畔⑷?zhǔn)備啟動(dòng) Activity,包括 caller、userId、flags,callingPackage 以及最重要的 intent 信息,如下:
private int startActivityAsUser(...) {
// ...
return getActivityStartController()
.obtainStarter(
intent, "startActivityAsUser")
.setCaller(caller)
.setCallingPackage(callingPackage)
.setCallingFeatureId(callingFeatureId)
.setResolvedType(resolvedType)
.setResultTo(resultTo)
.setResultWho(resultWho)
.setRequestCode(requestCode)
.setStartFlags(startFlags)
.setProfilerInfo(profilerInfo)
.setActivityOptions(bOptions)
.setUserId(userId)
.execute();
}ActivityStarter.execute() 主要的邏輯如下:
int execute() {
// ...
if (mRequest.activityInfo == null) {
mRequest.resolveActivity(mSupervisor);
}
res = resolveToHeavyWeightSwitcherIfNeeded();
res = executeRequest(mRequest);
}其中,resolveActivity 用于獲取要啟動(dòng)的 Activity 信息,例如在隱式啟動(dòng)的情況下,可能有多個(gè)符合要求的目標(biāo),也會(huì)彈出菜單詢(xún)問(wèn)用戶(hù)選用哪個(gè)應(yīng)用打開(kāi)。executeRequest 中則主要進(jìn)行相關(guān)權(quán)限檢查,在所有權(quán)限滿(mǎn)足條件后再調(diào)用 startActivityUnchecked 去執(zhí)行真正的調(diào)用。
其中大部分流程我在 Android12 應(yīng)用啟動(dòng)流程分析[3] 中已經(jīng)介紹過(guò)了,這里更多是關(guān)注 Intent 本身的作用。從上面的分析中發(fā)現(xiàn),可以將其看作是多進(jìn)程通信中的消息載體,而其源碼定義也能看出 Intent 本身是可以可以序列化并在進(jìn)程間傳遞的結(jié)構(gòu)。
public class Intent implements Parcelable, Cloneable { ... }Intent 本身有很多方法和屬性,這里暫時(shí)先不展開(kāi),后面介紹具體漏洞的時(shí)候再進(jìn)行針對(duì)性的分析。后文主要以四大組件為著手點(diǎn),分別介紹一些常見(jiàn)的漏洞模式和設(shè)計(jì)陷阱。
Activity
Activity[4] 也稱(chēng)為活動(dòng)窗口,是與用戶(hù)直接交互的圖形界面。APP 主要開(kāi)發(fā)工作之一就是設(shè)計(jì)各個(gè) activity,并規(guī)劃他們之間的跳轉(zhuǎn)和連結(jié)。通常一個(gè) activity 表示一個(gè)全屏的活動(dòng)窗口,但也可以有其他的存在形式,比如浮動(dòng)窗口、多窗口等。作為 UI 窗口,一般使用 XML 文件進(jìn)行布局,并繼承 Activity 類(lèi)實(shí)現(xiàn)其生命周期函數(shù) onCreate 和 onPause 等生命周期方法。
如果開(kāi)發(fā)者定義的 Activity 想通過(guò) Context.startActivity 啟動(dòng)的話(huà),就必須將其聲明到 APP 的 manifest 文件中,即 AndroidManifest.xml[5]。應(yīng)用被安裝時(shí),PackageManager 會(huì)解析其 manifest 文件中的相關(guān)信息并將其注冊(cè)到系統(tǒng)中,以便在 resolve 時(shí)進(jìn)行搜索。
在 adb shell 中可以通過(guò) am start-activity 去打開(kāi)指定的 Activity,通過(guò)指定 Intent 去進(jìn)行啟動(dòng):
am start-activity [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]
[--sampling INTERVAL] [--streaming] [-R COUNT] [-S]
[--track-allocation] [--user <USER_ID> | current] <INTENT>作為用戶(hù)界面的載體,Activity 承載了許多用戶(hù)輸入/處理、以及外部數(shù)據(jù)接收/展示等工作,因此是應(yīng)用對(duì)外的一個(gè)主要攻擊面。下面就介紹幾種較為常見(jiàn)的攻擊場(chǎng)景。
生命周期
Activity 經(jīng)典的生命周期圖示如下:

通常開(kāi)發(fā)者只需要實(shí)現(xiàn) onCreate 方法,但是對(duì)于一些復(fù)雜的業(yè)務(wù)場(chǎng)景,正確理解其生命周期也是很必要的。以筆者在內(nèi)測(cè)中遇到的某應(yīng)用為例,其中某個(gè) Activity 中執(zhí)行了一些敏感的操作,比如開(kāi)啟攝像頭推流,或者開(kāi)啟了錄音,但只在 onDestroy 中進(jìn)行了推流/錄音的關(guān)閉。這樣會(huì)導(dǎo)致在 APP 進(jìn)入后臺(tái)時(shí)候,這些操作依然在后臺(tái)運(yùn)行,攻擊者可以構(gòu)造任務(wù)棧使得受害者在面對(duì)惡意應(yīng)用的釣魚(yú)界面時(shí)候仍然執(zhí)行目標(biāo)應(yīng)用的后臺(tái)功能,從而形成特殊的釣魚(yú)場(chǎng)景。正確的做法應(yīng)該是在 onPaused 回調(diào)中對(duì)敏感操作進(jìn)行關(guān)閉。
攻擊者實(shí)際可以通過(guò)連續(xù)發(fā)送不同的 Intent 去精確控制目標(biāo) Activity 生命周期回調(diào)函數(shù)的觸發(fā)時(shí)機(jī),如果開(kāi)發(fā)時(shí)沒(méi)有注意也會(huì)造成應(yīng)用功能的狀態(tài)機(jī)異常甚至是安全問(wèn)題。
Implicit Exported
前面說(shuō)過(guò),開(kāi)發(fā)者定義的 Activity 要想使用 startActivity 去啟動(dòng),就必須在 AndroidManifest.xml 中使用 <activity> 進(jìn)行聲明,一個(gè)聲明的示例如下:
<activity xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@android:01030055" android:name="com.evilpan.RouterActivity">
<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="demo" android:host="router"/>
</intent-filter>
</activity>activity[6] 中支持許多屬性。其中一個(gè)重要的屬性就是 android:exported,表示當(dāng)前 Activity 是否可以被其他應(yīng)用的組件啟動(dòng)。該屬性有幾個(gè)特點(diǎn):
1. 屬性可以缺省,缺省值默認(rèn)為
false;2. 如果 Activity 沒(méi)有顯式設(shè)置該屬性,且該 Activity 中定義了
<intent-filter>,那么缺省值就默認(rèn)為true;
也就是說(shuō),開(kāi)發(fā)者可能沒(méi)有顯式指定 Activity 導(dǎo)出,但由于指定了 intent-filter,因此實(shí)際上也是導(dǎo)出的,即可以被其他應(yīng)用喚起對(duì)應(yīng)的 Activity。這種情況在早期很常見(jiàn),比如 APP 設(shè)計(jì)了一組更換密碼的界面,需要先輸入舊密碼然后再跳轉(zhuǎn)到輸入新密碼的界面,如果后者是導(dǎo)出的,攻擊者就可以直接喚起輸入新密碼的界面,從而繞過(guò)了舊密碼的校驗(yàn)邏輯。
Google 已經(jīng)深刻意識(shí)到了這個(gè)問(wèn)題,因此規(guī)定在 Android 12 之后,如果應(yīng)用的 Activity 中包含 intent-filter,就必須要顯式指定 android:exported 為 true 或者 false,不允許缺省。在 Android 12 中未顯式指定 exported 屬性且?guī)в?intent-filter 的 Activity 的應(yīng)用在安裝時(shí)候會(huì)直接被 PackageManager 拒絕。
Fragment Injection
Activity 作為 UI 核心組件,同時(shí)也支持模塊化的開(kāi)發(fā),比如在同一個(gè)界面中展示若干個(gè)可復(fù)用的子界面。隨著這種設(shè)計(jì)思路誕生的就是 Fragments[7] 組件,即 “片段”。使用 FragmentActivity 可以在一個(gè) Activity 中組合一個(gè)或者多個(gè)片段,方便進(jìn)行代碼復(fù)用,片段的生命周期受到宿主 Activity 的影響。
Fragment Injection 漏洞最早在 2013 年爆出,這里只介紹其原理,本節(jié)末尾附有原始的文章以及論文。漏洞的核心是系統(tǒng)提供的 PreferenceActivity 類(lèi),開(kāi)發(fā)者可以對(duì)其進(jìn)行繼承實(shí)現(xiàn)方便的設(shè)置功能,該類(lèi)的 onCreate 函數(shù)有下面的功能:
protected void onCreate() {
// ...
String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
// ...
if (initialFragment != null) {
switchToHeader(initialFragment, initialArguments);
}
}
private void switchToHeaderInner(String fragmentName, Bundle args) {
getFragmentManager().popBackStack(BACK_STACK_PREFS,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (!isValidFragment(fragmentName)) {
throw new IllegalArgumentException("Invalid fragment for this activity: "
+ fragmentName);
}
Fragment f = Fragment.instantiate(this, fragmentName, args);
}可以看到從 Intent 中獲取了一個(gè)字符串和一個(gè) Bundle 參數(shù),并最終傳入 switchToHeaderInner 中,用于實(shí)例化具體的 Fragment。實(shí)例化的過(guò)程如下:
public static Fragment instantiate(Context context, String fname, Bundle args) {
// ...
Class clazz = sClassMap.get(fname);
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(fname);
sClassMap.put(fname, clazz);
}
Fragment f = (Fragment)clazz.newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.mArguments = args;
}
return f;
}經(jīng)典的反射調(diào)用,將傳入的字符串實(shí)例化為 Java 類(lèi),并設(shè)置其參數(shù)。這是什么,這就是反序列化??!而實(shí)際的漏洞也正是出自這里,由于傳入的參數(shù)攻擊者可控,那么攻擊者可以將其設(shè)置為某個(gè)內(nèi)部類(lèi),從而觸及開(kāi)發(fā)者預(yù)期之外的功能。在原始的報(bào)告中,作者使用了 Settings 應(yīng)用中的某個(gè)設(shè)置 PIN 密碼的 Fragment 作為目標(biāo)傳入,這是個(gè)私有片段,從而導(dǎo)致了越權(quán)修改 PIN 碼的功能。在當(dāng)時(shí)的其他用戶(hù)應(yīng)用中,還有許多也使用了 PreferenceActivity,因此漏洞影響廣泛,而且造成的利用根據(jù)應(yīng)用本身的功能而異(也就是看有沒(méi)有好用的 Gadget)。
注意上面的代碼摘自最新的 Android 13,其中 switchToHeaderInner 方法加入了 isValidFragment 的判斷,這正是 Android 當(dāng)初的修復(fù)方案之一,即強(qiáng)制要求 PreferenceActivity 的子類(lèi)實(shí)現(xiàn)該方法,不然就在運(yùn)行時(shí)拋出異常。不過(guò)即便如此,還是有很多開(kāi)發(fā)者為了圖方便直接繼承然后返回 true 的。
Fragment Injection 看似是 PreferenceActivity 的問(wèn)題,但其核心還是對(duì)于不可信輸入的校驗(yàn)不完善,在后文的例子中我們會(huì)多次看到類(lèi)似的漏洞模式。
參考文章:
? A New Vulnerability in the Android Framework: Fragment Injection[8]
? ANDROID COLLAPSES INTO FRAGMENTS.pdf (wp)[9]
? Understanding fragment injection[10]
? How to fix Fragment Injection vulnerability[11]
點(diǎn)擊劫持
Activity 既然作為 UI 的主要載體,那么與用戶(hù)的交互也是其中關(guān)鍵的一項(xiàng)功能。在傳統(tǒng) Web 安全中就已經(jīng)有過(guò)點(diǎn)擊劫持的方法,即將目標(biāo)網(wǎng)站想要讓受害者點(diǎn)擊的案件放在指定位置(如iframe),并在宿主中使用相關(guān)組件對(duì)目標(biāo)進(jìn)行覆蓋和引導(dǎo),令受害者在不知不覺(jué)中執(zhí)行了敏感操作,比如點(diǎn)贊投幣收藏一鍵離職等。
Android 中也出現(xiàn)過(guò)類(lèi)似的攻擊手段,比如在系統(tǒng)的敏感彈窗前面覆蓋攻擊者自定義的 TextView,引導(dǎo)受害者確認(rèn)某些有害操作。當(dāng)然這需要攻擊者的應(yīng)用擁有浮窗權(quán)限(SYSTEM_ALERT_WINDOW),在較新的 Android 系統(tǒng)中,該權(quán)限的申請(qǐng)需要用戶(hù)多次的確認(rèn)。
近兩年中在 AOSP 中也出現(xiàn)過(guò)一些點(diǎn)擊劫持漏洞,包括但不限于:
? CVE-2020-0306:藍(lán)牙發(fā)現(xiàn)請(qǐng)求確認(rèn)框覆蓋
? CVE-2020-0394:藍(lán)牙配對(duì)對(duì)話(huà)框覆蓋
? CVE-2020-0015:證書(shū)安裝對(duì)話(huà)框覆蓋
? CVE-2021-0314:卸載確認(rèn)對(duì)話(huà)框覆蓋
? CVE-2021-0487:日歷調(diào)試對(duì)話(huà)框覆蓋
? ...
對(duì)于系統(tǒng)應(yīng)用而言,防御點(diǎn)擊劫持的方法一般是通過(guò)使用 android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS 權(quán)限并在布局參數(shù)中指定 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS 來(lái)防止 UI 被覆蓋。
而對(duì)于普通應(yīng)用,沒(méi)法申請(qǐng) HIDE_NON_SYSTEM_OVERLAY_WINDOWS 權(quán)限,防御措施一般有兩種,一是通過(guò)將布局的 filterTouchesWhenObscured 設(shè)置為 true 來(lái)禁止窗體被覆蓋后的輸入事件;二是重載 View.onFilterTouchEventForSecurity 方法,并在其中檢測(cè)其他應(yīng)用的覆蓋情況。在 Android 12 中系統(tǒng)已經(jīng)默認(rèn)開(kāi)啟了 filterTouchesWhenObscured 屬性,這也是 security by default 的一種經(jīng)典實(shí)現(xiàn)。
關(guān)于點(diǎn)擊劫持的操作細(xì)節(jié)和緩解方案,可以參考 OPPO 安全實(shí)驗(yàn)室的這篇文章: 《不可忽視的威脅:Android中的點(diǎn)擊劫持攻擊》
另外一個(gè)與點(diǎn)擊劫持類(lèi)似的漏洞稱(chēng)為 StrandHogg,細(xì)節(jié)可以參考下述的原始文章。其關(guān)鍵點(diǎn)是使用了 Activity 的 allowTaskReparenting 和 taskAffinity 屬性,將其任務(wù)棧偽裝成目標(biāo)應(yīng)用,這樣在打開(kāi)目標(biāo)應(yīng)用時(shí)由于 TaskStack 后進(jìn)先出的特性會(huì)導(dǎo)致用戶(hù)看到的是攻擊者的應(yīng)用,從而造成應(yīng)用的釣魚(yú)場(chǎng)景。
后來(lái)還是同一個(gè)安全團(tuán)隊(duì)有提出了 StrandHogg 2.0 版本,主要利用了 ActivityStarter 中的 AUTOMERGE 特性。假設(shè)有 A、B 兩個(gè)應(yīng)用,在 A1 中調(diào)用 startActivites(B1, A2, B2) 之后,任務(wù)棧會(huì)從 (A1, B1) 以及 (A2, B2) 合并為 (A1, B1, A2, B2),也就是在同一個(gè)任務(wù)棧中覆蓋了其他應(yīng)用的 Activity,從而導(dǎo)致釣魚(yú)場(chǎng)景。不過(guò)這個(gè)漏洞比較特化,因此谷歌很早就已經(jīng)修復(fù)了,詳情可以閱讀下面的參考文章:
? The StrandHogg vulnerability[12]
? StrandHogg 2.0 – New serious Android vulnerability[13]
? StrandHogg 2.0 (CVE-2020-0096) 修復(fù)方案[14]
Intent Redirection
Intent Redirection,顧名思義就是將用戶(hù)傳入的不可信輸入進(jìn)行了轉(zhuǎn)發(fā),類(lèi)似于服務(wù)端的 SSRF 漏洞。一個(gè)典型漏洞例子如下:
protected void onCreate (Bundle savedInstanceState) {
Intent target = (Intent) getIntent().getParcelableExtra("target");
startActivity(target);
}將用戶(hù)傳入的 target Parcelable 直接轉(zhuǎn)換成了 Intent 對(duì)象,并將這個(gè)對(duì)象作為 startActivity 的參數(shù)進(jìn)行調(diào)用。就這個(gè)例子而言,可能造成的危害就是攻擊者可以用任意構(gòu)造的 Intent 數(shù)據(jù)去啟動(dòng)目標(biāo) APP 中的任意應(yīng)用,哪怕是未導(dǎo)出的私有應(yīng)用。而目標(biāo)未導(dǎo)出的應(yīng)用中可能進(jìn)一步解析了攻擊者提供的 Intent 中的參數(shù),去造成進(jìn)一步的危害,比如在內(nèi)置 Webview 中執(zhí)行任意 Javascript 代碼,或者下載保存文件等。
實(shí)際上 Intent Redirection 除了可能用來(lái)啟動(dòng)私有 Activity 組件,還可以用于其他的的接口,包括:
? startActivity[15]
? startService[16]
? sendBroadcast[17]
? setResult[18]
注:每種方法可能還有若干衍生方法,比如 startActivityForResult
前面三個(gè)可能比較好理解,分別是啟動(dòng)界面、啟動(dòng)服務(wù)和發(fā)送廣播。最后一個(gè) setResult 可能會(huì)在排查的時(shí)候忽略,這主要用來(lái)給當(dāng)前 Activity 的調(diào)用者返回額外數(shù)據(jù),主要用于 startActivityForResult 的場(chǎng)景,這同樣也可能將用戶(hù)的不可信數(shù)據(jù)污染到調(diào)用者處。
從防御的角度上來(lái)說(shuō),建議不要直接把外部傳入的 Intent 作為參數(shù)發(fā)送到上述四個(gè)接口中,如果一定要這么做的話(huà),需要事先進(jìn)行充分的過(guò)濾和安全校驗(yàn),比如:
1. 將組件本身的
android:exported設(shè)置為false,但這只是防止了用戶(hù)主動(dòng)發(fā)送的數(shù)據(jù),無(wú)法攔截通過(guò)setResult返回的數(shù)據(jù);2. 確保獲取到的
Intent來(lái)自于可信的應(yīng)用,比如在組件上下文中調(diào)用getCallingActivity().getPackageName().equals("trust.app"),但注意惡意的應(yīng)用可以通過(guò)構(gòu)造數(shù)據(jù)令getCallingActivity返回null;3. 確保待轉(zhuǎn)發(fā)的
Intent沒(méi)有有害行為,比如 component 不指向自身的非導(dǎo)出組件,不帶有FLAG_GRANT_READ_URI_PERMISSION等(詳見(jiàn)后文 ContentProvider 漏洞);4. ...
但事實(shí)證明,即便是 Google 自己,也未必能夠確保完善的校驗(yàn)。無(wú)恒實(shí)驗(yàn)室近期提交的高危漏洞 CVE-2022-20223 就是個(gè)很典型的例子:
private void assertSafeToStartCustomActivity(Intent intent) {
// Activity can be started if it belongs to the same app
if (intent.getPackage() != null && intent.getPackage().equals(packageName)) {
return;
}
// Activity can be started if intent resolves to multiple activities
List<ResolveInfo> resolveInfos = AppRestrictionsFragment.this.mPackageManager
.queryIntentActivities(intent, 0 /* no flags */);
if (resolveInfos.size() != 1) {
return;
}
// Prevent potential privilege escalation
ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
if (!packageName.equals(activityInfo.packageName)) {
throw new SecurityException("Application " + packageName
+ " is not allowed to start activity " + intent);
}
}其中使用了 ActivityInfo.packageName 來(lái)判斷啟動(dòng)目標(biāo)的包名是否與當(dāng)前 caller 的包名一致,可事實(shí)上顯式 Intent 是通過(guò) componentName 去指定啟動(dòng)目標(biāo),優(yōu)先級(jí)高于 Intent.packageName 且后者可以被偽造,這就造成了檢查的繞過(guò)。上述短短幾行代碼中其實(shí)還有另外一個(gè)漏洞,感興趣的可以參考下面的參考鏈接。
因此,遇到潛在的 Intent 重定向問(wèn)題時(shí),可以多花點(diǎn)時(shí)間仔細(xì)審查,說(shuō)不定就能夠找到一個(gè)可利用的場(chǎng)景。
? Remediation for Intent Redirection Vulnerability[19]
Service
Service[20] 的主要功能有兩個(gè),一是給 APP 提供一個(gè)后臺(tái)的長(zhǎng)時(shí)間運(yùn)行環(huán)境,二是對(duì)外提供自身的服務(wù)。與 Activity 的定義類(lèi)似,Service 必須要在 manifest 中進(jìn)行聲明才能使用。注意 Service 中的代碼也是和 Activity 一樣運(yùn)行在主線(xiàn)程的,并且默認(rèn)和應(yīng)用處于進(jìn)程。
根據(jù) Service 的兩大主要功能區(qū)分,啟動(dòng) Service 也有對(duì)應(yīng)的兩種形式:
1.
Context.startService():?jiǎn)?dòng)后臺(tái)服務(wù)并讓系統(tǒng)進(jìn)行調(diào)度;2.
Context.bindService():讓?zhuān)ㄍ獠浚?yīng)用綁定服務(wù),并使用其提供的接口,可以理解為 RPC 的服務(wù)端;
兩種方式啟動(dòng)服務(wù)的生命周期圖示如下:

藍(lán)色部分都是在客戶(hù)端去進(jìn)行調(diào)用,系統(tǒng)收到請(qǐng)求后會(huì)啟動(dòng)對(duì)應(yīng)的服務(wù),如果對(duì)應(yīng)的進(jìn)程沒(méi)有啟動(dòng)也會(huì)通知 zygote 去啟動(dòng)。不管是哪種方法創(chuàng)建服務(wù),系統(tǒng)都會(huì)為其調(diào)用 onCreate 和 onDestroy 方法。整體流程和 Activity 的啟動(dòng)流程類(lèi)似,這里不再贅述。
shell 中同樣提供了 start-activity 命令來(lái)方便啟動(dòng)服務(wù):
am start-service [--user <USER_ID> | current] <INTENT>下面來(lái)介紹一些 Service 組件相關(guān)的漏洞。
生命周期
前面介紹了 Service 啟動(dòng)的生命周期,總體和 Activity 流程差不多,但需要注意有幾點(diǎn)不同:
1. 與 Activity 生命周期回調(diào)方法不同,不需要調(diào)用 Serivce 回調(diào)方法的超類(lèi)實(shí)現(xiàn),比如 onCreate、onDestory 等;
2.
Service類(lèi)的直接子類(lèi)運(yùn)行在主線(xiàn)程中,同時(shí)處理多個(gè)阻塞的請(qǐng)求時(shí)候一般需要在新建線(xiàn)程中執(zhí)行;3.
IntentService是 Service 的子類(lèi),被設(shè)計(jì)用于運(yùn)行在 Worker 線(xiàn)程中,可以串行處理多個(gè)阻塞的 Intent 請(qǐng)求;API-30 以后被標(biāo)記為廢棄接口,建議使用 WorkManager 或者 JobIntentService 去實(shí)現(xiàn);4. 客戶(hù)端通過(guò)
stopSelf或者stopService來(lái)停止綁定服務(wù),但服務(wù)端并沒(méi)有對(duì)應(yīng)的onStop回調(diào),只有在銷(xiāo)毀前收到onDestory;5. 前臺(tái)服務(wù)必須為狀態(tài)欄提供通知,讓用于意識(shí)到服務(wù)正在運(yùn)行;
對(duì)于綁定服務(wù)[21]而言,Android 系統(tǒng)會(huì)根據(jù)綁定的客戶(hù)端引用計(jì)數(shù)來(lái)自動(dòng)銷(xiāo)毀服務(wù),但如果服務(wù)實(shí)現(xiàn)了 onStartCommand() 回調(diào),就必須顯式地停止服務(wù),因?yàn)橄到y(tǒng)會(huì)將其視為已啟動(dòng)的狀態(tài)。此外,如果服務(wù)允許客戶(hù)端再次綁定,就需要實(shí)現(xiàn) onUnbind 方法并返回 true,這樣客戶(hù)端在下次綁定時(shí)候會(huì)接收到同樣的 IBinder,示例圖如下所示:

服務(wù)的聲明周期相比于 Activity 更加復(fù)雜,因?yàn)樯婕暗竭M(jìn)程間的綁定關(guān)系,因此也就更可能在不了解的情況下編寫(xiě)出不健壯甚至有問(wèn)題的代碼。
Implicit Export
和 Activity 一樣,Service 也要在 manifest 中使用 service[22] 去聲明,也有 android:exported 屬性。甚至關(guān)于該屬性的默認(rèn)值定義也是一樣的,即默認(rèn)是 false,但包含 intent-filter 時(shí),默認(rèn)就是 true。同樣,在 Android 12 及以后也強(qiáng)制性要求必須顯式指定服務(wù)的導(dǎo)出屬性。
服務(wù)劫持
與 Activity 不同的是,Android 不建議使用隱式 Intent 去啟動(dòng)服務(wù)。因?yàn)榉?wù)在后臺(tái)運(yùn)行,沒(méi)有可見(jiàn)的圖形界面,因此用戶(hù)看不到隱式 Intent 啟動(dòng)了哪個(gè)服務(wù),且發(fā)送者也不知道 Intent 會(huì)被誰(shuí)接收。
服務(wù)劫持是一個(gè)典型的漏洞,攻擊者可以為自己的 Service 聲明與目標(biāo)相同的 intent-filter 并設(shè)定更高的優(yōu)先級(jí),這樣可以截獲到本應(yīng)發(fā)往目標(biāo)服務(wù)的 Intent,如果帶有敏感信息的話(huà)還會(huì)造成數(shù)據(jù)泄露。
而在 bindService 中這種情況的危害則更加嚴(yán)重,攻擊者可以偽裝成目標(biāo) IPC 服務(wù)去返回錯(cuò)誤甚至是有害的數(shù)據(jù)。因此,在 Android 5.0 (API-21)開(kāi)始,使用隱式 Intent 去調(diào)用 bindService 會(huì)直接拋出異常。
如果待審計(jì)的目標(biāo)應(yīng)用在 Service 中提供了 intent-filter,那么就需要對(duì)其進(jìn)行重點(diǎn)排查。
AIDL
綁定服務(wù)可以被用來(lái)用作 IPC 服務(wù)端,如果服務(wù)端綁定的時(shí)候返回了 AIDL 接口的實(shí)例,那么就意味著客戶(hù)端可以調(diào)用該接口的任意方法。一個(gè)實(shí)際案例是 Tiktok 的 IndependentProcessDownloadService,在 DownloadService 的 onBind 中返回了上述 AIDL 接口的實(shí)例:
com/ss/android/socialbase/downloader/downloader/DownloadService.java:
if (this.downloadServiceHandler != null) {
return this.downloadServiceHandler.onBind(intent);
}而其中有個(gè) tryDownload 方法可以指定 url 和文件路徑將文件下載并保存到本地。雖然攻擊者沒(méi)有 AIDL 文件,但還是可以通過(guò)反射去構(gòu)造出合法的請(qǐng)求去進(jìn)行調(diào)用,PoC 中關(guān)鍵的代碼如下:
private ServiceConnection mServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName cName, IBinder service) {
processBinder(service);
}
public void onServiceDisconnected(ComponentName cName) { }
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent("com.ss.android.socialbase.downloader.remote");
intent.setClassName(
"com.zhiliaoapp.musically",
"com.ss.android.socialbase.downloader.downloader.IndependentProcessDownloadService");
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
private void processBinder(IBinder binder) {
ClassLoader cl = getForeignClassLoader(this, "com.zhiliaoapp.musically");
Object handler = cl.loadClass("com.ss.android.socialbase.downloader.downloader.i$a")
.getMethod("asInterface", IBinder.class)
.invoke(null, binder);
Object payload = getBinder(cl);
cl.loadClass("com.ss.android.socialbase.downloader.downloader.i")
.getMethod("tryDownload", cl.loadClass("com.ss.android.socialbase.downloader.model.a"))
.invoke(handler, payload);
}
private Object getBinder(ClassLoader cl) throws Throwable {
Class utilsClass = cl.loadClass("com.ss.android.socialbase.downloader.utils.g");
Class taskClass = cl.loadClass("com.ss.android.socialbase.downloader.model.DownloadTask");
return utilsClass.getDeclaredMethod("convertDownloadTaskToAidl", taskClass)
.invoke(null, getDownloadTask(taskClass, cl));
}關(guān)鍵在于使用 Context.getForeignClassLoader 獲取其他應(yīng)用的 ClassLoader。
漏洞細(xì)節(jié)參考: vulnerabilities in the TikTok Android app[23]
Intent Redirect
這個(gè)其實(shí)和 Activity 中的對(duì)應(yīng)漏洞類(lèi)似,客戶(hù)端啟動(dòng)/綁定 Service 的時(shí)候也指定了隱式或者顯式的 Intent,其中的不可信數(shù)據(jù)如果被服務(wù)端用來(lái)作為啟動(dòng)其他組件的參數(shù),就有可能造成一樣的 Intent 重定向問(wèn)題。注意除了 getIntent() 之外還有其他數(shù)據(jù)來(lái)源,比如服務(wù)中實(shí)現(xiàn)的 onHandleIntent 的參數(shù)。
其實(shí)最早提出 Intent 重定向危害的 "LaunchAnywhere" 漏洞就是出自系統(tǒng)服務(wù),準(zhǔn)確來(lái)說(shuō)是 AccountManagerService 的漏洞。AccountManager 正常的執(zhí)行流程為:
1. 普通應(yīng)用(記為 A)去請(qǐng)求添加某類(lèi)賬戶(hù),調(diào)用 AccountManager.addAccount;
2. AccountManager 會(huì)去查找提供賬號(hào)的應(yīng)用(記為 B)的 Authenticator 類(lèi);
3. AccountManager 調(diào)用 B 的 Authenticator.addAccount 方法;
4. AccountManager 根據(jù) B 返回的 Intent 去調(diào)起 B 的賬戶(hù)登錄界面(AccountManagerResponse.getParcelable);
在第 4 步時(shí),系統(tǒng)認(rèn)為 B 返回的數(shù)據(jù)是指向 B 的登陸界面的,但實(shí)際上 B 可以令其指向其他組件,甚至是系統(tǒng)組件,就造成了一個(gè) Intent 重定向的漏洞。這里 Intent 的來(lái)源比較曲折,但本質(zhì)還是攻擊者可控的。
關(guān)于該漏洞的細(xì)節(jié)和利用過(guò)程可參考:launchAnyWhere: Activity組件權(quán)限繞過(guò)漏洞解析(Google Bug 7699048 )[24]
Receiver
Broadcast Receiver[25],簡(jiǎn)稱(chēng) receiver,即廣播接收器。前面介紹的 Activity 和 Service 之間的聯(lián)動(dòng)都是一對(duì)一的,而很多情況下我們可能想要一對(duì)多或者多對(duì)多的通信方案,廣播就承擔(dān)了這個(gè)功能。比如,Android 系統(tǒng)本身就會(huì)在發(fā)生各種事件的時(shí)候發(fā)送廣播通知所有感興趣的應(yīng)用,比如開(kāi)啟飛行模式、網(wǎng)絡(luò)狀態(tài)變化、電量不足等等。這是一種典型的發(fā)布/訂閱的設(shè)計(jì)模式,廣播數(shù)據(jù)的載體也同樣是 Intent。
與前面 Activity 與 Service 不同的是,Receiver 可以在 manifest 中進(jìn)行聲明注冊(cè),稱(chēng)為靜態(tài)注冊(cè);也可以在應(yīng)用運(yùn)行過(guò)程中進(jìn)行動(dòng)態(tài)注冊(cè)。但無(wú)論如何,定義的廣播接收器都要繼承自 BroadcastReceiver[26] 并實(shí)現(xiàn)其聲明周期方法 onReceive(context, intent)。
注意 BroadcastReceiver 的父類(lèi)是 Object,不像 Activity 與 Service 是 Context,因此 onReceive 還會(huì)額外傳入一個(gè) context 對(duì)象。
shell 中發(fā)送廣播的命令如下:
am broadcast [--user <USER_ID> | all | current] <INTENT>下面還是按順序介紹一些常見(jiàn)的問(wèn)題。
Implicit Export
使用靜態(tài)注冊(cè)的 receiver 倒沒(méi)什么特殊,示例如下:
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>同樣存在和之前一樣的默認(rèn) export 問(wèn)題,相信大家已經(jīng)看膩了,就不再啰嗦了。接著看動(dòng)態(tài)注冊(cè)的情況,比如:
BroadcastReceiver br = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);與清單中的定義相比,動(dòng)態(tài)注冊(cè)的方式可能更容易忽略導(dǎo)出權(quán)限的問(wèn)題。上述代碼片段動(dòng)態(tài)注冊(cè)了一個(gè)廣播,但沒(méi)有顯式聲明 exported 屬性,因此默認(rèn)是導(dǎo)出的。事實(shí)上使用 registerReceiver 似乎沒(méi)有簡(jiǎn)單的方法去設(shè)置 exported=false,而 Google 官方的建議是對(duì)于不需要導(dǎo)出的廣播接收器使用 LocalBroadcastManager.registerReceiver 進(jìn)行注冊(cè),或者在注冊(cè)的時(shí)候指定 permission 權(quán)限。
對(duì)于指定 permission 權(quán)限的情況,如果是自定義權(quán)限,需要在應(yīng)用清單中聲明,比如:
<permission android:name="com.evilpan.MY_PERMISSION"
android:protectionLevel="signature"/>
<uses-permission android:name="com.evilpan.MY_PERMISSION" />signature 表示只有在請(qǐng)求授權(quán)的應(yīng)用使用與聲明權(quán)限的應(yīng)用相同的證書(shū)進(jìn)行簽名時(shí)系統(tǒng)才會(huì)授予的權(quán)限。如果證書(shū)匹配,則系統(tǒng)會(huì)在不通知用戶(hù)或征得用戶(hù)明確許可的情況下自動(dòng)授予權(quán)限。詳見(jiàn) protectionLevel[27]。
最后在動(dòng)態(tài)注冊(cè)時(shí)指定該權(quán)限即可:
this.registerReceiver(br, filter, "com.evilpan.MY_PERMISSION", null);注冊(cè)未帶有權(quán)限限制的導(dǎo)出廣播接收器會(huì)導(dǎo)致接收到攻擊者偽造的惡意數(shù)據(jù),如果在 onReceive 時(shí)校驗(yàn)不當(dāng),可能會(huì)出現(xiàn)越權(quán)或者 Intent 重定向等漏洞,造成進(jìn)一步的安全危害。
這類(lèi)安全問(wèn)題很多,比較典型的就有 Pwn2Own 上用于攻破三星 Galaxy S8 的 PpmtReceiver 漏洞[28]。
信息泄露
上面主要是從限制廣播發(fā)送方的角度去設(shè)置權(quán)限,但其實(shí)這個(gè)權(quán)限也能限制廣播的接收方,只不過(guò)發(fā)送消息的時(shí)候要進(jìn)行額外的指定,比如要想只讓擁有上述權(quán)限的接收方受到廣播,則發(fā)送代碼如下:
Intent it = new Intent(this, ...);
it.putExtra("secret", "chicken2beautiful")
sendBroadcast(it, "com.evilpan.MY_PERMISSION");如果不帶第二個(gè)參數(shù)的話(huà),默認(rèn)是所有滿(mǎn)足條件的接受方都能受到廣播信息的。此時(shí)若是發(fā)送的 Intent 中帶有敏感數(shù)據(jù),就可能會(huì)造成信息泄露問(wèn)題。
一個(gè)實(shí)際案例就是 CVE-2018-9581[29],系統(tǒng)在廣播 android.net.wifi.RSSI_CHANGED 時(shí)攜帶了敏感數(shù)據(jù) RSSI,此廣播能被所有應(yīng)用接收,從而間接導(dǎo)致物理位置信息泄露。(搞笑?)
可見(jiàn)對(duì)于 Broadcast Receiver 而言,permission 標(biāo)簽的作用尤其明顯。對(duì)于系統(tǒng)廣播而言,比如 BOOT_COMPLETED,通常只有系統(tǒng)應(yīng)用才有權(quán)限發(fā)送。這都是在 framework 的 AndroidManifest.xml[30] 中進(jìn)行定義的。
而對(duì)于應(yīng)用的自定義廣播,通常是使用上述自定義權(quán)限,那么也就自然想到一個(gè)問(wèn)題,如果多個(gè)應(yīng)用定義了同一個(gè)權(quán)限會(huì)怎么樣?其實(shí)這是正是一個(gè)歷史漏洞,在早期 Android 的策略是優(yōu)先采用第一個(gè)定義的權(quán)限,但在 Andorid 5 之后就已經(jīng)明確定義了兩個(gè)應(yīng)用不同定義相同的權(quán)限(除非他們的簽名相同),否則后安裝的應(yīng)用會(huì)出現(xiàn) INSTALL_FAILED_DUPLICATE_PERMISSION 錯(cuò)誤警告。感興趣的考古愛(ài)好者可以參考下面的相關(guān)文章:
? Vulnerabilities with Custom Permissions[31]
? Custom Permission Vulnerability and the 'L' Developer Preview[32]
Intent Redirection
原理不多說(shuō)了,直接看案例吧。漏洞出在 Tiktok 的 NotificationBroadcastReceiver 中,定義了 intent-filter 導(dǎo)致組件默認(rèn)被設(shè)置為導(dǎo)出,因此可以接收到外部應(yīng)用的廣播,而且又將廣播中的不可信數(shù)據(jù)直接拿來(lái)啟動(dòng) Activity,如下:

漏洞細(xì)節(jié)可參考:Oversecured detects dangerous vulnerabilities in the TikTok Android app[33]
ContentProvider
Content Provider[34],即內(nèi)容提供程序,簡(jiǎn)稱(chēng)為 Provider。Android 應(yīng)用通常實(shí)現(xiàn)為 MVC 結(jié)構(gòu)(Model-View-Controller),Model 部分即為數(shù)據(jù)來(lái)源,供自身的 View 即圖形界面進(jìn)行展示。但有時(shí)候應(yīng)用會(huì)想要將自身的數(shù)據(jù)提供給其他數(shù)據(jù)使用,或者從其他應(yīng)用中獲取數(shù)據(jù)。
定義一個(gè) ContentProvider 的方式,只需要繼承自 ContentProvider[35] 類(lèi)并實(shí)現(xiàn)六個(gè)方法: query,insert, update, delete, getType 以及 onCreate。其中除了 onCreate 是系統(tǒng)在主線(xiàn)程調(diào)用的,其他方法都由客戶(hù)端程序進(jìn)行主動(dòng)調(diào)用。自定義的 provider 必須在程序清單中進(jìn)行聲明,后文會(huì)詳細(xì)介紹。
可以看到 Provider 主要實(shí)現(xiàn)了類(lèi)似數(shù)據(jù)庫(kù)的增刪改查接口,從客戶(hù)端來(lái)看,查詢(xún)過(guò)程也和查詢(xún)傳統(tǒng)數(shù)據(jù)庫(kù)類(lèi)似,例如,下面是查詢(xún)系統(tǒng)短信的代碼片段:
Cursor cursor = getContentResolver().query(
Telephony.Sms.Inbox.CONTENT_URI, // 指定要查詢(xún)的表名
new String[] { Telephony.Sms.Inbox.BODY }, // projection 指定索要查詢(xún)的列名
selectionClause, // 查詢(xún)的過(guò)濾條件
selectionArgs, // 查詢(xún)過(guò)濾的參數(shù)
Telephony.Sms.Inbox.DEFAULT_SORT_ORDER); // 返回結(jié)果的排序
while (cursor.moveToNext()) {
Log.i(TAG, "msg: " + cursor.getString(0));
}其中 ContentResolver 是 ContentInterface 子類(lèi),后者是 ContentProvider 的客戶(hù)端遠(yuǎn)程接口,可以實(shí)現(xiàn)其透明的遠(yuǎn)程代理調(diào)用。 content_uri 可以看作是查詢(xún)的表名,projection 可以看作是列名,返回的 cursor 是查詢(xún)結(jié)果行的迭代器。
與前面三個(gè)組件不同,在 shell 中訪(fǎng)問(wèn) provider 組件的工具是 content。
下面來(lái)介紹 Provider 中常見(jiàn)的問(wèn)題。
Permissions
鑒于 provider 作為數(shù)據(jù)載體,那么安全訪(fǎng)問(wèn)與權(quán)限控制自然是重中之重。例如上面代碼示例中訪(fǎng)問(wèn)短信的接口,如果所有人都能隨意訪(fǎng)問(wèn),那就明顯會(huì)帶來(lái)信息泄露問(wèn)題。前面簡(jiǎn)單提到過(guò),應(yīng)用中定義的 Provider 必須要在其程序清單文件中進(jìn)行聲明,使用的是 provider[36] 標(biāo)簽。其中有我們常見(jiàn)的 exported 屬性,表示是否可被外部訪(fǎng)問(wèn),permission 屬性則表示訪(fǎng)問(wèn)所需的權(quán)限,當(dāng)然也可以分別對(duì)讀寫(xiě)使用不同的權(quán)限,比如 readPermission/writePermission 屬性。
比如,前文提到的短信數(shù)據(jù)庫(kù)聲明如下:
<provider android:name="SmsProvider"
android:authorities="sms"
android:multiprocess="false"
android:exported="true"
android:singleUser="true"
android:readPermission="android.permission.READ_SMS" />其他應(yīng)用若想訪(fǎng)問(wèn),則需在清單文件中聲明請(qǐng)求對(duì)應(yīng)權(quán)限。
<uses-permission android:name="android.permission.READ_SMS" />這都很好理解,其他組件也有類(lèi)似的特性。除此之外,Provider 本身還提供了更為細(xì)粒度的權(quán)限控制,即 grantUriPermissions[37]。這是一個(gè)布爾值,表示是否允許臨時(shí)為客戶(hù)端授予該 provider 的訪(fǎng)問(wèn)權(quán)限。臨時(shí)授予權(quán)限的運(yùn)行流程一般如下:
1. 客戶(hù)端給 Provider 所在應(yīng)用發(fā)送一個(gè) Intent,指定想要訪(fǎng)問(wèn)的 Content URI,比如使用
startActivityForResult發(fā)送;2. 應(yīng)用收到 Intent 后,判斷是否授權(quán),如果確認(rèn)則準(zhǔn)備一個(gè) Intent,并設(shè)置好 flags 標(biāo)志位
FLAG_GRANT_[READ|WRITE]_URL_PERMISSION,表示允許讀/寫(xiě)對(duì)應(yīng)的 Content URI(可以不和請(qǐng)求的 URI 一致),最后使用setResult(code, intent)返回給客戶(hù)端;3. 客戶(hù)端的 onActivityResult 收到返回的 Intent,使用其中的 URI 來(lái)臨時(shí)對(duì)目標(biāo) Provider 進(jìn)行訪(fǎng)問(wèn);
以讀為例,Intent.flags 中如果包含 FLAG_GRANT_READ_URI_PERMISSION[38],那么該 Intent 的接收方(即客戶(hù)端)會(huì)被授予 Intent.data 部分 URI 的臨時(shí)讀取權(quán)限,直至接收方的生命周期結(jié)束。另外,Provider 應(yīng)用也可以主動(dòng)調(diào)用 Context.grantUriPermission 方法來(lái)授予目標(biāo)應(yīng)用對(duì)應(yīng)權(quán)限:
public abstract void grantUriPermission (String toPackage,
Uri uri,
int modeFlags)
public abstract void revokeUriPermission (String toPackage,
Uri uri,
int modeFlags)grantUriPermissions 屬性可以在 URI 粒度對(duì)權(quán)限進(jìn)行讀寫(xiě)控制,但有一個(gè)需要注意的點(diǎn):通過(guò) grantUriPermissions 臨時(shí)授予的權(quán)限,會(huì)無(wú)視 readPermission、writePermission、permission 和 exported 屬性施加的限制。也就是說(shuō),即便 exported=false,客戶(hù)端也沒(méi)有申請(qǐng)對(duì)應(yīng)的 uses-permission,可一旦被授予權(quán)限,依然可以訪(fǎng)問(wèn)對(duì)應(yīng)的 Content Provider!
另外,<provider> 還有一個(gè)子標(biāo)簽 grant-uri-permission[39],即便 grantUriPermissions 被設(shè)置為 false,通過(guò)臨時(shí)獲取權(quán)限依然可以訪(fǎng)問(wèn)該標(biāo)簽下定義的 URI 子集,該子集可以用前綴或者通配符去指定 URI 的可授權(quán)路徑范圍。
Provider 權(quán)限設(shè)置不當(dāng)可能會(huì)導(dǎo)致應(yīng)用數(shù)據(jù)被預(yù)期之外的惡意程序訪(fǎng)問(wèn),輕則導(dǎo)致信息泄露,重則會(huì)使得自身沙盒數(shù)據(jù)被覆蓋而導(dǎo)致 RCE,后文會(huì)看到多個(gè)這樣的案例。
FileProvider
前面說(shuō)過(guò)自定義 Provider 需要實(shí)現(xiàn)六個(gè)方法,但 Android 中已經(jīng)針對(duì)某些常用場(chǎng)景的 Provider 編寫(xiě)好了對(duì)應(yīng)的子類(lèi),用戶(hù)可根據(jù)需要繼承這些子類(lèi)并實(shí)現(xiàn)少部分子類(lèi)方法即可。其中一個(gè)常用場(chǎng)景就是用 ContentProvider 分享應(yīng)用的文件,系統(tǒng)提供了 FileProvider 來(lái)方便應(yīng)用自定義文件分享和訪(fǎng)問(wèn),但是使用不當(dāng)?shù)脑?huà)很可能會(huì)出現(xiàn)任意文件讀寫(xiě)的問(wèn)題。
FileProvider[40] 提供了使用 XML 去指定文件訪(fǎng)問(wèn)控制的功能,一般 Provider 應(yīng)用只需繼承 FileProvider 類(lèi):
public class MyFileProvider extends FileProvider {
public MyFileProvider() {
super(R.xml.file_paths)
}
}file_paths 是用戶(hù)自定義的 XML,也可以在清單文件中使用 meta-data 去指定:
<provider xmlns:android="http://schemas.android.com/apk/res/android" android:name="com.evilpan.MyFileProvider" android:exported="false" android:authorities="com.evilpan.fileprovider" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@7F15000E"/>
</provider>resource 指向 res/xml/file_paths.xml。該文件中定義了可供訪(fǎng)問(wèn)的文件路徑,F(xiàn)ileProvider 只會(huì)對(duì)提前指定的文件生成 Content URI。一個(gè)文件路徑配置示例如下:
<paths>
<root-path name="root" path=""/>
<files-path name="internal_files" path="."/>
<cache-path name="cache" path=""/>
<external-path name="external_files" path="images"/>
</paths>paths 標(biāo)簽支持多種類(lèi)型的子標(biāo)簽,分別對(duì)應(yīng)不同目錄的子路徑:
?
files-path: Context.getFilesDir()?
cache-path: Context.getCacheDir()?
external-path: Environment.getExternalStorageDirectory()?
external-files-path: Context.getExternalFilesDir()?
external-cache-path: Context.getExternalCacheDir()?
external-media-path: Context.getExternalMediaDirs()[0]
比較特殊的是 root-path,表示系統(tǒng)的根目錄 /。FileProvider 生成的 URI 格式一般是 content://authority/{name}/{path},比如對(duì)于上述 Provider,可用 content://com.evilpan.fileprovider/root/proc/self/maps 來(lái)訪(fǎng)問(wèn) /proc/self/maps 文件。
由此可見(jiàn),F(xiàn)ileProvider 指定 root-path 是一個(gè)危險(xiǎn)的標(biāo)志,一旦攻擊者獲得了臨時(shí)權(quán)限,就可以讀取所有應(yīng)用的私有數(shù)據(jù)。
比如,TikTok 歷史上就有過(guò)這么一個(gè)真實(shí)的漏洞:
<provider android:name="android.support.v4.content.FileProvider" android:exported="false" android:authorities="com.zhiliaoapp.musically.fileprovider" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/k86"/>
</provider>這里直接使用了 FileProvider,甚至都不需要繼承。xml/k86.xml 文件內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:amazon="http://schemas.amazon.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<root-path name="name" path=""/>
<external-path name="share_path0" path="share/"/>
<external-path name="download_path2" path="Download/"/>
<cache-path name="gif" path="gif/"/>
...
</paths>獲取臨時(shí)權(quán)限之后就可以實(shí)現(xiàn)應(yīng)用的任意文件讀寫(xiě)。
The Hidden ...
在 ContentProvider 類(lèi)中,除了前面說(shuō)過(guò)的 6 個(gè)必須實(shí)現(xiàn)的方法,還有一些其他隱藏的方法,一般使用默認(rèn)實(shí)現(xiàn),也可以被子類(lèi)覆蓋實(shí)現(xiàn),比如
? openFile
? openFileHelper
? call
? ...
這些隱藏的方法可能在不經(jīng)意間造成安全問(wèn)題,本節(jié)會(huì)通過(guò)一些案例去分析其中的原因。
openFile
如果 ContentProvider 想要實(shí)現(xiàn)共享文件讀寫(xiě)的功能,還可以通過(guò)覆蓋 openFile 方法去實(shí)現(xiàn),該方法的默認(rèn)實(shí)現(xiàn)會(huì)拋出 FileNotFoundException 異常。
雖然開(kāi)發(fā)者實(shí)現(xiàn)上不太會(huì)直接就返回打開(kāi)的本地文件,而是有選擇地返回某些子目錄文件。但是如果代碼寫(xiě)得不嚴(yán)謹(jǐn),就可能會(huì)出現(xiàn)路徑穿越等問(wèn)題,一個(gè)經(jīng)典的漏洞實(shí)現(xiàn)如下:
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File file = new File(getContext().getFilesDir(), uri.getPath());
if(file.exists()){
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
throw new FileNotFoundException(uri.getPath());
}另外一個(gè)同族的類(lèi)似方法是 openAssetFile,其默認(rèn)實(shí)現(xiàn)是調(diào)用 openFile:
public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
throws FileNotFoundException {
ParcelFileDescriptor fd = openFile(uri, mode);
return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
}有時(shí)候開(kāi)發(fā)者雖然知道要要防御路徑穿越,但防御的姿勢(shì)不對(duì),也存在被繞過(guò)的可能,比如:
public ParcelFileDescriptor openFile(Uri uri, String mode) {
File f = new File(DIR, uri.getLastPathSegment());
return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
}這里想用 getLastPathSegment 去只獲取最后一級(jí)的文件名,但實(shí)際上可以被 URL encode 的路徑繞過(guò),比如 %2F..%2F..path%2Fto%2Fsecret.txt 會(huì)返回 /../../path/to/secret.txt。
還有一種錯(cuò)誤的防御是使用 UriMatcher.match 方法去查找 ../,這也會(huì)被 URL 編碼繞過(guò)。正確的防御和過(guò)濾方式如下:
public ParcelFileDescriptor openFile (Uri uri, String mode) throws FileNotFoundException {
File f = new File(DIR, uri.getLastPathSegment());
if (!f.getCanonicalPath().startsWith(DIR)) {
throw new IllegalArgumentException();
}
return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
}詳見(jiàn):Path Traversal Vulnerability[41]
openFileHelper
ContentProvider 中還有一個(gè)鮮為人知的 openFileHelper 方法,其默認(rèn)實(shí)現(xiàn)是使用當(dāng)前 Provider 中的 _data 列數(shù)據(jù)去打開(kāi)文件,源碼如下:
protected final @NonNull ParcelFileDescriptor openFileHelper(@NonNull Uri uri,
@NonNull String mode) throws FileNotFoundException {
Cursor c = query(uri, new String[]{"_data"}, null, null, null);
int count = (c != null) ? c.getCount() : 0;
if (count != 1) {
// If there is not exactly one result, throw an appropriate
// exception.
if (c != null) {
c.close();
}
if (count == 0) {
throw new FileNotFoundException("No entry for " + uri);
}
throw new FileNotFoundException("Multiple items at " + uri);
}
c.moveToFirst();
int i = c.getColumnIndex("_data");
String path = (i >= 0 ? c.getString(i) : null);
c.close();
if (path == null) {
throw new FileNotFoundException("Column _data not found.");
}
int modeBits = ParcelFileDescriptor.parseMode(mode);
return ParcelFileDescriptor.open(new File(path), modeBits);
}這個(gè)方法的主要作用是方便子類(lèi)用于快速實(shí)現(xiàn) openFile 方法,通常不會(huì)直接在子類(lèi)去覆蓋。不過(guò)由于其中基于 _data 列去打開(kāi)文件的特性可能會(huì)攻擊者插入惡意數(shù)據(jù)后間接地實(shí)現(xiàn)任意文件讀寫(xiě)。
一個(gè)經(jīng)典案例就是三星手機(jī)的 SemClipboardProvider,在插入時(shí)未校驗(yàn)用戶(hù)數(shù)據(jù):
public Uri insert(Uri uri, ContentValues values) {
long row = this.database.insert(TABLE_NAME, "", values);
if (row > 0) {
Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException("Fail to add a new record into " + uri);
}而該 Provider 又在 system_server 進(jìn)程中,擁有極高的運(yùn)行權(quán)限,攻擊者通過(guò)利用這個(gè)漏洞去就能實(shí)現(xiàn)系統(tǒng)層面的任意文件讀寫(xiě),其 PoC 如下:
ContentValues vals = new ContentValues();
vals.put("_data", "/data/system/users/0/newFile.bin");
URI semclipboard_uri = URI.parse("content://com.sec.android.semclipboardprovider")
ContentResolver resolver = getContentResolver();
URI newFile_uri = resolver.insert(semclipboard_uri, vals);
return resolver.openFileDescriptor(newFile_uri, "w").getFd(); 該漏洞與其他漏洞一起曾被用于在野攻擊中,由 Google TAG 團(tuán)隊(duì)捕獲,對(duì)這一條 Fullchain 的分析可以參考 Project Zero 近期的文章:A Very Powerful Clipboard: Analysis of a Samsung in-the-wild exploit chain[42]
call
ContentProvider 中提供了 call 方法,用于實(shí)現(xiàn)調(diào)用服務(wù)端定義方法,其函數(shù)簽名如下:
public Bundle call (String authority,
String method,
String arg,
Bundle extras)
public Bundle call (String method,
String arg,
Bundle extras)默認(rèn)的實(shí)現(xiàn)是個(gè)空函數(shù)直接返回 null,開(kāi)發(fā)者可以通過(guò)覆蓋該函數(shù)去實(shí)現(xiàn)一些動(dòng)態(tài)方法,返回值也會(huì)傳回到調(diào)用者中。
看起來(lái)和常規(guī)的 RPC 調(diào)用類(lèi)似,但這里有個(gè)小陷阱,開(kāi)發(fā)者文檔中也特別標(biāo)注了:Android 系統(tǒng)并沒(méi)有對(duì) call 函數(shù)進(jìn)行權(quán)限檢查,因?yàn)橄到y(tǒng)不知道在 call 之中對(duì)數(shù)據(jù)進(jìn)行了讀還是寫(xiě),因此也就無(wú)法根據(jù) Manifest 中定義的權(quán)限約束進(jìn)行判斷。因此要求開(kāi)發(fā)者自己對(duì) call 中的邏輯進(jìn)行權(quán)限校驗(yàn)。
如果開(kāi)發(fā)者實(shí)現(xiàn)了該方法,但是又未進(jìn)行校驗(yàn)或者校驗(yàn)不充分,就可能出現(xiàn)越權(quán)調(diào)用的情況。一個(gè)案例是 CVE-2021-23243, OPPO 某系統(tǒng)應(yīng)用中 HostContentProviderBase 的 call 方法實(shí)現(xiàn)中,直接用 DexClassLoader 去加載了傳入 dex 文件,直接導(dǎo)致攻擊者的代碼在特權(quán)進(jìn)程中運(yùn)行,所有繼承該基類(lèi)的 Provider 都會(huì)受到影響 ()。
另外在某些系統(tǒng) Provider 中,可以通過(guò) call 方法去獲取某些遠(yuǎn)程對(duì)象實(shí)例,例如在文章 Android 中的特殊攻擊面(三)—— 隱蔽的 call 函數(shù)[43] 中,作者就通過(guò) SliceProvider 與 KeyguardSliceProvider 獲取到了系統(tǒng)應(yīng)用內(nèi)部的 PendingIntent 對(duì)象,進(jìn)一步利用實(shí)現(xiàn)了偽造任意廣播的功能。
其他
除了上述和四大組件直接相關(guān)的漏洞,Android 系統(tǒng)中還有許多不太好分類(lèi)的漏洞,本節(jié)主要挑選其中幾個(gè)最為常見(jiàn)的漏洞進(jìn)行簡(jiǎn)單介紹。
PendingIntent
PendingIntent[44] 是對(duì) Intent 的表示,本身并不是 Intent 對(duì)象,但是是一個(gè) Parcelable 對(duì)象。將該對(duì)象傳遞給其他應(yīng)用后,其他應(yīng)用就可以以發(fā)送方的身份去執(zhí)行所指向的 Intent 指定的操作。 PendingIntent 使用下述靜態(tài)方法之一進(jìn)行創(chuàng)建:
? getActivity(Context, int, Intent, int);
? getActivities(Context, int, Intent[], int);
? getBroadcast(Context, int, Intent, int);
? getService(Context, int, Intent, int);
PendingIntent 本身只是系統(tǒng)對(duì)原始數(shù)據(jù)描述符的一個(gè)引用,可以大致將其理解為 Intent 的指針。也因?yàn)槿绱?,即便?chuàng)建 PendingIntent 的應(yīng)用關(guān)閉后,其他應(yīng)用仍然可以使用該數(shù)據(jù)。如果原始應(yīng)用后來(lái)進(jìn)行了重啟并以同樣的參數(shù)創(chuàng)建了一個(gè) PendingIntent,那么實(shí)際上返回 PendingIntent 與之前創(chuàng)建的會(huì)指向同樣的 token。注意判斷 Intent 是否相同是使用 filterEquals[45] 方法,其中會(huì)判斷 action,data, type,identity,class,categories 是否相同,注意 extra 并不在此列,因此僅有 extra 不同的 Intent 也會(huì)被認(rèn)為是相等的。
由于 PendingIntent 可代表其他應(yīng)用的特性,在某些場(chǎng)景下可能被用于濫用。例如,如果開(kāi)發(fā)者創(chuàng)建了這樣一個(gè)默認(rèn)的 PendingIntent 并傳遞給其他應(yīng)用:
pi = PendingIntent.getActivity(this, 0, new Intent(), 0);
bundle.putParcelable("pi", pi)
// send bundle惡意的應(yīng)用在收到此 PendingIntent 后,可以獲取到原始的 intent,并使用 Intent.fillin 去填充空字段,如果原始 Intent 是上述空 Intent,那么攻擊者就可以將其修改為特定的 Intent,從而以目標(biāo)的身份去啟動(dòng)應(yīng)用,包括未導(dǎo)出的私有應(yīng)用。一個(gè)經(jīng)典的案例就是早期的 broadAnywhere[46] 漏洞,Android Settings 應(yīng)用中的 addAccount 方法內(nèi)創(chuàng)建了一個(gè) PendingIntent 廣播,但 intent 內(nèi)容為空,這導(dǎo)致收到 intent 的的惡意應(yīng)用可以 fillin 填充廣播的 action,從而實(shí)現(xiàn)越權(quán)發(fā)送系統(tǒng)廣播,實(shí)現(xiàn)偽造短信、回復(fù)出廠(chǎng)設(shè)置等功能。
為了緩解這類(lèi)問(wèn)題,Andorid 中對(duì) Intent.fillin 的改寫(xiě)做了諸多限制,比如已有的字段不能修改,component 和 selector 字段不能修改(除非額外設(shè)置 FILL_IN_COMPONENT/SELECTOR),隱式 Intent 的 action 不能修改等。
不過(guò)有研究者提出了針對(duì)隱式 Intent 的利用方法,即通過(guò)修改 flag 添加 FLAG_GRANT_WRITE_URI_PERMISSION,并修改 data 的 URI 指向受害者私有的 Provider,將 package 改為攻擊者;同時(shí)攻擊者在自身的 Activity 中聲明相同的 intent filter,這樣在轉(zhuǎn)發(fā) intent 時(shí)會(huì)啟動(dòng)攻擊者應(yīng)用,同時(shí)也獲取了目標(biāo)私有 Provider 的訪(fǎng)問(wèn)權(quán)限,從而實(shí)現(xiàn)私有文件竊取或者覆蓋。關(guān)于該攻擊思路詳情可以閱讀下面的參考文章。
? broadAnywhere:Broadcast組件權(quán)限繞過(guò)漏洞(Bug: 17356824)[47]
? PendingIntent重定向:一種針對(duì)安卓系統(tǒng)和流行App的通用提權(quán)方法——BlackHat EU 2021議題詳解(上)[48]
? PendingIntent重定向:一種針對(duì)安卓系統(tǒng)和流行App的通用提權(quán)方法——BlackHat EU 2021議題詳解(下)[49]
在 Android 12+ 之后,PendingIntent 在創(chuàng)建時(shí)候要求顯式指定
FLAG_MUTABLE或者FLAG_IMMUTABLE,表示是否可以修改。
DeepLink
在大部分操作系統(tǒng)中都有 deeplink 的概念,即通過(guò)自定義 schema 打開(kāi)特定的應(yīng)用。比如通過(guò)點(diǎn)擊 https://evilpan.com/ 可以喚起默認(rèn)瀏覽器打開(kāi)目標(biāo)網(wǎng)頁(yè),點(diǎn)擊 tel://10086 會(huì)喚起撥號(hào)界面,點(diǎn)擊 weixin://qr/xxx 會(huì)喚起微信,等等。其他系統(tǒng)暫且不論,在 Android 中這主要是通過(guò)隱式 Intent 去實(shí)現(xiàn)的。
應(yīng)用要想注冊(cè)類(lèi)似的自定義協(xié)議,需要在應(yīng)用清單文件中進(jìn)行聲明:
<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="weixin" android:host="qr"/>
</intent-filter>由于這類(lèi)隱式 Intent 可以直接通過(guò)點(diǎn)擊鏈接去觸發(fā),因此更受攻擊者喜愛(ài)。如果處理對(duì)應(yīng) Intent 的組件沒(méi)有過(guò)濾好用戶(hù)傳入的內(nèi)容,很可能會(huì)造成 1-click 的漏洞。相關(guān)案例可以參考文章:Android 中的特殊攻擊面(二)——危險(xiǎn)的deeplink
Webview
在 Andorid 系統(tǒng)中,Webview[50] 主要用于應(yīng)用在自身的 Activty 中展示網(wǎng)頁(yè)內(nèi)容,并提供了一些額外的接口來(lái)給開(kāi)發(fā)者實(shí)現(xiàn)自定義的控制。更高的拓展性也就意味著更多出錯(cuò)的可能,尤其是如今 Android 客戶(hù)端開(kāi)發(fā)式微,Java 開(kāi)發(fā)也朝著 “大前端” 的方向發(fā)展。原本許多使用原生應(yīng)用實(shí)現(xiàn)的邏輯逐漸轉(zhuǎn)移到了 web 頁(yè)面中,比如 h5、小程序等,這樣一來(lái),webview 的攻擊面也就擴(kuò)寬了不少。
常規(guī)的 Webview 安全問(wèn)題主要是在與一些配置的不安全,比如覆蓋 onReceivedSslError 忽略 SSL 錯(cuò)誤導(dǎo)致中間人攻擊,setAllowFileAccessFromFileURLs 導(dǎo)致本地私有文件泄露等。但現(xiàn)在的漏洞更多出在 JSBridge 上,這是 Java 代碼與網(wǎng)頁(yè)中的 JavaScript 代碼溝通的橋梁。
由于 Webview 或者說(shuō) JS 引擎的沙箱特性,網(wǎng)頁(yè)中的 Javascript 代碼本身無(wú)法執(zhí)行許多原生應(yīng)用才能執(zhí)行的操作,比如無(wú)法從 Javascript 中發(fā)送廣播,無(wú)法訪(fǎng)問(wèn)應(yīng)用文件等。而由于業(yè)務(wù)的復(fù)雜性,很多邏輯又必須在 Java 層甚至是 Native 層才能實(shí)現(xiàn),因此這就需要用到 JSBridage。傳統(tǒng)的 JSBridge 通過(guò) Webview.addJavascriptInterface 實(shí)現(xiàn),一個(gè)簡(jiǎn)單示例如下:
class JsObject {
@JavascriptInterface
public String toString() { return "injectedObject"; }
}
webview.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");Java 層返回?cái)?shù)據(jù)給 Javascript 一般是通過(guò)直接使用 loadUrl 執(zhí)行 JS 代碼實(shí)現(xiàn)。當(dāng)然除了這種方式注冊(cè) Bridge,還有很多應(yīng)用特異的實(shí)現(xiàn),比如使用 console.log 傳輸數(shù)據(jù)并在 Java 層使用 onConsoleMessage 回調(diào)去接收。但無(wú)論如何,這都導(dǎo)致攻擊面的增加,大型應(yīng)用甚至注冊(cè)了上百個(gè) jsapi 來(lái)供網(wǎng)頁(yè)調(diào)用。
從歷史漏洞來(lái)看,Webview 漏洞的成因主要是 jsapi 域名校驗(yàn)問(wèn)題和 Bridge 代碼本身的漏洞,由于篇幅原因就不展開(kāi)了。
后記
本文中主要通過(guò) Android 中的四大組件介紹了一系列相關(guān)的邏輯問(wèn)題,盡可能地囊括了筆者所了解的歷史漏洞。由于個(gè)人認(rèn)知水平有限,總是難免掛一漏萬(wàn),但即便如此,文章的篇幅還是比預(yù)想中的超出了億點(diǎn)點(diǎn)。從溫故知新的角度看,挖掘這類(lèi)邏輯漏洞最好的策略還是使用靜態(tài)分析工具,搜集更多 Sink 模式并編寫(xiě)有效的規(guī)則去進(jìn)行掃描,實(shí)在沒(méi)有條件的話(huà)用 (rip)grep 也是可以的。
參考資料
? Galaxy Leapfrogging 蓋樂(lè)世蛙跳 Pwning the Galaxy S8[51]
? Chainspotting: Building Exploit Chains with Logic Bugs[52] (如何用11個(gè)exp攻破三星S8[53])
? Huawei Mate 9 Pro Mobile Pwn2Own 2017[54]
? Detect dangerous vulnerabilities in the TikTok Android app - Oversecured[55]
? 魔形女漏洞白皮書(shū) - 京東探索研究院信息安全實(shí)驗(yàn)室[56]
? HACKING XIAOMI'S ANDROID APPS - Part1[57]
? Automating Pwn2Own with Jandroid[58]
引用鏈接
[1] Application Fundamentals: https://developer.android.com/guide/components/fundamentals[2] Intent: https://developer.android.com/reference/android/content/Intent[3] Android12 應(yīng)用啟動(dòng)流程分析: https://evilpan.com/2021/12/05/apk-startup/[4] Activity: https://developer.android.com/reference/android/app/Activity[5] AndroidManifest.xml: https://developer.android.com/guide/topics/manifest/manifest-intro[6] activity: https://developer.android.com/guide/topics/manifest/activity-element[7] Fragments: https://developer.android.com/guide/components/fragments[8] A New Vulnerability in the Android Framework: Fragment Injection: https://securityintelligence.com/new-vulnerability-android-framework-fragment-injection/[9] ANDROID COLLAPSES INTO FRAGMENTS.pdf (wp): https://securityintelligence.com/wp-content/uploads/2013/12/android-collapses-into-fragments.pdf[10] Understanding fragment injection: https://www.synopsys.com/blogs/software-security/fragment-injection/[11] How to fix Fragment Injection vulnerability: https://support.google.com/faqs/answer/7188427?hl=en[12] The StrandHogg vulnerability: https://promon.co/security-news/the-strandhogg-vulnerability/[13] StrandHogg 2.0 – New serious Android vulnerability: https://promon.co/resources/downloads/strandhogg-2-0-new-serious-android-vulnerability/[14] StrandHogg 2.0 (CVE-2020-0096) 修復(fù)方案: https://android.googlesource.com/platform/frameworks/base/+/a952197bd161ac0e03abc6acb5f48e4ec2a56e9d[15] startActivity: https://developer.android.com/reference/android/app/Activity#startActivity(android.content.Intent)[16] startService: https://developer.android.com/reference/android/content/Context#startService(android.content.Intent)[17] sendBroadcast: https://developer.android.com/reference/android/content/Context#sendBroadcast(android.content.Intent)[18] setResult: https://developer.android.com/reference/android/app/Activity#setResult(int,%20android.content.Intent)[19] Remediation for Intent Redirection Vulnerability: https://support.google.com/faqs/answer/9267555?hl=en[20] Service: https://developer.android.com/guide/components/services[21] 綁定服務(wù): https://developer.android.com/guide/components/bound-services[22] service: https://developer.android.com/guide/topics/manifest/service-element[23] vulnerabilities in the TikTok Android app: https://blog.oversecured.com/Oversecured-detects-dangerous-vulnerabilities-in-the-TikTok-Android-app/#vulnerability-via-independentprocessdownloadservice-aidl-interface[24] launchAnyWhere: Activity組件權(quán)限繞過(guò)漏洞解析(Google Bug 7699048 ): http://retme.net/index.php/2014/08/20/launchAnyWhere.html[25] Broadcast Receiver: https://developer.android.com/guide/components/broadcasts[26] BroadcastReceiver: https://developer.android.com/reference/android/content/BroadcastReceiver[27] protectionLevel: https://developer.android.com/reference/android/R.attr#protectionLevel[28] PpmtReceiver 漏洞: https://paper.seebug.org/1050/[29] CVE-2018-9581: https://wwws.nightwatchcybersecurity.com/2018/11/11/cve-2018-9581/[30] framework 的 AndroidManifest.xml: https://android.googlesource.com/platform/frameworks/base/+/master/core/res/AndroidManifest.xml[31] Vulnerabilities with Custom Permissions: https://github.com/commonsguy/cwac-security/blob/master/PERMS.md[32] Custom Permission Vulnerability and the 'L' Developer Preview: https://commonsware.com/blog/2014/08/04/custom-permission-vulnerability-l-developer-preview.html[33] Oversecured detects dangerous vulnerabilities in the TikTok Android app: https://blog.oversecured.com/Oversecured-detects-dangerous-vulnerabilities-in-the-TikTok-Android-app/[34] Content Provider: https://developer.android.com/guide/topics/providers/content-providers[35] ContentProvider: https://developer.android.com/reference/android/content/ContentProvider[36] provider: https://developer.android.com/guide/topics/manifest/provider-element[37] grantUriPermissions: https://developer.android.com/guide/topics/manifest/provider-element#gprmsn[38] FLAG_GRANT_READ_URI_PERMISSION: https://developer.android.com/reference/android/content/Intent.html#FLAG_GRANT_READ_URI_PERMISSION[39] grant-uri-permission: https://developer.android.com/guide/topics/manifest/grant-uri-permission-element[40] FileProvider: https://developer.android.com/reference/androidx/core/content/FileProvider[41] Path Traversal Vulnerability: https://support.google.com/faqs/answer/7496913?hl=en[42] A Very Powerful Clipboard: Analysis of a Samsung in-the-wild exploit chain: https://googleprojectzero.blogspot.com/2022/11/a-very-powerful-clipboard-samsung-in-the-wild-exploit-chain.html[43] Android 中的特殊攻擊面(三)—— 隱蔽的 call 函數(shù): https://paper.seebug.org/1269/[44] PendingIntent: https://developer.android.com/reference/android/app/PendingIntent[45] filterEquals: https://developer.android.com/reference/android/content/Intent#filterEquals(android.content.Intent)[46] broadAnywhere: https://android.googlesource.com/platform/packages/apps/Settings/+/f5d3e74ecc2b973941d8adbe40c6b23094b5abb7%5E%21/#F0[47] broadAnywhere:Broadcast組件權(quán)限繞過(guò)漏洞(Bug: 17356824): http://retme.net/index.php/2014/11/14/broadAnywhere-bug-17356824.html[48] PendingIntent重定向:一種針對(duì)安卓系統(tǒng)和流行App的通用提權(quán)方法——BlackHat EU 2021議題詳解(上): https://blog.csdn.net/weixin_59152315/article/details/123481053[49] PendingIntent重定向:一種針對(duì)安卓系統(tǒng)和流行App的通用提權(quán)方法——BlackHat EU 2021議題詳解(下): https://blog.csdn.net/weixin_59152315/article/details/123503289[50] Webview: https://developer.android.com/reference/android/webkit/WebView[51] Galaxy Leapfrogging 蓋樂(lè)世蛙跳 Pwning the Galaxy S8: https://paper.seebug.org/1050/[52] Chainspotting: Building Exploit Chains with Logic Bugs: https://labs.f-secure.com/archive/chainspotting-building-exploit-chains-with-logic-bugs/[53] 如何用11個(gè)exp攻破三星S8: https://paper.seebug.org/628/[54] Huawei Mate 9 Pro Mobile Pwn2Own 2017: https://labs.withsecure.com/publications/nhuawew-blog-post[55] Detect dangerous vulnerabilities in the TikTok Android app - Oversecured: https://blog.oversecured.com/Oversecured-detects-dangerous-vulnerabilities-in-the-TikTok-Android-app/[56] 魔形女漏洞白皮書(shū) - 京東探索研究院信息安全實(shí)驗(yàn)室: https://dawnslab.jd.com/mystique-paper/[57] HACKING XIAOMI'S ANDROID APPS - Part1: http://blog.takemyhand.xyz/2021/07/hacking-on-xiaomis-android-apps.html[58] Automating Pwn2Own with Jandroid: https://labs.f-secure.com/blog/automating-pwn2own-with-jandroid/
