国产精品夜色视频一级区_hh99m福利毛片_国产一区二区成人久久免费影院_伊人久久大香线蕉综合影院75_国产精品久久果冻传媒

您的位置:首頁 >聚焦 >

Android 組件邏輯漏洞漫談

2022-11-18 16:35:04    來源:程序員客棧
前言

隨著社會越來越重視安全性,各種防御性編程或者漏洞緩解措施逐漸被加到了操作系統(tǒng)中,比如代碼簽名、指針簽名、地址隨機化、隔離堆等等,許多常見的內存破壞漏洞在這些緩解措施之下往往很難進行穩(wěn)定的利用。因此,攻擊者們的目光也逐漸更多地投入到邏輯漏洞上。邏輯漏洞通常具有很好的穩(wěn)定性,不用受到風水的影響;但同時也隱藏得較深、混跡在大量業(yè)務代碼中難以發(fā)現(xiàn)。而且由于形式各異,不太具有通用性,從投入產出比的角度來看可能不是一個高優(yōu)先級的研究方向。但無論如何,這都始終是一個值得關注的攻擊面。因此,本文就以 Android 平臺為目標介紹一些常見的邏輯漏洞。


(相關資料圖)

四大組件

接觸過 Android 的人應該都聽說過 “四大組件”,開發(fā)應用首先需要學習的就是各個組件的生命周期。所謂四大組件,分別是指Activity、Service、Broadcast Receiver和Content Provider,關于這些組件的實現(xiàn)細節(jié)可以參考官方的文檔:Application Fundamentals[1]。

在安全研究中,四大組件值得我們特別關注,因為這是應用與外界溝通的重要橋梁,甚至在應用內部也是通過這些組件構建起了相互間松耦合的聯(lián)系。比如應用本身可以不申請相機權限,但可以通過組件間的相互通信讓(系統(tǒng))相機應用打開攝像頭并取得拍到的照片,仿佛是自身進行拍照的一樣。

而在組件交互的過程中,最為核心的數(shù)據(jù)結構就是Intent[2],這是大部分組件之間進行通信的載體。

Intent 101

根據(jù)官方的說法,Intent 是 “對某種要執(zhí)行的操作的抽象描述”,直譯過來也可以叫做 “意圖”,比如說想要打開攝像機拍照、想要打開瀏覽器訪問網址,想要打開設置界面,……都可以用 Intent 來描述。

Intent 的主要形式有兩種,分別是顯式 Intent 和隱式 Intent;二者的差別主要在于前者顯式指定了Component,后者沒有指定 Component,但是會通過足夠的信息去幫助系統(tǒng)去理解意圖,比如ACTION、CATAGORY等。

Intent 的最主要功能是用來啟動 Activity,因此我們以這個場景為例,從源碼中分析一下 Intent 的具體實現(xiàn)。啟動 Activity 的常規(guī)代碼片段如下:

Intentintent=newIntent(context,SomeActivity.class);startActivity(intent);

這里用的是顯式 Intent,但不是重點。一般在某個 Activity 中調用,因此調用的是Activity.startActivity,代碼在frameworks/base/core/java/android/app/Activity.java中,這里不復制粘貼了,總而言之調用鏈路如下:

?Activity.startActivity()

?Activity.startActivityForResult()

?Instrumentation.execStartActivity()

?ActivityTaskManager.getService().startActivity()

?IActivityTaskManager.startActivity()

最后一條調用是個接口,這是個很常見的 pattern 了,下一步應該去找其實現(xiàn),不出意外的話這個實現(xiàn)應該在另一個進程中。事實上也正是在system_server中:

?ActivityTaskManagerService.startActivity()

?ActivityTaskManagerService.startActivityAsUser()

?ActivityStarter.execute()

最后一個方法通過前面?zhèn)魅氲男畔⑷蕚鋯?Activity,包括 caller、userId、flags,callingPackage 以及最重要的 intent 信息,如下:

privateintstartActivityAsUser(...){//...returngetActivityStartController().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()主要的邏輯如下:

intexecute(){//...if(mRequest.activityInfo==null){mRequest.resolveActivity(mSupervisor);}res=resolveToHeavyWeightSwitcherIfNeeded();res=executeRequest(mRequest);}

其中,resolveActivity用于獲取要啟動的 Activity 信息,例如在隱式啟動的情況下,可能有多個符合要求的目標,也會彈出菜單詢問用戶選用哪個應用打開。executeRequest中則主要進行相關權限檢查,在所有權限滿足條件后再調用startActivityUnchecked去執(zhí)行真正的調用。

其中大部分流程我在Android12 應用啟動流程分析[3]中已經介紹過了,這里更多是關注 Intent 本身的作用。從上面的分析中發(fā)現(xiàn),可以將其看作是多進程通信中的消息載體,而其源碼定義也能看出 Intent 本身是可以可以序列化并在進程間傳遞的結構。

publicclassIntentimplementsParcelable,Cloneable{...}

Intent本身有很多方法和屬性,這里暫時先不展開,后面介紹具體漏洞的時候再進行針對性的分析。后文主要以四大組件為著手點,分別介紹一些常見的漏洞模式和設計陷阱。

Activity

Activity[4]也稱為活動窗口,是與用戶直接交互的圖形界面。APP 主要開發(fā)工作之一就是設計各個 activity,并規(guī)劃他們之間的跳轉和連結。通常一個 activity 表示一個全屏的活動窗口,但也可以有其他的存在形式,比如浮動窗口、多窗口等。作為 UI 窗口,一般使用 XML 文件進行布局,并繼承 Activity 類實現(xiàn)其生命周期函數(shù)onCreate和onPause等生命周期方法。

如果開發(fā)者定義的 Activity 想通過Context.startActivity啟動的話,就必須將其聲明到 APP 的 manifest 文件中,即AndroidManifest.xml[5]。應用被安裝時,PackageManager會解析其 manifest 文件中的相關信息并將其注冊到系統(tǒng)中,以便在resolve時進行搜索。

在 adb shell 中可以通過am start-activity去打開指定的 Activity,通過指定 Intent 去進行啟動:

amstart-activity[-D][-N][-W][-P][--start-profiler][--samplingINTERVAL][--streaming][-RCOUNT][-S][--track-allocation][--user|current]

作為用戶界面的載體,Activity 承載了許多用戶輸入/處理、以及外部數(shù)據(jù)接收/展示等工作,因此是應用對外的一個主要攻擊面。下面就介紹幾種較為常見的攻擊場景。

生命周期

Activity 經典的生命周期圖示如下:

Activity Lifecycle

通常開發(fā)者只需要實現(xiàn)onCreate方法,但是對于一些復雜的業(yè)務場景,正確理解其生命周期也是很必要的。以筆者在內測中遇到的某應用為例,其中某個 Activity 中執(zhí)行了一些敏感的操作,比如開啟攝像頭推流,或者開啟了錄音,但只在onDestroy中進行了推流/錄音的關閉。這樣會導致在 APP 進入后臺時候,這些操作依然在后臺運行,攻擊者可以構造任務棧使得受害者在面對惡意應用的釣魚界面時候仍然執(zhí)行目標應用的后臺功能,從而形成特殊的釣魚場景。正確的做法應該是在onPaused回調中對敏感操作進行關閉。

攻擊者實際可以通過連續(xù)發(fā)送不同的 Intent 去精確控制目標 Activity 生命周期回調函數(shù)的觸發(fā)時機,如果開發(fā)時沒有注意也會造成應用功能的狀態(tài)機異常甚至是安全問題。

Implicit Exported

前面說過,開發(fā)者定義的 Activity 要想使用startActivity去啟動,就必須在 AndroidManifest.xml 中使用進行聲明,一個聲明的示例如下:

activity[6]中支持許多屬性。其中一個重要的屬性就是android:exported,表示當前 Activity 是否可以被其他應用的組件啟動。該屬性有幾個特點:

1.屬性可以缺省,缺省值默認為false;

2.如果 Activity 沒有顯式設置該屬性,且該 Activity 中定義了,那么缺省值就默認為true;

也就是說,開發(fā)者可能沒有顯式指定 Activity 導出,但由于指定了intent-filter,因此實際上也是導出的,即可以被其他應用喚起對應的 Activity。這種情況在早期很常見,比如 APP 設計了一組更換密碼的界面,需要先輸入舊密碼然后再跳轉到輸入新密碼的界面,如果后者是導出的,攻擊者就可以直接喚起輸入新密碼的界面,從而繞過了舊密碼的校驗邏輯。

Google 已經深刻意識到了這個問題,因此規(guī)定在 Android 12 之后,如果應用的 Activity 中包含 intent-filter,就必須要顯式指定android:exported為 true 或者 false,不允許缺省。在 Android 12 中未顯式指定 exported 屬性且?guī)в?intent-filter 的 Activity 的應用在安裝時候會直接被 PackageManager 拒絕。

Fragment Injection

Activity 作為 UI 核心組件,同時也支持模塊化的開發(fā),比如在同一個界面中展示若干個可復用的子界面。隨著這種設計思路誕生的就是Fragments[7]組件,即 “片段”。使用FragmentActivity可以在一個 Activity 中組合一個或者多個片段,方便進行代碼復用,片段的生命周期受到宿主 Activity 的影響。

Fragment Injection 漏洞最早在 2013 年爆出,這里只介紹其原理,本節(jié)末尾附有原始的文章以及論文。漏洞的核心是系統(tǒng)提供的PreferenceActivity類,開發(fā)者可以對其進行繼承實現(xiàn)方便的設置功能,該類的 onCreate 函數(shù)有下面的功能:

protectedvoidonCreate(){//...StringinitialFragment=getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);BundleinitialArguments=getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);//...if(initialFragment!=null){switchToHeader(initialFragment,initialArguments);}}privatevoidswitchToHeaderInner(StringfragmentName,Bundleargs){getFragmentManager().popBackStack(BACK_STACK_PREFS,FragmentManager.POP_BACK_STACK_INCLUSIVE);if(!isValidFragment(fragmentName)){thrownewIllegalArgumentException("Invalidfragmentforthisactivity:"+fragmentName);}Fragmentf=Fragment.instantiate(this,fragmentName,args);}

可以看到從 Intent 中獲取了一個字符串和一個 Bundle 參數(shù),并最終傳入switchToHeaderInner中,用于實例化具體的Fragment。實例化的過程如下:

publicstaticFragmentinstantiate(Contextcontext,Stringfname,Bundleargs){//...Classclazz=sClassMap.get(fname);if(clazz==null){//Classnotfoundinthecache,seeifit"sreal,andtrytoadditclazz=context.getClassLoader().loadClass(fname);sClassMap.put(fname,clazz);}Fragmentf=(Fragment)clazz.newInstance();if(args!=null){args.setClassLoader(f.getClass().getClassLoader());f.mArguments=args;}returnf;}

經典的反射調用,將傳入的字符串實例化為 Java 類,并設置其參數(shù)。這是什么,這就是反序列化?。《鴮嶋H的漏洞也正是出自這里,由于傳入的參數(shù)攻擊者可控,那么攻擊者可以將其設置為某個內部類,從而觸及開發(fā)者預期之外的功能。在原始的報告中,作者使用了 Settings 應用中的某個設置 PIN 密碼的 Fragment 作為目標傳入,這是個私有片段,從而導致了越權修改 PIN 碼的功能。在當時的其他用戶應用中,還有許多也使用了 PreferenceActivity,因此漏洞影響廣泛,而且造成的利用根據(jù)應用本身的功能而異(也就是看有沒有好用的 Gadget)。

注意上面的代碼摘自最新的 Android 13,其中switchToHeaderInner方法加入了isValidFragment的判斷,這正是 Android 當初的修復方案之一,即強制要求 PreferenceActivity 的子類實現(xiàn)該方法,不然就在運行時拋出異常。不過即便如此,還是有很多開發(fā)者為了圖方便直接繼承然后返回true的。

Fragment Injection 看似是 PreferenceActivity 的問題,但其核心還是對于不可信輸入的校驗不完善,在后文的例子中我們會多次看到類似的漏洞模式。

參考文章:

?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]

點擊劫持

Activity 既然作為 UI 的主要載體,那么與用戶的交互也是其中關鍵的一項功能。在傳統(tǒng) Web 安全中就已經有過點擊劫持的方法,即將目標網站想要讓受害者點擊的案件放在指定位置(如iframe),并在宿主中使用相關組件對目標進行覆蓋和引導,令受害者在不知不覺中執(zhí)行了敏感操作,比如點贊投幣收藏一鍵離職等。

Android 中也出現(xiàn)過類似的攻擊手段,比如在系統(tǒng)的敏感彈窗前面覆蓋攻擊者自定義的 TextView,引導受害者確認某些有害操作。當然這需要攻擊者的應用擁有浮窗權限(SYSTEM_ALERT_WINDOW),在較新的 Android 系統(tǒng)中,該權限的申請需要用戶多次的確認。

近兩年中在 AOSP 中也出現(xiàn)過一些點擊劫持漏洞,包括但不限于:

?CVE-2020-0306:藍牙發(fā)現(xiàn)請求確認框覆蓋

?CVE-2020-0394:藍牙配對對話框覆蓋

?CVE-2020-0015:證書安裝對話框覆蓋

?CVE-2021-0314:卸載確認對話框覆蓋

?CVE-2021-0487:日歷調試對話框覆蓋

?...

對于系統(tǒng)應用而言,防御點擊劫持的方法一般是通過使用 android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS 權限并在布局參數(shù)中指定SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS來防止 UI 被覆蓋。

而對于普通應用,沒法申請 HIDE_NON_SYSTEM_OVERLAY_WINDOWS 權限,防御措施一般有兩種,一是通過將布局的filterTouchesWhenObscured設置為true來禁止窗體被覆蓋后的輸入事件;二是重載 View.onFilterTouchEventForSecurity 方法,并在其中檢測其他應用的覆蓋情況。在 Android 12 中系統(tǒng)已經默認開啟了 filterTouchesWhenObscured 屬性,這也是 security by default 的一種經典實現(xiàn)。

關于點擊劫持的操作細節(jié)和緩解方案,可以參考 OPPO 安全實驗室的這篇文章:《不可忽視的威脅:Android中的點擊劫持攻擊》

另外一個與點擊劫持類似的漏洞稱為 StrandHogg,細節(jié)可以參考下述的原始文章。其關鍵點是使用了 Activity 的allowTaskReparenting和taskAffinity屬性,將其任務棧偽裝成目標應用,這樣在打開目標應用時由于 TaskStack 后進先出的特性會導致用戶看到的是攻擊者的應用,從而造成應用的釣魚場景。

后來還是同一個安全團隊有提出了StrandHogg 2.0版本,主要利用了ActivityStarter中的 AUTOMERGE 特性。假設有 A、B 兩個應用,在 A1 中調用startActivites(B1, A2, B2)之后,任務棧會從 (A1, B1) 以及 (A2, B2) 合并為 (A1, B1, A2, B2),也就是在同一個任務棧中覆蓋了其他應用的 Activity,從而導致釣魚場景。不過這個漏洞比較特化,因此谷歌很早就已經修復了,詳情可以閱讀下面的參考文章:

?The StrandHogg vulnerability[12]

?StrandHogg 2.0 – New serious Android vulnerability[13]

?StrandHogg 2.0 (CVE-2020-0096) 修復方案[14]

Intent Redirection

Intent Redirection,顧名思義就是將用戶傳入的不可信輸入進行了轉發(fā),類似于服務端的 SSRF 漏洞。一個典型漏洞例子如下:

protectedvoidonCreate(BundlesavedInstanceState){Intenttarget=(Intent)getIntent().getParcelableExtra("target");startActivity(target);}

將用戶傳入的 targetParcelable直接轉換成了Intent對象,并將這個對象作為startActivity的參數(shù)進行調用。就這個例子而言,可能造成的危害就是攻擊者可以用任意構造的 Intent 數(shù)據(jù)去啟動目標 APP 中的任意應用,哪怕是未導出的私有應用。而目標未導出的應用中可能進一步解析了攻擊者提供的 Intent 中的參數(shù),去造成進一步的危害,比如在內置 Webview 中執(zhí)行任意 Javascript 代碼,或者下載保存文件等。

實際上 Intent Redirection 除了可能用來啟動私有 Activity 組件,還可以用于其他的的接口,包括:

?startActivity[15]

?startService[16]

?sendBroadcast[17]

?setResult[18]

注:每種方法可能還有若干衍生方法,比如 startActivityForResult

前面三個可能比較好理解,分別是啟動界面、啟動服務和發(fā)送廣播。最后一個setResult可能會在排查的時候忽略,這主要用來給當前 Activity 的調用者返回額外數(shù)據(jù),主要用于startActivityForResult的場景,這同樣也可能將用戶的不可信數(shù)據(jù)污染到調用者處。

從防御的角度上來說,建議不要直接把外部傳入的 Intent 作為參數(shù)發(fā)送到上述四個接口中,如果一定要這么做的話,需要事先進行充分的過濾和安全校驗,比如:

1.將組件本身的android:exported設置為false,但這只是防止了用戶主動發(fā)送的數(shù)據(jù),無法攔截通過setResult返回的數(shù)據(jù);

2.確保獲取到的Intent來自于可信的應用,比如在組件上下文中調用getCallingActivity().getPackageName().equals("trust.app"),但注意惡意的應用可以通過構造數(shù)據(jù)令getCallingActivity返回null;

3.確保待轉發(fā)的Intent沒有有害行為,比如 component 不指向自身的非導出組件,不帶有FLAG_GRANT_READ_URI_PERMISSION等(詳見后文 ContentProvider 漏洞);

4....

但事實證明,即便是 Google 自己,也未必能夠確保完善的校驗。無恒實驗室近期提交的高危漏洞CVE-2022-20223就是個很典型的例子:

privatevoidassertSafeToStartCustomActivity(Intentintent){//Activitycanbestartedifitbelongstothesameappif(intent.getPackage()!=null&&intent.getPackage().equals(packageName)){return;}//ActivitycanbestartedifintentresolvestomultipleactivitiesListresolveInfos=AppRestrictionsFragment.this.mPackageManager.queryIntentActivities(intent,0/*noflags*/);if(resolveInfos.size()!=1){return;}//PreventpotentialprivilegeescalationActivityInfoactivityInfo=resolveInfos.get(0).activityInfo;if(!packageName.equals(activityInfo.packageName)){thrownewSecurityException("Application"+packageName+"isnotallowedtostartactivity"+intent);}}

其中使用了ActivityInfo.packageName來判斷啟動目標的包名是否與當前 caller 的包名一致,可事實上顯式 Intent 是通過 componentName 去指定啟動目標,優(yōu)先級高于Intent.packageName且后者可以被偽造,這就造成了檢查的繞過。上述短短幾行代碼中其實還有另外一個漏洞,感興趣的可以參考下面的參考鏈接。

因此,遇到潛在的 Intent 重定向問題時,可以多花點時間仔細審查,說不定就能夠找到一個可利用的場景。

?Remediation for Intent Redirection Vulnerability[19]

?AOSP Bug Hunting with appshark (1): Intent Redirection

Service

Service[20]的主要功能有兩個,一是給 APP 提供一個后臺的長時間運行環(huán)境,二是對外提供自身的服務。與 Activity 的定義類似,Service 必須要在 manifest 中進行聲明才能使用。注意 Service 中的代碼也是和 Activity 一樣運行在主線程的,并且默認和應用處于進程。

根據(jù) Service 的兩大主要功能區(qū)分,啟動 Service 也有對應的兩種形式:

1.Context.startService():啟動后臺服務并讓系統(tǒng)進行調度;

2.Context.bindService():讓(外部)應用綁定服務,并使用其提供的接口,可以理解為 RPC 的服務端;

兩種方式啟動服務的生命周期圖示如下:

Service Lifecycle

藍色部分都是在客戶端去進行調用,系統(tǒng)收到請求后會啟動對應的服務,如果對應的進程沒有啟動也會通知 zygote 去啟動。不管是哪種方法創(chuàng)建服務,系統(tǒng)都會為其調用onCreate和onDestroy方法。整體流程和 Activity 的啟動流程類似,這里不再贅述。

shell 中同樣提供了start-activity命令來方便啟動服務:

amstart-service[--user|current]

下面來介紹一些 Service 組件相關的漏洞。

生命周期

前面介紹了 Service 啟動的生命周期,總體和 Activity 流程差不多,但需要注意有幾點不同:

1.與 Activity 生命周期回調方法不同,不需要調用 Serivce 回調方法的超類實現(xiàn),比如 onCreate、onDestory 等;

2.Service類的直接子類運行在主線程中,同時處理多個阻塞的請求時候一般需要在新建線程中執(zhí)行;

3.IntentService是 Service 的子類,被設計用于運行在 Worker 線程中,可以串行處理多個阻塞的 Intent 請求;API-30 以后被標記為廢棄接口,建議使用 WorkManager 或者 JobIntentService 去實現(xiàn);

4.客戶端通過stopSelf或者stopService來停止綁定服務,但服務端并沒有對應的onStop回調,只有在銷毀前收到onDestory;

5.前臺服務必須為狀態(tài)欄提供通知,讓用于意識到服務正在運行;

對于綁定服務[21]而言,Android 系統(tǒng)會根據(jù)綁定的客戶端引用計數(shù)來自動銷毀服務,但如果服務實現(xiàn)了onStartCommand()回調,就必須顯式地停止服務,因為系統(tǒng)會將其視為已啟動的狀態(tài)。此外,如果服務允許客戶端再次綁定,就需要實現(xiàn) onUnbind 方法并返回 true,這樣客戶端在下次綁定時候會接收到同樣的 IBinder,示例圖如下所示:

Rebind

服務的聲明周期相比于 Activity 更加復雜,因為涉及到進程間的綁定關系,因此也就更可能在不了解的情況下編寫出不健壯甚至有問題的代碼。

Implicit Export

和 Activity 一樣,Service 也要在 manifest 中使用service[22]去聲明,也有android:exported屬性。甚至關于該屬性的默認值定義也是一樣的,即默認是false,但包含 intent-filter 時,默認就是true。同樣,在 Android 12 及以后也強制性要求必須顯式指定服務的導出屬性。

服務劫持

與 Activity 不同的是,Android 不建議使用隱式 Intent 去啟動服務。因為服務在后臺運行,沒有可見的圖形界面,因此用戶看不到隱式 Intent 啟動了哪個服務,且發(fā)送者也不知道 Intent 會被誰接收。

服務劫持是一個典型的漏洞,攻擊者可以為自己的 Service 聲明與目標相同的 intent-filter 并設定更高的優(yōu)先級,這樣可以截獲到本應發(fā)往目標服務的 Intent,如果帶有敏感信息的話還會造成數(shù)據(jù)泄露。

而在bindService中這種情況的危害則更加嚴重,攻擊者可以偽裝成目標 IPC 服務去返回錯誤甚至是有害的數(shù)據(jù)。因此,在 Android 5.0 (API-21)開始,使用隱式 Intent 去調用 bindService 會直接拋出異常。

如果待審計的目標應用在 Service 中提供了intent-filter,那么就需要對其進行重點排查。

AIDL

綁定服務可以被用來用作 IPC 服務端,如果服務端綁定的時候返回了 AIDL 接口的實例,那么就意味著客戶端可以調用該接口的任意方法。一個實際案例是 Tiktok 的IndependentProcessDownloadService,在DownloadService的 onBind 中返回了上述 AIDL 接口的實例:

com/ss/android/socialbase/downloader/downloader/DownloadService.java:

if(this.downloadServiceHandler!=null){returnthis.downloadServiceHandler.onBind(intent);}

而其中有個tryDownload方法可以指定 url 和文件路徑將文件下載并保存到本地。雖然攻擊者沒有 AIDL 文件,但還是可以通過反射去構造出合法的請求去進行調用,PoC 中關鍵的代碼如下:

privateServiceConnectionmServiceConnection=newServiceConnection(){publicvoidonServiceConnected(ComponentNamecName,IBinderservice){processBinder(service);}publicvoidonServiceDisconnected(ComponentNamecName){}};protectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);Intentintent=newIntent("com.ss.android.socialbase.downloader.remote");intent.setClassName("com.zhiliaoapp.musically","com.ss.android.socialbase.downloader.downloader.IndependentProcessDownloadService");bindService(intent,mServiceConnection,BIND_AUTO_CREATE);}privatevoidprocessBinder(IBinderbinder){ClassLoadercl=getForeignClassLoader(this,"com.zhiliaoapp.musically");Objecthandler=cl.loadClass("com.ss.android.socialbase.downloader.downloader.i$a").getMethod("asInterface",IBinder.class).invoke(null,binder);Objectpayload=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);}privateObjectgetBinder(ClassLoadercl)throwsThrowable{ClassutilsClass=cl.loadClass("com.ss.android.socialbase.downloader.utils.g");ClasstaskClass=cl.loadClass("com.ss.android.socialbase.downloader.model.DownloadTask");returnutilsClass.getDeclaredMethod("convertDownloadTaskToAidl",taskClass).invoke(null,getDownloadTask(taskClass,cl));}

關鍵在于使用Context.getForeignClassLoader獲取其他應用的 ClassLoader。

漏洞細節(jié)參考:vulnerabilities in the TikTok Android app[23]

Intent Redirect

這個其實和 Activity 中的對應漏洞類似,客戶端啟動/綁定 Service 的時候也指定了隱式或者顯式的 Intent,其中的不可信數(shù)據(jù)如果被服務端用來作為啟動其他組件的參數(shù),就有可能造成一樣的 Intent 重定向問題。注意除了getIntent()之外還有其他數(shù)據(jù)來源,比如服務中實現(xiàn)的onHandleIntent的參數(shù)。

其實最早提出 Intent 重定向危害的 "LaunchAnywhere" 漏洞就是出自系統(tǒng)服務,準確來說是AccountManagerService的漏洞。AccountManager 正常的執(zhí)行流程為:

1.普通應用(記為 A)去請求添加某類賬戶,調用 AccountManager.addAccount;

2.AccountManager 會去查找提供賬號的應用(記為 B)的 Authenticator 類;

3.AccountManager 調用 B 的 Authenticator.addAccount 方法;

4.AccountManager 根據(jù) B 返回的 Intent 去調起 B 的賬戶登錄界面(AccountManagerResponse.getParcelable);

在第 4 步時,系統(tǒng)認為 B 返回的數(shù)據(jù)是指向 B 的登陸界面的,但實際上 B 可以令其指向其他組件,甚至是系統(tǒng)組件,就造成了一個 Intent 重定向的漏洞。這里 Intent 的來源比較曲折,但本質還是攻擊者可控的。

