Android安全機制與sandbox機制詳解

  • 7296
  • 0

摘要:Android安全機制與sandbox機制詳解

Android是透過Linux的user與group權限來達到app的sandbox

與iOS中所有的app都是以「mobile」這個user的權限來執行有所不同

以下文章詳述了Android的機制:


第九章 Android安全存取機制

Android是一個多進程系統,在這個系統中,應用程式(或者系統的部分)會在自己的進程中運行。系統和應用之間的安全性通過Linux的 facilities(工具,功能)在進程級別來強制實現的,比如會給應用程式分配user ID和Group ID。更細化的安全特性是通過"Permission"機制對特定的進程的特定的操作進行限制,而"per-URI permissions"可以對獲取特定資料的access專門許可權進行限制。所以,應用程式之間的一般是不可以互相訪問的,但是anroid提供了一種permission機制,用於應用程式之間資料和功能的安全訪問。

9.1 安全架構
Android安全架構中一個中心思想就是:應用程式在預設的情況下不可以執行任何對其他應用程式,系統或者使用者帶來負面影響的操作。這包括讀或寫使用者的私有資料(如連絡人資料或email資料),讀或寫另一個應用程式的檔,網路連接,保持設備處於非睡眠狀態。
 
一個應用程式的進程就是一個安全的沙箱。它不能幹擾其它應用程式,除非顯式地聲明瞭“permissions”,以便它能夠獲取基本沙箱所不具備的額外的能力。它請求的這些許可權“permissions”可以被各種各樣的操作處理,如自動允許該許可權或者通過使用者提示或者證書來禁止該許可權。應用程式需要的那些“permissions”是靜態的在程式中聲明,所以他們會在程式安裝時被知曉,並不會再改變。
 
所有的Android應用程式(。apk檔)必須用證書進行簽名認證,而這個證書的私密金鑰是由開發者保有的。該證書可以用以識別應用程式的作者。該證書也不需要CA簽名認證(注:CA就是一個協力廠商的證書認證機構,如verisign等)。Android應用程式允許而且一般也都是使用self- signed證書(即自簽章憑證)。證書是用於在應用程式之間建立信任關係,而不是用於控制程式是否可以安裝。簽名影響安全性的最重要的方式是通過決定誰可以進入基於簽名的permisssions,以及誰可以share 使用者IDs。
 
9.2 使用者IDs和檔存取
 
每一個Android應用程式(。apk檔)都會在安裝時就分配一個獨有的Linux使用者ID,這就為它建立了一個沙箱,使其不能與其他應用程式進行接觸(也不會讓其它應用程式接觸它)。這個使用者ID會在安裝時分配給它,並在該設備上一直保持同一個數值。
 
由於安全性限制措施是發生在進程級,所以兩個package中的代碼不會運行在同一個進程當中,他們要作為不同的Linux使用者出現。我們可以通過使用AndroidManifest.xml檔中的manifest標籤中的sharedUserId屬性,來使不同的package共用同一個使用者 ID。通過這種方式,這兩個package就會被認為是同一個應用程式,擁有同一個使用者ID(實際不一定),並且擁有同樣的檔存取許可權。注意:為了保持安全,只有當兩個應用程式被同一個簽名簽署的時候(並且請求了同一個sharedUserId)才會被分配同樣的使用者ID。
 
所有存儲在應用程式中的資料都會賦予一個屬性——該應用程式的使用者ID,這使得其他package無法訪問這些資料。當 通過這些方法 getSharedPreferences(String, int),openFileOutput(String, int)或者 openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)來創建一個新檔時,你可以通過使用MODE_WORLD_READABLE and/or MODE_WORLD_WRITEABLE標誌位來設置是否允許其他package來訪問讀寫這個檔。當設置這些標誌位時,該檔仍然屬於該應用程式,但是它的global read and/or write許可權已經被設置,使得它對於其他任何應用程式都是可見的。

 

例如:APK A 和APK B 都是C公司的產品,那麼如果使用者從APK A中登陸成功。那麼打開APK B的時候就不用再次登陸。 具體實現就是A和B設置成同一個User ID:
packagename APK A的AndroidManifest:
< manifest xmlns:android="http://schemas.ndroid.com/apk/res/android" package="com.Android.demo.a1" android:sharedUserId="com.c">
 
packagename APK A的AndroidManifest:
< manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.Android.demo.b1" android:sharedUserId="com.c">
這個"com.c" 就是user id。 APK B就可以像打開本地資料庫那樣打開APK A中的資料庫了。APK A把登陸資訊存放在A的資料目錄下麵。APK B每次啟動的時候讀取APK A下麵的資料庫判斷是否已經登陸:
APK B中通過A的package name 就可以得到A的 packagecontext:
friendContext = this.createPackageContext( "com.android.demo.a1", Context,CONTEXT_IGNORE_SECURITY);
通過這個context就可以直接打開資料庫。

 

