android Security Tips

来源:互联网 发布:悦木之源泥娃娃知乎 编辑:程序博客网 时间:2024/06/05 06:22

简述

Android系统内置的安全功能,能够显著的降低安全困扰的频率及影响。若你是一名精明的开发者那么你可以配置它的权限,否则它会以默认的安全配置方式构建,以此来避免复杂的安全性配置。
系统内置的安全功能:
- Android Application Sandbox,将你的应用程序与其它应用程序隔离。
- Application Framework提供了一系列强大的安全工具如:cryptographypermissionssecure IPC
- 以及用于内存管理的技术如:ASLRNXProPolicesafe_iopOpenBSD dlmallocOpenBSD callocLinux mmap_min_addr
- 加密的文件系统,当你的设备丢失或被盗时会帮助你保护隐私数据。
- 让用户授权操作,来限制对系统功能及用户数据的访问。
- 应用程序自定义权限,来确保应用程序自身数据安全。

通过阅读本文,你能了解安全的应用程序的最佳实践。遵循实践,会有助于你编写安全的应用程序,减少安全问题。


数据存储

最普遍的安全问题,就是你的应用数据被第三方应用访问。下面有三种方法来帮你规避这个问题。

使用内部存储

默认情况下,使用internal storage创建的文件,只对创建应用程序本身可见。这种保护是由Android本身实现,并且适用于绝大多数情况。

通常不建议使用files来进行IPC。虽然你可以设置访问模式为MODE_WORLD_WRITEABLE或者MODE_WORLD_READABLE,来完成IPC需求。这是因为文件本身并没有提供任何访问控制权限,也没有提供数据格式的约束。

当你想将你的应用程序私有数据分享给第三方程序时,更推荐的做法是使用content provider。因为content provider不仅提供了读写的访问控制权限,也支持根据需求来动态申请/收取权限。

为了保护隐私数据数据,可以选择加密文件,额外需要注意的是保存好加密所用的 key。比如,你可以使用KeyStore放置加密的keyKeyStore私有存储,不要放置在 Application可触及到的地方。),当你需要使用key时,你需要输入一个额外的密码用以保护key的安全性。若你的设备以及Root了,不幸的是第三方程序仍然可以监听你的输入,从而得知你的key。若你的设备遗失了,文件加密会很好的保护你的隐私数据。

使用外部存储

创建在外部存储上的文件如:SDCard,是全局可读写的。由于外部存储器,可以由用户删除,也被任何应用程序修改,应使用外部存储不是存储敏感信息。若需读取外部存储的数据源,你应该执行数据源验证
我们强烈建议您不要动态加载那些存储在外部的可执行文件或类文件。若你确实需要这样的功能,则确保对需要动态加载的文件进行加密与签名,并且在动态加载前验签。

使用 Content Providers

Content Providers提供了结构化存储的机制,并且可以指定私有或者授权给其他应用程序访问。如果你不打算让让其他应用程序访问ContentProviers,那么在AndroidMainfest中标记android:exported=false。相反的,若设置android:exported=true,则允许其他应程序访问数据。

当创建ContentProviers并授权给第三方应用程序使用时,你可以为读、写操作赋予一个权限,当然你也可以分别设置读权限与写权限。请对于你手上正在执行的事项,随手添加权限。你需要记住,相比从未设置过权限的应用程序,在已有权限的基础上添加新的功能并不会导致原有用户被迫停止手上的事情。

若你使用ContentProviers只是为了在你可控的应用程序之间共享数据,最好的做好是设置android:protectionLevel = signature。signature权限不需要经过用户确认,所以当你手上的 app都是用相同的key签名,这种方式会提供更好的用户体验和更多的可控权限。

ContentProviers也提供了更细粒度的访问控制,通过定义android:grantUriPermissions属性,或在使用Intent激活组件时,标记FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION。这些权限可以通过<grant-uri-permission element>设置。

当你正在访问ContentProviers,多使用参数化的访问方法,诸如query()update()delete()来避免因为不可信的源导致 SQL Injection。仅仅使用上述方法,显然是不能够防范所有情况。你尤其要注意那些需要拼接在 sql中的参数,它串联用户的数据构建而成。了解SQL注入攻击。

不要忘记设置写权限,写权限允许SQL执行 。当攻击者试图将它与Where结合使用时,会变相的赋予了读权限。比如:攻击者 B翻阅你的联系人,并从通话记录中找到号码A。并修改 A 号码为 B 号码,如果你尝试对 A 进行转账,那么不幸的事情发生了。所以当ContentProviders具有可预测的结构,写的权限可能相等于读取与写入的权限。


定义权限

因为Android Sandbox相互隔离了应用程序,所以有应用需要明确的指出需要分享的资源或者数据。他们通过声明附加功能的权限来交换数据,而不是由Android Sandbox来提供。典型的应用如camera

请求权限

我们建议应用申请最小化的权限,以减少误用敏感权限。精炼恰当的权限申请,会让用户不会因为权限问题而拒绝使用,也减少了受攻击的机会。总而言之,你应用不需要的功能不要去申请,当然也不要遗漏权限以免造成程序异常退出。

如果可能,最好将你的应用程序设计成无需任何权限。
例如,相比读取设备信息生成一个唯一的识别码,更合适的做法是创建GUID(部分内容可阅读Handling User Data。
例如,相比使用外部存储(需要声明权限),将数据存储在内存存储空间也是个不错的选择。

除了请求权限之外,你可以使用<permissions>来保护开放给第三方应用使用的功能,如ContentProviders。总而言之,我们推荐使用访问控制权限胜过于使用需要用户确认的权限,因为需确认的权限往往会给用户带来疑惑。例如,当你需要交互的应用都可控时,可以选择signature protection level用以取代自定义权限。

Do not leak permission-protected data. This occurs when your app exposes data over IPC that is only available because it has a specific permission, but does not require that permission of any clients of it’s IPC interface. More details on the potential impacts, and frequency of this type of problem is provided in this research paper published at USENIX: http://www.cs.berkeley.edu/~afelt/felt_usenixsec2011.pdf

创建权限

一般来说,出于安全需求考虑总要定义那么几个权限。对于大多数应用程序而言,创建一个新的权限是非常罕见的,因为system-defined permissions已经覆盖面很广泛了。适当情况话,使用现有权限执行访问检查。

如果一定需要创建新的权限,首先考虑的是使用“signature” protection level能否满足你的需求。signature权限是对用户透明,并只允许由同一个开发者作为应用程序执行权限检查签名的应用程序访问。

如果你使用“dangerous” protection level,那么以下几点需要考虑:
- 该权限必要有明确的描述,以便告知用户为何需要开启这个权限。
- 该权限必须本地化翻译成不同的语言。
- 在应用安装时,用户可能因对权限的迷惑或不放心而不安装应用。
- 当权限未被授权时,应用程序会向用户请求该权限。

这样不仅会为开发者带来很多繁琐事项,同时也会打扰用户让用户困扰。正因这次,我们极力不推荐使用dangerous权限等级。


使用网络传输

网络传输本身是具有风险的,因为它涉及到传输用户的隐私数据。人们越来越重视移动设备的隐私问题,尤其是当进行网络传输时,你的应用程序的最佳实践方式是,在任何时候对保障用户的数据的安全。

使用 IP网络

Android的网络环境不同于Linux下的其他网络环境。重点考虑的是针对敏感数据使用适当的协议,比如使用HttpsURLConnection来确保安全网络传输。如果服务为Https提供了支持,我们更推荐使用Https来取代Http,因为手持设备经常暴露在不安全的网络环境中,如公共Wi-Fi。

经过身份验证,可以使用的SSLSocket类来轻松实现加密套接字层通信。考虑到手持设备连接到不安全的网络环境中的高频率问题,强烈建议所有的应用程序都使用SSLSocket来覆盖网络通讯。

我们看到了一些应用程序使用localhost来实现IPC。我们不鼓励这种方法,因为这些接口被其他应用程序访问。相反,你应该使用一个Android IPC机制,如:Service等,因为它提供了身份验证。(Even worse than using loopback is to bind to INADDR_ANY since then your application may receive requests from anywhere.)

共性的问题是,不要从不安全的Http或其他协议中下载不可信任的数据。This includes validation of input in WebView and any responses to intents issued against HTTP.

使用电话网络

SMS协议被用于用户与用户之间的通讯,用在两个应用程序之间传输数据并不恰当。由于短信的限制,我们强烈建议使用Google Cloud Messaging(GCM)和IP Networking环境,来完成从Web Server通过应用程序将数据发送邮件到用户的终端上。

请注意,SMS既没有加密,也没有在网络或设备上执行严格的身份验证。所以不要依赖未经验证的SMS来执行敏感的行为,这有可能被第三方程序所利用。你也应该知道SMS可能被伪造,或被网络拦截。于Android设备本身,SMS同时也是broadcast intent中的一种。所以其他程序直接拦截到SMS消息,或者标记READ_SMS来访问SMS数据。


执行输入验证

无论在何种平台影响应用最常见的安全问题之一:是输入验证功能设计的不足。Android提供平台级的对策,提供减少应用发生输入验证问题的功能,并在可能的情况你应该使用这些功能。选择类型安全的编程语言,来减少输入验证的可能性。

如果你正在使用JNI,那么从文件、网络或者使用IPC读取数据均有可能引入安全问题。最显著的问题是buffer overflows,use after free和off-by-one errors。Android提供了诸如ASLR(Address Space Layout Randomization)DEP(Data Execution Prevention)技术来减少犯错的可能性,但是这些技术并没有从根本上解决问题。你需要谨慎的处理指针和缓冲区管理,来避免此类问题。

基于字符串作为语言JavaScriptSQL等也受到因转义字符和脚本注入输入验证问题。

如果你不能使用上面提到的安全功能,我们强烈建议使用结构严谨的数据格式并且验证符合期望的格式。黑名单中的字符或者要替换的字符是一种有效的策略,这些技术在实践中是易错并且当可能发生的时候应该避免。


处理用户数据

总体来说,用户数据安全的最佳做法是最小化对用户敏感数据的接口。如果你可以访问到敏感数据,在不需要本地存储或传输的情况下就可以完成的功能,尽量不要讲它们保存或者传输。最后,考虑如果有一种你的应用逻辑可能被实现为使用hash或者其它non-reversible的数据的方法。恰当的用例是,当你需要传输你的邮件地址时,可以选择使用邮件地址的hash作为私钥来避免数据传输引入的安全问题。这减少不慎露出数据的可能性,同时也减少了攻击者试图利用应用程序的机会。

当你的应用程序访问了用户的个人隐私,诸如password或者username,提供一些隐私策略来确保(鉴别)敏感数据的有效性。所以下面最大限度地减少访问用户数据,也符合安全最佳实践。

你也应该考虑你应用是否会疏忽的暴露个人信息给其他方,比如广告第三方组件或者你应用使用的第三方服务。
如果你不知道为什么一个组件或者服务需要请求个人信息,那么可以拒绝提供该信息。总的来说,通过减少你应用中访问个人信息的频率,将会减少这方面的安全问题。

如果必须访问敏感数据,评估是否可以在客户端本地执行。如非必须,可以减少访问服务端的频次,以减少往服务端传输敏感数据。

再次确保,你不会在无意间开放用户信息,通过IPCworld writable files,或者network sockets。有关权限设置不周的问题,我们在Requesting Permissions章节中讨论。

如果需要GUID(Globally Unique Identifier),那么选择一个唯一的大数来保存。不要使用与设备或者个人信息相关的标识,诸如:手机号码IMEI等。这部分篇章,将在Android Developer Blog中展开讨论。

应用开发时需要写入Log时需要保持谨慎,不要无意间记录用户的敏感数据。因为在AndroidLog是可共享的资源(标记READ_LOGS),虽然这些Log是临时性的并且在设备重启时会被擦除。

使用 WebView

WebView具有装载web content的能力,包括HTMLJavascript,因此不恰当的做法会引入安全问题,如跨域攻击(JavaScript injection)。Android使用多种机制来限制WebView的能力,你在开发应用程序过程中,可以要求的最小的功能以减少这些潜在的安全问题的影响范围。

若你的应用程序不需要调用Javascript的能力,那么不要调用setJavaScriptEnabled()。在一些例子程序中,演示了如何使用这个方法。但是你在结合你的应用程序时,需要酌情判别是否需要这个方法。默认情况,WebView将不能够执行JavaScript引入跨域攻击也因此不可能达成。

使用addJavaScriptInterface()需要特别小心,因为它允许JavaScript来调用通常保留给Android应用程序的操作。如果确定要使用,请一定确保应用addJavaScriptInterface()的网页是值得信赖的。当然它无法判别你的网页是可信还是不可信的,若在一个不可信的网页中使用该接口,那么你需要小心它可能会调用你的应用程序中的某些方法。通常来说,我们建议的做法是只调用那些被打包进你的APK中的JavaScript脚本。

若你的应用使用WebView来传输敏感数据,你可能需要调用clearCache()来删除本地缓存的文件。你可以在Server-side Headers里包含no-cache来指示客户端不缓存特定的数据。

在低于Android 4.4 API-19的版本上使用的webkit存在一些已知的安全问题。应对的方法是:
- 确认WebView加载的内容都是可信任的。
- 使用可持续更新的Provider,确保您的应用程序没有SSL潜在漏洞暴露。更多可关注Updating Your Security Provider to Protect Against SSL Exploits。
- 如果你的应用程序需要加载开放的网页,那么确保你可以动态的获取安全补丁。


处理凭证

一般情况下,我们建议您尽量降低请求用户凭据的频率,以使网络钓鱼攻击更加容易被发现,并且更小的攻击成功率。转而使用授权令牌并且刷新它。

如果有可能,不建议将username&password等信息保存在设备上。相反,执行时利用用户提供的用户名和密码进行初始鉴定,然后用一个短生命周期、特定服务使用的授权令牌。

可以被多个应用访问的services应该使用AccountManager管理。如果可能,使用AccontManager来执行基于云端的服务,从而取代将敏感信息存储在本机。

使用的AccontManager传递任何凭据之前检索AccountCREATOR,这样你就不会在无意中将凭据传递给错误的应用程序。

若凭据只是用于你所创建的应用程序,则你可以调用AccontManager中的checkSignature()方法来检查。另外,如果只有一个应用程序使用凭证,也可以使用KeyStore去存储。


使用加密

除了提供数据隔离,支持完整文件系统加密,并提供安全的通信渠道之外,Android也提供加密算法来保护广泛的数据。

通常来说,建议使用当前发布的最新的Android API来支持你的应用场景的开发。如果需要从已知位置安全的检索文件,一个简单的HTTPS URI就已经足够,并不需要加密。如果你需要一个安全渠道,请考虑使用HttpsURLConnectionSSLSocket,而不是写自己的协议。

如果你发现自己需要实现自己的协议,我们强烈建议您不要自己实现加密算法。而是使用现有的加密算法,如在Cipher类提供AESRSA的实施。

使用安全的随机数生成器——SecureRandom。
初始化任何加密密钥——KeyGenerator。
一个未使用安全随机数生成器生成的密钥,显著削弱了算法的强度,这会提高线下攻击的可能性。

如果需要存储用于重复使用的密钥,请使用像KeyStore(提供用于长期存储和加密密钥的检索机制)。


使用进程间通讯

一些应用程序使用Linux中一些传统IPC的实现,如:network socketsshared files。我们鼓励你使用 Android提供的进程间通讯的功能,诸如:IntentBinderMessenger结合ServiceBroadcastReceiver。因为Android IPC的功能提供了验证身份和安全策略的机制。

如果安全组件均具有IPC的机制。如果你的应用程序并不打算使用IPC,那么你可以在对应组件(如Service)的mainfest文档中设置android:exportedfalse。这对由同一个UID内多个进程应用,或者你在开发后期决定不想通过IPC暴露功能但是你又不想重写代码是很有用的。

如果你设计IPC的目的是为了提供给其他应用程序使用,请结合使用<permission>标签。如果IPC是相同开发者开发的,那么设置android:protectionLevelsignature更恰当。

使用 intents

Intent是在Android中实现异步IPC的首选机制。取决于你的需求,选择使用sendBroadcast()sendOrderBroadcast()或者其它明确意图的应用程序组件。

注意,有序的广播可能被中途的接受者所消费掉,所以它并不适用于部分应用。如果你需要发送意图给特定的接收者,则你需要使用nameintent来指定明确的接收者。

当已定义一个Non-null权限时调用该方法,Intent的将只能被那些定义过权限的接收者接收到。如果广播的包含敏感数据,那么你需要注册一个权限以避免恶意程序接收这些消息。那么在这种情况下,考虑执行receiver而不是使用广播。

注意:Intent Filters(通过过滤数据确定组件行为)不应该被视作是安全的功能。您需要在意图接收器中执行输入验证,以确认它是正确的格式可调用BroadcastReceiverServiceActivity

使用 services

Service通常作为功能组件被其他应用程序使用,每个Service都必须在其AndroidMainfest.xml中相应的声明。

默认情况下,Service是不开放的,所以其他应用程序无法调用它。你可以使用android:exported属性来设置是否开放该组件。当然Service也可以使用android:permission属性,一但你在该组件中设置了权限。则第三方使用时,需要在他的AndroidMainfest中使用<uses-permission>以便于该服务能够startstop或者bind服务的行为。

Service之所以能够有权限保障的IPC调用,是因为通过在调用最终实现之间,先调用checkCallingPermission()检查。我们通常建议使用在清单中声明的​​权限,因为这些是不容易监督。

使用 binder 与 messenger interfaces

AndroidBindersRPC-style IPC的首选机制。因为它们提供了明确定义的接口,必要的话它们可以彼此间端点认证。

我们强烈鼓励在一定程度上设计不要求接口指定许可检查的接口。Binder不在应用的manifest中声明,并且因此你不能直接在Binder上应用声明的许可。Binder继承在应用在manifestService或者Activity声明的,Service或者Activity内实现了的许可。如果你打算建立一个接口,在一个指定binder接口上要求验证并且(或者)要求访问控制,这些控制必须在接口中清楚的在代码中添加。

如果提供了确实需要访问控制的接口,使用checkCallingPermission()来核实调用者是否具有所需的权限。This is especially important before accessing a service on behalf of the caller, as the identify of your application is passed to other interfaces. 如果调用由Service提供的接口,如果您没有权限访问给定的服务bindService()调用可能会失败。如果调用一个你自己应用提供的本地的接口,可以使用clearCallingIdentity()来消除内部的安全检查。

有关与服务进行IPC的更多信息,请参见绑定服务。

使用 broadcast receivers

一个BroadcastReceiver处理由一个Intent发起的异步请求。

默认情况下,receivers默认是开放性的,可以被其他程序调用。如果你的BroadcastReceiver是计划给其他营业使用的,那么请配合在&lt;receiver&gt;使用安全权限。这将防止没有相应权限的应用程序发送的Intent给对应的BroadcastReceiver


动态加载代码

强烈不建议从你的APK之外动态加载代码。因为这样做显著增加是由于代码注入或代码篡改的应用妥协的可能性。它还增加了周围版本管理和应用测试的复杂性。最后,它能够使人们无法验证的应用程序的行为,因此它可能在某些环境中被禁止。

若你的应用程序已经动态加载代码,最重要的是你需要意识到动态加载的代码与打包在你APK中的代码具有相同的安全权限。用于是基于你的身份(应用程序)才选择安装你的程序,这其中包括动态加载的代码。

用动态加载代码关联的重大安全隐患是代码需要来自可验证的来源。如果模块APK中直接包含,则它们不能被其他应用程序进行修改。无论这是一段本地JNI还是使用DexLoaderdex中解析的代码,这是确定无疑的。我们已经看到的尝试从不安全的地址,如从网络通过未加密的协议或者写入全局的存储文件,并在程序中动态加载。这些会增加在网络上分别修改用户设备在运输过程中的数据,或其他应用程序来修改设备上的数据的可能性。


VM的安全性

Dalvik的是Android运行的虚拟机(VM)。Dalvik是为Android系统定制的,但是适用于其他虚拟机的安全问题也同样适用于Dalvik。通常,你不需担心VM会危害你的程序。因为您的应用程序在一个安全沙盒环境中运行,因此,系统上的其他进程无法访问您的代码或私人数据。

如果你有志于研究VM的安全问题,我们推荐给你两篇帖子:
- http://www.securingjava.com/toc.html
- https://www.owasp.org/index.php/Java_Security_Resources

本文着重说明Dalvik与其他VM的区别。在其他VM平台有过开发经验的人,有两个不同之处需要额外提出:
- 有些虚拟机,如JVM.NET运行时从底层操作系统功能隔离代码,充当一个安全边界。在Android上,Dalvik虚拟机并不是在操作系统层面实现具有安全边界应用程序沙箱,所以Dalvik可在同一应用程序的本机代码进行互操作,没有安全限制。
- 由于移动设备上的存储空间有限,它的开发人员通常会希望构建模块化的应用和使用动态类加载。执行此操作时,您检索您的应用程序逻辑和您存储在本地同时考虑来源。不要使用动态类加载来自未经验证的来源,如不安全的网络资源或外部存储,因为这类代码可能被修改,以包括恶意行为。


Native Code的安全性

我们更推荐开发者使用Android SDK来开发应用程序,而不是使用Android NDK。使用JNI构建的应用程序通常比较复杂,具有较低的可移植性。并经常会因此引入常见的内存问题如:buffer overflows

Android是使用Linux内核,熟悉Linux开发安全的最佳实践构建会有助于你构建JNI代码。本文不讨论Linux的安全实践,更多可以阅读http://www.dwheeler.com/secure-programs。

Android和大多数Linux环境之间的一个重要区别是Application Sandbox。在Android上,所有的应用程序应用程序沙箱中运行,包括那些与本地编写的代码。在基础设计上,为每一个Application赋值一个UID(User Identifier)可以限制相应的权限(这点可以参考参考 LinuxGIDUIDEID的设计)。如果你正在使用JNI,那么更多有关安全问题可以前往Android Security OverView。


chenjunqi.china@foxmail.com

0 0
原创粉丝点击