iOS/Mac总是给人一种“安全”的印象,那它都有哪些安全技术?其中核心的“沙箱”技术又是如何实现的呢?对于Mac平台未沙盒化的应用,如何自定义启用进程沙盒化控制来降低安全风险?本文带你深入探究走入“沙箱”的世界~
iOS/Mac安全技术
iOS/Mac提供了”纵深的防御“的安全模型,就像城堡安全防御结构一样,如下图所示:
从外到内依次提供了:Develop ID签名及网闸、沙箱、POSIX安全模型及Keychain钥匙串存储加密。除此之外,还包括了比如网络传输安全、APP传输安全、防火墙、磁盘加密、文件保险箱等。
其中被大家所熟悉的更多的是签名+沙箱,iOS及Mac上架App Store的都需要进行签名及开启沙盒化,但对于Mac平台可以使用DevelopID签发而不需要上架App Store,这些应用大部分都未经沙盒化处理,因此存在一定的安全风险。那如何有效的控制”未沙盒化“的进程安全风险呢?我们就从沙箱原理道来~
沙箱概念
“沙箱|”给人的感觉就是进程运行在一个有保护的环境中执行,不会做出规定范围内不允许做的事情;其实质更多的是进程的系统资源访问受到系统的监控及限制,如网络、某些特殊路径、文件的读写等。
应用沙箱限制包括但不限于:
- 无法突破应用程序目录之外的目录。应用程序只能看到自己的根目录以及沙箱容器目录,对于iOS为根目录为
/var/mobile/Application/<app-GUID>
,沙箱目录为/var/mobile/containers
和/var/containers
;对于macOS而言,沙箱目录位于~/Library/Containers/<appid>
,或者使用了AppGroup
则为~/Library/Group Containers/<application-group-id>
。但是可以允许访问使用文件选择器用户自己指定的文件,.entitlements
指定的文件,以及临时目录、命令行工具目录以及特定的只读文件。对于根目录限制,类似chroot
系统调用来修改应用程序的根目录效果。 - 无法访问系统上的其他进程,即使具有同样UID的进程,应用程序认为自己就是系统上执行的唯一进程。
- 无法直接使用任何硬件设备,除非
.entitlement
权利文件中指定的硬件设备; - 无法动态生成代码,
mmap
和mprotect
系统调用(分别对应于Mach中的vm_map_enter
和vm_map_protect
调用)的底层实现被修改了,防止任何将可写内存页面设置为可执行的企图。具体代码中做了判定,做当前内存区域被修改为可读可执行,且不是动态代码签名允许的JIT
映射外,是不允许修改内存区域的权利的。 - 除了用户能执行的操作的一个子集外,无法执行任何其他操作。应用程序不具有
root
权限(除了Apple自己的应用程序外)。
可以通过“活动监视器”来查看进程的沙盒化情况,如下:
对于macOS Catalina及以下系统,也可以通过asctl sandbox check --pid [pid]
来检测是否开启沙盒化。
沙箱原理
强制访问控制
沙箱的实现依赖于Mac一项重要的安全特性:强制访问控制(Mandatory Access Control, MAC),这项特性来源于Trusted BSD
,其允许更为精细的安全模型,添加了对象级别的安全性,从而增强了简单粗暴地UNIX模型:限定特定进程针对具体文件或资源(例如套接字和IPC等)的访问权限,而不仅仅通过UNIX权限模型进行限制。
MAC中的关键概念是标签(label) ,来进行预定义的分类,系统中的文件集合或其他对象的集合都可以应用标签
来进行分类。如果请求访问的某个对象没有提供匹配的标签,则MAC就会拒绝访问请求。macOS对其进行了扩充,包含了安全策略的支持。这些安全可以应用于各种各样的操作
,而不只是对象
。
MAC提供了一个稳固的基础,可以向其添加组件,而组件并不要求是内核中的一部分,可以是插件的形式来对系统的安全性进行控制。可以在MAC中注册特殊的内核扩展,让其负责实施某个安全策略。从内核的角度看,各种系统调用的实现都插入了对MAC的调用,因此每个系统调用首先都必须通过MAC的验证。
如果策略模块没有注册特定的回调函数,则这些回调函数就直接返回0(表示验证通过)。由策略模块来提供验证逻辑,而MAC层本身并不会做任何决策。
内核还提供了一些MAC专用的系统调用,大部分调用都和FreeBSD一致,比如execve
的MAC专用系统调用__mac_execv
,以及支持MAC的对indirect
系统调用封装的__mac_syscall
。
沙箱架构
沙箱整个结构图如下:
其中主要包括了如下模块:
- libSystem.dylib: 提供
sandbox_init
、sandbox_free_error
等函数。 - libSandbox.dylib: 提供解析,编译,生成
*.sb
的沙盒profile
的函数。 - sandbox.kext:提供了system call的hook函数
- AppleMatch.kext:提供了解析
profile
的函数
沙箱整个大致流程如下图所示:
沙盒化的进程通过系统调用访问系统资源,比如open
来打开文件,就会从用户态陷入内核态。由于系统调用被”安插“了MAC层检查,因此会被拦截从而遍历检查策略模块。这里系统启动时会加载sandbox.kext
沙箱内核扩展驱动,以及AppleMobileFileIntegrity.kext(AMFI)
签名校验内核扩展,并注册至MAC强制访问层。因此,沙盒化的进程所有系统调用会被这两个策略模块过滤进行策略实施,来禁止或放行该系统调用,从而达到限制沙盒化进程访问系统资源的能力。
那沙盒化进程是如何被系统识别并注册至沙箱sandbox.kext策略模块的呢?别着急我们慢慢道来。
所有进程都依赖于libSystem.B.dylib
动态库,如下图所示:
当进程启动加载该动态库时,会从程序的可执行文件中提取沙盒化权限,并通过xpc消息发送至secinitd
守护进程。该守护进程会调用AppSandbox.framework
来创建沙箱配置文件,进而调用__sandbox_ms
即__mac_syscall
系统调用,进而来通过sandbox.kext
来实施沙盒化权限策略。
整个流程如下图所示:
对于沙盒化进程权限,可通过codesign -d --entitlements :- [exec_file]
来查看如下图所示:
进程的沙箱控制使用及实现
通过上面的沙箱原理分析可以看出,对于沙盒进程的沙盒化实施在用户态层主要依赖libSandbox.dylib
动态库。该动态库包含了实施沙箱的主要API,包括但不限于如下API:
/* 开启沙箱
@param profile见下,flags必须为SANDBOX_NAMED, errorbuf返回错误信息
@return 0 and errorbuf NULL -> success -1 and errorbuf -> failed
*/
int sandbox_init(const char *profile, uint64_t flags, char **errorbuf);
//释放沙箱错误缓冲区
void sandbox_free_error(char *errorbuf);
进程可以通过调用sandbox_init
主动进入沙盒,其中profile
配置项包括如下:
上面可配置项可实现:限制网络、文件访问以及纯计算而不适用系统资源。示例代码如下:
#include <sandbox.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, const char * argv[]) {
char *error_buf = NULL;
if (sandbox_init(kSBXProfileNoWrite, SANDBOX_NAMED, &error_buf) != 0)
perror("sandbox_init error:");
FILE *fp = fopen("/tmp/sandbox.txt" , "w");
if (!fp) {
perror("fopen error:");
return 1;
}
char read_buf[1024] = {0};
while (gets(read_buf) != NULL) {
fwrite(read_buf, strlen(read_buf), 1, fp);
}
fclose(fp);
return 0;
}
上述代码直接fopen
报错:fopen error:: Operation not permitted
。因为开启了kSBXProfileNoWrite
即禁止任何文件写,比如fopen("xxx", "w")
,或者fwrite()
,但是对于fopen("xxx", "r")
是不会限制的,或者对于沙箱开启前已经打开的文件描述符仍然是可用的。
虽然
sandbox_init
接口已经明确被弃用,但仍然是可用的,比如chrominum
毅然使用了该接口。并且沙箱权利是可以被继承的,即对于fork衍生的子进程(不管是否变为孤儿进程)也同样具有父进程的沙箱权利,比如kSBXProfileNoWrite
不可写则子进程也不具备写权利。不过对于已经开启沙盒化的进程,不能再次调用上传API,否则会存在冲突报错!
除了上面预先配置的配置文件之外,OS X 的沙箱包装命令 sandbox-exec
提供了一种灵活的配置语法,允许创建一个自定义的沙箱,该沙箱将在其中执行的应用程序的特定功能列入黑名单或白名单。
该工具实际是sandbox_init
的包装器,在fork/exec
调用前被执行,其核心实现如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> // fchmod
#include <sandbox.h>
#include "sandbox.h" // My sandbox.h
typedef int bool;
#include <dlfcn.h>
void usage(void)
{
fprintf(stderr, "Usage: sandbox-exec [options] command [args]\nOptions:\n -f profile-file Read profile from file.\n -n profile-name Use pre-defined profile.\n -p profile-string Specify profile on the command line.\n -D key=value Define a profile parameter.\n -t trace_file Trace operations to trace_file\n Exactly one of -f, -n, -p must be specified.\n");
exit(0x40);
} // usage
int debug = 0;
#define dprintf if (debug) printf
int main (int argc, char **argv)
{
debug = (getenv ("JDEBUG") != NULL);
// This is as close as possible to the decompilation of OS X's (10.11.4) sandbox-exec
// including calling sandbox_create_params() before processing arguments.
sbProfile_t *compiled_profile;
char *err = NULL;
sbParams_t *params=sandbox_create_params();
if (!params) { fprintf(stderr,"Can't create params!\n"); exit(1);}
if (argc < 2) { usage(); }
int opt;
char *profile = NULL;
int cmd = 0;
char *profileName = NULL;
char *profileString = NULL;
char *tracePath = NULL;
while ((opt = getopt(argc,argv,"D:c:de:f:n:p:t:")) > -1)
{
switch (opt)
{
case 'f': profile = optarg; break;
case 'n': profileName = optarg; break;
case 'p': profileString = optarg; break;
case 't': tracePath = optarg; break;
default: usage();
}
}
cmd = optind;
if (tracePath && profileName)
{
fprintf(stderr, "tracing isn't implemented for named profiles; use -f or -p to specify a profile\n");
exit(0x40);
}
//compiled_profile = sandbox_compile_entitlements ("no-internet", params, &err);
if (profile) compiled_profile = sandbox_compile_file (profile, params, &err);
// The built-in profiles:
// ----------------------
// kSBXProfileNoInternet (no-internet)
// kSBXProfileNoWriteExceptTemporary (no-write-except-temporary)
// kSBXProfileNoWrite (no-write)
// kSBXProfileNoNetwork (no-network)
// kSBXProfilePureComputation (pure-computation)
if (profileName) compiled_profile = sandbox_compile_named (profileName, params, &err);
if (profileString) compiled_profile = sandbox_compile_string (profileString, params, &err);
//sandbox_set_param (params, "x", "y");
if(!compiled_profile) { fprintf(stderr, "No compiled profile. Error: %s\n", err); exit(2); }
int dump= 1;
if (dump && compiled_profile->blob)
{
fprintf(stderr,"Profile: %s, Blob: %p Length: %d\n",
(compiled_profile->name? compiled_profile->name : "(custom)" ),
compiled_profile->blob, compiled_profile->len);
int fd = open("/tmp/out.bin", O_WRONLY | O_TRUNC| O_CREAT);
fchmod (fd, 0666);
write(fd, compiled_profile->blob, compiled_profile->len);
fprintf(stderr,"dumped compiled profile to /tmp/out.bin\n");
}
int flags = 0;
int rc = 0;
if (tracePath) {
#ifdef SB459
rc = sandbox_set_trace_path(compiled_profile,tracePath);
#else
void *sblibhandle = dlopen ("libsandbox.dylib", RTLD_GLOBAL);
typedef int sstp(void *, char *);
sstp * sandbox_set_trace_path = dlsym (sblibhandle, "sandbox_set_trace_path");
if (!sandbox_set_trace_path)
fprintf(stderr,"Warning: Tracing not supported in this sandbox version (can't get set_trace_path - %p)\n", sblibhandle);
else {
rc = sandbox_set_trace_path(compiled_profile,tracePath);
}
#endif
if (rc == 0) fprintf(stderr,"Tracing to %s\n", tracePath);
else fprintf(stderr,"Tracing error - Unable to trace to %s\n", tracePath);
}
fprintf(stderr,"Applying container\n");
rc = sandbox_apply_container (compiled_profile, flags);
if (rc != 0) { perror("sandbox_apply_container"); }
fflush(NULL);
if (compiled_profile) sandbox_free_profile(compiled_profile);
fprintf (stderr, "EXECING %s\n", argv[optind]);
execvp (argv[optind], argv+optind);
perror("execvp");
}
我们所熟知的Chrome沙箱也是基于libsandbox.dylib
中的API来实现,具体代码可见content/common/sandbox_mac.mm,最新的源码可见chromium.googlesource.com/chromium/sr…。
沙箱开源限制如下类型资源:
- 文件:读、写以及任意类型的操作
- IPC:Posix以及SysV
- Mach
- 网络:输入/输出
- 进程:执行及克隆
- 信号
- Sysctl
- System
sandbox-exe
使用规则如下:
$ sandbox-exec [-f profile-file] [-n profile-name] [-p profile-string] [-D key=value ...] command [arguments ...]
可以通过-f
作用于配置文件,-n
来作用于配置名称,以及-p
直接通过配置字符串来生效,如下:
$ sandbox-exec –f /usr/share/sandbox/bsd.sb /bin/ls
$ sandbox-exec -n no-internet ping www.google.com
可以参考系统的沙箱配置文件,路径包括:
/Library/Sandbox/Profiles
/System/Library/Sandbox/Profiles
/usr/share/sandbox
比如限制特定端口的访问如下:
$ sandbox-exec -p '
(version 1)
(allow default)
(deny network-outbound (remote ip "*:80"))' curl www.baidu.com
具体的语法规则可见Apple's Sandbox Guide v1.0 @2011
友情感谢
[1] macOS 的安全性
[2] Security and Your Apps-wwdc15
[3] iOS和macOS沙盒技术分析
[6] apple沙盒研究之基础知识