天天看點

安卓應用安全指南 5.2.3 權限和保護級别 進階話題

5.2.3 權限和保護級别 進階話題

原書: Android Application Secure Design/Secure Coding Guidebook 譯者: 飛龍 協定: CC BY-NC-SA 4.0

5.2.3.1 繞過自定義簽名許可的 Android 作業系統特性及其對策

自定義簽名權限是一種權限,實作使用相同開發人員密鑰簽名的應用之間的應用間通信。 由于開發人員密鑰是私鑰,不能公開,是以隻有在内部應用互相通信的情況下,才有權使用簽名權限進行保護。

首先,我們将描述在 Android 的開發者指南(

http://developer.android.com/guide/topics/security/security.html

)中解釋的自定義簽名權限的基本用法。 但是,後面将解釋,存在繞過許可方面的問題。 是以,本指南中描述的對策是必要的。

以下是自定義簽名權限的基本用法。

在提供方應用的

AndroidManifest.xml

中定義内部簽名權限。(權限定義)

例如:

<permission android:name="xxx" android:protectionLevel="signature" />

AndroidManifest.xml

中,使用要保護的元件的權限屬性強制執行權限。 (執行權限)

<activity android:permission="xxx" ... >...</activity>

在每個使用者方應用的

AndroidManifest.xml

中,使用

uses-permission

标簽聲明内部定義的簽名權限,來通路要保護的元件。 (使用權限聲明)

<uses-permission android:name="xxx" />

使用相同的開發人員密鑰,對所有互相通信的應用的 APK 進行簽名。

實際上,如果滿足以下條件,這種方法會存在漏洞,可以繞過簽名權限。

為了便于說明,我們将受自定義簽名權限保護的應用稱為

ProtectedApp

,并且

AttackerApp

是已由不同于

ProtectedApp

的開發人員密鑰簽名的應用。 繞過簽名權限的漏洞意味着,即使

AttackerApp

的簽名不比對,也有可能通路

ProtectedApp

的元件。

條件 1:

AttackerApp

也定義了正常權限,與

ProtectedApp

所定義的簽名權限名稱相同(嚴格來說,簽名權限也是可以接受的)。

<permission android:name=" xxx" android:protectionLevel="normal" />

條件 2:

AttackerApp

使用

uses-permission

聲明了自定義的正常權限。

<uses-permission android:name="xxx" />

條件 3:

AttackerApp

安裝在

ProtectedApp

之前。

滿足條件 1 和條件 2 所需的權限名稱,很容易從 APK

AndroidManifest.xml

檔案中取出,被攻擊者知道。 攻擊者也可以用一定的努力滿足條件 3(例如欺騙使用者)。

如果隻采用基本用法,就有自定義簽名權限的繞過風險,需要采取防範此類漏洞的對策。 具體而言,你可以通過使用“5.2.2.4 驗證内部定義的簽名權限是否由内部應用定義”中描述的方法來發現如何解決上述問題。

5.2.3.2 使用者僞造的

AndroidManifest.xml

我們已經談到,自定義權限的保護級别可能會被改變。 為了防止由于這種情況導緻的故障,需要在 Java 的源代碼一側實施某些對策。 從

AndroidManifest.xml

僞造的角度來看,我們将讨論在源代碼方面要采取的對策。 我們将示範一個可以檢測僞造的簡單安裝案例。 但請注意,對于出于犯罪目的而僞造的專業黑客來說,這些對策效果甚微。

這部分内容關于應用僞造和惡意使用者。 盡管這本來不屬于指導手冊的範圍,但由于這與權限有關,并且這種僞造的工具作為 Android 應用公開提供,是以我們決定将其稱為“針對業餘黑客的簡單對策”。

必須記住的是,可以從市場安裝的應用,是可以在沒有 root 權限的情況下,被僞造的應用。原因是應用可以重建和簽署

AndroidManifest.xml

檔案。通過使用這些應用,任何人都可以删除已安裝應用的任何權限。

舉個例子,似乎有些情況下重建的 APK 具有不同的簽名,

AndroidManifest.xml

發生改變,并删除了

INTERNET

權限,來使應用中附加的廣告子產品失效。有些使用者稱贊這些類型的工具,因為任何個人資訊沒有被洩漏到任何地方。由于這些附加在應用中的廣告停止運作,此類行為會對依靠廣告收入的開發者造成金錢損失。而且相信大多數使用者沒有任何反感。

在下面的代碼中,我們展示了一個實作的執行個體,一個使用

uses-permission

聲明了

INTERNET

權限的應用,驗證

INTERNET

權限是否在運作時在

AndroidManifest.xml

檔案中描述。

public class CheckPermissionActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // Acquire Permission defined in AndroidManifest.xml
        List<String> list = getDefinedPermissionList();
        // Detect falsification
        if( checkPermissions(list) ){
            // OK
            Log.d("dbg", "OK.");
        }else{
            Log.d("dbg", "manifest file is stale.");
            finish();
        }
    }

    /**
    * Acquire Permission through list that was defined in AndroidManifest.xml
    * @return
    */
    private List<String> getDefinedPermissionList(){
        List<String> list = new ArrayList<String>();
        list.add("android.permission.INTERNET");
        return list;
    }

    /**
    * Verify that Permission has not been changed Permission
    * @param permissionList
    * @return
    */
    private boolean checkPermissions(List<String> permissionList){
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(
            getPackageName(), PackageManager.GET_PERMISSIONS);
            String[] permissionArray = packageInfo.requestedPermissions;
            if (permissionArray != null) {
                for (String permission : permissionArray) {
                    if(! permissionList.remove(permission)){
                        // Unintended Permission has been added
                        return false;
                    }
                }
            }
            if(permissionList.size() == 0){
                // OK
                return true;
            }
        } catch (NameNotFoundException e) { }
        return false;
    }
}           

5.2.3.3 APK 僞造的檢測

我們在“5.2.3.2 使用者僞造的

AndroidManifest.xml

”中,解釋了使用者對權限的僞造檢測。但是,應用僞造并不僅限于權限,在許多其他情況下,應用在沒有任何源代碼更改的情況下被占用。例如,隻是通過将資源替換為自己的應用,他們将其他開發人員的應用(僞造)分發到市場中,就好像它們是自己的應用一樣。在這裡,我們将展示一個更通用的方法,來檢測 APK 檔案的僞造。

為了僞造 APK,需要将 APK 檔案解碼為檔案夾和檔案,修改其内容,然後将其重建為新的 APK 檔案。由于僞造者沒有原始開發者的密鑰,他必須用他自己的鑰匙簽署新的 APK 檔案。由于 APK 的僞造不可避免地會産生簽名(證書)的變化,是以可以通過比較 APK 中的證書,和源代碼中嵌入的開發人員證書,在運作時檢測 APK 是否被僞造。

以下是示例代碼。另外,如果使用這個實作示例,專業黑客将能夠輕松繞過僞造檢測。請注意這是一個簡單的實作示例,請将此示例代碼應用于你的應用。

要點:

  1. 在開始主要操作之前,驗證應用的證書是否屬于開發人員。

SignatureCheckActivity.java

package org.jssec.android.permission.signcheckactivity;

import org.jssec.android.shared.PkgCert;
import org.jssec.android.shared.Utils;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;

public class SignatureCheckActivity extends Activity {

    // Self signed certificate hash value
    private static String sMyCertHash = null;

    private static String myCertHash(Context context) {
        if (sMyCertHash == null) {
            if (Utils.isDebuggable(context)) {
                // Certificate hash value of "androiddebugkey" of debug.
                sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
            } else {
                // Certificate hash value of "my company key" of keystore
                sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
            }
        }
        return sMyCertHash;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // *** POINT 1 *** Verify that an application's certificate belongs to the developer before major processing is started
        if (!PkgCert.test(this, this.getPackageName(), myCertHash(this))) {
            Toast.makeText(this, "Self-sign match NG", Toast.LENGTH_LONG).show();
            finish();
            return;
        }
        Toast.makeText(this, "Self-sign match OK", Toast.LENGTH_LONG).show();
    }
}           