9.3 許可權(permission)
 
許可權用來描述是否擁有做某件事的權力。Android系統中許可權分為普通級別(Normal),危險級別(dangerous),簽名級別(signature)和系統/簽名級別(signature or system)。系統中所有預定義的許可權根據作用的不同,分別屬於不同的級別。
 
對於普通和危險級別的許可權,我們稱之為低級許可權,應用申請即授予。其他兩級許可權,我們稱之為高級許可權或系統許可權,應用擁有platform級別的認證才能申請。當應用試圖在沒有許可權的情況下做受限操作,應用將被系統殺掉以警示。
 
系統應用可以使用任何許可權。許可權的聲明者可無條件使用該許可權。

 

目前Android系統定義了許多許可權,通過SDK文檔使用者可以查詢到哪些操作需要哪些許可權,然後按需申請。
 
為了執行你自己的許可權,你必須首先在你的AndroidManifest.xml中使用一個或多個

 

< permission> 標籤聲明。例如,一個應用程式想用控制誰能啟動一個activities,它可以為聲明一個做這個操作的許可,如下:
< manifest xmlns:android="http://schemas。android。com/apk/res/android"package="com.me.app.myapp" >
< permission android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY" android:label="@string/permlab_deadlyActivity" android:description="@string/permdesc_deadlyActivity" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="dangerous" />
< /manifest>

 

9.4 使用許可權(uses-permission)
應用需要的許可權應當在users-permission屬性中申請,所申請的許可權應當被系統或某個應用所定義,否則視為無效申請。同時,使用許可權的申請需要遵循許可權授予條件,非platform認證的應用無法申請高級許可權。
所以,程式間存取權限大致分為兩種:
第一種低級點的(permission的protectlevel屬性為normal或者dangerous),其調用者apk只需聲明即可擁有其permission。
第二種高級點的(permission的protectlevel屬性為signature或者signatureorsystem),其調用者apk就需要和被調用的apk一樣擁有相同的signature。
若想擁有使用許可權,必須在AndroidManifest.xml檔中包含一個或更多的 標籤來聲明此許可權。

 

例如低級許可權需要監聽來自SMS消息的應用程式將要指定如下內容:
 
應用程式安裝的時候,應用程式請求的permissions是通過package installer來批准獲取的。packageinstaller是通過檢查該應用程式的簽名來確定是否給予該程式request的許可權。在使用者使用過程中不會去檢查許可權,也就是說要麼在安裝的時候就批准該許可權,使其按照設計可以使用該許可權;要麼就不批准,這樣使用者也就根本無法使用該feature,也不會有任何提示告知使用者嘗試失敗。
 
例如高級許可權用有system級別許可權設定的api時,需要使其apk擁有system許可權。比如在 android 的API中有供給SystemClock.setCurrentTimeMillis()函數來修改系統時間。有兩個方法:

 

第一個方法簡單點,不過需要在Android系統源碼的情況下用make來編譯:
1. 在應用程式的AndroidManifest.xml中的manifest節點中插入android:sharedUserId="android.uid.system"這個屬性。
2. 修改Android.mk檔,插入LOCAL_CERTIFICATE := platform這一行
3. 使用mm命令來編譯,生成的apk就有修改系統時間的職權範圍了。
第2個方法麻煩點,不外不消開虛擬機器跑到源碼情況下用make來編譯:
1. 同上,插手android:sharedUserId="android.uid.system"這個屬性。
2. 使用eclipse編譯出apk檔,但是這個apk檔是不能用的。
3. 使用針系統的platform密碼鑰匙來從頭給apk檔簽名。
signapk platform.x509.pem platform.pk8 input.apk output.apk

 

9.5 自訂Permission

 

Android系統定義的許可權可以在Manifest.permission中找到。任何一個程式都可以定義並強制執行自己獨有的 permissions,因此Manifest.permission中定義的permissions並不是一個完整的清單(即能有自訂的 permissions)。
一個特定的permission可能會在程式操作的很多地方都被強制實施:
當系統有來電的時候,用以阻止程式執行其它功能。
當啟動一個activity的時候,會阻止應用程式啟動其它應用的Acitivity。
在發送和接收廣播的時候,去控制誰可以接收你的廣播或誰可以發送廣播給你。
當進入並操作一個content provider的時候。
當綁定或開始一個service的時候。

 

9.6 元件許可權

通過 AndroidManifest.xml 檔可以設置高級許可權,以限制訪問系統的所有元件或者使用應用程式。所有的這些請求都包含在你所需要的元件中的 android:permission屬性,命名這個許可權可以控制訪問此元件。Activity 許可權 (使用 標籤) 限制能夠啟動與 Activity 許可權相關聯的元件或應用程式。在 Context.startActivity() 和 Activity.startActivityForResult() 期間檢查;

