Android 廣播許可權保護

Mindjet發表於2017-11-23

為什麼需要許可權保護

無條件接收廣播

比如說,一位將軍對軍隊下令說:“如果聽到衝啊,就發起攻擊”。結果軍隊出了個奸細,大喊一聲衝啊,所有士兵都開始衝鋒,掉入敵人的陷阱,將軍在軍帳裡傻了眼。

因為這位將軍只告訴軍隊聽到衝啊就發起攻擊,並沒有告知只有將軍自己喊的才有效。

在 Android 中,就好像你註冊了一個廣播接受者(軍隊),卻沒有說明廣播傳送者需要的許可權(將軍令),無論哪裡發來的廣播都會響應,當然前提是 action 一致(都是衝啊)。如果廣播接收者的響應涉及到敏感操作,那麼很容易就會被攻擊。

無條件傳送廣播

比如說,皇帝說:“五更時分,到太和殿上朝”。上朝時分一到,人叫那個多啊,不止是文武百官,後宮三千啊,太監啊,宮女啊,侍衛啊,全都擠進太和殿,皇帝在龍椅上傻了眼。

因為皇帝只告知五更時分就到太和殿上朝,並沒有說明誰可以去誰不可以去。

這裡說無條件傳送廣播,其實是不大恰當的,我們並沒有能力去限制廣播的傳送,就好像限制不了五更時分的到來,我們只能限制哪些能作為廣播接收者(文武百官)。

舉個 Android 的例子,開機或者切換到新使用者的時候都會傳送一個 BOOT_COMPLETED 廣播,如果這個廣播不檢查接收者的許可權,假如三方應用都去註冊這個廣播,而且收到這個廣播的時候把自己喚起,那麼一開機一大堆 app 自啟動,手機就得卡成馬了。

限制廣播傳送者

上面我們說到,不能讓廣播接收者無條件地接收廣播,只能接受擁有約定許可權的傳送者發來的廣播。

我們在廣播接收者一側定義傳送者應擁有的許可權,並且在註冊廣播接收者時指明該許可權:

<permission android:name="io.github.mindjet.SEND_PERMISSION" />

<receiver android:name="MyBroadcastReceiver"
        android:permission="io.github.mindjet.SEND_PERMISSION">
    <intent-filter>
            <action android:name="io.github.mindjet.JUST_BROADCAST" />
    </intent-filter>
</receiver>複製程式碼

在廣播傳送者一側使用該許可權:

<uses-permission android:name="io.github.mindjet.SEND_PERMISSION" />複製程式碼

之後便可以傳送廣播並接收了:

sendBroadcast(new Intent("io.github.mindjet.JUST_BROADCAST"));複製程式碼

其他不擁有 io.github.mindjet.SEND_PERMISSION 許可權的傳送者可以將廣播傳送出去,但是接收者對其不理不睬。

限制廣播接收者

上面說到,我們不能讓廣播傳送者的廣播隨隨便便地被接收者接收,只能讓那些擁有約定許可權的接收者接受。

我們在廣播傳送者一側定義接收者應擁有的許可權:

<permission android:name="io.github.mindjet.RECEIVE_PERMISSION" />複製程式碼

然後,在傳送廣播時,宣告這是一條有許可權檢查的廣播(第二個引數代表接收者應擁有的許可權):

sendBroadcast(new Intent("io.github.mindjet.JUST_BROADCAST"), "io.github.mindjet.RECEIVE_PERMISSION");複製程式碼

接收者一側則需要使用該許可權:

<uses-permission android:name="io.github.mindjet.RECEIVE_PERMISSION" />複製程式碼

其他不擁有 io.github.mindjet.RECEIVE_PERMISSION 的接收者無權接收這個廣播。

雙向許可權保護

雙向保護其實就是把上面兩種限制手段結合起來。

傳送方說,你得有 XXX 許可權才能接收我的廣播;接收方說,你也得有 YYY 許可權我才接收你的廣播。

也就是說,傳送方有接收方規定的傳送許可權的同時,接收方也得有傳送方規定的接收許可權。

那麼傳送方一側:

<!-- 宣告接收許可權 -->
<permission android:name="io.github.mindjet.RECEIVE_PERMISSION" />
<!-- 使用傳送許可權 -->
<uses-permission android:name="io.github.mindjet.SEND_PERMISSION" />複製程式碼

同時傳送廣播時,使用帶許可權檢查的方式:

sendBroadcast(new Intent("io.github.mindjet.JUST_BROADCAST"), "io.github.mindjet.RECEIVE_PERMISSION");複製程式碼

相應地,接收方一側:

<!-- 宣告傳送許可權 -->
<permission android:name="io.github.mindjet.SEND_PERMISSION" />
<!-- 使用接收許可權 -->
<uses-permission android:name="io.github.mindjet.RECEIVE_PERMISSION" />

<receiver android:name="MyBroadcastReceiver"
        android:permission="io.github.mindjet.SEND_PERMISSION">
    <intent-filter>
            <action android:name="io.github.mindjet.JUST_BROADCAST" />
    </intent-filter>
</receiver>複製程式碼

這樣就能實現雙向的許可權保護了。

終於,將軍在軍帳裡運籌帷幄,天子在龍椅上君臨天下。

protectionLevel

看了上面的例子,有人說,如果我是廣播傳送者,那我在 AndroidManifest.xml 裡面宣告使用傳送許可權不就好了,那許可權保護還有什麼用。

情況確實是這樣,只要你能申請到,就有許可權傳送或者接收。所以為了更好地控制,我們需要宣告許可權的保護等級,即 protectionLevel 屬性。

protectionLevel 有以下幾個屬性值:

normal              預設值,只要申請便能使用
dangerous           同上
signature           apk 簽名一致才能使用
signatureOrSystem   apk 簽名一致或者系統應用才能使用複製程式碼

比如說,一家公司開發出了幾款產品(簽名一致),並且定義了廣播傳送和接收許可權,保護等級都是 signature

<permission android:name="io.github.mindjet.SEND_PERMISSION"
        protectionLevel="signature" />
<permission android:name="io.github.mindjet.RECEIVE_PERMISSION"
        protectionLevel="signature" />複製程式碼

那麼這兩個許可權,只有這家公司自家的產品可以申請到,也就避免了廣播被外部 app 利用的情況。

———

技術上的問題,歡迎討論。

最近在 Github 上維護的專案:

  • LiteWeather [一款用 Kotlin 編寫,基於 MD 風格的輕量天氣 App],對使用 Kotlin 進行實際開發感興趣的同學可以看看,專案中會使用到 Kotlin 的委託機制、擴充套件機制和各種新奇的玩意。
  • LiteReader [一款基於 MD 的極輕閱讀 App,提供知乎日報、豆瓣電影等資源],專案主要使用了 MVVM 設計模式,介面遵循 Material Design 規範,提供輕量的閱讀體驗。
  • LiveMVVM [Kotlin 編寫的 Android MVVM 框架,基於 android-architecture],輕量 MVVM+Databinding 開發框架。
  • AnkoUtil [Kotlin 編寫的 Android 擴充套件庫]

歡迎 star/fork/follow 提 issue 和 PR。

相關文章