2013年8月3日星期六

最佳实践-android程序安全

自文章主要是翻译的官网文章,同时也加入了自己的理解,如有不准确之处,请指正
http://developer.android.com/guide/practices/security.html
一些虚拟机运行在一个安全边界内,与所在的操作系统的程序隔离开来,比如java虚拟机和.net 运行环境。
在android上,Dalvik虚拟机没有这样的安全边界,应用程序沙箱是实现在操作系统级别的,因此
在同一个程序,Dalvik与native code进行交互没有任何的安全边界。
给移动设备闲置存储策略。正常情况下,开发者想创建模块化的应用程序,通过动态类加载实现。当做这件事的
时候要考虑两个问题:程序逻辑和存储位置。不要使用没有检查的class资源,比如不安全的网络资源或者外部存储
设备上的资源,因为这样的资源可以被修改,可以包含进恶意的动作。
使用native code
一般情况下,我们建议开发者使用android sdk开发大多数的程序,而不是使用native code。程序使用native code去
构建会更加的复杂,庞大,还有可能会有一些常见的内存使用错误,比如缓冲区异常。
android是构建在Linux内核之上的,如果你熟悉Linux开发的话,使用native code是比较好。这个文档只是用了很少的篇幅
介绍了这个最近实践,要想了解更多关于为LInux和Unix编程安全的资源,请访问这个网址http://www.dwheeler.com/secure-programs
android与其他的大多数Linux运行环境一个重要的不同是:程序沙箱。在android上,所有的程序都是在沙箱内运行,即使程序包含
native 代码。熟悉Linux的开发者都知道每个程序被分配一个唯一的UID和有限的权限,当然即使你对Linux不熟悉,也要明白这一点。
如果你使用native code需要熟悉程序权限的内容。http://source.android.com/tech/security/index.html
存储数据
使用程序内部存储
默认情况下,在程序内部创建的文件只能被本程序使用。对于大多数程序的安全存储已经足够了。
在进程间使用全局的读或者写权限不建议,这样的话不能限制一些特定的程序去访问数据,同时也不能控制数据格式。
做为一种选择是,使用ContentProvider同读写允许,也可以根据具体情况动态的授予是否允许访问数据。
为了给敏感数据提供更多的保护,一些程序选择使用密钥对本地文件加密的方式。比如,密钥放在keystore中,用
来保护用户的密码,而不是直接存储在设备上。然而这样的情况下不能保护数据:设备是root过的,用户的输入的密码
可以被监听到。可以保护有文件加密系统的设备丢失的情况。
使用外部存储
在外部存储设备上存储文件时全局的读写权限,比如在SD卡上。因为外部存储设备可以用户移除,可以被任何程序修改,
不要把敏感的数据使用外部存储设备存储。
对于从任何不可以信任的源获取的数据,程序从外部存储获取的都应该对输入进行检查。我们强烈的建议不要在外部存储设备上
存储动态加载的可执行文件或者类文件。如果程序从外部存储加载可执行的文件,文件必须是签过名的和加密的,并且在加载前检查。
使用内容提供者
ContentProvider提供一个存储机制,这个机制可以限制自己的程序,也可以为其他程序提供一个访问入口。默认情况下,
一个ContentProvider是其他程序提供给用户的入口。如果你不想让其他程序访问你的ContentProvider,在manifest文件中
这样表明android:exported=false.
创建一个ContentProvider,将会为其他程序开启了一个入口,你可以指定一个读写权限或者把读写权限分开指定。我们建议
你把权限分开,按需分配。记住,通常情况为以后的新功能增加权限更容易一些,而不是去移走他们,而影响已经存在的用户。
如果ContentProvider是用来在同一个开发者开发的两个程序之间共享数据,应该倾向于使用签名级别的权限,签名权限不需要用户确认,
因此会有更加好的用户体验,更多对ContentProvider的访问控制。
ContentProvider还尅提供更多反问声明,通过grantUriPermison元素指定,使用FLAG_GRANT_READ_URI_PERMISSION 和 FLAG_GRANT_WRITE_URI_PERMISSION
这些标志在Intent对象上。权限的范围可以同grant-uri-permisstion元素进一步限制。
当访问ContentProvider的时候,使用带参数的方法,例如query(),update()和delete(),可以防止不信任数据潜在的sql注入风险。
注意,如果使用组装起来的数据提交到这些方法的话,使用参数化的方法还不够安全。
不要有对写权限有这样一个错误的看法,可以想象一下,写权限允许使用sql语句,使一些数据可以使用where子句组装结果。
举个例子,一个攻击者可能为了查看一个已经存在的电话号码,如果那个号码已经存在的话,就在call-log修改一行数据。
如果这个内容提供者有个可被预见的结构的话,这个写权限会相当于同时提供了读与写权限。
Using Interprocess Communication (IPC)
使用进程间通信
一些android程序企图使用传统的Linux进程间通信技术,比如网络socket和共享文件。我们强烈件事使用android系统的进程
通讯技术,不如Intent,Binder,Service和Receiver.android的IPC机制允许你验证程序连接你的IPC程序是否合法,可以
为IPC设置安全策略。
很多安全组件都是通过IPC机制共享数据的。广播接收者,活动,服务都是在程序的manifest中声明的。如果你的IPC机制不想
让其他的程序访问,设置android:exported的属性为false。这是非常有用处,对于多个程序的多个进程使用同一UID,或者
你后来决定不想通过IPC共享数据,就不需要重写代码了。
如果你的IPC是为了让其他的程序器访问的,你可以通过设置权限标志作为安全策略。如果IPC是同一个开发者的程序,
应该使用签名级别的权限控制,签名权限不需要用户确认,因此会有一个更好的用户体验,更多的IPC访问控制。
使用intent过滤的地方容易混淆,注意,intent过滤不应该认为是安全的特征,这个组件可以被直接调用,并且不会有
对intent过滤的确认。你应该执行输入验证,确保你的intent接受者接到receiver,service,和activity发送的格式正确。
Using intents
意图是android中推荐的异步IPC机制。基于你的程序需求,你可以使用sendBroadcaset(),sendOrderedBraodcast()或者
直接发送intent给指定的程序组件。
注意,ordered broadcast可以被所有的接收者消费掉,可能不会发送到所有的程序。如果你想发送给一个指定的接收者一个intent,这个intent必须直接发送
给指定的这个接受者。
如果没有指定权限限制的intent,可能会被别的接收者收到。限制成只有指定权限的接收者才能收到这个intent.设置intent的权限,可以
提高安全性。
使用 binder 和 AIDL 接口
binder是android远程调用IPC的推荐机制。
We strongly encourage designing interfaces in a manner that does not require interface specific
permission checks. Binders are not declared within the application manifest, and therefore you
cannot apply declarative permissions directly to a Binder. Binders generally inherit permissions
declared in the application manifest for the Service or Activity within which they are implemented.
If you are creating an interface that requires authentication and/or access controls on a specific
binder interface, those controls must be explicitly added as code in the interface.
If providing an interface that does require access controls, use checkCallingPermission()
to verify whether the caller of the Binder has a required permission. This is especially
important before accessing a Service on behalf of the caller, as the identify of your
application is passed to other interfaces. If invoking an interface provided by a Service,
the bindService() invocation may fail if you do not have permission to access the given Service.
If calling an interface provided locally by your own application, it may be useful to use the
clearCallingIdentity() to satisfy internal security checks.
使用ip网络:
建议使用Https协议的网络,其他不安全协议的网络尽量不要使用。当使用公共wiff热点的时候,这些开放的网络都是没有
加密的网络。
使用电话网络:
有sms这个技术是为了人对人之间的通信设计的,所以对于某些程序来说并不合适,建议使用C2DM和ip网络发送数据信息到设备。
sms在网络传输的过程中,没有对数据加密,没有很强的验证机制。特别是,任何sms接收者都可以接收到恶意者发送的信息到你的程序,
不要使用没有经过认证的sms执行操作敏感信息的命令。还有认识到一点,sms可能会被截获或者通过sms进行欺骗。在android的设备
上,SMS信息传输是通过广播意图的形式发送的,因此消息有可能被拥有能够阅读SMS权限的程序给截获阅读。
动态加载代码
我们强烈的不推荐apk程序不要从外部动态加载代码。如果这样做的话会增加代码注入和代码篡改的风险。
并且还会影响版本控制盒程序测试。最后,还会导致程序的行为不好验证,因此在某些情况下程序会被阻止掉。
如果你的程序做了动态加载代码,最重要的一点,要时刻记住动态加载的代码所在的apk要和加载程序要有相同的安全权限。用户决定
安装你的程序是基于统一的标示认证。
动态加载代码最大的安全风险是验证代码来源。如果程序模块直接包含到你的apk中,那么其他程序是不能去修改的。不管是native库还是通过DexClassLoader加载的
代码都是如此。我们已经遇到过很多程序去试图加载不安全位置的代码,比如像从网络上下载没有经过安全协议加密的代码,或者从想外部存储等
有全局写权限的位置加载代码。这些位置会允许一些人在网络传输过程中修改数据的内容,或者安装在设备上的程序去修改数据内容。
使用 WebView
因为WebView这个组件可以解析HTML和Javascript等网页内容,这样可能会遇到一些常见的安全问题,比如跨站脚本攻击(cross-site-scripting,javascript注入)。
android包含了大量的安全机制用来减少这种跨域的谴责问题,像通过限制WebView使用最少功能这样的措施。
如果你的程序中的WebView不直接使用Javascript的话,不要调用setJavaScriptEnabled()。默认情况下,WebView不会执行跨站脚本
javascript的执行。
使用addJavaScriptInterface()时要特别注意,因为这样可以允许javascript去调用android程序代码去执行程序。
只把addJavaScriptInterface()开放给信任的输入源,如果不是这样的话有时会使不信任的javascript去调用android的函数。
通常情况下,我们推荐只对本程序内的javascript开放addJavaScriptInterface()入口。
不要相信通过http下载的数据,要使用Https代替。如果你仅是访问单个网站,即使是信任并且可控的,HTTP可能会受到中间过程的攻击,
传输过程中数据被截取。在使用addJavaScriptInterface()的时候要提高警惕,一定不要相信通过http下载的没有经过验证的脚本。
注意:即使是用的https,addJavaScriptInterface()也会增加了程序被攻击的可能性,虽然包括服务器的进行了安全设置和android设备拥有所有的信任的CA。
如果你同过WebView访问敏感信息,你应该通过clearCache()方法去删除保存在本地的任何缓存文件。服务器端的小消息头可以使用
像no-cache这样的标志去告诉程序不要缓存特定的内容。
执行输入检查
一个常见的安全问题是没有对输入进行充足的安全检查就直接让其运行。android有一个平台级别的措施去减少暴露给程序没有进行输入检查的问题,
你应该尽可能的在可以使用的任何地方去使用这个特性。同时要注意,选择安全类型的语言可以减少输入验证的问题。我们强烈建议使用android SDK
去构建你的程序。
如果使用从文件,网络,IPC上获取的native code,也会增加潜在的安全问题。常见的问题是缓冲区溢出,用户退出,off-by-one错误等。android
提供了很多像ASLR和DEP的技术去减少开发时的那些错误,但是他们不能解决这些潜在的错误。那些技术可以用来预防指针控制和缓存区管理的问题。
动态的,基于字符串的语言,像javascript和sql语言,进行输入检查可以防止特殊字符和脚本注入问题。
如果经行数据查询的时候有使用sql数据库或者内容提供者的时候,sql注入会使一个问题。最后的反击措施是使用参数化的查询,就想在内容提供者
部分介绍的一样。限制成只读和只写权限也可以减少潜在的sql注入风险。
如果你使用WebView一定要考虑夸张脚本攻击(XSS)的可能性。如果你的程序,WebView不直接使用javascript,
不要调用setJavaScriptEnabled(),那么XSS就不会发生。如果程序必须使用javascript,那么要考虑其他的安全措施
去防止XSS。
如果你不能使用上述的安全特性,我们强烈建议使用良好结构的数据格式,并且充分验证数据格式,同时要做好特殊字符黑名单,和字符替换库,这样
会使一个比较有效的策略。这些技术是使用的过程中非常容易出错,所以一定要尽量的避免出现错误。
处理用户数据
通常情况下,最有效的方式是尽量的少使用API去访问敏感数据和个人信息。如果真的访问的了这些数据的话,那么就不乣存储和传输这些数据。
最后,考虑使用一种方式是,使用哈希算法或者不可逆的算法方式去处理数据。比如,如果你的程序使用hash算法用对email的哈希值作为key,一定要
避免存储和传输email地址。这样可以防止因为疏忽而把数据暴露在外面,这样也可以减少暴露在外面的程序被攻击的机会。
如果你的程序要访问想用户名,密码之类的个人信息是,一定要记住使用隐私协议去解释你使用和存储的信息。因此,
在后面的的安全最佳实践中,要尽量的减少访问用户数据
你也需要考虑你的程序是否会在不经意间把个人信息暴露给其他的程序部分,比如像在你的程序中使用的第三方广告组件或者第三方服务等。
如果你不知道为什么这些组件或者服务需要个人信息干什么的话,就不要提供给程序。通常情况下,减少访问个人信息,可以减少程序潜在的
安全问题。
如果必须要访问敏感数据,要评估一下这些数据是否要传输到服务器,或者是否要在客户端执行。允许在客户端的代码用到敏感数据的
时候要尽量禁止用户数据的传输。
同时要确保,不要把用户数据通过宽松的IPC,全局可写的文件,或者网络不经意间暴露给其他程序。这里有一个关于权限重复授权的特殊案例将在请求权限
的部分讨论。
创建一个数比较大,唯一的全局唯一标识保存它是非常必要的。不要使用像电话号码或者IMEI这样的电话的标识,因为这些关系到个人信息。
这个话题的更多信息在android开发者博客上。http://android-developers.blogspot.com/2011/03/identifying-app-installations.html
程序开发者需要特别注意写在设备上的日志信息,在android上,日志是共享资源,要防止程序拥有READ_LOGS权限。
即使手机的日志是临时的或者重启就删除的,记录用户信息的日志有可能会泄露用户的信息给其他程序。
Handling Credentials
处理凭证
通常情况下,我建议最小频率的要用户的凭证信息,可以减少钓鱼攻击的成功率。而是使用授权令牌的方式,并且要刷新令牌。
不管任何情况下都不要把用户名密码存储在设备上。
只有在第一次认证的时候需要使用用户提供的用户名和密码,并且使用短活动周期,指定服务的授权令牌。
如果一个服务需要访问多个程序的话,应该需要使用AccountManager.如果可能的话,使用AcctountMangaer类去调用云服务,不要把
密码存储在设备上。http://developer.android.com/reference/android/accounts/AccountManager.html
After using AccountManager to retrieve an Account, check the CREATOR before passing in any credentials,
so that you do not inadvertently pass credentials to the wrong application.
使用AccountManager取回账号后,传递凭证之前都要检查一下创建者,不要把凭证传给错误的程序。
如果凭证信息之允许自己程序使用,当程序访问AccounManager的时候要使用checkSignature()这个方法去雁阵程序的签名。
如果只有一个程序访问凭证信息,可以使用KeyStore去存储凭证信息。
Using Cryptography
使用加密技术
增加数据的隔离,可以支持全文件系统的加密,提供安全通道访问。android提供了多种数组加密算法保护数据。
通常情况下,使用已经存在的高级别的框架可以支持大多数使用情况。如果你需要安全的从一个位置取回文件,简单的Https URI已经足够满足了。
如果你需要一个安全通道,考虑使用HttpsURLConnection or SSLSocket,而不是去写自己的协议。
If you do find yourself needing to implement your own protocol, we strongly recommend that you not implement your
own cryptographic algorithms. Use existing cryptographic algorithms such as those in the implementation of AES or
RSA provided in the Cipher class.
如果你需要实现自己的协议,强烈建议不要实现自己的加密算法。通过Cipher类使用已经存在的加密算法,比如AES或者RSA。
使用安全的随机数生成器(SecureRandom)去初始化任何加密的key(KeyGenerator).如果使用的key不是用安全生成的随机数的话,会减少加密强度,
并且会允许离线攻击。
如果你想把key存起来重复使用的话,可以使用想KeyStore之类的机制,通过加密密钥进行长期的存取。
结论
android为开发者提哦那个了设计程序安全多种措施。这些最佳实践确保你的程序能从安全获取一些受益。
You can receive more information on these topics and discuss security best practices with
other developers in the Android Security Discuss Google Group
你可以在Android Security Discuss Google Group中看到更多关于安全的话题或者讨论。
声明:eoe文章著作权属于作者,受法律保护,转载时请务必以超链接形式附带如下信息
原文作者: com360