Android 具有内置的安全功能,可显著降低应用安全问题的频率和影响。该系统的设计使得您通常可以使用默认系统和文件权限构建应用,并避免有关安全的难题。
以下核心安全功能可帮助您构建安全的应用
- Android 应用沙盒,它将您的应用数据和代码执行与其他应用隔离开来。
- 一个应用框架,其中包含对常用安全功能(如密码学、权限和安全进程间通信 (IPC))的强大实现。
- 诸如地址空间布局随机化 (ASLR)、无执行 (NX)、ProPolice、safe_iop、OpenBSD
dlmalloc
和calloc
以及 Linuxmmap_min_addr
等技术,可降低与常见内存管理错误相关的风险。 - 用户授予的权限,以限制对系统功能和用户数据的访问。
- 应用定义的权限,用于按应用控制应用数据。
熟悉此页面上的 Android 安全最佳实践非常重要。将这些实践作为一般的编码习惯,有助于避免无意中引入对用户产生负面影响的安全问题。
身份验证
身份验证是许多关键安全操作的先决条件。要控制对受保护的资产(如用户数据、应用功能和其他资源)的访问,您需要向您的 Android 应用添加身份验证。
您可以通过将您的应用与凭据管理器集成来改善用户的身份验证体验。凭据管理器是一个 Android Jetpack 库,它统一了对大多数主要身份验证方法的 API 支持,包括密钥、密码和联合登录解决方案,例如使用 Google 登录。
为了进一步增强应用的安全性能,请考虑添加生物识别身份验证方法,例如指纹扫描或面部识别。适合添加生物识别身份验证的应用可能包括金融、医疗保健或身份管理应用。
Android 的自动填充框架可以简化注册和登录流程,从而降低错误率并减少用户摩擦。自动填充与密码管理器集成,使用户可以选择复杂、随机的密码,这些密码可以轻松安全地存储和检索。
应用完整性
Play Integrity API 可帮助您检查交互和服务器请求是否来自在正版 Android 设备上运行的正版应用二进制文件。通过检测潜在的风险和欺诈性交互(例如来自篡改的应用版本和不可靠的环境),您的应用后端服务器可以采取适当的操作来防止攻击并减少滥用。
数据存储
Android 应用最常见的安全问题是您在设备上保存的数据是否可供其他应用访问。在设备上保存数据有三种基本方法
- 内部存储
- 外部存储
- 内容提供程序
以下部分介绍了与每种方法相关的安全问题。
内部存储
默认情况下,您在内部存储上创建的文件仅供您的应用访问。Android 会实施此保护措施,对于大多数应用来说已足够。
避免使用已弃用的MODE_WORLD_WRITEABLE
和MODE_WORLD_READABLE
IPC 文件模式。它们无法限制对特定应用的数据访问权限,也不能控制数据格式。如果您想与其他应用进程共享数据,请考虑改用内容提供程序,它可以向其他应用提供读写权限,并可以根据具体情况动态授予权限。
外部存储
在外部存储(例如SD卡)上创建的文件是全局可读和可写的。由于外部存储可以被用户移除,也可以被任何应用程序修改,因此只应使用外部存储来存储非敏感信息。
处理来自外部存储的数据时,应像处理来自任何不受信任来源的数据一样执行输入验证。不要在动态加载之前将可执行文件或类文件存储在外部存储中。如果您的应用确实从外部存储检索可执行文件,请确保在动态加载之前对文件进行签名和加密验证。
内容提供程序
内容提供程序提供了一种结构化的存储机制,可以限制为仅限于您自己的应用程序,也可以导出以允许其他应用程序访问。如果您不打算让其他应用程序访问您的ContentProvider
,请在应用程序清单中将其标记为android:exported=false
。否则,将android:exported
属性设置为true
,以允许其他应用访问存储的数据。
创建供其他应用程序使用的已导出的ContentProvider
时,您可以指定单个权限用于读写,也可以指定不同的读写权限。将您的权限限制为完成手头任务所需的那些权限。请记住,通常情况下,以后添加权限以公开新功能比删除权限并影响现有用户更容易。
如果您使用内容提供程序在您自己的应用程序之间共享数据,我们建议将android:protectionLevel
属性设置为signature
保护。签名权限不需要用户确认,因此当访问数据的应用程序使用相同的密钥签名时,它们可以提供更好的用户体验和对内容提供程序数据的更严格的访问控制。
内容提供程序还可以通过声明android:grantUriPermissions
属性并在Intent
对象中使用FLAG_GRANT_READ_URI_PERMISSION
和FLAG_GRANT_WRITE_URI_PERMISSION
标志来激活组件,从而提供更细粒度的访问控制。这些权限的范围可以通过<grant-uri-permission>
元素进一步限制。
访问内容提供程序时,使用参数化查询方法,例如query
、update
和delete()
,以避免来自不受信任来源的潜在SQL注入。请注意,如果selection
参数是在提交给方法之前通过连接用户数据构建的,则使用参数化方法是不够的。
不要对写入权限抱有虚假的安全感。写入权限允许SQL语句,使得可以通过巧妙的WHERE
子句和解析结果来确认某些数据。例如,攻击者可以通过仅在该电话号码已存在时修改行来探测呼叫日志中是否存在特定电话号码。如果内容提供程序数据具有可预测的结构,则写入权限可能等同于提供读写权限。
权限
由于Android将应用程序彼此隔离,因此应用程序必须显式共享资源和数据。它们通过声明其需要的权限来实现这一点,这些权限是为了提供基本沙箱未提供的附加功能,包括访问设备功能(例如摄像头)。
权限请求
尽量减少应用程序请求的权限数量。限制对敏感权限的访问可以降低意外滥用这些权限的风险,提高用户采用率,并使您的应用程序不易受到攻击者的攻击。一般来说,如果您的应用不需要某个权限才能运行,请不要请求它。请参阅有关评估您的应用是否需要声明权限的指南。
如果可能,请以不需要任何权限的方式设计您的应用程序。例如,与其请求访问设备信息以创建唯一标识符,不如为您的应用程序创建一个UUID。(在关于用户数据的部分中了解更多信息)。或者,与其使用外部存储(这需要权限),不如将数据存储在内部存储中。
除了请求权限外,您的应用程序还可以使用<permission>
元素来保护对其他应用程序公开的安全敏感IPC,例如ContentProvider
。一般来说,我们建议尽可能使用除用户确认权限以外的其他访问控制,因为权限可能会让用户感到困惑。例如,可以考虑对单个开发人员提供的应用程序之间的IPC通信使用签名保护级别。
不要泄露受权限保护的数据。当您的应用程序通过IPC公开仅因为您的应用程序有权访问该数据而可用的数据时,就会发生这种情况。您应用程序的IPC接口的客户端可能没有相同的数访问权限。有关此问题的频率和潜在影响的更多详细信息,请参阅在USENIX上发表的研究论文Permission Re-Delegation: Attacks and Defenses。
权限定义
定义满足您的安全要求的最小权限集。对于大多数应用程序来说,创建新权限相对不常见,因为系统定义的权限涵盖了许多情况。在适当的情况下,使用现有权限执行访问检查。
如果您需要新的权限,请考虑是否可以使用签名保护级别来完成您的任务。签名权限对用户是透明的,并且仅允许与执行权限检查的应用程序由同一开发人员签名的应用程序访问。
如果仍然需要创建新的权限,请使用<permission>
元素在应用程序清单中声明它。使用新权限的应用程序可以通过在其清单文件中添加<uses-permission>
元素来引用它。您还可以使用addPermission()
方法动态添加权限。
如果您创建具有危险保护级别的权限,则需要考虑许多复杂因素。
- 权限必须包含一个简洁地向用户表达他们需要做出的安全决策的字符串。
- 权限字符串必须本地化为许多不同的语言。
- 用户可能会因为权限令人困惑或被认为有风险而选择不安装应用程序。
- 在权限创建者尚未安装的情况下,应用程序可能会请求该权限。
这些都对您作为开发人员提出了重大的非技术性挑战,同时也让您的用户感到困惑,这就是我们不鼓励使用危险权限级别的原因。
网络
网络事务本质上存在安全风险,因为它们涉及传输对用户来说可能私密的数据。人们越来越意识到移动设备的隐私问题,尤其是在设备执行网络事务时,因此您的应用程序始终实施所有最佳实践以确保用户数据的安全非常重要。
IP网络
Android上的网络与其他Linux环境没有显著区别。关键的考虑因素是确保对敏感数据使用适当的协议,例如针对安全网络流量使用HttpsURLConnection
。在服务器支持HTTPS的任何地方使用HTTPS而不是HTTP,因为移动设备经常连接到不安全的网络,例如公共Wi-Fi热点。
可以使用SSLSocket
类轻松实现经过身份验证的加密套接字级通信。鉴于Android设备使用Wi-Fi连接到不安全无线网络的频率,强烈建议所有通过网络通信的应用程序使用安全网络。
有些应用程序使用localhost网络端口来处理敏感IPC。不要使用这种方法,因为这些接口可以被设备上的其他应用程序访问。相反,请使用可以进行身份验证的Android IPC机制,例如使用Service
。绑定到非特定IP地址INADDR_ANY
比使用环回更糟糕,因为它允许您的应用程序接收来自任何IP地址的请求。
确保您不信任从HTTP或其他不安全协议下载的数据。这包括WebView
中的输入验证以及对针对HTTP发出的意图的任何响应。
电话网络
短消息服务(SMS)协议主要设计用于用户间通信,并不适合想要传输数据的应用程序。由于SMS的局限性,我们建议使用Firebase Cloud Messaging(FCM)和IP网络来发送来自Web服务器到用户设备上应用程序的数据消息。
请注意,SMS在网络或设备上既未加密也未进行强身份验证。特别是,任何SMS接收器都应预期恶意用户可能已将SMS发送到您的应用程序。不要依赖未经身份验证的SMS数据来执行敏感命令。此外,请注意,SMS可能会受到网络上的欺骗和/或拦截。在Android设备本身,SMS消息作为广播意图传输,因此其他具有READ_SMS
权限的应用程序可以读取或捕获这些消息。
输入验证
无论应用程序运行在哪个平台上,输入验证不足都是影响应用程序的最常见安全问题之一。Android具有平台级对策,可以减少应用程序面临的输入验证问题的风险,我们建议您在可能的情况下使用这些功能。此外,我们建议使用类型安全语言来降低输入验证问题的可能性。
如果您使用的是原生代码,则从文件读取、通过网络接收或从 IPC 接收的任何数据都可能导致安全问题。最常见的问题包括缓冲区溢出、释放后使用和越界错误。Android 提供了许多技术,例如 ASLR 和数据执行保护 (DEP),可以降低这些错误的可利用性,但它们并不能解决根本问题。您可以通过仔细处理指针和管理缓冲区来防止这些漏洞。
动态的基于字符串的语言(例如 JavaScript 和 SQL)也容易受到输入验证问题的困扰,这些问题是由转义字符和脚本注入造成的。
如果您在提交到 SQL 数据库或内容提供程序的查询中使用数据,则 SQL 注入可能是一个问题。最好的防御方法是使用参数化查询,如内容提供程序部分所述。将权限限制为只读或只写也可以减少与 SQL 注入相关的潜在危害。
如果您无法使用本节中讨论的安全功能,请确保使用结构良好的数据格式,并验证数据是否符合预期格式。虽然阻止特定字符或执行字符替换可能是一种有效的策略,但这些技术在实践中容易出错,我们建议尽可能避免使用它们。
用户数据
保护用户数据安全性的最佳方法是尽量减少使用访问敏感或个人信息的 API。如果您有权访问用户数据,请避免在可能的情况下存储或传输它。考虑您的应用程序逻辑是否可以使用数据的哈希值或不可逆形式来实现。例如,您的应用可以使用电子邮件地址的哈希值作为主键,以避免传输或存储电子邮件地址。这减少了意外泄露数据的可能性,也减少了攻击者试图利用您的应用程序的可能性。
每当需要访问私人数据时,都要对用户进行身份验证,并使用现代身份验证方法,例如密码密钥和凭据管理器。如果您的应用需要访问个人信息,请记住,某些司法管辖区可能要求您提供隐私政策,解释您对该数据的用途和存储方式。遵循最小化访问用户数据的安全最佳实践,以简化合规性。
此外,请考虑您的应用程序是否可能无意中将个人信息泄露给其他方,例如用于广告的第三方组件或您的应用程序使用的第三方服务。如果您不知道组件或服务需要个人信息的原因,请不要提供它。一般来说,减少应用程序对个人信息的访问权限可以减少该领域出现问题的可能性。
如果您的应用需要访问敏感数据,请评估您是否需要将其传输到服务器,或者您是否可以在客户端运行该操作。考虑在客户端运行使用敏感数据的任何代码,以避免传输用户数据。此外,请确保您不会通过过于宽松的 IPC、世界可写文件或网络套接字无意中将用户数据泄露给设备上的其他应用程序。过于宽松的 IPC 是泄露权限保护数据的特例,在权限请求部分进行了讨论。
如果需要全局唯一标识符 (GUID),请创建一个大型的唯一数字并将其存储。不要使用与个人信息相关的电话标识符,例如电话号码或 IMEI。本主题在关于唯一标识符最佳实践的页面中有更详细的讨论。
写入设备日志时要小心。在 Android 中,日志是共享资源,可供具有READ_LOGS
权限的应用程序使用。即使手机日志数据是临时的,并且会在重新启动时被清除,但对用户信息的不当记录也可能无意中将用户数据泄露给其他应用程序。除了不要记录 PII 之外,还要限制生产应用中的日志使用。为了轻松实现这一点,请使用调试标志和具有易于配置的日志级别的自定义Log
类。
WebView
因为WebView
使用可能包含 HTML 和 JavaScript 的 Web 内容,所以使用不当可能会导致常见的 Web 安全问题,例如跨站点脚本编写(JavaScript 注入)。Android 包含许多机制,通过将WebView
的功能限制在您的应用程序所需的最低功能来减少这些潜在问题的范围。
如果您的应用程序不在WebView
中直接使用 JavaScript,则不要调用setJavaScriptEnabled
。一些示例代码使用了此方法;如果您在生产应用程序中重新使用使用它的示例代码,如果不需要,请删除该方法调用。默认情况下,WebView
不会执行 JavaScript,因此跨站点脚本编写是不可能的。
谨慎使用addJavaScriptInterface()
,因为它允许 JavaScript 调用通常为 Android 应用程序保留的操作。如果您使用它,请仅将其公开给所有输入都可信的网页。如果允许不受信任的输入,不受信任的 JavaScript 可能会能够在您的应用中调用 Android 方法。一般来说,我们建议仅将addJavaScriptInterface()
公开给包含在您的应用程序 APK 中的 JavaScript。
如果您的应用程序使用WebView
访问敏感数据,请考虑使用clearCache()
方法删除本地存储的任何文件。您还可以使用服务器端标头(例如no-store
)来指示应用程序不应缓存特定内容。
运行低于 Android 4.4(API 级别 19)的平台的设备使用的是webkit
的版本,该版本存在许多安全问题。作为解决方法,如果您的应用在这些设备上运行,它必须确认WebView
对象仅显示可信内容。为确保您的应用不会暴露于 SSL 中潜在的漏洞,请使用可更新的安全Provider
对象,如更新您的安全提供程序以防范 SSL 漏洞中所述。如果您的应用程序必须呈现来自开放 Web 的内容,请考虑提供您自己的渲染器,以便您可以使其与最新的安全补丁保持同步。
凭据请求
凭据请求是攻击的媒介。以下是一些提示,可帮助您使 Android 应用中的凭据请求更安全。
最大限度地减少凭据泄露
- 避免不必要的凭据请求。为了使网络钓鱼攻击更显眼且不太可能成功,请最大限度地减少请求用户凭据的频率。相反,请使用授权令牌并刷新它。仅请求身份验证和授权所需的最少凭据信息。
- 安全地存储凭据。使用凭据管理器启用使用密码密钥的无密码身份验证,或使用诸如“使用 Google 登录”之类的方案实现联合登录。如果您必须使用传统的密码身份验证,请不要在设备上存储用户 ID 和密码。相反,请使用用户提供的用户名和密码执行初始身份验证,然后使用短期、特定于服务的授权令牌。
- 限制权限范围。不要为只需要更窄范围的任务请求广泛的权限。
- 限制访问令牌。使用短期令牌操作和 API 调用。
- 限制身份验证速率。快速连续的身份验证或授权请求可能是暴力攻击的迹象。将这些速率限制在合理的频率内,同时仍然允许具有功能性和用户友好的应用体验。
使用安全身份验证
- 实施密码密钥。启用密码密钥作为对密码更安全、更用户友好的升级。
- 添加生物识别功能。提供使用生物识别身份验证(例如指纹或面部识别)以增强安全性的能力。
- 使用联合身份提供商。凭据管理器支持联合身份验证提供商,例如使用 Google 登录。
- 加密通信 使用 HTTPS 和类似技术确保您的应用通过网络发送的数据受到保护。
实践安全的帐户管理
- 使用
AccountManager
连接到可供多个应用程序访问的服务。使用AccountManager
类调用基于云的服务,并且不要在设备上存储密码。 - 使用
AccountManager
检索Account
后,在传入任何凭据之前使用CREATOR
,以免无意中将凭据传递给错误的应用程序。 - 如果凭据仅由您创建的应用程序使用,您可以使用
checkSignatures
验证访问AccountManager
的应用程序。或者,如果只有一个应用程序使用凭据,您可以使用KeyStore
进行存储。
保持警惕
- 使您的代码保持最新。请务必更新您的源代码,包括任何第三方库和依赖项,以防范最新的漏洞。
- 监控可疑活动。查找潜在的滥用行为,例如授权滥用的模式。
- 审核您的代码。对您的代码库进行定期安全检查,以查找潜在的凭据请求问题。
API 密钥管理
API 密钥是许多 Android 应用的关键组件,使它们能够访问外部服务并执行连接到地图服务、身份验证和天气服务等基本功能。但是,泄露这些敏感密钥可能会造成严重后果,包括数据泄露、未经授权的访问和财务损失。为了防止这种情况,开发人员应在整个开发过程中实施安全策略来处理 API 密钥。
为了保护服务免受滥用,必须仔细保护 API 密钥。要保护应用与使用 API 密钥的服务之间的连接,您需要保护对 API 的访问。当您的应用被编译,并且您的应用的源代码包含 API 密钥时,攻击者可能会反编译应用并找到这些资源。
本节面向两组 Android 开发人员:那些在其持续交付管道上与基础设施团队合作的人员,以及那些在 Play 商店中部署独立应用的人员。本节概述了处理 API 密钥的最佳实践,以便您的应用可以安全地与服务通信。
生成和存储
开发人员应将 API 密钥存储视为数据保护和用户隐私的关键组成部分,并采用纵深防御方法。
强大的密钥存储
为了获得最佳的密钥管理安全性,请使用Android 密钥库,并使用强大的工具(例如Tink Java)加密存储的密钥。
源代码控制排除
切勿将 API 密钥提交到您的源代码存储库。将 API 密钥添加到源代码会将密钥暴露给公共存储库、共享代码示例和意外共享的文件。相反,请使用 Gradle 插件(例如secrets-gradle-plugin)在您的项目中使用 API 密钥。
特定于环境的密钥
如果可能,请为开发、测试和生产环境使用单独的 API 密钥。使用特定于环境的密钥隔离每个环境,降低暴露生产数据的风险,并允许您在不影响生产环境的情况下禁用受损密钥。
使用和访问控制
安全的 API 密钥实践对于保护您的 API 和用户至关重要。以下是准备密钥以获得最佳安全性的方法
- 为每个应用生成唯一的密钥:为每个应用使用单独的 API 密钥,以帮助识别和隔离受损的访问。
- 实施 IP 限制:如果可能,将 API 密钥的使用限制在特定的 IP 地址或范围。
- 限制移动应用密钥的使用:通过将密钥与移动应用捆绑在一起或使用应用证书,将 API 密钥的使用限制在特定的移动应用。
- 记录和监控可疑活动:实施 API 使用日志记录和监控机制,以检测可疑活动并防止潜在的滥用。
注意:您的服务应提供将密钥限制在特定包或平台的功能。例如,Google Maps API 通过包名和签名密钥限制密钥访问。
OAuth 2.0 提供了一个授权访问资源的框架。它定义了客户端和服务器如何交互的标准,并允许进行安全授权。您可以使用 OAuth 2.0 将 API 密钥的使用限制在特定的客户端,并定义访问范围,以便每个 API 密钥仅具有其预期用途所需的最低访问级别。
密钥轮换和过期
为了降低通过未发现的 API 漏洞进行未授权访问的风险,定期轮换 API 密钥非常重要。ISO 27001 标准定义了一个关于多久执行一次密钥轮换的合规性框架。对于大多数情况,90 天到 6 个月的密钥轮换周期应该足够。实施强大的密钥管理系统可以帮助您简化这些流程,提高密钥轮换和过期需求的效率。
一般最佳实践
- 使用 SSL/HTTPS:始终使用 HTTPS 通信来加密您的 API 请求。
- 证书固定:为了增加一层安全性,您可以考虑实施证书固定以检查哪些证书被认为有效。
- 验证和清理用户输入:验证和清理用户输入,以防止可能泄露 API 密钥的注入攻击。
- 遵循安全最佳实践:在您的开发过程中实施通用的安全最佳实践,包括安全的编码技术、代码审查和漏洞扫描。
- 保持知情:随时了解最新的安全威胁和 API 密钥管理的最佳实践。
- SDK 保持最新:确保您的 SDK 和库已更新到最新版本。
密码学
除了提供数据隔离、支持全文件系统加密和提供安全的通信通道外,Android 还提供各种算法,用于使用密码学保护数据。
了解您的软件使用哪些 Java 密码体系结构 (JCA) 安全提供程序。尝试使用现有框架实现中能够支持您的用例的最高级别。如果适用,请按 Google 指定的顺序使用 Google 提供的提供程序。
如果您需要从已知的网络位置安全地检索文件,简单的 HTTPS URI 可能就足够了,不需要任何密码学知识。如果您需要安全的隧道,请考虑使用HttpsURLConnection
或 SSLSocket
,而不是编写您自己的协议。如果您使用SSLSocket
,请注意它不执行主机名验证。请参阅直接使用SSLSocket
的警告。
如果您发现需要实现您自己的协议,请不要实现您自己的加密算法。使用现有的加密算法,例如Cipher
类中提供的 AES 和 RSA 实现。此外,请遵循以下最佳实践
- 出于商业目的,使用 256 位 AES。(如果不可用,请使用 128 位 AES。)
- 对于椭圆曲线 (EC) 密码学,使用 224 位或 256 位公钥大小。
- 了解何时使用 CBC、CTR 或 GCM 块模式。
- 避免在 CTR 模式下重复使用 IV/计数器。确保它们是加密安全的随机数。
- 使用加密时,使用以下函数之一使用 CBC 或 CTR 模式实现完整性
- HMAC-SHA1
- HMAC-SHA-256
- HMAC-SHA-512
- GCM 模式
使用安全随机数生成器SecureRandom
来初始化由KeyGenerator
生成的任何加密密钥。使用未用安全随机数生成器生成的密钥会大大削弱算法的强度,并可能允许脱机攻击。
如果您需要存储密钥以重复使用,请使用一种机制(例如KeyStore
),该机制提供加密密钥的长期存储和检索。
进程间通信
某些应用尝试使用传统的 Linux 技术(例如网络套接字和共享文件)来实现 IPC。但是,我们建议您改为使用 Android 系统功能进行 IPC,例如Intent
、Binder
或带有Service
的Messenger
,以及BroadcastReceiver
。Android IPC 机制允许您验证连接到您的 IPC 的应用程序的身份,并为每个 IPC 机制设置安全策略。
许多安全元素在 IPC 机制之间共享。如果您的 IPC 机制并非供其他应用程序使用,请在组件的清单元素中将android:exported
属性设置为false
,例如对于<service>
元素。这对于由同一 UID 中的多个进程组成的应用程序很有用,或者如果您在开发后期决定实际上不想将功能公开为 IPC,但又不想重写代码。
如果您的 IPC 可供其他应用程序访问,您可以使用<permission>
元素应用安全策略。如果 IPC 存在于您自己的应用程序之间,并且使用相同的密钥签名,请在android:protectionLevel
中使用signature-level
权限。
意图
对于活动和广播接收器,意图是 Android 上异步 IPC 的首选机制。根据您的应用程序需求,您可以使用sendBroadcast
、sendOrderedBroadcast
或指向特定应用程序组件的显式意图。出于安全考虑,显式意图是首选。
警告:如果您使用意图绑定到**Service**
,请使用显式意图来确保您的应用程序安全。使用隐式意图启动服务是一种安全隐患,因为您无法确定哪个服务将响应意图,并且用户也看不到哪个服务启动。从 Android 5.0(API 级别 21)开始,如果您使用隐式意图调用**bindService()**
,系统将抛出异常。
请注意,有序广播可以被接收者使用,因此它们可能不会传递给所有应用程序。如果您发送的意图必须传递给特定的接收者,则必须使用按名称声明接收者的显式意图。
意图的发件人可以通过使用方法调用指定非空权限来验证接收者是否具有权限。只有具有该权限的应用程序才能接收意图。如果广播意图中的数据可能是敏感的,请考虑应用权限以确保恶意应用程序无法注册接收这些消息而无需适当的权限。在这些情况下,您也可以考虑直接调用接收器,而不是发出广播。
注意:意图过滤器不是安全功能。可以使用显式意图调用组件,并且可能没有符合意图过滤器的的数据。要确认它是否已正确格式化以供调用的接收器、服务或活动使用,请在您的意图接收器中执行输入验证。
服务
一个Service
通常用于为其他应用程序提供功能。每个服务类都必须在其清单文件中具有相应的<service>
声明。
默认情况下,服务不会导出,并且任何其他应用程序都无法调用它。但是,如果您向服务声明添加任何意图过滤器,则它将默认导出。最好显式声明android:exported
属性,以确保它的行为符合您的预期。服务还可以使用android:permission
属性进行保护。这样做,其他应用程序需要在其自己的清单中声明相应的<uses-permission>
元素,才能启动、停止或绑定到该服务。
注意:如果您的应用面向 Android 5.0(API 级别 21)或更高版本,请使用**JobScheduler**
来执行后台服务。
服务可以保护对其进行的单个 IPC 调用权限。这是在执行调用的实现之前调用checkCallingPermission()
来完成的。我们建议使用清单中的声明性权限,因为这些权限不太容易被忽视。
警告:不要混淆客户端和服务器权限;确保被调用应用程序具有适当的权限,并验证您是否向调用应用程序授予相同的权限。
Binder 和 Messenger 接口
使用Binder
或Messenger
是 Android 上 RPC 样式 IPC 的首选机制。它们提供明确定义的接口,如果需要,可以启用端点的相互身份验证。
我们建议您以不需要接口特定权限检查的方式设计您的应用程序接口。Binder
和Messenger
对象未在应用程序清单中声明,因此您不能直接对它们应用声明性权限。它们通常继承在实现它们的Service
或Activity
的应用程序清单中声明的权限。如果您正在创建一个需要身份验证和/或访问控制的接口,则必须在Binder
或Messenger
接口中显式添加这些控件。
如果您提供的接口需要访问控制,请使用checkCallingPermission()
来验证调用者是否具有所需的权限。这在代表调用者访问服务之前尤其重要,因为您的应用程序的身份会传递给其他接口。如果您调用由Service
提供的接口,则如果您没有访问给定服务的权限,bindService()
调用可能会失败。如果您需要允许外部进程与您的应用交互,但它没有执行此操作所需的权限,则可以使用clearCallingIdentity()
方法。此方法将对您的应用接口的调用执行为您的应用本身进行调用,而不是外部调用者进行调用。您可以稍后使用restoreCallingIdentity()
方法恢复调用者的权限。
有关使用服务执行 IPC 的更多信息,请参阅绑定服务。
广播接收器
一个BroadcastReceiver
处理由Intent
启动的异步请求。
默认情况下,接收器是导出的,可以由任何其他应用程序调用。如果您的BroadcastReceiver
旨在供其他应用程序使用,则可能需要使用应用程序清单中的<receiver>
元素为接收器应用安全权限。这可以防止没有相应权限的应用程序将意图发送到BroadcastReceiver
。
动态加载代码的安全问题
我们强烈建议不要加载应用程序 APK 外部的代码。这样做会大大增加由于代码注入或代码篡改而导致应用程序受损的可能性。它还会增加版本管理和应用程序测试的复杂性,并且可能无法验证应用程序的行为,因此在某些环境中可能会被禁止。
如果您的应用程序确实动态加载代码,那么最重要的是要记住,动态加载的代码使用与应用程序 APK 相同的安全权限运行。用户会根据您的身份决定是否安装您的应用程序,并且用户期望您提供在应用程序中运行的任何代码,包括动态加载的代码。
许多应用程序尝试从不安全的位置加载代码,例如从通过未加密协议从网络下载的代码或从可由世界写入的位置(例如外部存储)加载的代码。这些位置可能会让网络上的某个人修改传输中的内容或用户设备上的另一个应用程序修改设备上的内容。另一方面,直接包含在您的 APK 中的模块无法被其他应用程序修改。这对于代码是原生库还是使用DexClassLoader
加载的类都适用。
虚拟机中的安全问题
Dalvik 是 Android 的运行时虚拟机 (VM)。Dalvik 是专门为 Android 构建的,但其他虚拟机中关于安全代码的许多问题也适用于 Android。通常,您无需关注与虚拟机相关的安全问题。您的应用程序在安全的沙盒环境中运行,因此系统上的其他进程无法访问您的代码或私有数据。
如果您有兴趣了解有关虚拟机安全的更多信息,请熟悉有关该主题的一些现有文献。两个比较受欢迎的资源是
本文档重点介绍 Android 特定的或与其他 VM 环境不同的方面。对于在其他环境中具有 VM 编程经验的开发人员来说,编写 Android 应用可能存在两个主要区别
- 某些虚拟机(例如 JVM 或 .NET 运行时)充当安全边界,将代码与底层操作系统的功能隔离开来。在 Android 上,Dalvik VM 不是安全边界——应用程序沙盒是在操作系统级别实现的,因此 Dalvik 可以与同一应用程序中的原生代码进行互操作,而没有任何安全限制。
- 鉴于移动设备上的存储空间有限,开发人员通常希望构建模块化应用程序并使用动态类加载。执行此操作时,请同时考虑您从中检索应用程序逻辑的源以及您在本地存储它的位置。不要从未经验证的源(例如不安全的网络源或外部存储)使用动态类加载,因为该代码可能会被修改为包含恶意行为。
原生代码中的安全问题
通常,我们建议使用 Android SDK 进行应用程序开发,而不是使用Android NDK使用原生代码。使用原生代码构建的应用程序更复杂、可移植性更差,并且更可能包含常见的内存损坏错误,例如缓冲区溢出。
Android 是使用 Linux 内核构建的,如果您使用的是原生代码,那么熟悉 Linux 开发安全最佳实践特别有用。Linux 安全实践超出了本文档的范围,但其中一个最受欢迎的资源是安全编程 HOWTO - 创建安全软件。
Android 与大多数 Linux 环境之间的一个重要区别是应用程序沙盒。在 Android 上,所有应用程序都在应用程序沙盒中运行,包括使用原生代码编写的应用程序。对于熟悉 Linux 的开发人员来说,一个很好的思考方法是知道每个应用程序都获得了具有非常有限权限的唯一用户标识符 (UID)。这在Android 安全概述中进行了更详细的讨论,即使您使用的是原生代码,也应该熟悉应用程序权限。