微信公众号:计算机与网络安全
安全机制
Android是一种基于Linux的、自由的、开源的操作系统。它主要使用于移动设备,如智能手机和平板电脑,由Google公司和开放手机联盟开发。Android系统架构可以分为4层结构,由上至下分别是应用程序层、应用程序框架层、系统运行库层以及内核层,如图1所示。
图1 Android系统结构
Android应用层允许开发者无须修改底层代码就能对设备的功能进行拓展,Android的应用程序框架层为开发者提供了大量的API来访问Android的设备。
Android 应用和Android 框架都是用 Java 语言开发的,并运行在 DalvikVM 中运行。DalvikVM的作用主要就是为操作系统底层提供一个高效的抽象层。DalvikVM是一种基于寄存器的虚拟机,能够解释执行Dalvik可执行格式DEX的字节码,Android 应用和Android 框架都是用 Java 语言开发的,并运行在 DalvikVM 中运行。DalvikVM的作用主要就是为操作系统底层提供一个高效的抽象层。DalvikVM是一种基于寄存器的虚拟机,能够解释执行Dalvik可执行格式DEX的字节码,
Android 将安全设计贯穿系统架构的各个层面,覆盖系统内核、虚拟机、应用程序框架层以及应用层各个环节,力求在开放的同时,也能保护用户的数据、应用程序和设备安全。
1. Android进程沙箱隔离机制
进程沙箱隔离机制,使Android应用程序在安装时被赋予独特的用户标识(UID),并永久保持。应用程序及其运行的Dalvik虚拟机运行在独立的Linux进程空间,与其他应用程序完全隔离,如图2所示。
图2 Android进程沙箱隔离机制
在特殊情况下,进程间还可以存在相互信任关系。如源自同一开发者或同一开发机构的应用程序,通过Android提供的共享UID(Shared UserId)机制,使具备信任关系的应用程序可以运行在同一进程空间。
2. 应用程序签名机制
规定 APK 文件必须被开发者进行数字签名,以便标识应用程序作者和在应用程序之间的信任关系。在安装应用程序APK时,系统安装程序首先检查APK是否被签名,有签名才能安装。当应用程序升级时,需要检查新版应用的数字签名与已安装的应用程序的签名是否相同,否则,会被当作一个新的应用程序。Android 开发者有可能把安装包命名为相同的名字,通过不同的签名可以把它们区分开来,也保证签名不同的安装包不被替换,同时防止恶意软件替换安装的应用。
3. 权限声明机制
要想在对象上进行操作,就需要把权限和此对象的操作进行绑定。不同级别要求应用程序行使权限的认证方式也不一样,Normal级申请就可以使用,Dangerous级需要安装时由用户确认,Signature和SignatureOrSystem级则必须是系统用户才可用。
4. 进程通信机制
基于共享内存的 Binder 实现,提供轻量级的远程进程调用(RPC)。通过接口描述语言(AIDL)定义接口与交换数据的类型,确保进程间通信的数据不会溢出越界,如图3所示。
图3 进程通信机制
5. 内存管理机制
基于Linux的低内存管理机制,设计实现了独特的LMK,将进程重要性分级、分组,当内存不足时,自动清理级别进程所占用的内存空间。同时,引入的 Ashmem 内存机制,使Android具备清理不再使用共享内存区域的能力。
正是因为Android采用多层架构,在保护信息安全的同时,也保证开放平台的灵活性。
Root
在Android设备上获得超级用户权限的过程通常叫作Root,因为超级用户在任何一类的Unix系统中都被叫作Root,拥有所有的文件与程序的权限,能对操作系统进行完全控制。一般情况下,手机厂商出于安全考虑会关闭手机的Root权限,手机系统是运行在普通用户权限下的,用户是无法操作系统中的文件与数据的。
1. Root的作用
(1)卸载系统自带软件
手机出厂时会预装一些软件,这些软件通常无法卸载,必须Root后获取手机的最高权限才可以卸载。
(2)安装一些特殊的软件
有些安卓软件必须Root拥有系统最高权限后才可以安装。
(3)启用或禁用自启动程序
通过Root获取最高权限,可以设置启用或禁用某些自启动程序,即设置某些程序是否随系统一起启动。
(4)打游戏刷高分
有些游戏爱好者通过Root刷分在游戏中快速升级拿高分。
(5)好奇
有些用户纯粹出于好奇去Root手机。当然还有其他的原因,如享受Root成功后的那种喜悦感等。
2. Root的危害
Root 之后虽然可以享受很多便利,但是要意识到 Root 设备将会损害设备的安全性。所有的用户数据都将暴露给被授予Root权限的应用。当您的设备丢失后,其他人就能从中提前数据。带来较大的危害。
(1)硬件危害
手机Root一般情况下不会对硬件造成危害。但Root后可能会导致一些数据出错或丢失,如改变充电电流或照明功率等,从而烧坏手机的元器件。
(2)系统危害
手机Root后,因为一些系统数据的丢失或出错,可能会出现一些系统问题,如手机无法开机、不停地重启无法进入桌面、触屏失灵、Wi-Fi或蓝牙失效等意想不到的系统故障。
(3)软件危害
手机Root后,可能还会造成一些软件无法正常使用。例如除充电缓慢、待机缩短、发热等故障外,照相功能也出现了异常,照相时无法正常预览画面,拍的照片发暗。
(4)信息泄露风险
手机Root后,给用户使用手机带来了较大的自主权,但同时也给恶意软件打开了方便之门。恶意软件可以比平常更容易侵入到手机,并潜伏在用户的手机伺机而动。当用户登录支付宝、淘宝、手机银行或其他账户时,某个人信息可能就会被人窃取,甚至个人财产也存在被人盗取的风险。
(5)影响保修
目前各大手机厂商对于手机Root后有3种售后处理政策。
第一种:手机Root后,不再享有保修的权利,且刷回原系统需收费。
第二种:手机Root后,若IMEI号丢失,不再享有保修的权利,且刷回原系统需收费;若IMEI号丢失,三包期内可继续享有保修的权利,但刷回原系统需收费。
第三种:手机Root后,三包期内继续享有保修的权利,刷回原系统也不收费。
绝大部分手机厂商执行的是第二种售后政策,只有极少数的手机厂商执行的第一种或第三种售后政策。所以如果Root失败或出现故障时,需要去进行售后处理,消费者都一般需要付出付费的代价。即便少数厂商刷机不收费,也可继续保修,若Root失败或出现异常,也会浪费消费者很多时间和精力,而且要接受手机暂时无法正常使用带来的不便和麻烦。
3. Root的原理
Android获取Root其实和Linux切换Root用户是一样的。在Linux下,只要执行su或sudo,输入Root的密码就能获取Root的权限了,其实就是将uid和gid设置为0。在Android5.0之前没有多用户切换,对su也没有密码验证,也没有sudo命令,所以很多Rom都去掉了su命令。
其实在Android系统中,获得了Root比没有获得Root的系统多了两个东西,一个是su二进制文件,一个是superuser.apk,su是用来获得Root权限的命令,superuser.apk是一个管理工具,用来对Root权限进行管理和安全提示。
当某些程序执行su指令想取得系统最高权限的时候,Superuser就会自动启动,拦截该动作并做出询问,当用户认为该程序可以安全使用的时候,就选择允许,否则,可以禁止该程序继续取得最高权限。Root的过程其实就是把su文件放到/system/bin/Superuser.apk放到system/app下面,还需要设置/system/bin/su可以让任意用户可运行,有set uid和set gid的权限。
即要在Android机器上运行以下命令。
adb shell chmod 4755/system/bin/su
下面简单介绍一下su文件的源码,如下。
int main(int argc,char**argv)
{
struct passwd*pw;
int uid,gid,myuid;
/*Until we have something better,only root and the shell can use su.*/
myuid=getuid;
if(myuid!=AID_ROOT&&myuid!=AID_SHELL){
fprintf(stderr,"su:uid %d not allowed to su\n",myuid);
return 1;
}
if(argc<2){
uid=gid=0;
}else{
pw=getpwnam(argv[1]);
if(pw==0){
uid=gid=atoi(argv[1]);
}else{
uid=pw->pw_uid;
gid=pw->pw_gid;
}
}
if(setgid(gid)||setuid(uid)){
fprintf(stderr,"su:permission denied\n");
return 1;
}
/*User specified command for exec.*/
if(argc==3){
if(execlp(argv[2],argv[2],NULL)<0){
fprintf(stderr,"su:exec failed for %s Error:%s\n",argv[2],
strerror(errno));
return-errno;
}
}else if(argc>3){
/*Copy the rest of the args frommain.*/
char*exec_args[argc-1];
memset(exec_args,0,sizeof(exec_args));
memcpy(exec_args,&argv[2],sizeof(exec_args));
if(execvp(argv[2],exec_args)<0){
fprintf(stderr,"su:exec failed for %s Error:%s\n",argv[2],
strerror(errno));
return-errno;
}
}
/*Default exec shell.*/
execlp("/system/bin/sh","sh",NULL);
fprintf(stderr,"su:exec failed\n");
return 1;
}
4. Root的思路
-rwxr-xr-x:r代表该文件可读,w代表可写,x代表可执行,-代表没有该权限。第一个rwx代表文件所有者的权限,第二个rwx代表和所有者同组人的权限,第三个rwx代表其他用户对该文件的权限。但下面这个文件就比较特殊。
rws。它的执行权限标识位是一个 s,s 代表当任何一个用户执行该文件的时候都拥有文件所有者的权限,这文件的所有者是Root,简单来说就是不管谁执行这个文件,他执行的时候都是以Root身份执行的。
也就说即使不是 Root 也有可能以 Root 身份来执行程序,那么就把一个所有者是 Root的su程序权限标识位置成-rwsr-xr-x,不管谁执行它,都是Root身份执行,su就可以顺利执行了,执行成功之后就是Root身份了。
所以,需要把一个所有者是Root的su拷贝到Android手机上,并且把su的权限标识位置成-rwsr-xr-x,就成功Root了一个手机。代码如下。
cp/data/tmp/su/system/bin/#copy su 到/system/分区
chown root:root su #su的所有者置成root
chmod 4775/system/bin/su #把su置成-rwsr-xr-x
但是每一行代码都要Root权限才能执行成功,就是说只有在有Root权限的情况下才能执行上面3行代码,而这3行代码就是为了获得Root权限的,这是一个逻辑闭环,正常情况下是无法实现的。
目前有以下两种思路。
找到一个已经有Root权限的进程来完成上述的命令,一般就是利用系统漏洞提升权限到Root,init进程启动的服务进程,如adbd、rild、mtpd、vold都是有Root权限的。比较经典的是RageAgainstTheCage漏洞,通过adbd启动时候自动降级Shell权限失败,adbd仍然运行在Root权限下面,再用adb连接设备,adb就运行在了Root权限下。
另一个就是通过系统之外植入,如通过Recovery刷机的形式刷入su。
5. Root的分类
可以把Root分成临时性Root与永久性Root。一直保留Root权限有安全隐患,不Root又不能获得一些个性化的体验。所以就提出了临时性Root,不需要的时候再把系统还原回去。
(1)临时性Root
临时性Root实质就是通过一系列操作让系统在短时间内获取Root权限,不需要的时候又将系统恢复到非Root状态下,一般操作方式是判断设备有无重新启动,如果设备重新启动了,则还原用户的ID级别到非Root级别,以取消当前用户的临时Root权限。一方面避免多次弹出授权提醒的问题,造成不好的用户体验,另一方面,Root权限自动消失相对比较安全。有些应用程序只希望自己获得Root权限,很多时候连su和Superuser都没有植入到系统中,这样在自己进程结束的时候就能释放掉Root权限,更加安全。
(2)永久性Root
重启之后不清空Root权限,保持系统的Root状态。通过superuser.apk应用来给出用户提示,以判断何时需要进行Root。
设备一旦被Root,任何应用程序都可以调用su命令来执行Root权限,这样设备是很不安全的,所以需要superuser来完成以下几个工作来保证设备的安全。
Su文件是否被替换修改,建立白名单,只允许特定的应用来使用Root权限,应用程序调用su命令的时候,向用户弹出Root申请界面,如图1所示。
图1 Root申请
系统原生的su对于所有的应用程序是平等的,所以原生的su是无法保证su的安全的,Superuser必须安装入自定义的su,以及能够保证自身su不被替换的Deamon进程。应用请求Root权限的时候,自定义的su则会通知Superuser。由Superuser来进行白名单存储于Root权限授予提示,让用户选择是否给予该应用Root权限。而su与Superuser之间的通信是靠一个阻塞的Socket来完成的。
具体的操作流程如图2所示。
图2 申请Root流程
权限攻击
1. Android权限
相比于Apple,Microsoft严格控制生态系统,只允许通过官方应用商店安装应用,Android的开放就意味着,Google需要向用户提供一系列用于为自己负责的流程和工具。所以在安装应用前,Android总是要事无巨细地告诉您,App需要什么权限。
Android权限指Android中的一系列“Android.Permission.*”对象。Google在Android框架内把各种对象(包括设备上的各类数据、传感器、拨打电话、发送信息、控制别的应用程序等)的访问权限进行了详细划分,列出了约100条"Android.Permission"。应用程序在运行前必须向Android系统声明它将会用到的权限,否则Android将会拒绝该应用程序访问通过该"Permission"许可的内容。
例如,某输入法提供了一个智能通讯录的功能,用户可以在输入联系人拼音的前几个字符或首字母后,输入法就能自动呈现相关联系人的名字。为了实现这个功能,输入法必须声明它需要读取手机中联系人的能力,也就是在相关代码中加上声明"android.permission.READ_CONTACTS"对象。
2. Android权限分类
由于基于Linux内核,Android系统中的权限分为以下3类。
(1)Android手机所有者权限
这个和厂商相关,可以理解为系统权限。
(2)Android Root权限
类似于Linux,这是Android系统中的最高权限。如果拥有该权限,就可以对Android系统中的任何文件、数据、资源进行任意操作。所谓“越狱”,就是令用户获得最高的Root权限。
(3)Android应用程序权限
该权限在AndroidManifest文件中由程序开发者声明,在程序安装时由用户授权,共有4类不同的权限保护级别(Protection Level)。
我们经常在AndroidManifest中使用权限,如果我们想让应用程序可以发短信,那么应该使用如下命令。
<uses-permission android:name="android.permission.SEND_SMS"/>
其权限的定义是在frameworks/base/core/res/AndroidManifest.xml中,如下。
<permission android:name=”android.permission.SEND_SMS” android:permissionGroup=”android.permissiongroup.COST_MONEY” android:protectionLeve=”dangerous” android:label=”@string/permlab_ sendSms”android:deion=”@string/permdesc_sendSms”/>
这个XML可以认为是系统APK使用的AndroidManifest.xml,该APK使用系统的私钥进行签名。
下面分别简单介绍下各个标签的含义。
android:name:权限的名字,uses-permisson使用的。
android:permissionGroup:权限的分类,在提示用户安装时会把某些功能差不多的权限放到一类。
android:protectionLevel:分为Normal、Dangerous、Signature、SignatureOrSystem。
android:label:提示给用户的权限名。
android:deion:提示给用户的权限描述。
其中,android:protectionLevel各个属性说明如下。
① Normal
风险较低的权限,任何应用都可以申请,在安装应用时,不会直接提示给用户,单击全部才会展示。
② Dangerous
风险较高的权限,任何应用都可以申请,安装时需要用户确认才能使用。
③ Signature
仅当申请该权限的应用程序与声明该权限的程序使用相同的签名时,才赋予该权限。
④ SignatureOrSystem
仅当申请该权限的应用程序位于相同的Android系统镜像中,或申请该权限的应用程序与声明该权限的程序使用相同的签名时,才赋予该权限。
3. Android权限处理
Android的策略如下。
(1)文件和设备访问,使用Linux的权限访问控制
部分权限声明之后,应用程序启动的时候,AMS会从PKMS那里获得该应用进程的uid、gid和组id信息,然后通过Zygote来创建一个指定id的进程。获得指定组id的进程,也会获得部分文件的访问权限,如声明 android.permission.WRITE_EXTERNAL_STORAGE 来访问 SDcard 会被赋予 sdcard_rw 的组 id。权限所对应的组 id 在 frameworks/base/data/etc/platform.xml当中。
特别注意:内核检查 id 的顺序是 uid 然后再到 gid和组 id,所以,当声明 android.permission.WRITE_EXTERNAL_STORAGE的同时,声明shareUserId为system,是没有读写SDcard权限的。
(2)Android接口调用控制
首先是 Root 用户和System 用户拥有所有的接口调用权限,然后对于其他用户使用Context的这几个函数来实现,如下。
Context.checkCallingOrSelfPermission(String);
Context.checkCallingOrSelfUriPermission(Uri,int);
Context.checkCallingPermission(Permission);
Context.checkCallingUriPermission(Uri,int);
Context.checkPermission(String,int,int);
Context.checkUriPermission(Uri,int,int,int);
Context.checkUriPermission(Uri,String,String,int,int,int);
Context.enforceCallingOrSelfPermission(String,String);
Context.enforceCallingOrSelfUriPermission(Uri,int,String);
Context.enforceCallingPermission(String,String);
Context.enforceCallingUriPermission(String,String);
Context.enforcePermission(String,int,int,String);
Context.enforceUriPermission(Uri,int,int,int,String);
Context.enforceUriPermission(Uri,String,String,int,int,int,String);
其中,以check开头的,只做检查,以enforce开头的,不单检查,没有权限的还会抛出异常。
这几个函数最后会调用到PKMS的checkUidPermission,该函数通过对比应用权限信息来判断该应用是否获得权限。
(3)Android权限等级
划分为Normal、Dangerous、Signature、SignatureOrSystem、System和Development,其中,Signature需要签名才能赋予权限,SignatureOrSystem需要签名或系统级应用(放置在/system/app目录下)才能赋予权限,SignatureOrSystem需要签名或系统级应用(放置在/system/app 目录下)才能赋予权限,系统权限的描述在 frameworks/base/core/res/AndroidManifest.xml当中。
如果需要在系统中增加一个权限,则将按照下列步骤进行。
① 确定权限属于文件访问控制,还是接口调用控制。
② 在frameworks/base/core/res/AndroidManifest.xml中增加权限描述。
③ 如果是文件访问控制,那就在frameworks/base/data/etc/platform.xml为权限依附指定的组id。
④ 如果是接口调用控制,那就在接口调用里面,加入上述Context检查权限的函数。
4. Android权限危害
Android 应用程序权限所造成的危害主要是由该 Android 应用程序拥有的权限超过其本身所需要的所造成的。
该应用程序在安装时就直接获得了Root权限,这就意味着该应用程序有一把万能钥匙,可以在手机上为所欲为,不仅可以获取手机使用者所有的个人信息,进行扣费、发送消息等操作,还能直接对手机系统本身进行攻击,造成系统不稳定、直接关机、软件闪退或反应慢等,甚至有可能造成手机本身硬件问题,如充电缓慢、待机缩短、发热发烫等。
应用程序在安装时所要求的权限不合理,超过本身所需,很多应用程序会在安装的时候或多或少地多申明一些权限,一方面为该应用程序本身后续版本迭代做准备,另一方面也为了该应用程序能更好地运行。但是,这样会导致权限供大于求,从而造成不必要的麻烦。如一些应用程序习惯性地申明了获取精确地理位置、自动连接网络、绑定用户设备、使用蓝牙、通话权限、拍照权限、底层访问权限等,将对用户的个人隐私及个人财产造成巨大危害获风险。
一些应用程序设置了自动更新版本迭代,当有攻击者仿照该应用程序签名伪造了一个新版本的应用程序时,手机自动更新后将直接被该应用程序攻击。
串权限攻击的主要思想是一个程序A有某个特定的执行权限,一个程序B没有这个权限,但是B可以利用A的权限来执行这个权限。
5. Android权限思考
Android是开放型的系统,因此,需要开发者和使用者双方对Android权限安全进行维护和防护。
对于开发者而言,在开发应用程序的时候实事求是地申明所需权限,并减少外界应用程序对该应用程序的非法调用和访问。
对于使用者而言,需要学会判断应用权限要求的合理性。一款应用应该根据自身提供的功能来要求合理的权限,因此,用户可以根据该应用的功能来简单判定其所要求的权限是否合理。如一款小游戏如果需要获取手机浏览器历史记录、手机联系人资料和本机号码等个人隐私信息,那么它对权限的要求是不合理的,而且很有可能存在短信扣费风险。
组件安全
Android组件包括Activity、BroadcastReceiver、Service和ContentProvider。
1. Activity安全
Activity类型和使用方式决定了其风险和防御方式,Activity分类如表1所示。
表1 Activity分类
Android中提供了Intent机制来协助应用间的交互与通信,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用程序之间,也可用于应用程序内部的 Activity/Service 之间的交互。因此,Intent 在这里起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。在SDK中给出了Intent作用的表现形式如下。
1)通过Context.startActivityorActivity.startActivityForResult,启动一个Activity。
2)通过Context.startService启动一个服务,或通过Context.bindService和后台服务交互。
3)通过广播方法(如Context.sendBroadcast、Context.sendOrderedBroadcast,Context.sendStickyBroadcast)发给BroadcastReceiver。
Activity的安全主要体现在Activity访问权限的控制和Activity被劫持。
2. BroadcastReceiver安全
BroadcastReceiver 翻译成中文为广播接收者,用于处理接收到的广播,广播接收者的安全分为发送安全与接收安全两个方面。
广播是一种广泛运用的在应用程序之间传输信息的机制,广播的分类及其特点如表2所示,而BroadcastReceiver是对发送出来的广播进行过滤接收并响应的一类组件;广播接收者用于接收广播 Intent,广播 Intent 的发送是通过调用 Context.sendBroadcast、Context.sendOrderedBroadcast来实现的。通常一个广播Intent可以被订阅了此Intent的多个广播接收者所接收。在AndroidManifest.xml中,组件的Action是通过Intent过滤器来设置的,使用了Intent过滤器的Android组件默认情况下都是可以被外部访问的,解决方法就是组件声明时设置它的android:exported属性为False,这样广播接收者只能接收本程序组件发出的广播。
表2 广播的分类及其特点
广播的使用场景如下。
1)同一App内部的同一组件内的消息通信(单个或多个线程之间)。
2)同一App内部的不同组件之间的消息通信(单个进程)。
3)同一App具有多个进程的不同组件之间的消息通信。
4)不同App之间的组件之间消息通信。
5)Android系统在特定情况下与App之间的消息通信。
3. Service安全
在Android系统开发中,Service是一个重要的组成部分。如果现在某些程序中的某部分操作是很耗时的,那么可以将这些程序定义在 Service 中,这样就可以在后台运行,也可以在不显示界面的形式下运行,即Service实际上就是相当于一个没有图形界面的Activity程序,而且当用户执行某些操作需要进行跨进程访问的时候也可以使用Service来完成。
Service的分类有本地服务和远程服务。
(1)本地服务(Lo cal)
该服务依附在主进程上,不是独立的进程。本地服务在一定程度上节约了资源,由于是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。主进程被杀死后,服务便会终止。
(2)远程服务(Remote)
该服务是独立的进程,对应进程名格式为所在包名加上指定的android:process字符串。由于是独立的进程,因此,在Activity所在进程被杀死的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。但由于独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。一些提供系统服务的Service通常是常驻的。
Service组件已知产生的安全问题有:权限提升、Service劫持、拒绝服务和消息伪造。
Intent-filter 与 Exported 属性配置方法有:私有 Service 不定义 Intent-filter 并且设置Exported为False;公开的Service设置Exported为True,Intent-filter可以定义或不定义;内部或可信Service设置Exported为True,Intent-filter不定义。
安全研发建议有:应用内部使用的Service应设置为私有;针对Service接收到的数据应该验证并谨慎处理;内部Service需要使用签名级别的protectionLevel来判断是否未内部应用调用;不建议在 onCreate 方法调用的时候决定是否提供服务,建议在 onStartCommand、onBind、onHandleIntent等方法被调用的时候做判断;使用显示意图只针对有明确服务需求的情况尽量不发送敏感信息,可信任的Service需要对第三方可信公司的App签名做校验。
4. ContentProvider安全
Android 平台提供了 ContentProvider,将一个应用程序的指定数据集提供给其他应用程序。这些数据可以存储在文件系统、SQLite数据库中,或以任何其他合理的方式存储。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。
ContentProvider通过URI(统一资源定位符)来访问数据,URI可以理解为访问数据的唯一地址。
ContentProvider 类提供了一种机制用来管理以及与其他应用程序共享数据。在与其他应用程序共享Content Provider的数据时,应该实现访问控制,禁止对敏感数据未经授权的访问。
更细化的权限优先于作用域较大的权限。
(1)统一读写提供程序级别权限
一个同时控制对整个提供程序读取和写入访问的权限,通过<provider>元素的android:permission 属性指定。
(2)单独的读取和写入提供程序级别权限
针对整个提供程序的读取权限和写入权限。可以通过<provider>元素的 android:readPermission属性和android:writePermission属性指定它们。它们优先于android:permission所需的权限。
(3)路径级别权限
针对提供程序中内容 URI 的读取、写入或读取/写入权限。可以通过<provider>元素的<path-permission>子元素指定想控制的每个 URI。对于指定的每个内容 URI,都可以指定读取/写入权限、读取权限或写入权限,或同时指定所有3种权限。读取权限和写入权限优先于读取/写入权限。此外,路径级别权限优先于提供程序级别权限。
(4)临时权限
一种权限级别,即使应用不具备通常需要的权限,该级别也能授予对应用的临时访问权限。临时访问功能可减少应用需要在其清单文件中请求的权限数量。当启用临时权限时,只有持续访问所有数据的应用才需要“永久性”提供程序访问权限。
假设需要实现电子邮件提供程序和应用的权限,如果要允许外部图像查看器应用显示提供程序中的照片附件,为了在不请求权限的情况下为图像查看器提供必要的访问权限,可以为照片的内容 URI 设置临时权限。对电子邮件应用进行相应设计,使应用能够在用户想要显示照片时向图像查看器发送一个 Intent,其中包含照片的内容 URI 以及权限标识。图像查看器可随后查询电子邮件提供程序以检索照片,即使查看器不具备提供程序的正常读取权限,也不受影响。
要想启用临时权限,请设置<provider>元素的 android:grantUriPermissions 属性,或向<provider>元素添加一个或多个<grant-uri-permission>子元素。如果使用了临时权限,则每当用户从提供程序中移除对某个内容URI的支持,并且该内容 URI 关联了临时权限时,都需要调用Context.revokeUriPermission。
该属性的值决定可访问的提供程序范围。如果该属性设置为True,则系统会向整个提供程序授予临时权限,该权限将替代您的提供程序级别或路径级别权限所需的任何其他权限。
如果此标识设置为False,则必须向<provider>元素添加<grant-uri-permission>子元素。每个子元素都指定授予的临时权限所对应的一个或多个内容URI。
要向应用授予临时访问权限,Intent 必须包含 FLAG_GRANT_READ_URI_PERMISSION和/或 FLAG_GRANT_WRITE_URI_PERMISSION 标识。它们通过 setFlags方法进行设置。
如果android:grantUriPermissions 属性不存在,则假设其为False。
数据安全
1. 外部数据安全
外部存储通常是指将数据存入到设备的SD卡上。
外部存储是一种不安全的数据存储机制,因为存储到SD卡上的文件默认是提供给others读文件的权限的,设备上安装的其他App只要在其AndroidMenifest.xml上声明如下的语句。
<uses-permission android:name='android.permission.WRITE_EXTERNAL_STORAGE'></uses-permission>
那么该App就具有了对于SD卡的完全的读写权限,即是说一个App放在SD卡上的任何数据都可以被其他的App进行读/写操作,所以将重要数据存储在SD卡上具有相当大的安全隐患。
2. 内部数据安全
内部数据存储主要分为两种方式:SharedPreference存储和File存储。内部数据存储的安全问题主要需要注意的是创建的模式以及向文件中写入的内容。
SharedPreference 存储是一种轻量级的数据存储方式,它的本质是基于 XML 文件存储Key-Value键值对数据,通常用来存储一些简单的配置信息。
File存储即常说的文件(I/O)存储方法,常用于存储大量的数据。
内部数据存储通常较为安全,因为它们可以受到Android系统的安全机制的保护。
Android的安全机制本质上就是Linux的安全机制,系统会为在Android系统上运行的每一个App创建一个进程,并为该进程分配一个UID。Android系统将会为每一个App创建一个特定的目录/data/data/app_package_name,这个目录的权限只与UID相关,且只有UID关联的用户才有该目录相关的权限。
因此,在对应目录下生成的SharedPreference文件与File文件如果以正确的方式去创建将会受到Android系统权限机制的保护。
这个正确的创建方式是指文件创建的模式,SharedPreference与文件的创建模式主要有以下3种。
MODE_PRIVATE:默认的创建模式,该进程的 UID 对应的用户将会对该文件拥有完全的控制的权限,而其他UID的用户将没有权限去读/写文件。
MODE_WORLD_WRITABLE:该权限将允许设备上所有的App对于该文件拥有写的权限。
MODE_WORLD_READABLE:该权限将允许设备上所有的App对于该文件拥有读的权限。
为了确保内部数据的安全,有如下建议。
(1)创建文件时的权限控制
如果在创建文件的时候没有注意控制权限,那么该文件的内容将会被其他的应用程序所读取,这样就造成了用户相关信息的泄露,SharedPreference中存储的往往是一些免登token、session id等和用户身份息息相关的重要信息,因此,在创建的时候一定要注意选取好创建的模式;免登token也一定要具有时效性,否则与存储了明文的用户名和密码无异。
(2)SharedPreference中不要存入明文密码等重要信息
由于有Root的存在,那么Root过后的手机就打破了Linux提供的沙箱机制,那么无论以何种方式去创建SharedPreference都已经不再安全了,如果存储的是用户明文的密码,那么用户的密码将会泄露,因此,绝对不要向SharedPreference中写入任何无时效性的重要的数据。
3. 通信数据安全
这里的通信数据安全是指软件与软件、软件与网络服务器之间进行数据通信时,所引发的安全问题。
软件与软件的通信,Android有4大组件:Activity、Content Provider、Service、Broadcast Receiver。
这些如果在Androidmanifest.xml配置不当,会被其他应用调用,引起风险。Android应用内部的Activity、Service、Broadcast Receiver等,它们通过Intent通信,组件间需要通信就需要在Androidmanifest.xml文件中暴露组件。
Intent的两种基本用法:一种是显式的Intent,即在构造Intent对象时就指定接收者;另一种是隐式的Intent,即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于降低发送者和接收者之间的耦合。
带来的风险有恶意调用、恶意接收数据、仿冒应用、恶意发送广播、启动应用服务、调用组件、接收组件返回的数据、拦截有序广播等。
常见的有以下的防护手段。
(1)最小化组件暴露
不参与跨应用调用的组件添加android:exported="false"属性,这个属性说明它是私有的,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。
<activity android:name=".LoginActivity" android:label="@string/app_name" android: screenOrientation="portrait" android:exported="false">
(2)设置组件访问权限
参与跨应用调用的组件或公开的广播、服务设置权限。
① 组件添加android:permission属性。
<activity android:name=".Another" android:label="@string/app_name"android:permission="com.test.custempermission"> </activity>
② 声明属性
<permission android:deion="test" android:label="test" android:name="com.test.custempermission"android:protectionLevel="normal"></permission>
protectionLevel有4种级别:Normal、Dangerous、Signature、SignatureOrSystem。Signature、SignatureOrSystem级别只有相同签名时才能调用。
③ 调用组件者声明
<uses-permission android:name="com.test.custempermission"/>
(3)暴露组件的代码检查
Android 提供各种 API 在运行时检查、执行、授予和撤销权限。这些 API 是android.content.Context类的一部分,这个类提供有关应用程序环境的全局信息。
网络数据通信可能面临的攻击是网络流量嗅探,如果网络上传没有加密的数据,网络嗅探就能截获到数据,ARP攻击可以轻松嗅探到账号、密码等。比较常见是通过HTTPS,HTTPS能有效地防止数据暴露、防止第三方截获应用的通信数据。
Android中实现HTTPS基本就这两种方式,一种是不验证证书,一种是有验证证书(预防钓鱼)。
第二种方式实现复杂一些,需要将cer证书转换成BKS类型。这种方式也只能简单地防止钓鱼,不能有效地防止钓鱼。防止钓鱼最终还是靠用户分辨,在正规渠道下载应用。应用证书也能起到验证客户端的功能,使用证书验证客户端不合适,如果使用证书验证客户端,证书必须存放在应用程序中或使用时下载,Android应用都是一个APK文件,很容易获取到里面的文件,如果是下载方式,更容易通过下载地址获取。如果想验证客户端的话,使用so文件封装数据更好。