Android安全与隐私相关特性的行为变更分析
Posted Tr0e
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android安全与隐私相关特性的行为变更分析相关的知识,希望对你有一定的参考价值。
文章目录
前言
2022年8月16日,谷歌已经向 Pixel 机型推送了 android 13 正式版更新,同时宣布 Android 13 已经正式开源,Android 13 新系统的源代码已经上传到 Android 开源项目(AOSP)中。
在 Android 的版本迭代过程中,可以看到 Google 为了更好地保护用户的隐私和手机安全性,不断地在新的版本中增加新的安全与隐私的特性。Android 13 也再次将“隐私和安全”作为一个主要更新,重点是“通过在设备上提供更安
全的环境和向用户提供更多控制,为所有人构建一个负责任的高质量平台”。
作为 Android 安全测试人员,了解 Android 的隐私和安全特性是必不可少的,它能帮助我们更好地认识到 Android 漏洞挖掘过程中有哪些数据、系统机制是 Google 所希望保护的。本文来统计、分析下 Android 近几年的一些核心安全与隐私的特性。
Android 13
官方文档:行为变更:以 Android 13 或更高版本为目标平台的应用。
1.1 新增通知运行时权限
官方介绍:Notification runtime permission。
Android 13(API 级别 33)引入了新的运行时权限,用于从应用发送非豁免通知:POST_NOTIFICATIONS
。如果用户在搭载 Android 13 的设备上安装应用,应用的通知默认处于关闭状态。 此更改有助于用户专注于最重要的通知。
如需向应用请求新的通知权限,需要在应用的清单文件中声明的权限会显示在以下代码段中:
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application ...>
...
</application>
</manifest>
注意:应用无需请求 POST_NOTIFICATIONS 权限即可启动前台服务。但是,应用必须在启动前台服务时提供一个通知,就像在以前的 Android 版本中一样。
【个人补充】我表示举双手赞成这个变更!Android 13 以前所有应用默认都能得到发送通知的能力,这个太烦人了……每次买新机或者刷新系统后我都习惯性地手动关闭大多数 app 的通知权限。
1.2 新增WIFI运行时权限
官方介绍:Request permission to access nearby Wi-Fi devices 。
面向 Android 13(API 级别 33)或更高版本并希望管理 Wi-Fi 连接的应用必须请求 NEARBY_WIFI_DEVICES
运行时权限。
<manifest ...>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"/>
<application ...>
...
</application>
</manifest>
此权限可以更轻松地证明应用访问附近 Wi-Fi 设备的合理性; 在以前的 Android 版本中,这些应用程序需要声明 ACCESS_FINE_LOCATION
权限。这其实是不合理的设计,因为用户很难理解为什么 Wi-Fi 连接会跟位置信息有关。
从 Android 13 系统开始,ACCESS_FINE_LOCATION 精确位置权限是可选项,只要应用不会通过 Wi-Fi 推导物理位置信息,就不需要再请求。如果不会,你需要在 Manifest 中显式做出 usesPermissionFlags
声明(这与声明蓝牙设备信息不会用于获取位置信息类似):
<manifest ...>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<application ...>
...
</application>
</manifest>
另外,NEARBY_WIFI_DEVICES
权限是 Nearby devices 权限组的一部分。 此权限组在 Android 12(API 级别 31)中添加,还包括与蓝牙和超宽带相关的权限(参见 “2.5 新增蓝牙运行时权限” 章节)。
当您从该权限组请求任何权限组合时,系统会显示一个运行时对话框并要求用户批准您的应用程序对附近设备的访问。
在系统设置中,用户必须作为一个组启用和禁用附近的设备权限, 例如用户无法在禁用 Wi-Fi 访问的情况下保持蓝牙访问的开启。可以看出,这次的改动 Google 是希望连接 Wi-Fi 设备的权限授予能够给用户更精准的权限功能描述。
1.3 精细化媒体访问权限
如果您的应用面向 Android 13 或更高版本并且需要访问其他应用创建的媒体文件,则您必须请求以下一项或多项精细媒体权限,而不是 READ_EXTERNAL_STORAGE
权限:
1.4 Intent 调用组件过滤
官方介绍:match-intent-filter。
如果另一个应用程序以 Android 13(API 级别 33)或更高版本为目标,则只有当您的 Intent 与该应用程序中某个 Intent 设置的 <intent-filter>
的 actions 和 categories 元素匹配时,它才能处理您的应用程序的 Intent。
同样,如果您更新您的应用以使其面向 Android 13 或更高版本,则只有当该 Intent 与您的应用声明的<intent-filter>
中 actions 和 categories 元素匹配时,源自外部应用的所有 Intent 才会传递到您的应用的导出组件。无论发送应用程序的目标 SDK 版本如何,都会发生此行为。
未强制执行意图匹配的例外情况:
- 传递给未声明任何意图过滤器的组件的 Intent;
- 来自同一应用程序的 Intent;
- 源自系统的 Intent:也就是说,从“系统 UID”(uid=1000)发送的 Intent,系统应用程序包括 system_server 设置
android:sharedUserId
为android.uid.system
; - 源自 root 用户的 Intent。
个人理解谷歌做出此限制的原因在于保护存在意图过滤器的组件,因为存在意图过滤器的组件默认是可导出的(exported=“true”),虽然 Android 12 要求开发者必须显示指定组件的 exported 属性值,但是还是有的开发人员会错误地将一些组件不必要地设置为 exported=“true”,导致应用组件存在被攻击的风险。
1.5 动态广播接收器限制
在旧版本中,应用动态注册的 BroadcastReceiver 广播接收器会接收到任何应用发送的广播(除非该接收器使用了权限保护),这会让动态注册的广播接收器存在安全风险。从 Android 13 系统开始,应用动态注册的广播接收器必须显式指出是否允许其他应用访问,即其他应用是否可以向其发送广播。否则,在动态注册时系统会抛出 SecurityException。
// 这相当于静态注册 android:exported="true"
context.registerReceiver(sharedBroadcastReceiver, intentFilter, RECEIVER_EXPORTED)
// 这相当于静态注册 android:exported="false"
context.registerReceiver(privateBroadcastReceiver, intentFilter, RECEIVER_NOT_EXPORTED)
1.6 后台访问身体传感器
Android 13 系统引入了新的运行时权限 android.permission.BODY_SENSORS_BACKGROUND
后台身体传感器权限,用于更好地管理应用在后台时访问身体传感器(例如心率、体温和血氧饱和度等)的行为。
现在应用在后台使用身体传感器,除了要请求现有的 BODY_SENSORS
权限外,还需要请求 BODY_SENSORS_BACKGROUND
权限(这与 ACCESS_FINE_LOCATION
和 ACCESS_BACKGROUND_LOCATION
的关系类似)。
Android 12
Android 官方文档:Behavior changes: all apps。
2.1 剪贴板内容访问提醒
在 Android 12 及更高版本中,当某个应用首次调用 getPrimaryClip()
以从另一个应用访问剪辑数据时,会弹出一个消息框消息,通知用户对剪贴板的访问。消息框消息内的文本包含以下格式:APP pasted from your clipboard。
你没看错,在 Android 系统中,三方应用程序是可以访问到用户的剪切板内容的,如果你不小心复制了银行卡密码等敏感信息……下面是一段获取当前用户剪切板数据的示例代码:
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboardManager.getPrimaryClip();
if(clipData != null && clipData.getItemCount()>0)
ClipData.Item itemAt = clipData.getItemAt(0);
Log.e(TAG,"获取到到剪切板中的内容:"+itemAt.getText().toString());
而对于 A 应用获取来自 B 应用存放到剪切板里面的数据的行为,Android 系统目前唯一会做的,就是弹窗提醒你 “XX正在访问剪切板!”
这种业务很常见,比如你从微信复制一条你的好友分享给你的淘口令,然后打开淘宝 app,淘宝会自动访问你的剪切板并自动识别淘口令对应的购物链接并帮你自打开。看起来很爽的功能,但是我也留意到了多数 app 都有在启动时获取剪切板数据的行为……
我个人觉得这有点危险,虽然系统会弹窗提醒我,但是提醒归提醒,数据已经被拿走了你再提醒我……你倒是帮我拦一下啊!hh当然这是我个人意见,期待 Android 后面进一步对剪切板的访问进行限制。
2.2 软件包可见性的限制
在运行 Android 12 或更高版本的设备上,以 Android 11(API 级别 30)或更高版本为目标并调用以下方法之一的应用会收到一组过滤的结果,具体取决于应用对其他应用的 包可见性(下文关于 Android 11 相关章节会补充解释):
- getAllPermissionGroups();
- getPermissionGroupInfo();
- getPermissionInfo();
- queryPermissionsByGroup()
以下示例程序可以获取到系统已安装的部分应用列表(整体而言,就是系统预装应用可见,用户自行安装的三方应用基本不可见,详见下文 Android 11 相关章节):
public static void getPackageList(Context context)
List<ApplicationInfo> allApps = context.getPackageManager().getInstalledApplications(0);
for(ApplicationInfo app : allApps)
Log.e(TAG, "Get: " + app.packageName);
上述示例代码,APP 的开发者可以在 AndroidMainfest.xml 中申请 QUERY_ALL_PACKAGES
权限(Normal 级别)即可查询到系统所有的已安装应用的信息。
谷歌干这个限制是好事,毕竟在可怕的大数据时代下,你总不会希望你手机上安装的随便一个应用程序都能知道你手机上还安装了啥吧?数据统计导致的定向推送咱先不说,万一你手机有什么见不得人的 app 是吧……
2.3 限制悬浮窗点击透传
为了保持系统安全和良好的用户体验,Android 12 会阻止应用使用覆盖以不安全方式遮盖应用的触摸事件。换句话说,系统会阻止通过某些窗口的触摸,但有少数例外。
受影响的应用
此更改会影响那些企图让触摸动作产生的点击事件透传到其悬浮窗口底下的视图控件的应用程序。
为什么要限制?
App 应用程序可以申请 SYSTEM_ALERT_WINDOW
悬浮窗特殊权限(需要用户手动授权确认),同时创建 TYPE_APPLICATION_OVERLAY
和 FLAG_NOT_TOUCHABLE
标志的悬浮窗口,来实现悬浮覆盖于其它应用视图上方的同时,可以将用户的点击事件透传给悬浮窗的下层视图控件。
至于悬浮窗点击事件透传可以造成的危害……那就是你看到一个抢红包的页面极有可能是恶意应用制造的悬浮窗,而你傻乎乎地很开心地点击“开”红包的按钮,结果发现点击事件透传到悬浮窗底下的转账按钮……
历史上有诸多 CVE 漏洞就是通过这种方式造成的攻击,如 CVE-2020-0306:蓝牙发现请求确认框覆盖。
更多关于悬浮窗覆盖导致的攻击请参见 Oppo 子午安全实验室的文章:不可点击观察的威胁:Android中的不可点击的劫持攻击,此处不再展开,毕竟在高版本的 Android 上,此攻击路径已行不通(下文解释为什么)……
哪些点击事件不受限制?
如果您正在使用 FLAG_NOT_TOUCHABLE
,那么您的应用可能会被影响,除非您的应用符合以下某个豁免条件:
豁免场景 | 解释 |
---|---|
应用中的交互 | 简单点理解就是 A 应用自己创建的悬浮窗,其点击事件可以透传给自己的被覆盖的视图控件(自己搞自己不犯法……) |
可信窗口 | 这些窗口包括但不仅限于: 无障碍窗口、输入法 (IME) 窗口 和 Google 助理窗口 |
不可见窗口 | 窗口根视图是 GONE 或 INVISIBLE |
全透明窗口 | 窗口的 alpha 属性值为 0.0 |
足够半透明的系统警报窗口 | 该窗口的 alpha 值足够小 |
如果您的使用场景并不包含于上述列表内,那么触摸事件将会被屏蔽。否则的话,如果您想阻止触摸事件传递,可以考虑删除 FLAG_NOT_TOUCHABLE
标志,而如果想让触摸事件穿透下去,可以调整您的代码来符合上述的几种情况之一。
基本上悬浮窗点击透传劫持类的漏洞和攻击路径,在 Android 12 后宣布寿终正寝了,本人不想继续过多关注,也不想放 Demo 程序。有兴趣的可以再看看:行为变更 | Android 12 中不受信任的触摸事件。
隐藏应用叠加窗口(新功能)
顺便补充一下,Android 12 系统引入了隐藏 TYPE_APPLICATION_OVERLAY
窗口的功能。声明 HIDE_OVERLAY_WINDOWS
权限后,应用可以调用 setHideOverlayWindows
指明当应用的窗口可见时隐藏所有可见的 TYPE_APPLICATION_OVERLAY
窗口。例如在显示敏感页面(如交易)时,应用可以选择隐藏其他悬浮窗。
2.4 显式指定的组件属性
1、组件的 exported 属性
Android 组件属性 android:exported
用于设置该组件是否支持其他应用交互,exported 为 false 表示不允许该组件被其他应用启动。一般 exported 属性默认为 false,除非组件声明了 <intent-filter>
过滤器(即支持隐式启动),则 exported 属性默认为 true。
从 Android 12 系统开始,声明了 过滤器的组件必须显式设置 android:exported
属性。例如:
<service android:name="com.example.app.backgroundService"
android:exported="false">
<intent-filter>
<action android:name="com.example.app.START_BACKGROUND" />
</intent-filter>
</service>
否则,在编译应用时就会有报错:
Manifest merger failed :
Apps targeting Android 12 and higher are required to specify an explicit value for android: exported when the corresponding component has an intent filter defined.
如果使用低版本的 Android Gradle 插件虽然可以编译成功,但安装时会报错:
Installation did not succeed.
The application could not be installed: INSTALL_FAILED_VERIFICATION_FAILURE
可以看出,这次改动背后的理念是 “不要相信默认值”,因为不符合预期的默认值会产生更严重的风险。举个例子,由于开发者的疏忽,一个原本不允许外部应用启动的组件未显式声明 android:exported=“false”
,而正好该组件声明了 过滤器,那么就因为默认值的影响的产生了一个安全风险。而强制开发者对声明过滤器的组件显式声明 android:exported
的值,就可以避免了默认值的安全风险。
2、显式指定 PendingIntent 可变性
为了使 PendingIntent 的处理更加安全,Android 12 要求 PendingIntent 必须显式声明一个可变性标志即 FLAG_MUTABLE
或 FLAG_IMMUTABLE
。
在此之前,PendingIntent 默认是可变的( FLAG_MUTABLE
),导致引发了一种 PendingIntent 的攻击模式—— PendingIntent 劫持攻击。具体参见我的另一篇文章:PendingIntent劫持导致app任意文件读写漏洞,本文不展开。
2.5 新增蓝牙运行时权限
参考官网文档:蓝牙权限。
要在您的应用程序中使用蓝牙功能,您必须声明多个权限。您在应用中声明的权限集取决于应用的目标 SDK 版本。Android 12 系统引入了新的运行时权限 BLUETOOTH_SCAN
、BLUETOOTH_ADVERTISE
和 BLUETOOTH_CONNECT
权限,用于更好地管理应用于附近蓝牙设备的连接。
面向 Android 11 或更低版本
如果您的应用面向 Android 11(API 级别 30)或更低版本,请在应用的清单文件中声明以下权限:
- BLUETOOTH 是执行任何蓝牙经典或 BLE 通信所必需的,例如请求连接、接受连接和传输数据。
- ACCESS_FINE_LOCATION 是必要的,因为在 Android 11 及更低版本上,蓝牙扫描可能会用于收集有关用户位置的信息。
如果您的应用面向 Android 9(API 级别 28)或更低版本,您可以声明
ACCESS_COARSE_LOCATION
权限而不是ACCESS_FINE_LOCATION
权限。
因为位置权限是运行时权限,所以您必须在运行时请求这些权限,并在清单中声明它们。
面向 Android 12 或更高版本
在低版本中,应用与附近蓝牙设备连接需要用户授予 ACCESS_FINE_LOCATION
精确位置权限,这其实是不合理的设计,因为用户很难理解为什么蓝牙连接会跟位置信息有关。
从 Android 12 系统开始,ACCESS_FINE_LOCATION
精确位置权限是可选项,只要应用不会通过蓝牙推导物理位置信息,就不再需要请求。如果不会,你需要在 Manifest 中显式做出 usesPermissionFlags 声明:
<manifest>
<!-- Include "neverForLocation" only if you can strongly assert that
your app never derives physical location from Bluetooth scan results. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
...
</manifest>
新蓝牙权限体系(以 Android 12 为目标版本):
- BLUETOOTH_SCAN:允许搜索附近蓝牙设备;
- BLUETOOTH_ADVERTISE:允许当前设备暴露给其他蓝牙设备;
- BLUETOOTH_CONNECT:允许当前设备连接其他蓝牙设备;
- ACCESS_FINE_LOCATION(可选):允许由蓝牙信息推导设备位置信息。
Android 12 之前的旧蓝牙权限体系:
- BLUETOOTH:允许与蓝牙相关的交互;
- ACCESS_FINE_LOCATION(必选):允许由蓝牙信息推导设备位置信息,在 Android 9 或以下版本,可以用 ACCESS_COARSE_LOCATION 替代。
另外,BLUETOOTH_SCAN 等权限是 NEARBY_DEVICES
附近设备权限组的一部分。请求该权限组的权限,权限授予对话框会提示用户批准访问附近的设备。
2.6 新增模糊化定位功能
官方介绍文档:请求位置权限。
Android 系统支持两个精度级别的位置信息,并且分别对应一个权限。虽然有两个精度级别的权限,但是因为它们处于同一个权限组中,所以应用只要请求授予其中一个权限,另一个权限就自动授予了。
- 粗略位置: 精确到 3 平方公里的位置值,请求
ACCESS_COARSE_LOCATION
权限可以获得; - 精确位置: 精确到 50 米以内的位置值,请求
ACCESS_FINE_LOCATION
权限可以获得。
然而,从 Android 12 系统开始,这一规则不再成立了。从 Android 12 系统开始,用户可以只授予应用模糊位置 ACCESS_COARSE_LOCATION
权限,即使应用请求的是精确位置 ACCESS_FINE_LOCATION
权限。
举个例子,我们通过以下代码请求 ACCESS_FINE_LOCATION
权限:
val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) isGrant ->
Log.i("权限","isGrant: $isGrant")
if (PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION))
Log.i("权限", "ACCESS_COARSE_LOCATION is Grant")
if (PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION))
Log.i("权限", "ACCESS_FINE_LOCATION is Grant")
findViewById<View>(R.id.tv).setOnClickListener
locationPermissionRequest.launch( Manifest.permission.ACCESS_FINE_LOCATION)
在 Android 12 系统上的权限请求弹窗会给用户两个选项:Precise 精确位置 和 Approximate 粗略位置。如果用户选择授予粗略位置,那么最终应用获得的权限反而是 ACCESS_COARSE_LOCATION
权限,而不是一开始请求的 ACCESS_FINE_LOCATION
权限,并且应用也只能获取粗略位置信息。
那么从 Android 12 开始,你在请求位置权限时就要注意以下问题,稍不注意就出现兼容问题了:
- 请求位置权限时要同时请求
ACCESS_FINE_LOCATION
权限和ACCESS_COARSE_LOCATION
权限,如果应用只请求ACCESS_FINE_LOCATION
权限,系统会直接忽略该请求。如果应用以 Android 12 或更高版本为目标版本,系统会在 logcat 中提示错误:ACCESS_FINE_LOCATION must be requested with ACCESS_COARSE_LOCATION
. - 即使用户已经授予了精确位置权限,用户依然可以进入系统设置中直接修改到粗略位置权限,修改后系统会自动杀死进程;
- 为了更好地尊重用户隐私,尽量只请求
ACCESS_COARSE_LOCATION
权限,因为粗略位置信息已经能满足大多数应用场景。仅请求ACCESS_COARSE_LOCATION
权限时,授权弹窗只有一个选项; - 如果你的应用场景确实需要请求
ACCESS_FINE_LOCATION
权限,那么你可以再次同时请求ACCESS_FINE_LOCATION
和ACCESS_COARSE_LOCATION
权限。由于之前用户已经授予过粗略位置权限,这次的系统弹窗会变成询问是否升级到精确位置权限。
最后一个问题,怎么确定应用场景时需要精确位置还是粗略位置呢?其实并不是依靠纯主观判断,这块是有行业标准的,参见工信部发布的:APP收集使用个人信息最小必要评估规范。
Android 11
Android 官方文档:行为变化:面向 Android 11 的应用 。
3.1 软件包可见性的限制
官方特性解释:Android 上的包可见性过滤。
如果您的应用面向 Android 10(API 级别 29)或更低版本,则所有应用都会自动对您的应用可见。以下示例程序可以获取到系统已安装的全部应用列表:
public static void getPackageList(Context context)
List<ApplicationInfo> allApps = context.getPackageManager().getInstalledApplications(0);
for(ApplicationInfo app : allApps)
Log.e(TAG, "Get: " + app.packageName);
但是当应用程序以 Android 11(API 级别 30)或更高版本为目标并查询有关设备上安装的其他应用程序的信息时,系统会默认过滤此信息。从您的应用的角度来看,有限的包可见性减少了似乎安装在设备上的应用数量。
这种过滤行为有助于最大限度地减少您的应用程序不需要以实现其用例但您的应用程序仍然可以访问的潜在敏感信息的数量。此外,过滤后的包可见性有助于 Google Play 等应用商店评估您的应用为用户提供的隐私和安全性。例如,Google Play 将已安装应用程序列表视为个人敏感用户数据。
有限的应用程序可见性会影响提供有关其他应用程序信息的方法的返回结果,例如 queryIntentActivities()
、 getPackageInfo()
和 getInstalledApplications()
。有限的可见性也会影响与其他应用程序的显式交互,例如启动另一个应用程序的服务。
如果要查看其它不可见的包, 请声明您的应用需要使用该 <queries>
元素来增加包的可见性。
<manifest package="com.example.game">
<queries>
<!-- 您与之交互的特定应用,例如:-->
<package android:name="com.example.store" />
<package android:name ="com.example.service" />
<!--
您查询的特定意图,
例如:自定义共享 UI
-->
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/jpeg " />
</intent>
</queries>
...
</manifest>
在<queries>
元素无法提供足够的包可见性的极少数情况下,您可以使用该QUERY_ALL_PACKAGES
权限。如果您在 Google Play 上发布您的应用,则您的应用使用此权限需要根据 即将出台的政策:《使用广泛的软件包(应用)可见性 (QUERY_ALL_PACKAGES) 权限》 进行权限申请并获得批准。
最后请注意,有些包仍然是自动可见的,系统会自动使某些应用对您的应用可见,以便您的应用可以与它们进行交互,而无需声明该 <queries>
元素。此行为有助于支持基本功能和常见用例。即使您的应用面向 Android 11(API 级别 30)或更高版本,以下类型的应用始终对您的应用可见:
- 您自己的应用程序;
- 实现核心 Android 功能的某些系统包,例如媒体提供程序;
- 安装您的应用的应用;
- 在您的应用中启动或绑定到服务的任何应用;
- 在您的应用程序中访问内容提供程序的任何应用程序;
- 任何具有内容提供器的应用,您的应用已被授予访问该内容提供者的 URI 权限;
- 从您的应用接收输入的任何应用。这种情况仅适用于您的应用程序作为输入法编辑器提供输入时。
此外,您可以使用 隐式或 显式 Intent 启动另一个应用程序的 Activity,无论该应用程序是否对您的应用程序可见。
即使您的应用面向 Android 11 或更高版本,一些实现核心 Android 功能的系统包也会自动对您的应用可见。具体的包集取决于运行您的应用程序的设备。要查看特定设备的完整软件包列表,请在开发机器上的终端中运行以下命令:
adb shell dumpsys package queries
在命令输出中,找到该 forceQueryable
部分,此部分包括设备自动对您的应用程序可见的软件包列表。
3.2 权限授予机制的变更
Android 11 总共涉及几个权限授予机制相关的变更,下面逐一分析。
1、授予一次性临时权限
官方文档:one-time 解释得很好,直接拿来主义:
在 Android 10 中,Android 引入了精细的位置权限控制,让用户能够限制应用仅在使用时 (即仅在应用处于前台时) 访问位置信息。当系统显示新的运行时权限选项时,超过 50% 的情况下用户会选择仅允许应用在前台时访问位置。
这一点证明用户确实想要更精细的权限控制。因此在 Android 11 中,Android 引入了 单次授权 功能,通过该功能,用户可授权应用访问设备的麦克风、摄像头或位置信息,但该访问权限仅在授权当时有效。
2、重置未使用的权限机制
如果您的应用面向 Android 11 或更高版本并且几个月未使用,系统会通过自动重置用户授予您的应用的敏感运行时权限来保护用户数据。此操作与用户查看系统设置中的权限并将您的应用的访问级别更改为 Deny 具有相同的效果。
3、权限对话框的可见性(handle-denial)
从 Android 11 开始,如果用户在您的应用在设备上安装的整个生命周期内多次点击“拒绝”以获得特定权限,那么如果您的应用再次请求该权限,用户将不会看到系统权限对话框。用户的动作暗示“不要再问”。在以前的版本中,每次您的应用请求权限时,用户都会看到系统权限对话框,除非用户之前选择了“不再询问”复选框或选项。Android 11 中的这种行为更改不鼓励用户重复请求已选择拒绝的权限。
3.3 后台访问位置的控制
官方特性文档:request-background-location。
如果应用的功能在下列某种情况下访问设备的当前位置信息,系统就会认为应用需要使用前台位置信息:
- 属于应用的某个 Activity 可见;
- 应用的某个前台服务正在运行中。当有前台服务在运行时,系统会显示一条常驻通知来提醒用户注意。当应用被置于后台时(例如当用户按设备上的主屏幕按钮或关闭设备的显示屏时),其位置信息访问权限会得到保留。
除了前台位置信息部分所述的情况之外,如果应用在任何其他情况下访问设备的当前位置信息,系统就会认为应用需要使用后台位置信息。后台位置信息精确度与前台位置信息精确度相同,具体取决于应用声明的位置信息权限。
Android 10 新增后台位置权限
在 Android 10(API 级别 29)及更高版本中,您必须在应用的清单中声明 ACCESS_BACKGROUND_LOCATION
权限,以便请求在运行时于后台访问位置信息。在较低版本的 Android 系统中,当应用获得前台位置信息访问权限时,也会自动获得后台位置信息访问权限。
<manifest ... >
<!-- Required only when requesting background location access on
Android 10 (API level 29) and higher. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>
Android 11 进一步控制后台位置权限
来看看 AndroidMainfest.xml 中声明了 "android.permission.ACCESS_BACKGROUND_LOCATION"
权限的应用与不声明该权限的应用,在系统的位置权限设置处的区别:
可以看到,在系统设置的位置权限管理处,相比于高德地图,微博 app 并无访问位置信息的 “始终允许” 选项供用户选择,因为它没声明后台位置权限。
3.4 后台自定义toast限制
出于安全原因和保持良好的用户体验,如果包含自定义视图的 toast 是由面向 Android 11 或更高版本的应用从后台发送的,系统会阻止这些 toast。
从 Android11 已经弃用了对 Toast 调用 setView() 的能力,当然只是不允许从后台显示自定义 View 的 Toast,即如果 APP 进入后台还想显示 Toast,只能使用 Toast.makeText()
。
个人觉得这个更改的主要目的是防止恶意应用从后台借助 Toast 弹出各种花里胡哨的广告视图或者恶意页面。
关于 Toast 弹窗控件曾经出现过一个 CVE 漏洞:通过安卓最新 Toast 漏洞进行 Tapjacking,还没来得及分析学习,后面补充下。
3.5 前台服务的使用限制
特性官方说明文档:foreground-services。
为了保护敏感的用户数据,当前台服务可以访问设备的位置、摄像头和麦克风时,Android 11 会发生以下两个主要变化:
1、摄像头和麦克风前台服务类型
如果您的应用面向 Android 11 或更高版本并在前台服务中访问摄像头或麦克风,则您必须包括 camera 和 microphone 前台服务类型。如以下代码段所示:
<manifest>
...
<service ... android:foregroundServiceType="location|camera" />
</manifest>
在运行时,如果前台服务只需要访问清单中声明的类型的子集,您可以使用以下代码片段中的逻辑来限制服务的访问:
Notification notification = ...;
Service.startForeground(notification, FOREGROUND_SERVICE_TYPE_LOCATION);
2、使用时的访问限制
为了帮助保护用户隐私,Android 11(API 级别 30)对前台服务何时可以访问设备的位置、摄像头或麦克风引入了限制。当您的应用在后台运行时启动前台服务时,前台服务有以下限制:
- 除非用户已授予您的应用
ACCESS_BACKGROUND_LOCATION
权限,否则前台服务无法访问位置; - 前台服务无法访问麦克风或摄像头。
【限制的豁免】在某些情况下,即使在应用程序 在后台运行时启动了前台服务,它仍然可以在应用程序在前台运行时(“使用时”)访问位置、摄像头和麦克风信息。
以下列表包含这些情况:
- 该服务由系统组件启动;
- 该服务是通过与应用小部件交互来启动的;
- 该服务通过与通知交互来启动;
- 该服务作为 PendingIntent 从不同的可见应用程序发送的 a 启动。
- 该服务由一个应用程序启动,该应用程序是在设备所有者模式下运行的设备策略控制器;
- 该服务由具有
START_ACTIVITIES_FROM_BACKGROUND
特权权限的应用程序启动。
测试您的应用程序时,启动其前台服务。如果启动的服务限制了对位置、麦克风和摄像头的访问,Logcat 中会出现以下消息:
Foreground service started from background can not have \\
location/camera/microphone access: service SERVICE_NAME
3.6 强制性分区存储机制
官方特性说明文档:storage。
分区存储是 Android 10 开始引进的 Android 系统存储管理机制,Android 系统根据存储位置的不同,可以分为内部内部存储和外部存储,内部存储就不用多说了,而外部存储又分为私有空间和公共空间。
Android 外部存储空间(sdcard)中数据存储可以分为两大类:
在 Android 10 以前,只要程序获得了 READ_EXTERNAL_STORAGE
权限,就可以随意读取外部的存储公有目录;同时只要程序获得了WRITE_EXTERNAL_STORAGE
权限,就可以随意在写入外部存储的公有目录上新建文件或文件夹。
于是 Google 在 Android 10 中提出了分区存储,意在限制程序对外部存储中公有目录的使用,分区存储对内部存储私有目录和外部存储私有目录都没有影响。
在 Android 11 上运行但面向 Android 10(API 级别 29)的应用仍然可以请求 requestLegacyExternalStorage
属性,此标志允许应用程序暂时选择退出与范围存储相关的更改,例如授予对不同目录和不同类型媒体文件的访问权限。将应用更新为面向 Android 11 后,系统会忽略该requestLegacyExternalStorage
标志。
关于分区存储的更多信息请参见下文 Android 10 的相关章节或者我的另一文章:Android FileProvider特性与Intent重定向漏洞。
Adnroid 10
官方文档:Android 10 中的安全和隐私增强功能。
4.1 新增存储分区的机制
特性官方说明文档:从外部存储空间访问。
紧接上文 Android 11 对外部存储的分区机制的增强,继续补充 Android 10 新增的分区机制的详情。
为了避免混乱,先来总结下 Android 10 分区存储机制带来的数据访问的特点和区分:
分区存储最大的影响就是外部存储空间的共享目录不再可以被 APP 随意访问了。共享目录下的文件需要通过 MediaStore API 或者 Storage Access Framework 方式访问:
- MediaStore API 在共享目录指定目录下创建文件或者访问应用自己创建文件,不需要申请任何存储权限,所有者拥有文件的所有权;
- MediaStore API 访问其他应用在共享目录创建的媒体文件(图片、音频、视频),需要申请存储权限;
- MediaStore API 不能够访问其他应用创建的非媒体文件(pdf、office、doc、txt 等), 只能够通过 Storage Access Framework 方式访问,调用 Storage Access Framework API 会启动系统的文件选择器向用户申请操作指定的文件。
MediaStore API
Android 系统会自动扫描外部存储空间,将媒体文件按类型添加到系统预定义的 Images、Videos、Audio files、Downloaded files 集合中。Android Q 通过 MediaStore.Images、MediaStore.Video、MediaStore.Audio、MediaStore.Downloads 访问相对应共享目录文件资源。预定义集合所对应的目录如下表所示:
Android安全与隐私相关特性的行为变更分析