關于該漏洞的細節(jié)和利用過程可參考:launchAnyWhere: Activity組件權限繞過漏洞解析(Google Bug 7699048 )[24]

Receiver

Broadcast Receiver[25],簡稱 receiver,即廣播接收器。前面介紹的 Activity 和 Service 之間的聯(lián)動都是一對一的,而很多情況下我們可能想要一對多或者多對多的通信方案,廣播就承擔了這個功能。比如,Android 系統(tǒng)本身就會在發(fā)生各種事件的時候發(fā)送廣播通知所有感興趣的應用,比如開啟飛行模式、網絡狀態(tài)變化、電量不足等等。這是一種典型的發(fā)布/訂閱的設計模式,廣播數(shù)據(jù)的載體也同樣是Intent。

與前面 Activity 與 Service 不同的是,Receiver 可以在 manifest 中進行聲明注冊,稱為靜態(tài)注冊;也可以在應用運行過程中進行動態(tài)注冊。但無論如何,定義的廣播接收器都要繼承自BroadcastReceiver[26]并實現(xiàn)其聲明周期方法onReceive(context, intent)。

注意 BroadcastReceiver 的父類是 Object,不像 Activity 與 Service 是 Context,因此 onReceive 還會額外傳入一個 context 對象。

shell 中發(fā)送廣播的命令如下:

ambroadcast[--user|all|current]

下面還是按順序介紹一些常見的問題。

Implicit Export

使用靜態(tài)注冊的 receiver 倒沒什么特殊,示例如下:

同樣存在和之前一樣的默認 export 問題,相信大家已經看膩了,就不再啰嗦了。接著看動態(tài)注冊的情況,比如:

BroadcastReceiverbr=newMyBroadcastReceiver();IntentFilterfilter=newIntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);this.registerReceiver(br,filter);

與清單中的定義相比,動態(tài)注冊的方式可能更容易忽略導出權限的問題。上述代碼片段動態(tài)注冊了一個廣播,但沒有顯式聲明 exported 屬性,因此默認是導出的。事實上使用 registerReceiver 似乎沒有簡單的方法去設置exported=false,而 Google 官方的建議是對于不需要導出的廣播接收器使用LocalBroadcastManager.registerReceiver進行注冊,或者在注冊的時候指定 permission 權限。

對于指定 permission 權限的情況,如果是自定義權限,需要在應用清單中聲明,比如:

signature 表示只有在請求授權的應用使用與聲明權限的應用相同的證書進行簽名時系統(tǒng)才會授予的權限。如果證書匹配,則系統(tǒng)會在不通知用戶或征得用戶明確許可的情況下自動授予權限。詳見protectionLevel[27]。

最后在動態(tài)注冊時指定該權限即可:

this.registerReceiver(br,filter,"com.evilpan.MY_PERMISSION",null);

注冊未帶有權限限制的導出廣播接收器會導致接收到攻擊者偽造的惡意數(shù)據(jù),如果在 onReceive 時校驗不當,可能會出現(xiàn)越權或者 Intent 重定向等漏洞,造成進一步的安全危害。

這類安全問題很多,比較典型的就有 Pwn2Own 上用于攻破三星 Galaxy S8 的PpmtReceiver 漏洞[28]。

信息泄露

上面主要是從限制廣播發(fā)送方的角度去設置權限,但其實這個權限也能限制廣播的接收方,只不過發(fā)送消息的時候要進行額外的指定,比如要想只讓擁有上述權限的接收方受到廣播,則發(fā)送代碼如下:

Intentit=newIntent(this,...);it.putExtra("secret","chicken2beautiful")sendBroadcast(it,"com.evilpan.MY_PERMISSION");

如果不帶第二個參數(shù)的話,默認是所有滿足條件的接受方都能受到廣播信息的。此時若是發(fā)送的 Intent 中帶有敏感數(shù)據(jù),就可能會造成信息泄露問題。

一個實際案例就是CVE-2018-9581[29],系統(tǒng)在廣播 android.net.wifi.RSSI_CHANGED 時攜帶了敏感數(shù)據(jù) RSSI,此廣播能被所有應用接收,從而間接導致物理位置信息泄露。(搞笑?)

可見對于 Broadcast Receiver 而言,permission 標簽的作用尤其明顯。對于系統(tǒng)廣播而言,比如BOOT_COMPLETED,通常只有系統(tǒng)應用才有權限發(fā)送。這都是在framework 的 AndroidManifest.xml[30]中進行定義的。

而對于應用的自定義廣播,通常是使用上述自定義權限,那么也就自然想到一個問題,如果多個應用定義了同一個權限會怎么樣?其實這是正是一個歷史漏洞,在早期 Android 的策略是優(yōu)先采用第一個定義的權限,但在 Andorid 5 之后就已經明確定義了兩個應用不同定義相同的權限(除非他們的簽名相同),否則后安裝的應用會出現(xiàn)INSTALL_FAILED_DUPLICATE_PERMISSION錯誤警告。感興趣的考古愛好者可以參考下面的相關文章:

?Vulnerabilities with Custom Permissions[31]

?Custom Permission Vulnerability and the "L" Developer Preview[32]

Intent Redirection

原理不多說了,直接看案例吧。漏洞出在 Tiktok 的NotificationBroadcastReceiver中,定義了 intent-filter 導致組件默認被設置為導出,因此可以接收到外部應用的廣播,而且又將廣播中的不可信數(shù)據(jù)直接拿來啟動 Activity,如下:

NotificationBroadcastReceiver

漏洞細節(jié)可參考:Oversecured detects dangerous vulnerabilities in the TikTok Android app[33]

ContentProvider

Content Provider[34],即內容提供程序,簡稱為 Provider。Android 應用通常實現(xiàn)為 MVC 結構(Model-View-Controller),Model 部分即為數(shù)據(jù)來源,供自身的 View 即圖形界面進行展示。但有時候應用會想要將自身的數(shù)據(jù)提供給其他數(shù)據(jù)使用,或者從其他應用中獲取數(shù)據(jù)。

定義一個 ContentProvider 的方式,只需要繼承自ContentProvider[35]類并實現(xiàn)六個方法:query,insert,update,delete,getType以及onCreate。其中除了 onCreate 是系統(tǒng)在主線程調用的,其他方法都由客戶端程序進行主動調用。自定義的 provider 必須在程序清單中進行聲明,后文會詳細介紹。

可以看到 Provider 主要實現(xiàn)了類似數(shù)據(jù)庫的增刪改查接口,從客戶端來看,查詢過程也和查詢傳統(tǒng)數(shù)據(jù)庫類似,例如,下面是查詢系統(tǒng)短信的代碼片段:

