MASTG-TEST-0024: 测试应用权限
概述¶
在测试应用权限时,目标是尽量将应用使用的权限数量降至最低。在检查每个权限时,请记住,最佳实践是首先评估您的应用是否需要使用此权限,因为许多功能(例如拍照)可以在不需要权限的情况下完成,从而限制了对敏感数据的访问量。如果需要权限,您将确保对权限访问的请求/响应得到正确处理。
静态分析¶
Android 权限¶
检查权限以确保应用确实需要它们,并删除不必要的权限。例如,AndroidManifest.xml 文件中的 INTERNET
权限是 Activity 将网页加载到 WebView 中所必需的。由于用户可以撤销应用使用危险权限的权利,因此开发者应在每次执行需要该权限的操作时检查应用是否具有适当的权限。
<uses-permission android:name="android.permission.INTERNET" />
与开发者一起检查权限,以确定每个权限集的用途并删除不必要的权限。
除了手动检查 AndroidManifest.xml 文件外,您还可以使用 Android Asset Packaging tool (aapt) 来检查 APK 文件的权限。
aapt 随 Android SDK 一起提供,位于 build-tools 文件夹中。它需要一个 APK 文件作为输入。您可以通过运行
adb shell pm list packages -f | grep -i <keyword>
来列出设备中的 APK,如 列出已安装的应用中所述。
$ aapt d permissions app-x86-debug.apk
package: sg.vp.owasp_mobile.omtg_android
uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE'
uses-permission: name='android.permission.INTERNET'
或者,您可以通过 adb 和 dumpsys 工具获取更详细的权限列表
$ adb shell dumpsys package sg.vp.owasp_mobile.omtg_android | grep permission
requested permissions:
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.INTERNET
android.permission.READ_EXTERNAL_STORAGE
install permissions:
android.permission.INTERNET: granted=true
runtime permissions:
请参考此权限概述,了解被认为是危险权限的描述。
READ_CALENDAR
WRITE_CALENDAR
READ_CALL_LOG
WRITE_CALL_LOG
PROCESS_OUTGOING_CALLS
CAMERA
READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
RECORD_AUDIO
READ_PHONE_STATE
READ_PHONE_NUMBERS
CALL_PHONE
ANSWER_PHONE_CALLS
ADD_VOICEMAIL
USE_SIP
BODY_SENSORS
SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
自定义权限¶
除了通过应用程序清单文件强制执行自定义权限外,您还可以以编程方式检查权限。然而,不建议这样做,因为它更容易出错,并且更容易通过例如运行时插桩进行绕过。建议调用 ContextCompat.checkSelfPermission
方法来检查 activity 是否具有指定的权限。每当您看到以下代码片段时,请确保在清单文件中强制执行相同的权限。
private static final String TAG = "LOG";
int canProcess = checkCallingOrSelfPermission("com.example.perm.READ_INCOMING_MSG");
if (canProcess != PERMISSION_GRANTED)
throw new SecurityException();
或者使用 ContextCompat.checkSelfPermission
方法将其与清单文件进行比较。
if (ContextCompat.checkSelfPermission(secureActivity.this, Manifest.READ_INCOMING_MSG)
!= PackageManager.PERMISSION_GRANTED) {
//!= stands for not equals PERMISSION_GRANTED
Log.v(TAG, "Permission denied");
}
请求权限¶
如果您的应用程序具有需要在运行时请求的权限,则应用程序必须调用 requestPermissions
方法才能获取它们。应用将所需的权限和您指定的整数请求码异步传递给用户,一旦用户在同一线程中选择接受或拒绝请求,便会返回。响应返回后,相同的请求码会传递给应用的 callback 方法。
private static final String TAG = "LOG";
// We start by checking the permission of the current Activity
if (ContextCompat.checkSelfPermission(secureActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(secureActivity.this,
//Gets whether you should show UI with rationale for requesting permission.
//You should do this only if you do not have permission and the permission requested rationale is not communicated clearly to the user.
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Asynchronous thread waits for the users response.
// After the user sees the explanation try requesting the permission again.
} else {
// Request a permission that doesn't need to be explained.
ActivityCompat.requestPermissions(secureActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
// MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE will be the app-defined int constant.
// The callback method gets the result of the request.
}
} else {
// Permission already granted debug message printed in terminal.
Log.v(TAG, "Permission already granted.");
}
请注意,如果您需要向用户提供任何信息或解释,则需要在调用 requestPermissions
之前完成,因为系统对话框一旦调用就无法更改。
处理权限请求的响应¶
现在您的应用必须重写系统方法 onRequestPermissionsResult
以查看权限是否已授予。此方法接收 requestCode
整数作为输入参数(该参数与在 requestPermissions
中创建的请求码相同)。
以下 callback 方法可用于 WRITE_EXTERNAL_STORAGE
。
@Override //Needed to override system method onRequestPermissionsResult()
public void onRequestPermissionsResult(int requestCode, //requestCode is what you specified in requestPermissions()
String permissions[], int[] permissionResults) {
switch (requestCode) {
case MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE: {
if (grantResults.length > 0
&& permissionResults[0] == PackageManager.PERMISSION_GRANTED) {
// 0 is a canceled request, if int array equals requestCode permission is granted.
} else {
// permission denied code goes here.
Log.v(TAG, "Permission denied");
}
return;
}
// Other switch cases can be added here for multiple permission checks.
}
}
每个需要的权限都应显式请求,即使同一组中已请求过类似的权限。对于面向 Android 7.1 (API level 25) 及更早版本的应用程序,如果用户授予该组中请求的权限之一,Android 将自动授予应用程序该权限组中的所有权限。从 Android 8.0 (API level 26) 开始,如果用户已授予同一权限组中的权限,权限仍将自动授予,但应用程序仍需要显式请求权限。在这种情况下,onRequestPermissionsResult
处理程序将自动触发,无需任何用户交互。
例如,如果 READ_EXTERNAL_STORAGE
和 WRITE_EXTERNAL_STORAGE
都列在 Android Manifest 中,但仅授予了 READ_EXTERNAL_STORAGE
的权限,那么请求 WRITE_EXTERNAL_STORAGE
将自动拥有权限而无需用户交互,因为它们属于同一组并且未明确请求。
权限分析¶
始终检查应用程序是否请求了其实际需要的权限。确保没有请求与应用目标无关的权限,特别是 DANGEROUS
和 SIGNATURE
权限,因为如果处理不当,它们可能会影响用户和应用程序。例如,如果一个单人游戏应用需要访问 android.permission.WRITE_SMS
,就应该引起怀疑。
在分析权限时,您应该调查应用的具体使用场景,并始终检查是否有任何 DANGEROUS
权限的替代 API。一个很好的例子是 SMS Retriever API,它在执行基于短信的用户验证时简化了短信权限的使用。通过使用此 API,应用程序无需声明 DANGEROUS
权限,这既有利于用户,也有利于应用程序开发者,因为他们无需提交权限声明表。
动态分析¶
已安装应用程序的权限可以通过 adb
获取。以下摘录演示了如何检查应用程序使用的权限。
$ adb shell dumpsys package com.google.android.youtube
...
declared permissions:
com.google.android.youtube.permission.C2D_MESSAGE: prot=signature, INSTALLED
requested permissions:
android.permission.INTERNET
android.permission.ACCESS_NETWORK_STATE
install permissions:
com.google.android.c2dm.permission.RECEIVE: granted=true
android.permission.USE_CREDENTIALS: granted=true
com.google.android.providers.gsf.permission.READ_GSERVICES: granted=true
...
输出显示了所有权限,并按以下类别分类
- 已声明权限: 所有自定义权限的列表。
- 请求和安装权限: 所有安装时权限的列表,包括普通和签名权限。
- 运行时权限: 所有危险权限的列表。
进行动态分析时
- 评估应用是否确实需要请求的权限。例如:一个需要访问
android.permission.WRITE_SMS
的单人游戏可能不是一个好主意。 - 在许多情况下,应用可以选择声明权限的替代方案,例如
- 请求
ACCESS_COARSE_LOCATION
权限而不是ACCESS_FINE_LOCATION
。或者更好的是根本不请求权限,而是要求用户输入邮政编码。 - 调用
ACTION_IMAGE_CAPTURE
或ACTION_VIDEO_CAPTURE
intent 动作,而不是请求CAMERA
权限。 - 与蓝牙设备配对时,使用伴生设备配对(Android 8.0 (API level 26) 及更高版本),而不是声明
ACCESS_FINE_LOCATION
、ACCESS_COARSE_LOCATIION
或BLUETOOTH_ADMIN
权限。
- 请求
- 使用隐私仪表板(Android 12 (API level 31) 及更高版本)来验证应用如何解释对敏感信息的访问。
要获取特定权限的详细信息,您可以参考 Android 文档。