Service 許可權(應用 標籤)限制啟動、綁定或啟動和綁定關聯服務的元件或應用程式。此許可權在 Context.startService(), Context.stopService() 和 Context.bindService() 期間要經過檢查;

BroadcastReceiver 許可權(應用 標籤)限制能夠為相關聯的接收者發送廣播的元件或應用程式。在 Context.sendBroadcast() 返回後此許可權將被檢查,同時系統設法將廣播遞送至相關接收者。因此,許可權失敗將會導致拋回給調用者一個異常;它將不能遞送到目的地。在相同方式下,可以使 Context.registerReceiver() 支援一個許可權,使其控制能夠遞送廣播至已登記節目接收者的元件或應用程式。其它的,當調用 Context.sendBroadcast() 以限制能夠被允許接收廣播的廣播接收者物件一個許可權(見下文)。

ContentProvider 許可權(使用 標籤)用於限制能夠訪問 ContentProvider 中的資料的元件或應用程式。

如果調用者沒有請求許可權,那麼會為調用拋出一個安全異常( SecurityException )。在所有這些情況下,一個SecurityException異常從一個調用者那裡拋出時不會存儲請求許可權結果。

9.7 發送廣播時支援許可權

當發送一個廣播時你能總指定一個請求許可權,此許可權除了許可權執行外,其它能發送Intent到一個已註冊的BroadcastReceiver的許可權均可以。通過調用Context.sendBroadcast()及一些許可權字串,為了接收你的廣播,你請求一個接收器應用程式必須持有那個許可權。注意,接收者和廣播者都能夠請求一個許可權。當這樣的事發生了,對於Intent來說,這兩個許可權檢查都必須通過,為了交付到共同的目的地。

9.8 其它許可權支援

在調用service的過程中可以設置任意的fine-grained permissions(更為細化的許可權)。這是通過Context.checkCallingPermission()方法來完成的。使用一個想得到的 permission string來進行呼叫,然後當該許可權獲批的時候可以返回給呼叫方一個Integer(沒有獲批也會返回一個Integer)。需要注意的是這種情況只能發生在來自另一個進程的呼叫,通常是一個service發佈的IDL介面或者是其他方式提供給其他的進程。

Android提供了很多其他的方式用於檢查permissions。如果你有另一個進程的pid,你就可以通過Context的方法Context.checkPermission(String, int, int)去針對那個pid去檢查permission。如果你有另一個應用程式的package name,你可以直接用PackageManager的方法 PackageManager.checkPermission(String, String) 來確定該package是否已經擁有了相應的許可權。

9.9 URI許可權

到目前為止我們討論的標準的permission系統對於content provider來說是不夠的。一個content provider可能想保護它的讀寫許可權,而同時與它對應的直屬用戶端也需要將特定的URI傳遞給其它應用程式,以便其它應用程式對該URI進行操作。一個典型的例子就是郵件程式處理帶有附件的郵件。進入郵件需要使用permission來保護,因為這些是敏感的使用者資料。然而,如果有一個指向圖片附件的 URI需要傳遞給圖片流覽器,那個圖片流覽器是不會有訪問附件的權利的,因為他不可能擁有所有的郵件的存取權限。

針 對這個問題的解決方案就是per-URI permission:當啟動一個activity或者給一個activity返回結果的時候,呼叫方可以設置 Intent.FLAG_GRANT_READ_URI_PERMISSION和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION . 這會使接收該intent的activity獲取到進入該Intent指定的URI的許可權,而不論它是否有許可權進入該intent對應的content provider。

這種機制允許一個通常的capability-style模型,這種模型是以使用者交互(如打開一個附件,從清單中選擇一個連絡人)為驅動,特別獲取fine-grained permissions(更細粒化的許可權)。這是一種減少不必要許可權的重要方式,這種方式主要針對的就是那些和程式的行為直接相關的許可權。

這些URI permission的獲取需要content provider(包含那些URI)的配合。強烈推薦在content provider中提供這種能力,並通過android:grantUriPermissions或者標籤來聲明支援。

更多的資訊可以參考Context.grantUriPermission(),Context.revokeUriPermission()和 Context.checkUriPermission() methods。

QA

1.擁有signature的許可權是否可以不用聲明就能access帶normal或dangerous許可權設定的資料或功能?

只要signature相同,就算不顯式聲明也能access設定了normal或dangerous許可權設定的資料或功能。

2.若需要system級別許可權使用系統api(即使用system級別的簽名),如何同時使用其他signature許可權設定(即使用signature級別的簽名)的其他apk的功能?

擁有system級別許可權的使用者可以access其他普通signature許可權聲明設定過的功能。所以,設定為擁有system級別許可權即可。

來源: