2017年12月4日 星期一

Android安全機制與sandbox機制詳解



Source : https://dotblogs.com.tw/cheng/2014/01/28/142415

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級別許可權即可。
來源:

http://fecbob.pixnet.net/blog/post/36156917-%5B%E8%BD%89%E8%BC%89%5Dandroid%E5%AE%89%E5%85%A8%E5%AD%98%E5%8F%96%E6%A9%9F%E5%88%B6
http://rritw.com/a/caozuoxitong/android/2011/1113/142239.html

沒有留言: