Mac 開發(一) 蘋果沙盒機制sandbox簡介
@[TOC]
Mac 開發(一) 蘋果沙盒機制sandbox簡介
mac沙盒實戰demo點選這裡下載:【MacFileAccessInSandbox】
1 Mac sandbox簡介
1.1 關於應用程式沙盒
- 什麼是沙盒?
維基百科的解釋:
- 在電腦保安領域,沙盒(英語:sandbox,又譯為沙箱)是一種安全機制,為執行中的程式提供的隔離環境。通常是作為一些來源不可信、具破壞力或無法判定程式意圖的程式提供實驗之用
- 沙盒通常嚴格控制其中的程式所能訪問的資源,比如,沙盒可以提供用後即回收的磁碟及記憶體空間。在沙盒中,網路訪問、對真實系統的訪問、對輸入裝置的讀取通常被禁止或是嚴格限制。從這個角度來說,沙盒屬於虛擬化的一種。
- 沙盒中的所有改動對作業系統不會造成任何損失。通常,這種技術被計算機技術人員廣泛用於測試可能帶毒的程式或是其他的惡意程式碼
在OS X以及IOS系統中限制了程式對一些資源的訪問許可權,例如網路、某些特殊路徑、檔案的讀寫等等,限定了程式的一些行為,從而保證程式不會做出超越許可權的操作。
下面看看蘋果對沙盒的解釋:
應用沙箱是macOS提供的一種訪問控制技術,在核心級別執行。如果應用程式受到威脅,它的目的是防止系統和使用者資料受到損害。通過Mac應用商店釋出的應用必須採用應用沙箱。通過開發者ID在Mac應用商店外簽名和分發的應用程式也可以(在大多數情況下應該)使用應用沙箱。
Mac OSX自從10.6系統開始引入沙盒機制,規定釋出到Mac AppStore的應用,必須遵守沙盒約定。沙盒對應用訪問的系統資源,硬體外設,檔案,網路,XPC,都做了嚴格的限制,這樣能防止惡意的App通過系統漏洞,攻擊系統,獲取控制許可權,保證了OSX系統的安全。
沙盒相當於給每個App一個獨立的空間,你只能在自己的小天地裡面玩。要獲取自己空間之外的資源必須獲得授權。
那麼蘋果為啥要限制提交Appstore的app必須使用蘋果的沙盒機制呢?
1.2 為啥要用沙盒機制
複雜的系統總是會有漏洞,而軟體的複雜性只會隨著時間的推移而增加。無論您多麼小心地採用安全編碼實踐並防範bug,攻擊者只需要通過一次防禦就可以成功。雖然應用沙箱不能阻止對您的應用程式的攻擊,但它可以最小化一個成功的攻擊所造成的傷害。
非沙箱應用程式擁有執行該應用程式的使用者的全部許可權,並可以訪問使用者可以訪問的任何資源。如果該應用程式或與之連結的任何框架包含安全漏洞,攻擊者可能會利用這些漏洞來控制該應用程式,這樣,攻擊者就可以做使用者可以做的任何事情。
為了緩解這個問題,應用沙箱策略有兩個方面:
- 應用沙箱允許你描述你的應用如何與系統互動。然後,系統授予應用程式完成工作所需的訪問許可權,僅此而已。
- 通過開啟和儲存對話方塊、拖放和其他熟悉的使用者互動,應用沙箱允許使用者透明地授予應用額外的訪問許可權。
2 沙盒原理
- 沙盒的大致工作流程入下圖所示:
1
程式嘗試進行一次系統呼叫(system call),呼叫核心功能。2、3
MAC層需要根據該程式的安全策略判斷此次系統呼叫是否可以執行。4、5、6、7、8、9
如果存在策略的話,通過sandbox.kext(hook函式)和AppleMatch.kext(沙盒的profile解析)兩個核心擴充套件實現許可權的檢查。10
返回呼叫結果
與沙盒系統相關的模組大致如下:
.libSystem.dylib
: 提供sandbox_init
、sandbox_free_error
等函式。libSandbox.dylib
: 提供解析,編譯,生成*.sb
的沙盒profile
的函式。sandbox.kext
:提供了system call
的hook
函式AppleMatch.kext
:提供瞭解析profile
的函式
- 結構圖大致如下:
- 沙盒的工作流程大致可以總結為:
- 通過
sandbox_init
初始化某沙盒策略指令碼並編譯為二進位制檔案- 在程式進行
system call
時,通過TrustedBSD
提供的hook
模組,利用Sandbox.kext
提供的system call hook
函式,結合沙盒策略進行判斷,該程式是否有許可權執行該system call
。
3 xcode中開啟沙盒許可權
3.1 XCode Capabilities 開啟Sandbox許可權
3.1.1 Capabilities 開啟Sandbox許可權
應用開發完成提交到App Store時,必須進行沙盒化。切換到工程target設定Tab的Capabilities中。
- 第一項就是App Sandbox開關,點選ON,表示應用使用沙盒。
- Network:網路訪問控制
Incoming Connections (Server)
: 應用做為Server對外提供HTTP,FTP等服務時需要開啟。如果你的App擔任伺服器角色,需要連線通訊需要開啟此許可權。Outgoing Connections (Client)
: 做為客戶端,訪問伺服器時需要開啟。如果你的App需要作為客戶端進行socket連線通訊需要開啟此許可權。
- Hardware:硬體資源控 它包含下面這些子項:
Camera
: 如果你需要開啟攝像頭功能,勾選此項。Audio Input
: 如果你需要獲取音訊 輸入許可權(如麥克風),勾選此項。USB
: 如果你需要使用USB傳輸檔案,需要開啟此功能 4:Printing
: 如果你需要列印檔案裡面的內容,需要開啟此功能
- App Data:獲取系統的聯絡人,位置,日曆服務時需要開啟
Contacts
: 如果要訪問聯絡人,需要勾選此項Location
: 如果需要定位,需要勾選此項。Calendar
: 如果需要訪問日曆,需要勾選此項。
-
File Access:檔案和使用者目錄的訪問控制,分為禁止
none
,只讀,讀寫3類
User Selected File
:檔案類應用或者需要使用者選擇開啟某個檔案時,需要選擇合適的訪問許可權.Downloads Folder
: 如果需要訪問當前使用者 Downloads資料夾,需要勾選此項,可以設定為只讀,或者可讀可寫Pictures Folder
: 如果需要訪問當前使用者 Pictures資料夾,需要勾選此項,可以設定為只讀,或者可讀可寫Music Folder
: 如果需要訪問當前使用者 Music資料夾,需要勾選此項,可以設定為只讀,或者可讀可寫Movies Folder
: 如果需要訪問當前使用者 Movies資料夾,需要勾選此項,可以設定為只讀,或者可讀可寫
特別注意:如果應用中不需要的許可權項,一律不要開啟。否則App Review團隊會拒絕你的應用上架.
3.1.2 Entitlements 直接變xml,開啟Sandbox許可權
實際上,在沙盒中每個需要訪問許可權的項都對應一個key,對應的value,YES 或 NO表示是否允許訪問。當你選擇了項後,都會記錄在一個副檔名為.entitlements的plist 的檔案中,如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.movies.read-write</key>
<true/>
<key>com.apple.security.assets.music.read-only</key>
<true/>
<key>com.apple.security.assets.pictures.read-only</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.usb</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
<key>com.apple.security.print</key>
<true/>
<key>com.apple.security.temporary-exception.apple-events</key>
<array>
<string>com.apple.itunes</string>
</array>
<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
<true/>
<key>com.apple.security.temporary-exception.shared-preference.read-only</key>
<array>
<string>com.apple.iphoto</string>
<string>com.apple.photobooth</string>
<string>com.apple.photos</string>
</array>
</dict>
</plist>
複製程式碼
用plist屬性顯示如下:
應用打包時會對這個檔案進行簽名。
當應用執行期間要獲取某個許可權時,系統都會通過.entitlements
去檢查應用是否有授權,如果沒有就拒絕訪問。
3.2 Mac 檔案沙盒化實戰
3.2.1 沒有使用sandbox使可以任意訪問檔案
mac沙盒實戰demo點選這裡下載:【MacFileAccessInSandbox】
-
首先我們建立一個Mac app工程,我這裡選擇的是Swift版本的工程。
-
新增一些呼叫api的UI控制元件
-
連線繫結UI事件
-
實現一個簡單的開啟檔案功能如下:
extension ViewController {
fileprivate func openFile() {
let openPanel = NSOpenPanel()
openPanel.prompt = "Open"
openPanel.allowsMultipleSelection = true
openPanel.canChooseDirectories = true
openPanel.canChooseFiles = true
openPanel.resolvesAliases = true
openPanel.nameFieldLabel = "Open File"
openPanel.title = "Open"
openPanel.allowedFileTypes = ["txt"]
openPanel.begin { (response) in
if response == .OK {
let urls = openPanel.urls
for url in urls {
UserDefaults.standard.set(url,forKey: LastSaveFilePathKey)
UserDefaults.standard.synchronize()
self.filePathField.stringValue = url.path
let text = try? String(contentsOf: url,encoding: .utf8 )
self.textV.string = text ?? ""
}
}
}
}
}
複製程式碼
- 接下來我們實現儲存檔案功能
/// 儲存檔案
fileprivate func saveFile() {
guard let lastSelectUrl = UserDefaults.standard.value(forKey: LastSaveFilePathKey) as? URL else {
return
}
let text = textV.string
try? text.write(to: lastSelectUrl,atomically: true,encoding: .utf8)
}
複製程式碼
接下來我們驗證一些儲存檔案功能: 沒有修改內容前:
檢視真實檔案: 我們在不使用sandbox的功能的時候,是可以正常讀寫檔案的,如下是我先把sandbox功能關閉:注意:
有兩種方式關閉sandbox:
- 直接在Capabilities中將Sandbox刪除
- 不使用證書籤名的方式編譯,沙盒機制只會在證書籤名的方式下才生效。
這裡我採用的是第二種方式關閉sandbox,如下圖所示,先在buildsetting中搜索Signing選項,找到 “Code Signing Identity” 雙擊把裡面的內容刪除。
或者選擇Other- 我們先來看看關閉sandbox的情況:
關閉沙盒後,我們可以隨意訪問任意的有許可權的目錄的檔案,如下面是我在關閉沙盒的情況下可以訪問電腦桌面的任意檔案,這裡我選擇的是:
/Users/kongyulu/Desktop/筆記/test.txt
- 這裡我增加了再次啟動app時讀取上一次儲存路徑的檔案功能,如下:
fileprivate func initData() {
guard let path = UserDefaults.standard.value(forKey: LastSaveFilePathKey) as? String else {
return
}
filePathField.stringValue = path
let url = URL(fileURLWithPath: path)
do {
let text = try String(contentsOf: url)
textV.string = text
} catch {
}
}
複製程式碼
關閉沙盒的情況下,可以正常訪問,並讀取到給的路徑的檔案的內容,如下:
- 接下來我們來加上證書籤名,讓Sandbox生效,這個時候,我們會發現我剛剛儲存的那個沙盒之外的路徑
在APP啟動時不能讀取到txt文字的內容了,點選儲存按鈕也無法將檔案修改,這是為啥呢?
蘋果爸爸規定:我們程式剛剛啟動的時候是不能去訪問沙盒之外的路徑的,蘋果預設只允許訪問APP它自己沙盒的內容是不受限制的,如果要訪問沙盒之外的路徑是需要使用者授權的,我們可以呼叫
NSOpenPanel
類 彈出一個對話方塊給使用者去選擇他要開啟的檔案,當使用者點選了OK按鈕,則表示使用者已經授權了這個檔案,這個時候我們需要通過bookmark去儲存這個已經授權的檔案路徑資訊(我們可以儲存到系統偏好Prefer裡面,使用UserDefaults.standard.set(url,forKey: LastSaveFilePathKey)
),下次App啟動的時候,直接從bookmark獲取到URL,然後呼叫allowedURL = [NSURL URLByResolvingBookmarkData:bookmarkData options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:&bookmarkDataIsStale error:NULL];
函式獲取到授權的URL, 這個時候我們去訪問url路徑下的檔案,可以直接訪問,它原理使用者給定的是什麼許可權,現在就可以得到什麼許可權。不需要再次彈出對話方塊讓使用者去選擇路徑授權了。
- 那麼問題來了,怎麼去儲存這個bookmark,獲取它呢,這裡我在我的demo裡面都給出了方法,可以自己去檢視這個demo: 【MacFileAccessInSandbox】
主要涉及到的程式碼是:
- (BOOL)requestAccessPermissionsForFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(SandboxFileSecurityScopeBlock)block {
NSParameterAssert(fileURL);
NSURL *allowedURL = nil;
// standardize the file url and remove any symlinks so that the url we lookup in bookmark data would match a url given by the askPermissionForURL method
fileURL = [[fileURL URLByStandardizingPath] URLByResolvingSymlinksInPath];
// lookup bookmark data for this url,this will automatically load bookmark data for a parent path if we have it
NSData *bookmarkData = [self.bookmarkPersistanceDelegate bookmarkDataForURL:fileURL];
if (bookmarkData) {
// resolve the bookmark data into an NSURL object that will allow us to use the file
BOOL bookmarkDataIsStale;
allowedURL = [NSURL URLByResolvingBookmarkData:bookmarkData options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:&bookmarkDataIsStale error:NULL];
// if the bookmark data is stale we'll attempt to recreate it with the existing url object if possible (not guaranteed)
if (bookmarkDataIsStale) {
bookmarkData = nil;
[self.bookmarkPersistanceDelegate clearBookmarkDataForURL:fileURL];
if (allowedURL) {
bookmarkData = [self persistPermissionURL:allowedURL];
if (!bookmarkData) {
allowedURL = nil;
}
}
}
}
// if allowed url is nil,we need to ask the user for permission
if (!allowedURL) {
allowedURL = [self askPermissionForURL:fileURL];
if (!allowedURL) {
// if the user did not give permission,exit out here
return NO;
}
}
// if we have no bookmark data and we want to persist,we need to create it
if (persist && !bookmarkData) {
bookmarkData = [self persistPermissionURL:allowedURL];
}
if (block) {
block(allowedURL,bookmarkData);
}
return YES;
}
複製程式碼
-
這個函式是先去這個請求的路徑是否已經儲存了,如果儲存了是否可以獲得它的bookmark, 如果能獲取這個bookmkark,則通過
[NSURL URLByResolvingBookmarkData:]
這個方法去獲取授權的URL,獲得後就可以直接訪問路徑的檔案了,如果沒有授權或者儲存過bookmark,則從新去彈框,讓使用者授權。 -
獲取授權路徑後,需要呼叫
startAccessingSecurityScopedResource
開啟訪問許可權,在使操作完檔案後,需要呼叫stopAccessingSecurityScopedResource
關閉訪問許可權,防止惡意程式直接訪問檔案。
- (BOOL)accessFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(SandboxFileAccessBlock)block {
NSParameterAssert(fileURL);
NSParameterAssert(block);
BOOL success = [self requestAccessPermissionsForFileURL:fileURL persistPermission:persist withBlock:^(NSURL *securityScopedFileURL,NSData *bookmarkData) {
// execute the block with the file access permissions
@try {
[securityScopedFileURL startAccessingSecurityScopedResource];
block();
} @finally {
[securityScopedFileURL stopAccessingSecurityScopedResource];
}
}];
return success;
}
複製程式碼