Cursorcursor=getContentResolver().query(Telephony.Sms.Inbox.CONTENT_URI,//指定要查詢的表名newString[]{Telephony.Sms.Inbox.BODY},//projection指定索要查詢的列名selectionClause,//查詢的過濾條件selectionArgs,//查詢過濾的參數(shù)Telephony.Sms.Inbox.DEFAULT_SORT_ORDER);//返回結果的排序while(cursor.moveToNext()){Log.i(TAG,"msg:"+cursor.getString(0));}

其中ContentResolver是ContentInterface子類,后者是 ContentProvider 的客戶端遠程接口,可以實現(xiàn)其透明的遠程代理調用。content_uri可以看作是查詢的表名,projection可以看作是列名,返回的 cursor 是查詢結果行的迭代器。

與前面三個組件不同,在 shell 中訪問 provider 組件的工具是content。

下面來介紹 Provider 中常見的問題。

Permissions

鑒于 provider 作為數(shù)據(jù)載體,那么安全訪問與權限控制自然是重中之重。例如上面代碼示例中訪問短信的接口,如果所有人都能隨意訪問,那就明顯會帶來信息泄露問題。前面簡單提到過,應用中定義的 Provider 必須要在其程序清單文件中進行聲明,使用的是provider[36]標簽。其中有我們常見的exported屬性,表示是否可被外部訪問,permission屬性則表示訪問所需的權限,當然也可以分別對讀寫使用不同的權限,比如readPermission/writePermission屬性。

比如,前文提到的短信數(shù)據(jù)庫聲明如下:

其他應用若想訪問,則需在清單文件中聲明請求對應權限。

這都很好理解,其他組件也有類似的特性。除此之外,Provider 本身還提供了更為細粒度的權限控制,即grantUriPermissions[37]。這是一個布爾值,表示是否允許臨時為客戶端授予該 provider 的訪問權限。臨時授予權限的運行流程一般如下:

1.客戶端給 Provider 所在應用發(fā)送一個 Intent,指定想要訪問的 Content URI,比如使用startActivityForResult發(fā)送;

2.應用收到 Intent 后,判斷是否授權,如果確認則準備一個 Intent,并設置好 flags 標志位FLAG_GRANT_[READ|WRITE]_URL_PERMISSION,表示允許讀/寫對應的 Content URI(可以不和請求的 URI 一致),最后使用setResult(code, intent)返回給客戶端;

3.客戶端的 onActivityResult 收到返回的 Intent,使用其中的 URI 來臨時對目標 Provider 進行訪問;

以讀為例,Intent.flags中如果包含F(xiàn)LAG_GRANT_READ_URI_PERMISSION[38],那么該 Intent 的接收方(即客戶端)會被授予Intent.data部分 URI 的臨時讀取權限,直至接收方的生命周期結束。另外,Provider 應用也可以主動調用Context.grantUriPermission方法來授予目標應用對應權限:

publicabstractvoidgrantUriPermission(StringtoPackage,Uriuri,intmodeFlags)publicabstractvoidrevokeUriPermission(StringtoPackage,Uriuri,intmodeFlags)

grantUriPermissions 屬性可以在 URI 粒度對權限進行讀寫控制,但有一個需要注意的點:通過 grantUriPermissions 臨時授予的權限,會無視 readPermission、writePermission、permission 和 exported 屬性施加的限制。也就是說,即便exported=false,客戶端也沒有申請對應的uses-permission,可一旦被授予權限,依然可以訪問對應的 Content Provider!

另外,還有一個子標簽grant-uri-permission[39],即便 grantUriPermissions 被設置為false,通過臨時獲取權限依然可以訪問該標簽下定義的 URI 子集,該子集可以用前綴或者通配符去指定 URI 的可授權路徑范圍。

Provider 權限設置不當可能會導致應用數(shù)據(jù)被預期之外的惡意程序訪問,輕則導致信息泄露,重則會使得自身沙盒數(shù)據(jù)被覆蓋而導致 RCE,后文會看到多個這樣的案例。

FileProvider

前面說過自定義 Provider 需要實現(xiàn)六個方法,但 Android 中已經針對某些常用場景的 Provider 編寫好了對應的子類,用戶可根據(jù)需要繼承這些子類并實現(xiàn)少部分子類方法即可。其中一個常用場景就是用 ContentProvider 分享應用的文件,系統(tǒng)提供了FileProvider來方便應用自定義文件分享和訪問,但是使用不當?shù)脑捄芸赡軙霈F(xiàn)任意文件讀寫的問題。

FileProvider[40]提供了使用 XML 去指定文件訪問控制的功能,一般 Provider 應用只需繼承 FileProvider 類:

publicclassMyFileProviderextendsFileProvider{publicMyFileProvider(){super(R.xml.file_paths)}}

file_paths是用戶自定義的 XML,也可以在清單文件中使用meta-data去指定:

resource指向res/xml/file_paths.xml。該文件中定義了可供訪問的文件路徑,F(xiàn)ileProvider 只會對提前指定的文件生成 Content URI。一個文件路徑配置示例如下:

paths標簽支持多種類型的子標簽,分別對應不同目錄的子路徑:

?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},比如對于上述 Provider,可用content://com.evilpan.fileprovider/root/proc/self/maps來訪問/proc/self/maps文件。

由此可見,F(xiàn)ileProvider 指定root-path是一個危險的標志,一旦攻擊者獲得了臨時權限,就可以讀取所有應用的私有數(shù)據(jù)。

比如,TikTok 歷史上就有過這么一個真實的漏洞:

這里直接使用了FileProvider,甚至都不需要繼承。xml/k86.xml文件內容如下:

...

獲取臨時權限之后就可以實現(xiàn)應用的任意文件讀寫。

The Hidden ...

在 ContentProvider 類中,除了前面說過的 6 個必須實現(xiàn)的方法,還有一些其他隱藏的方法,一般使用默認實現(xiàn),也可以被子類覆蓋實現(xiàn),比如

?openFile

?openFileHelper

?call

?...

這些隱藏的方法可能在不經意間造成安全問題,本節(jié)會通過一些案例去分析其中的原因。

openFile

如果 ContentProvider 想要實現(xiàn)共享文件讀寫的功能,還可以通過覆蓋openFile方法去實現(xiàn),該方法的默認實現(xiàn)會拋出FileNotFoundException異常。

雖然開發(fā)者實現(xiàn)上不太會直接就返回打開的本地文件,而是有選擇地返回某些子目錄文件。但是如果代碼寫得不嚴謹,就可能會出現(xiàn)路徑穿越等問題,一個經典的漏洞實現(xiàn)如下:

@OverridepublicParcelFileDescriptoropenFile(Uriuri,Stringmode)throwsFileNotFoundException{Filefile=newFile(getContext().getFilesDir(),uri.getPath());if(file.exists()){returnParcelFileDescriptor.open(file,ParcelFileDescriptor.MODE_READ_ONLY);}thrownewFileNotFoundException(uri.getPath());}

另外一個同族的類似方法是openAssetFile,其默認實現(xiàn)是調用 openFile:

public@NullableAssetFileDescriptoropenAssetFile(@NonNullUriuri,@NonNullStringmode)throwsFileNotFoundException{ParcelFileDescriptorfd=openFile(uri,mode);returnfd!=null?newAssetFileDescriptor(fd,0,-1):null;}

有時候開發(fā)者雖然知道要要防御路徑穿越,但防御的姿勢不對,也存在被繞過的可能,比如:

publicParcelFileDescriptoropenFile(Uriuri,Stringmode){Filef=newFile(DIR,uri.getLastPathSegment());returnParcelFileDescriptor.open(f,ParcelFileDescriptor.MODE_READ_ONLY);}

這里想用getLastPathSegment去只獲取最后一級的文件名,但實際上可以被 URL encode 的路徑繞過,比如%2F..%2F..path%2Fto%2Fsecret.txt會返回/../../path/to/secret.txt。

還有一種錯誤的防御是使用UriMatcher.match方法去查找../,這也會被 URL 編碼繞過。正確的防御和過濾方式如下:

publicParcelFileDescriptoropenFile(Uriuri,Stringmode)throwsFileNotFoundException{Filef=newFile(DIR,uri.getLastPathSegment());if(!f.getCanonicalPath().startsWith(DIR)){thrownewIllegalArgumentException();}returnParcelFileDescriptor.open(f,ParcelFileDescriptor.MODE_READ_ONLY);}

詳見:Path Traversal Vulnerability[41]

openFileHelper

ContentProvider 中還有一個鮮為人知的openFileHelper方法,其默認實現(xiàn)是使用當前 Provider 中的_data列數(shù)據(jù)去打開文件,源碼如下:

protectedfinal@NonNullParcelFileDescriptoropenFileHelper(@NonNullUriuri,@NonNullStringmode)throwsFileNotFoundException{Cursorc=query(uri,newString[]{"_data"},null,null,null);intcount=(c!=null)?c.getCount():0;if(count!=1){//Ifthereisnotexactlyoneresult,throwanappropriate//exception.if(c!=null){c.close();}if(count==0){thrownewFileNotFoundException("Noentryfor"+uri);}thrownewFileNotFoundException("Multipleitemsat"+uri);}c.moveToFirst();inti=c.getColumnIndex("_data");Stringpath=(i>=0?c.getString(i):null);c.close();if(path==null){thrownewFileNotFoundException("Column_datanotfound.");}intmodeBits=ParcelFileDescriptor.parseMode(mode);returnParcelFileDescriptor.open(newFile(path),modeBits);}

這個方法的主要作用是方便子類用于快速實現(xiàn)openFile方法,通常不會直接在子類去覆蓋。不過由于其中基于_data列去打開文件的特性可能會攻擊者插入惡意數(shù)據(jù)后間接地實現(xiàn)任意文件讀寫。

一個經典案例就是三星手機的SemClipboardProvider,在插入時未校驗用戶數(shù)據(jù):

publicUriinsert(Uriuri,ContentValuesvalues){longrow=this.database.insert(TABLE_NAME,"",values);if(row>0){UrinewUri=ContentUris.withAppendedId(CONTENT_URI,row);getContext().getContentResolver().notifyChange(newUri,null);returnnewUri;}thrownewSQLException("Failtoaddanewrecordinto"+uri);}

而該 Provider 又在system_server進程中,擁有極高的運行權限,攻擊者通過利用這個漏洞去就能實現(xiàn)系統(tǒng)層面的任意文件讀寫,其 PoC 如下:

ContentValuesvals=newContentValues();vals.put("_data","/data/system/users/0/newFile.bin");URIsemclipboard_uri=URI.parse("content://com.sec.android.semclipboardprovider")ContentResolverresolver=getContentResolver();URInewFile_uri=resolver.insert(semclipboard_uri,vals);returnresolver.openFileDescriptor(newFile_uri,"w").getFd();

該漏洞與其他漏洞一起曾被用于在野攻擊中,由 Google TAG 團隊捕獲,對這一條 Fullchain 的分析可以參考 Project Zero 近期的文章:A Very Powerful Clipboard: Analysis of a Samsung in-the-wild exploit chain[42]

call

ContentProvider 中提供了call方法,用于實現(xiàn)調用服務端定義方法,其函數(shù)簽名如下:

publicBundlecall(Stringauthority,Stringmethod,Stringarg,Bundleextras)publicBundlecall(Stringmethod,Stringarg,Bundleextras)

默認的實現(xiàn)是個空函數(shù)直接返回null,開發(fā)者可以通過覆蓋該函數(shù)去實現(xiàn)一些動態(tài)方法,返回值也會傳回到調用者中。

看起來和常規(guī)的 RPC 調用類似,但這里有個小陷阱,開發(fā)者文檔中也特別標注了:Android 系統(tǒng)并沒有對call函數(shù)進行權限檢查,因為系統(tǒng)不知道在 call 之中對數(shù)據(jù)進行了讀還是寫,因此也就無法根據(jù) Manifest 中定義的權限約束進行判斷。因此要求開發(fā)者自己對 call 中的邏輯進行權限校驗。

如果開發(fā)者實現(xiàn)了該方法,但是又未進行校驗或者校驗不充分,就可能出現(xiàn)越權調用的情況。一個案例是CVE-2021-23243, OPPO 某系統(tǒng)應用中HostContentProviderBase的 call 方法實現(xiàn)中,直接用 DexClassLoader 去加載了傳入 dex 文件,直接導致攻擊者的代碼在特權進程中運行,所有繼承該基類的 Provider 都會受到影響 ()。

另外在某些系統(tǒng) Provider 中,可以通過 call 方法去獲取某些遠程對象實例,例如在文章Android 中的特殊攻擊面(三)—— 隱蔽的 call 函數(shù)[43]中,作者就通過SliceProvider與KeyguardSliceProvider獲取到了系統(tǒng)應用內部的 PendingIntent 對象,進一步利用實現(xiàn)了偽造任意廣播的功能。

其他

除了上述和四大組件直接相關的漏洞,Android 系統(tǒng)中還有許多不太好分類的漏洞,本節(jié)主要挑選其中幾個最為常見的漏洞進行簡單介紹。

PendingIntent

PendingIntent[44]是對 Intent 的表示,本身并不是 Intent 對象,但是是一個 Parcelable 對象。將該對象傳遞給其他應用后,其他應用就可以以發(fā)送方的身份去執(zhí)行所指向的 Intent 指定的操作。 PendingIntent 使用下述靜態(tài)方法之一進行創(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)對原始數(shù)據(jù)描述符的一個引用,可以大致將其理解為Intent 的指針。也因為如此,即便創(chuàng)建 PendingIntent 的應用關閉后,其他應用仍然可以使用該數(shù)據(jù)。如果原始應用后來進行了重啟并以同樣的參數(shù)創(chuàng)建了一個 PendingIntent,那么實際上返回 PendingIntent 與之前創(chuàng)建的會指向同樣的 token。注意判斷 Intent 是否相同是使用filterEquals[45]方法,其中會判斷 action,data, type,identity,class,categories 是否相同,注意extra并不在此列,因此僅有 extra 不同的 Intent 也會被認為是相等的。

由于 PendingIntent 可代表其他應用的特性,在某些場景下可能被用于濫用。例如,如果開發(fā)者創(chuàng)建了這樣一個默認的 PendingIntent 并傳遞給其他應用:

pi=PendingIntent.getActivity(this,0,newIntent(),0);bundle.putParcelable("pi",pi)//sendbundle

惡意的應用在收到此 PendingIntent 后,可以獲取到原始的 intent,并使用Intent.fillin去填充空字段,如果原始 Intent 是上述空 Intent,那么攻擊者就可以將其修改為特定的 Intent,從而以目標的身份去啟動應用,包括未導出的私有應用。一個經典的案例就是早期的broadAnywhere[46]漏洞,Android Settings 應用中的 addAccount 方法內創(chuàng)建了一個 PendingIntent 廣播,但 intent 內容為空,這導致收到 intent 的的惡意應用可以 fillin 填充廣播的 action,從而實現(xiàn)越權發(fā)送系統(tǒng)廣播,實現(xiàn)偽造短信、回復出廠設置等功能。

為了緩解這類問題,Andorid 中對 Intent.fillin 的改寫做了諸多限制,比如已有的字段不能修改,component 和 selector 字段不能修改(除非額外設置 FILL_IN_COMPONENT/SELECTOR),隱式 Intent 的 action 不能修改等。

不過有研究者提出了針對隱式 Intent 的利用方法,即通過修改 flag 添加 FLAG_GRANT_WRITE_URI_PERMISSION,并修改 data 的 URI 指向受害者私有的 Provider,將 package 改為攻擊者;同時攻擊者在自身的 Activity 中聲明相同的 intent filter,這樣在轉發(fā) intent 時會啟動攻擊者應用,同時也獲取了目標私有 Provider 的訪問權限,從而實現(xiàn)私有文件竊取或者覆蓋。關于該攻擊思路詳情可以閱讀下面的參考文章。

?broadAnywhere:Broadcast組件權限繞過漏洞(Bug: 17356824)[47]

?PendingIntent重定向:一種針對安卓系統(tǒng)和流行App的通用提權方法——BlackHat EU 2021議題詳解(上)[48]

?PendingIntent重定向:一種針對安卓系統(tǒng)和流行App的通用提權方法——BlackHat EU 2021議題詳解(下)[49]

在 Android 12+ 之后,PendingIntent 在創(chuàng)建時候要求顯式指定FLAG_MUTABLE或者FLAG_IMMUTABLE,表示是否可以修改。

DeepLink

在大部分操作系統(tǒng)中都有 deeplink 的概念,即通過自定義 schema 打開特定的應用。比如通過點擊 https://evilpan.com/ 可以喚起默認瀏覽器打開目標網頁,點擊 tel://10086 會喚起撥號界面,點擊 weixin://qr/xxx 會喚起微信,等等。其他系統(tǒng)暫且不論,在 Android 中這主要是通過隱式 Intent 去實現(xiàn)的。

應用要想注冊類似的自定義協(xié)議,需要在應用清單文件中進行聲明:

由于這類隱式 Intent 可以直接通過點擊鏈接去觸發(fā),因此更受攻擊者喜愛。如果處理對應 Intent 的組件沒有過濾好用戶傳入的內容,很可能會造成 1-click 的漏洞。相關案例可以參考文章:Android 中的特殊攻擊面(二)——危險的deeplink

Webview

在 Andorid 系統(tǒng)中,Webview[50]主要用于應用在自身的 Activty 中展示網頁內容,并提供了一些額外的接口來給開發(fā)者實現(xiàn)自定義的控制。更高的拓展性也就意味著更多出錯的可能,尤其是如今 Android 客戶端開發(fā)式微,Java 開發(fā)也朝著 “大前端” 的方向發(fā)展。原本許多使用原生應用實現(xiàn)的邏輯逐漸轉移到了 web 頁面中,比如 h5、小程序等,這樣一來,webview 的攻擊面也就擴寬了不少。

常規(guī)的 Webview 安全問題主要是在與一些配置的不安全,比如覆蓋onReceivedSslError忽略 SSL 錯誤導致中間人攻擊,setAllowFileAccessFromFileURLs導致本地私有文件泄露等。但現(xiàn)在的漏洞更多出在 JSBridge 上,這是 Java 代碼與網頁中的 JavaScript 代碼溝通的橋梁。

由于 Webview 或者說 JS 引擎的沙箱特性,網頁中的 Javascript 代碼本身無法執(zhí)行許多原生應用才能執(zhí)行的操作,比如無法從 Javascript 中發(fā)送廣播,無法訪問應用文件等。而由于業(yè)務的復雜性,很多邏輯又必須在 Java 層甚至是 Native 層才能實現(xiàn),因此這就需要用到 JSBridage。傳統(tǒng)的 JSBridge 通過Webview.addJavascriptInterface實現(xiàn),一個簡單示例如下:

classJsObject{@JavascriptInterfacepublicStringtoString(){return"injectedObject";}}webview.getSettings().setJavaScriptEnabled(true);webView.addJavascriptInterface(newJsObject(),"injectedObject");webView.loadData("","text/html",null);webView.loadUrl("javascript:alert(injectedObject.toString())");

Java 層返回數(shù)據(jù)給 Javascript 一般是通過直接使用 loadUrl 執(zhí)行 JS 代碼實現(xiàn)。當然除了這種方式注冊 Bridge,還有很多應用特異的實現(xiàn),比如使用console.log傳輸數(shù)據(jù)并在 Java 層使用onConsoleMessage回調去接收。但無論如何,這都導致攻擊面的增加,大型應用甚至注冊了上百個 jsapi 來供網頁調用。

從歷史漏洞來看,Webview 漏洞的成因主要是 jsapi 域名校驗問題和 Bridge 代碼本身的漏洞,由于篇幅原因就不展開了。

后記

本文中主要通過 Android 中的四大組件介紹了一系列相關的邏輯問題,盡可能地囊括了筆者所了解的歷史漏洞。由于個人認知水平有限,總是難免掛一漏萬,但即便如此,文章的篇幅還是比預想中的超出了億點點。從溫故知新的角度看,挖掘這類邏輯漏洞最好的策略還是使用靜態(tài)分析工具,搜集更多 Sink 模式并編寫有效的規(guī)則去進行掃描,實在沒有條件的話用 (rip)grep 也是可以的。

參考資料

?Galaxy Leapfrogging 蓋樂世蛙跳 Pwning the Galaxy S8[51]

?Chainspotting: Building Exploit Chains with Logic Bugs[52](如何用11個exp攻破三星S8[53])

?Huawei Mate 9 Pro Mobile Pwn2Own 2017[54]

?Detect dangerous vulnerabilities in the TikTok Android app - Oversecured[55]

?魔形女漏洞白皮書 - 京東探索研究院信息安全實驗室[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 應用啟動流程分析: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) 修復方案: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]綁定服務: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組件權限繞過漏洞解析(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組件權限繞過漏洞(Bug: 17356824):http://retme.net/index.php/2014/11/14/broadAnywhere-bug-17356824.html[48]PendingIntent重定向:一種針對安卓系統(tǒng)和流行App的通用提權方法——BlackHat EU 2021議題詳解(上):https://blog.csdn.net/weixin_59152315/article/details/123481053[49]PendingIntent重定向:一種針對安卓系統(tǒng)和流行App的通用提權方法——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 蓋樂世蛙跳 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個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]魔形女漏洞白皮書 - 京東探索研究院信息安全實驗室: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/

關鍵詞: 生命周期 安卓系統(tǒng) 文件讀寫

相關閱讀