PkgCert.java

package org.jssec.android.shared;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;

public class PkgCert {

    public static boolean test(Context ctx, String pkgname, String correctHash) {
        if (correctHash == null) return false;
        correctHash = correctHash.replaceAll(" ", "");
        return correctHash.equals(hash(ctx, pkgname));
    }

    public static String hash(Context ctx, String pkgname) {
        if (pkgname == null) return null;
        try {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);
            if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.
            Signature sig = pkginfo.signatures[0];
            byte[] cert = sig.toByteArray();
            byte[] sha256 = computeSha256(cert);
            return byte2hex(sha256);
        } catch (NameNotFoundException e) {
            return null;
        }
    }

    private static byte[] computeSha256(byte[] data) {
        try {
            return MessageDigest.getInstance("SHA-256").digest(data);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    private static String byte2hex(byte[] data) {
        if (data == null) return null;
        final StringBuilder hexadecimal = new StringBuilder();
        for (final byte b : data) {
            hexadecimal.append(String.format("%02X", b));
        }
        return hexadecimal.toString();
    }
}           

5.2.3.4 權限重委托問題

通路聯系人或 GPS,它們帶有受 Android OS 保護的資訊和功能時,應用必須聲明使用權限。當所需的權限被授予時,權限被委托給應用,應用将能夠通路受權限保護的資訊和功能。

根據程式的設計方式,被授予權限的應用可以擷取受權限保護的資料。此外,應用可以向另一個應用提供受保護資料,而不必強制確定相同的權限,這無異于,沒有權限的應用可以通路受權限保護的資料。這實際上是重新授權,稱為權限重新授權問題。是以,隻有 Android 的權限機制的規範,才能夠管理從來自用程式的,保護資料的直接通路的權限。

圖 5.2-9 展示了一個具體的例子。 中心的應用表明,已聲明

android.permission.READ_CONTACTS

的應用使用它來讀取聯系人,然後将它們存儲到其自己的資料庫中。 當已經存儲的資訊通過内容供應器,提供給另一個應用,而沒有任何限制時,就會發生重新授權問題。

安卓應用安全指南 5.2.3 權限和保護級别 進階話題

作為一個類似的例子,聲明了

android.permission.CALL_PHONE

的應用,使用它從另一個應用接收電話号碼(可能是使用者輸入的),它未聲明相同權限。如果該号碼在未經使用者驗證的情況下被呼叫,那麼也存在重新授權問題。

在某些情況下,通過權限獲得的,幾乎完整的資訊或功能資産,需要由其他應用二次提供。在這些情況下,供應方應用必須要求相同權限,才能保持原始的保護級别。此外,在僅以間接方式提供資訊和功能資産的一部分的情況下,根據資訊或功能資産的一部分的損害程度,需要适當保護。由“4.1.1.1 建立/使用私有活動”或“4.1.1.4 建立/使用私有活動”,我們可以使用類似于前者的保護措施,驗證使用者的同意,并設定目标應用的活動限制,以及其他。

這種重新授權問題不僅限于 Android 權限。對于 Android 應用,應用從不同的應用,網絡和存儲媒體中擷取必要的資訊/功能,這是常見的。在很多情況下,通路它們需要一些權限和限制。例如,如果提供者來源的 Android 應用,則它是權限;如果它是網絡,那麼它是登入機制;如果它是存儲媒體,則會存在通路限制。是以,在仔細考慮後,需要對應用實作這些措施,因為資訊/功能不是以與使用者意圖相反的方式使用的。以間接方式将獲得的資訊/功能提供給另一應用,或轉移到網絡或存儲媒體時,這一點尤其重要。根據需要,你必須強制確定權限或限制使用權限,如 Android 權限。詢問使用者的同意是解決方案的一部分。

在以下代碼中,我們示範了一個情況,使用

READ_CONTACTS

權限,從聯系人資料庫中擷取清單的應用,對資訊的目标強制確定相同的

READ_CONTACTS

權限。

強制確定提供者的相同權限。

AndroidManifest.xml

<?xml version="1.0" encofing="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.permission.transferpermission"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
    android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
        android:name=".TransferPermissionActivity"
        android:label="@string/title_activity_transfer_permission" >
        <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
        </activity>
        <provider
            android:name=".TransferPermissionContentProvider"
            <!-- *** Point1 *** Enforce the same permission that the provider does. -->
            android:authorities="org.jssec.android.permission.transferpermission"
            android:enabled="true"
            android:exported="true"
            android:readPermission="android.permission.READ_CONTACTS" >
        </provider>
    </application>
</manifest>           

當一個應用確定多個權限時,上述方法不會解決它。 通過使用源代碼中的

Context#checkCallingPermission()

PackageManager#checkPermission()

,它驗證調用者應用是否在清單中,使用

uses-permission

聲明了所有權限。

在活動中:

public void onCreate(Bundle savedInstanceState) {
    [...]

    if (checkCallingPermission("android.permission.READ_CONTACTS") == PackageManager.PERMISSION_GRANTED
        && checkCallingPermission("android.permission.WRITE_CONTACTS") == PackageManager.PERMISSION_GRANTED) {
        // Processing during the time when an invoker is correctly declaring to use
        return;
    }
    finish();
}           

5.2.3.5 自定義權限的簽名檢查機制(Android 5.0 及以上)

在 Android 5.0(API Level 21)及更高版本中,如果滿足以下條件,則無法安裝定義其自定義權限的應用。

  1. 在裝置上已經安裝了另一個應用,用相同名稱定義了自定義權限。
  2. 應用使用不同的密鑰簽名

當具有受保護函數(元件)的應用,和使用該函數的應用,定義了具有相同名稱的自定義權限,并且使用相同密鑰簽名時,上述機制将防止安裝定義了自定義權限的其他公司的應用同名。 但是,如“5.2.2.3 你自己的簽名權限必須僅在提供方應用中定義(必需)”中所述,該機制對于檢查自定義權限是否由你自己的公司定義是行不通的,因為權限 如果多個應用定義相同的權限,在你自己不知道的情況下,可能通過解除安裝應用來使其失效。

總而言之,在 Android 5.0(API Level 21)和更高版本中,當你的應用定義你自己的簽名權限時,你還需要遵守兩個規則:“5.2.2.3 你自己的簽名權限隻能在提供方應用上定義(必需) “和”5.2.2.4 驗證内部定義的簽名權限是否由内部應用定義(必需)“。

5.2.3.6 Android 版本 6.0 和更高版本中對權限模型規範的修改

Android 6.0(API Level 23)引入了權限模型的修改規範,這些規範影響了應用的設計和規範。 在本節中,我們将概述 Android 6.0 及更高版本中的權限模型。

權限授予和拒絕的時機

如果應用聲明使用需要使用者确認的權限(危險權限)【請參見“5.2.2.1 Android 系統危險權限必須僅用于保護使用者資産(必需)”一節】,Android 5.1(API 級别 22)和更早的版本,要求在安裝應用時顯示這些權限的清單,并且使用者必須授予所有權限才能繼續安裝。 此時,應用聲明的所有權限(包括危險權限以外的權限)均已授予該應用;一旦這些權限被授予應用,它們就會一直有效,直到應用從終端上解除安裝。

但是,在 Android 6.0 及更高版本的規範中,應用執行時會授予權限。 在安裝應用時不會發生權限授予和使用者的權限确認。 當應用執行需要危險權限的過程時,需要檢查是否已将這些權限提前授予應用;如果沒有,則必須在 Android 作業系統中顯示确認視窗,來請求使用者的同意 [25]。如果使用者從确認視窗授予權限,則将權限授予應用。 但是,使用者授予應用的權限(危險權限)可以随時通過設定菜單撤銷(圖 5.2-10)。 出于這個原因,必須實作适當的過程,來確定應用不會産生不規則的行為,即使在因為未授予權限,而無法通路所需的資訊或功能的情況下。

[25] 由于正常權限和簽名權限是由 Android OS 自動授予的,是以不需要擷取使用者對這些權限的确認。

權限授予和拒絕的機關

根據與之相關的功能和資訊類型,可以将多個權限組合在一起稱為權限組。 例如,讀取月曆資訊所需的權限

android.permission.READ_CALENDAR

以及寫入月曆資訊所需的權限

android.permission.WRITE_CALENDAR

都關聯權限組

android.permission-group.CALENDAR

在 Android 6.0 及更高版本的新權限模型中,權限的授予和撤銷可以使用權限組統一執行。 是以,當一個應用在運作時請求

android.permission.READ_CALENDAR

并且使用者同意該請求時,Android OS 的行為就像

android.permission.READ_CALENDAR

android.permission.WRITE_CALENDAR

都已被授權一樣。 如果随後請求

android.permission.WRITE_CALENDAR

權限,則作業系統不會向使用者顯示對話框,而是直接授予權限。

權限組分類的更多資訊,請參閱開發人員參考(

http://developer.android.com/intl/ja/guide/topics/security/permissions.html#perm-groups

)。

修改後的規範的影響範圍

應用在運作時需要權限請求的情況,僅限于終端運作 Android 6.0 或更高版本,并且應用的

targetSDKVersion

為 23 或更高的情況。 如果終端運作的是 Android 5.1 或更低版本,或者應用的

targetSDKVersion

為 22 或更低,則安裝時會完全請求和授予權限,這與傳統情況相同。 但是,如果終端運作的是 Android 6.0 或更高版本,則即使應用的

targetSDKVersion

低于 23,使用者在安裝時授予的權限也可能随時被使用者撤銷。 這會造成應用意外終止的可能性。 開發人員必須遵守修改後的規範,或将應用的

maxSDKVersion

設定為 22 或更低版本,來確定該應用不能安裝在運作 Android 6.0(API Level 23)或更高版本(表 5.2-1)的終端上。

表.2-1

Android OS 終端版本 應用的

targetSDKVersion

應用被授予權限的時機 使用者是否能控制權限
= 6.0
= 23
執行時
< 23 安裝時 是(需要快速響應)
<= 5.1

但是,應該注意,

maxSdkVersion

的影響是有限的。 當

maxSdkVersion

的值設定為 22 或更低時,Android 6.0(API Level 23)和更高版本的裝置,不再被列為 Google Play 中目标應用的可安裝裝置。 另一方面,由于未在 Google Play 以外的市場中檢查

maxSdkVersion

的值,是以可能會在 Android 6.0(API Level 23)或更高版本中安裝目标應用。 由于

maxSdkVersion

的效果有限,Google 不建議使用

maxSdkVersion

,是以建議開發人員立即遵守修改後的規範。

在 Android 6.0 及更高版本中,以下網絡通信權限的保護級别從危險更改為正常。 是以,即使應用聲明使用這些權限,也不需要獲得使用者的顯式統一,是以修改後的規範在此情況下不會産生影響。

  • android.permission.BLUETOOTH

  • android.permission.BLUETOOTH_ADMIN

  • android.permission.CHANGE_WIFI_MULTICAST_STATE

  • android.permission.CHANGE_WIFI_STATE

  • android.permission.CHANGE_WIMAX_STATE

  • android.permission.DISABLE_KEYGUARD

  • android.permission.INTERNET

  • android.permission.NFC