以前发布过一份Web安全开发规范手册V1.0,看到收藏文章的读者挺多,发现整理这些文档还挺有意义。html
最近周末抽了些时间把以前收集关于安卓安全开发的资料也整理了一下,整理出一份安卓安全开发手册,大部份内容都是在一些博客看到各位师傅的分享。java
PermissionGroup
能够对permission进行一个逻辑上的分组。若是PermissionGroup
的属性为空,会致使权限定义无效,且其余app没法使用该权限。android
设置PermissionGroup
属性值或者不使用PermissionGroup
。web
App若是使用一些系统限制权限,诸如android.permission.WRITE_SECURE_SETTINGS和android.permission.INSTALL_PACKAGES,则该app应该是设备自带的系统或google自带的app,而且应该放置在/system/app目录下。不然就是一个恶意app。算法
App使用下述权限,则该app有较高权限,要谨慎使用。sql
android.permission.MOUNT\_FORMAT\_FILESYSTEMS, android.permission.MOUNT\_UNMOUNT\_FILESYSTEMS, android.permission.RESTART\_PACKAGES。
根据业务需求,如非必要,移除该权限。数据库
因为对app的自定义permission的protectionLevel属性设置不当,会致使组件(如:content provider
)数据泄露危险。最好的权限设置应为signature或signatureOrSystem,进而避免被第三方应用利用。segmentfault
注意使用signature或signatureOrSystem防止其余app注册或接受该app的消息,提升安全性。api
经过sharedUserId,可让拥有同一个User Id的多个apk运行在同一个进程中,互相访问任意资源。将sharedUserId设置为android.uid.system,能够把app放到系统进程中,app将得到极大的权限。若是app同时有master key漏洞,容易致使被root。跨域
合理设置软件权限。
当这个标志被设置成true或不设置该标志位时,应用程序数据能够备份和恢复,adb调试备份容许恶意攻击者复制应用程序数据。
设置AndroidManifest.xml的android:allowBackup标志为false。
API >= 8
在AndroidManifest.xml中定义Debuggable项,若是该项被打开,app存在被恶意程序调试的风险,可能致使泄露敏感信息等问题。
显示的设置AndroidManifest.xml的debuggable标志为false。
Activity、activity-alias、service、receiver组件对外暴露会致使数据泄露和恶意的dos攻击。
provider
信息泄露风险provider
组件导出可能会带来信息泄露隐患。api level在17如下的全部应用的android:exported属性默认值为true,17及以上默认值为false。
provider
组件设置权限,同时将权限的protectionLevel设置为signature或signatureOrSystem。provider
没法在2(API-8)申明为私有。故建议将min sdk设为8以上。api level在17如下的全部应用的android:exported属性默认值为true,17及以上默认值为false。
该漏洞因为Content provider
组件暴露,没有对Content provider
组件访问权限进行限制且对Uri路径没有进行过滤,攻击者经过Content provider
实现的OpenFile接口进行攻击,如经过../的方式访问任意的目录文件,形成隐私泄露。
provider
设置为不导出provider
没法在Android 2.2(即API Level 8)系统上设为不导出,所以若是应用的Content provider
没必要要导出,阿里聚安全建议声明最低SDK版本为8以上版本;provider
没必要要导出,阿里聚安全建议显示设置注册的Content provider
组件的android:exported属性为false;provider
组件没有必要实现penFile()接口,阿里聚安全建议移除该Content provider
的没必要要的penFile()接口。provider
的数据共享provider
共享数据的访问权限,如设置protectionLevel=signature或signatureOrSystem;provider
确保不存储敏感数据建立隐式Intent 时,Android 系统经过将Intent 的内容与在设备上其余应用的清单文件中声明的Intent 过滤器进行比较,从而找到要启动的相应组件。若是Intent 与Intent 过滤器匹配,则系统将启动该组件,并将其传递给对象。若是多个Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。
为了确保应用的安全性,启动Service 时,请始终使用显式Intent,且不要为服务声明Intent 过滤器。使用隐式Intent 启动服务存在安全隐患,由于您没法肯定哪些服务将响应Intent,且用户没法看到哪些服务已启动。从Android 5.0(API 级别21)开始,若是使用隐式Intent 调用bindService(),系统会抛出异常。
为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,由于您没法肯定哪些服务将响应Intent,且用户没法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,若是使用隐式 Intent 调用 bindService()),系统会抛出异常。
所有。从Android 5.0(API 级别21)开始,若是使用隐式Intent 调用bindService(),系统会抛出异常。
Intent Scheme URI是一种特殊的URL格式,用来经过Web页面启动已安装应用的Activity组件,大多数主流浏览器都支持此功能。
Android Browser的攻击手段——Intent Scheme URLs攻击。这种攻击方式利用了浏览器保护措施的不足,经过浏览器做为桥梁间接实现Intend-Based攻击。相比于普通Intend-Based攻击,这种方式极具隐蔽性,
若是在app中,没有检查获取到的load_url的值,攻击者能够构造钓鱼网站,诱导用户点击加载,就能够盗取用户信息。因此,对Intent URI的处理不当时,就会致使基于Intent的攻击。
若是浏览器支持Intent Scheme URI语法,通常会分三个步骤进行处理:
Intent.parseUri
函数,经过扫描出全部调用了Intent.parseUri
方法的路径,并检测是否使用以下的策略。
比较安全的使用Intent Scheme URI方法是:
若是使用了Intent.parseUri
函数,获取的intent必须严格过滤,intent至少包含addCategory(android.intent.category.BROWSABLE),setComponent(null),setSelector(null)3个策略。
因此,在检的时候只要根据Intent.parseUri
函数返回的Intent对象有没有按照如下方式实现便可作出判断:
// convert intent scheme URL to intent object Intent intent = Intent.parseUri(uri); // forbid launching activities without BROWSABLE category intent.addCategory(android.intent.category.BROWSABLE); // forbid explicit call intent.setComponent(null); // forbid intent with selector intent intent.setSelector(null); // start the activity by the intent context.startActivityIfNeeded(intent, -1)
若是使用了Intent.parseUri
函数,获取的intent必须严格过滤,intent至少包含addCategory(android.intent.category.BROWSABLE),setComponent(null),setSelector(null)3个策略。除了以上作法,最佳处理不要信任任何来自网页端的任何intent,为了安全起见,使用网页传过来的intent时,要进行过滤和检查
Android系统提供了Activity、Service和Broadcast Receiver等组件,并提供了Intent机制来协助应用间的交互与通信,Intent负责对应用中一次操做的动做、动做涉及数据、附加数据进行描述,Android系统则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。Android应用本地拒绝服务漏洞源于程序没有对Intent.GetXXXExtra()获取的异常或者畸形数据处理时没有进行异常捕获,从而致使攻击者可经过向受害者应用发送此类空数据、异常或者畸形数据来达到使该应用Crash的目的,简单的说就是攻击者经过Intent发送空数据、异常或畸形数据给受害者应用,致使其崩溃。
对导出的组件传递一个不存在的序列化对象,若没有try...catch捕获异常就会崩溃
ComponentName cn = new ComponentName(com.test, com.test.TargetActivity) Intent i = new Intent() i.setComponentName(cn) i.putExtra(key, new CustomSeriable()) startActivity(i) **public class DataSchema implements Serializable {** public DataSchema() { super(); } }
源于程序没有对getAction()等获取到的数据进行空指针判断,从而致使了空指针异常致使应用崩溃
风险代码:
Intent i = new Intent(); if (i.getAction().equals(TestForNullPointerException)) { Log.d(TAG, Test for Android Refuse Service Bug); }
源于程序没有对getSerializableExtra()等获取到的数据进行类型判断而进行强制类型转换,从而致使类型转换异常致使拒绝服务漏洞
风险代码:
Intent i = getIntent(); String test = (String) i.getSerializableExtra(serializable\_key); **IndexOutOfBoundsException 异常致使拒绝服务漏洞**
源于程序没有对getIntegerArrayListExtra()等获取到的数据数组元素大小判断,致使数组访问越界而形成拒绝服务漏洞
风险代码:
Intent intent = getIntent(); ArrayList<Integer> intArray = intent.getIntegerArrayListExtra(user\_id); if (intArray != null) { for (int i = 0; i < 10; i++) { intArray.get(i); } }
Intent i = getIntent(); getSerializableExtra(key);
Try{ .... xxx.getXXXExtra() .... }Catch Exception{ ** ** **// 为空便可** }
在AndroidManifest文件中定义了android.intent.category.BROWSABLE属性的组件,能够经过浏览器唤起,这会致使远程命令执行漏洞攻击
一些app在正式发布前,为了方便调试app,都会在app里集成一些调试或测试界面。这些测试界面可能包含敏感的信息。
在正式发布前移除全部的测试组件
经过Intent接收的Extra参数来构造反射对象会致使从不受信任的源加载类。攻击者能够经过巧妙地构造达到加载其它类的目的
两个关键函数,分别是:getIntent()和Class.forName(....)
public class SecondActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity\_second); Intent intent = getIntent(); String className = intent.getStringExtra(className); String methodName = intent.getStringExtra(methodName); try { Class<?> clz = null; clz = Class.forName(className); Date object = (Date) clz.newInstance(); Method method = clz.getMethod(methodName); Toast.makeText(getApplicationContext(), method.invoke(object, null) + ======, Toast.LENGTH\_LONG).show(); } catch (Exception e) { e.printStackTrace(); } } }
逆向后对应的smali代码以下:
invoke-virtual {p0}, Lcom/bug/intent/reflection/SecondActivity;->getIntent()Landroid/content/Intent; invoke-static {v0}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;
和WebView远程代码执行相关的漏洞主要有CVE-2012-6336,CVE-2014-1939,CVE-2014-7224, 这些漏洞中最核心的漏洞是CVE-2012-6336,另外两个CVE只是发现了几个默认存在的接口。
Android API < 17以前版本存在远程代码执行安全漏洞,该漏洞源于程序没有正确限制使用addJavaScriptInterface(CVE-2012-6636)方法,攻击者能够经过Java反射利用该漏洞执行任意Java对象的方法,致使远程代码执行安全漏洞除。
范围:
Android API 16.0及以前的版本中存在安全漏洞,该漏洞源于程序没有正确限制使用WebView.addJavascriptInterface方法。远程攻击者可经过使用Java Reflection API利用该漏洞执行任意Java对象的方法
Google Android <= 4.1.2 (API level 16) 受到此漏洞的影响。
java/android/webkit/BrowserFrame.java
使用addJavascriptInterface API并建立了SearchBoxImpl类的对象。攻击者可经过访问searchBoxJavaBridge_接口利用该漏洞执行任意Java代码。
Google Android <= 4.3.1 受到此漏洞的影响
香港理工大学的研究人员发现当系统辅助功能中的任意一项服务被开启后,全部由系统提供的WebView都会被加入两个JS objects,分别为是accessibility和accessibilityTraversal。恶意攻击者就可使用accessibility和accessibilityTraversal这两个Java Bridge来执行远程攻击代码.
Google Android < 4.4 受到此漏洞的影响。
容许WebView执行JavaScript(setJavaScriptEnabled),有可能致使XSS攻击。
应尽可能避免使用。若是必定要使用:
Android api <17
应用程序一旦使用WebView并支持File域,就会受到该漏洞的攻击。该漏洞源于:JavaScript的延时执行可以绕过file协议的同源检查,并可以访问受害应用的全部私有文件,即经过WebView对Javascript的延时执行和将当前Html文件删除掉并软链接指向其余文件就能够读取到被符号连接所指的文件,而后经过JavaScript再次读取HTML文件,便可获取到被符号连接所指的文件。
大多数使用WebView的应用都会受到该漏洞的影响,恶意应用经过该漏洞,可在无特殊权限下盗取应用的任意私有文件,尤为是浏览器,可经过利用该漏洞,获取到浏览器所保存的密码、Cookie、收藏夹以及历史记录等敏感信息,从而形成敏感信息泄露。
若是应用的组件没必要要导出,建议显式设置所注册组件的android:exported属性为false;
若是应用的须要导出包含WebView的组件,建议禁止使用File域协议:
myWebView.getSettings. setAllowFileAccess(false);
若是应用的WebView须要使用File域协议,建议禁止File域协议调用JavaScript:
myWebView.getSettings. setJavaScriptEnabled(false);
webview的保存密码功能默认设置为true。Webview会明文保存网站上的密码到本地私有文件databases/webview.db中。对于能够被root的系统环境或者配合其余漏洞(如webview的同源绕过漏洞),攻击者能够获取到用户密码。
显示设置webView.getSetting().setSavePassword(false)
自定义HostnameVerifier类,却不实现verify方法验证域名,致使中间人攻击漏洞。
自定义HostnameVerifier类并实现verify方法验证域名。
App在实现X509TrustManager时,默认覆盖google默认的证书检查机制方法:checkClientTrusted、checkServerTrusted和getAcceptedIssuers,会致使中间人攻击漏洞。
若是本身建立X509Certificate
,则在覆盖checkClientTrusted、checkServerTrusted和getAcceptedIssuers后要进行校验。
App调用setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER),信任全部主机名,会致使中间人攻击。
查找全部设置了ALLOW_ALL_HOSTNAME_VERIFIER字段属性的方法路径;对信任的主机严格认证
Android WebView组件加载网页发生证书认证错误时,会调用WebViewClient类的onReceivedSslError方法,若是该方法实现调用了handler.proceed()来忽略该证书错误,则会受到中间人攻击的威胁,可能致使隐私泄露。
自定义实现的WebViewClient类在onReceivedSslError是否调用proceed()方法。
当发生证书认证错误时,采用默认的处理方法handler.cancel(),中止加载问题页面当发生证书认证错误时,采用默认的处理方法handler.cancel(),中止加载问题页面
android webview组件包含3个隐藏的系统接口:searchBoxJavaBridge_,accessibilityTraversal以及accessibility,恶意程序能够利用它们实现远程代码执行。
风险代码:
const-string v3, searchBoxJavaBridge\_ invoke-virtual {v1, v3}, Landroid/webkit/WebView;->removeJavascriptInterface(Ljava/lang/String;)V const-string v3, accessibility invoke-virtual {v1, v3}, Landroid/webkit/WebView;->removeJavascriptInterface(Ljava/lang/String;)V const-string v3, accessibilityTraversal invoke-virtual {v1, v3}, Landroid/webkit/WebView;->removeJavascriptInterface(Ljava/lang/String;)V
使用了WebView,那么使用WebView.removeJavascriptInterface(String name) API,显示的移除searchBoxJavaBridge_、accessibility、accessibilityTraversal这三个接口
4.0~4.4(不包含)
SQLite作为android平台的数据库,对于数据库查询,若是开发者采用字符串连接方式构造sql语句,就会产生sql注入。
provider
不须要导出,请将export属性设置为falseAPP在使用openOrCreateDatabase建立数据库时,将数据库设置了全局的可读权限,攻击者恶意读取数据库内容,获取敏感信息。在设置数据库属性时若是设置全局可写,攻击者可能会篡改、伪造内容,能够能会进行诈骗等行为,形成用户财产损失。
网络通讯安全
SSLCertificateSocketFactory#getInsecure方法没法执行SSL验证检查,使得网络通讯遭受中间人攻击。
移除SSLCertificateSocketFactory#getInsecure方法。
HttpHost target = new HttpHost(uri.getHost(), uri.getPort(), HttpHost.DEFAULT_SCHEME_NAME);
HttpHost.DEFAULT_SCHEME_NAME默认是http,不安全。
改为使用https
在Android 2.2版本以前,HttpURLConnection一直存在着一些使人厌烦的bug。好比说对一个可读的InputStream调用close()方法时,就有可能会致使链接池失效了。
判断Android版本,并设置http.keepAlive为false。
private void disableConnectionReuseIfNecessary() { // Work around pre-Froyo bugs in HTTP connection reuse. if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION\_CODES.FROYO) { System.setProperty(http.keepAlive, false); } }
2.2版本以前
安全性要求高的应用程序必须避免使用不安全的或者强度弱的加密算法,现代计算机的计算能力使得攻击者经过暴力破解能够攻破强度弱的算法。例如,数据加密标准算法DES(密钥默认是56位长度、算法半公开、迭代次数少)是极度不安全的,使用相似EFF(Electronic Frontier Foundaton)Deep Crack的计算机在一天内能够暴力破解由DES加密的消息。
使用DES弱加密算法,样例
风险代码:
SecretKeySpec key = new SecretKeySpec(rawKeyData, DES); Cipher cipher = Cipher.getInstance(DES/ECB/PKCS5Padding); cipher.init(Cipher.DECRYPT\_MODE, key);
建议使用安全性更高的AES加密算法
在使用RSA加密时,密钥长度小于512bit,小于512bit的密钥很容易被破解,计算出密钥。
风险代码:
public static KeyPair getRSAKey() throws NoSuchAlgorithmException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA); keyGen.initialize(512); KeyPair key = keyGen.generateKeyPair(); return key; }
使用RSA加密时,建议密钥长度大于1024bit
AES的ECB加密模式容易遭到字典攻击,安全性不够。
风险代码:
SecretKeySpec key = new SecretKeySpec(keyBytes, AES); **Cipher cipher = Cipher.getInstance(AES/ECB/PKCS7Padding, BC);** cipher.init(Cipher.ENCRYPT\_MODE, key);
避免使用ECB模式,建议使用CBC。
使用IVParameterSpec函数,若是使用了固定的初始化向量,那么密码文本可预测性高得多,容易受到字典攻击等。
风险代码:
byte[] iv = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; IvParameterSpec ips = new IvParameterSpec(iv)
IVParameterSpec初始化时,不使用常量vector。
使用RSA公钥时一般会绑定一个padding,缘由是为了防止一些依赖于no padding时对RSA算法的攻击。
风险代码:
Cipher rsa = null; try { rsa = javax.crypto.Cipher.getInstance(RSA/NONE/NoPadding); }catch (java.security.NoSuchAlgorithmException e) {} catch (javax.crypto.NoSuchPaddingException e) {} SecretKeySpec key = new SecretKeySpec(rawKeyData, RSA); Cipher cipher = Cipher.getInstance(RSA/NONE/NoPadding); cipher.init(Cipher.DECRYPT\_MODE, key);
建议使用Padding模式。
keytool是一个Java数据证书的管理工具,Keytool将密钥(key,私钥和公钥配对)和证书(certificates)存在一个称为keystore的文件中,并经过密码保护keystore中的密钥。若是密码设置过于简单,例如:12345六、android等,则会致使keystore文件的私钥泄露,从而致使一系列的信息泄露风险。
提升keystore保护密码的强度
因为Android剪贴板的内容向任何权限的app开放,很容易就被嗅探泄密。同一部手机中安装的其余app,甚至是一些权限不高的app,均可以经过剪贴板功能获取剪贴板中的敏感信息。
风险代码:
clipBtn = (Button) findViewById(R.id.btn\_clip); clipBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD\_SERVICE); ClipData clip1 = ClipData.newPlainText(label,password=123456); clipboard.setPrimaryClip(clip1); } });
避免使用剪贴板敏文存储敏感信息或进行加密
在代码中禁止硬编码私钥等敏感信息,攻击者反编译代码,便可拿到。
APP建立Intent传递数据到其余Activity,若是建立的Activity不是在同一个Task中打开,就极可能被其余的Activity劫持读取到Intent内容,跨Task的Activity经过Intent传递敏感信息是不安全的。
尽可能避免使用包含FLAG_ACTIVITY_NEW_TASK标志的Intent来传递敏感信息。
使用pendingIntent时候,若是使用了一个空Intent,会致使恶意用户劫持Intent的内容。禁止使用空intent去构造pendingIntent。
禁止使用空intent去构造pendingIntent。
程序在加载外部dex、so文件是否判断文件来源、是否存放可信区域;程序删除文件是否可篡改文件路劲
在使用getDir、getSharedPreferences(SharedPreference)或openFileOutput时,若是设置了全局的可读权限,攻击者恶意读取文件内容,获取敏感信息。在设置文件属性时若是设置全局可写,攻击者可能会篡改、伪造内容,可能会进行诈骗等行为,形成用户财产损失。其中getSharedPreferences若是设置全局写权限,则当攻击app跟被攻击app具备相同的Android:sharedUserId属性时和签名时,攻击app则能够访问到内部存储文件进行写入操做。
若是两个appAndroid:sharedUserId属性相同,切使用的签名也相同,则这两个app能够互相访问内部存储文件数据
在APP的开发过程当中,为了方便调试,一般会使用log函数输出一些关键流程的信息,这些信息中一般会包含敏感内容,如执行流程、明文的用户名密码等,这会让攻击者更加容易的了解APP内部结构方便破解和攻击,甚至直接获取到有价值的敏感信息。
禁止打印敏感信息
在程序须要执行系统命令等函数,须要谨慎使用,严格控制命令来源,防止黑客替换命令攻击。
风险代码:
Example Java code: Runtime rr = Runtime.getRuntime(); Process p = rr.exec(ls -al); Example Bytecode code: const-string v2, ls -al invoke-virtual {v1, v2}, Ljava/lang/Runtime;->exec(Ljava/lang/String;)Ljava/lang/Process;
严格按照要求使用
在api level 小于19的app,全部继承了PreferenceActivity
类的activity并将该类置为exported的应用都受到Fragment注入漏洞的威胁。
Google在Android 4.4 KitKat 里面修正了该问题,引入了PreferenceActivity
.isValidFragment函数,要求用户重写该函数验证Fragment来源正确性。
PreferenceActivity
类下的isValidFragment方法以免异常抛出;PreferenceActivity
内没有引用任何fragment,建议覆盖isValidFragment并返回false小于Android 4.4(API level 19)
Android SQLite数据库journal文件可被全部应用程序读取,全部目录对应程序数据库目录拥有执行权限,意味着应用程序数据目录全局访问,/data/data/<app package>/databases目录以[rwxrwx--x]权限建立,可致使全局读写。数据库目录下建立的journal文件以[-rw-r--r--]权限建立,可被全部app读取。
升级到Android4.0.1以上版本或者使用SQLCipher或其余库加密数据库和日志信息。
Android2.3.7版本存在该漏洞,其余版本可能也受到影响,4.0.1不受影响
SecureRandom的使用不当会致使生成的随机数可被预测,该漏洞存在于Android系统随机生成数字串安全密钥的环节中。该漏洞的生成缘由是对SecureRandom类的不正确使用方式致使生成的随机数不随机。
风险代码:
SecureRandom secureRandom = new SecureRandom(); byte[] b = new byte[] { (byte) 1 }; secureRandom.setSeed(b); // Prior to Android 4.2, the next line would always return the same number! Log.v(wgc,-------------------------------); Log.v(wgc,Test1: + secureRandom.nextInt()); SecureRandom secureRandom2 = new SecureRandom(new byte[] { (byte) 1 }); Log.v(wgc,Test2: + secureRandom2.nextInt()); SecureRandom secureRandom3 = new SecureRandom(); secureRandom3.setSeed(10L); Log.v(wgc,Test3: + secureRandom3.nextInt()) SecureRandom secureRandom4 = new SecureRandom(); secureRandom4.nextBytes(b); secureRandom4.setSeed(10L); Log.v(wgc,Test4: + secureRandom4.nextInt()); SecureRandom secureRandom5 = new SecureRandom(); Log.v(wgc,Test5: + secureRandom4.nextInt());
Android 4.2以前,Android API 17之后SecureRandom的默认实现方式从Cipher.RSA换到了OpenSSL。SecureRandom新的实现方式不能将本身的seed替换掉系统的seed。
发布的软件,应对app进行加固,防止攻击者获取app代码、业务逻辑、API接口等,对业务和公司声誉形成必定影响,防止app被破解二次打包,致使损失。
内容编辑:汤青松
更新时间:2019-09-03