前言
在公司每当上架APP或SDK的时候,很多的安全点是在提交前都要避免掉的,不然提交安全测试也是会被打回,改好再提交。有安全部门审核给业务部门建立一道安全护城墙。假如没有呢?所以以下这些点值得Android工程师明确。
Activity安全
我们 APP 内的 AndroidManifest.xml 中声明的 Activity 有一个属性 android:exported="true/false"
,如果设为ture的话,就可以被外部调用,假如此时 Activity 接收 Intent 传输数据,就可能恶意伪造数据攻击,导致我们的App出现异常甚至崩溃。
注意:没有intent filter默认值为false,有intent filter默认值为true。
我们可以利用adb shell命令查看当前Activity是否为android:exported="true"
可导出,adb shell am start -n [packname]/[**.ConnectActivity]
。若android:exported="false"
,就会爆出下面的异常(Security exception: Permission Denial)。
Starting: Intent { cmp= packname/**.ConnectActivity }
Security exception: Permission Denial: starting Intent { flg=0x10000000 cmp= packname/**.ConnectActivity } from null (pid=7861, uid=2000) not exported from uid 10643
在没有非常必要的时候,非常有必要手动设置为android:exported=”false”。若是必须设置为可导出,因为我们此时无法知道调用方的身份,所以一定对数据做正确校验或try-catch当前逻辑,避免出现麻烦。
BroadcastReceiver 安全
Broadcast Receiver
跟 Activity 都属于四大组件,它也是有导出风险的,当android:exported="true"
,就可以导致外部的广播,若是恶意攻击,便会导致我们的App出现异常甚至崩溃。
我们可以利用adb shell命令查看当前 Broadcast Receiver
是否为 android:exported="true"
可导出,adb shell am broadcast send -n [packname]/[**.ConnectReciever]
。
当然,拒绝外部的广播,设置属性android:exported="false"
。
Content Provider漏洞
Content Provider组件,默认的android:exported="true"
,APP定义可以访问本地文件的ContentProvider并实现了ParcelFileDescriptor openFile(Uri uri, String mode)
接口方法,它访问内部存储app_webview目录下的数据。如果没有对目标文件地址进行有效判断,通过 ../
实现目录跨越对任意私有数据j进行访问。当然这样也可以访问任意外部存储数据。所以内部调用的组件都要设置 android:exported="false"
,并且 ContentProvider 及时做好调用方的身份确认工作,比如获取当前调用者的 uid 或反查调用者的包名与签名进行验证。
Logcat 信息安全
我们做java中控制台打印信息System.out.println("打印日志关键信息");
在Android中用Log类打印日志信息,并在上线时关闭。但难免有些同志会用一下System.out.println
导致关键信息在 adb shell logcat
可以被查看到,导致关键信息泄露,被坏人利用。删除一切使用System.out.println("打印日志关键信息");
的代码。
Webview 组件安全
WebView组件中的接口函数 addJavascriptInterface
存在远程代码执行漏洞,远程攻击者利用此漏洞能实现本地 Java 和 javascript 的交互,可对 Android 移动终端进行网页挂木马来控制受影响设备。
访问构造的恶意 html 文件,导致执行 adb 系统命令。针对 4.2 以下版本移除JavascriptInterface。在 Android 3.0 以下也需要删除 searchBoxJavaBridge JavaScript 接口。searchBoxJavaBridge_ 跟 Google 搜索框相关。
推荐文章
另外,Webview 接口调用系统功能时,我们还可以仅限于该应用的功能范围之内调用。这样的加入白名单机制也可以有效的避免使用第三方程序恶意使用发送短信、拨打电话、删除文件等。
accessibility和 accessibilityTraversal接口会引起远程代码执行,删除accessibility和 accessibilityTraversal接口。
APP 更新资源安全
当我们App升级更新apk,热更新部分资源时,对于下载的文件,必须进行校验,包括而不仅限于是否是服务器下载的文件、安装时校验该文件完整性。一般使用APK公钥签名对比、CRC校验完整性等,md5不推荐。我们在利用开源的热更新框架时候,必须要考虑到这样,不然就可能会导致致命性的APP伤害,这样的损失将会巨大。
访问https通信不检查证书有效性安全
在发起HTTPS请求时,如果开发者这样设置了较验证逻辑,setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
即忽略了服务端的证书验证,接受任何证书,假如这个时候有中间代理人出现,就是我们常说的中间人攻击的操作,在建立起链接后传输的信息,就会被中间人查看修改,可想而知,非常危险。中间人可以通过设置DNS服务器使客户端与指定的服务器进行通信。另外,也可以使用用的 Fiddler 工具模拟这样的中间人。
这样的问题,可以实现证书锁定的方法来解决:
一种是实现X509TrustManager接口:
public class HTTPSTrustManager implements X509TrustManager {
private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {};
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws java.security.cert.CertificateException {
// To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws java.security.cert.CertificateException {
// To change body of implemented methods use File | Settings | File Templates.
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
public static void allowAllSSL() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
SSLContext context = null;
if (trustManagers == null) {
trustManagers = new TrustManager[] { new HTTPSTrustManager() };
}
try {
context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
}
}
在所有https开始进行请求之前,执行一次即可:
HTTPSTrustManager.allowAllSSL();//信任所有证书
就是正常发起https访问:
httpPostData(context, url, content);
一种是使用KeyStore:
获取到证书放到assert目录下,例如这里使用的证书的文件名为“root.crt”,通过如下函数来读取,并返回SSLContext:
public static SSLContext getSSLContext(Context inputContext){
SSLContext context = null;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream in = inputContext.getAssets().open("root.crt");
Certificate ca = cf.generateCertificate(in);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(null, null);
keystore.setCertificateEntry("ca", ca);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keystore);
// Create an SSLContext that uses our TrustManager
context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
} catch (Exception e){
e.printStackTrace();
}
return context;
}
在使用 HttpsURLConnection 的过程中,也就是httpsPostData()函数中使用指定证书的 SSLContext 即可:
conn.setSSLSocketFactory(getSSLContext(context).getSocketFactory());
再正常发起网络请求,httpPostData(context, url, content);
信息传递安全
我们在进行网络信息传递过程中要对数据进行加密,而不是让他们裸奔。不加密的数据,就像脱光了站在大庭广众之下,暴露无遗。这样的解决方案,由开发者视情况而定。比如使用HTTPS,在HTTPS TLS1.2之后已支持ECC加密方式,或制定私有协议(如RSA+AES方案)。保证信息在网络中传输确保不被泄露用户隐私信息。
设备存储信息安全
在存储信息时,比如 Sqlite、 xml 、txt 、dat,当然不仅限这几种。都要实施有效的加密手段,对关键的数据存储更应该注意。当然,比如用户密码、加密KEY都是不允许存在本地的。重要信息如果存的是hash值,这也要引起足够的注意,简单Hash可以利用彩虹库有很大概率能碰撞出来。为了极大降低本碰撞出的几率,使用时用一些组合的方式得到hash值,比如password+IMEI。
SharedPreference安全
当使用SharedPreference来保存数据时,对于敏感信息的key尽量使用缩写或者其他不易知其意的命名,比如:KEY_USER_NAME 应该改为KEY_U_N,或 直接一串md5值做key
键盘安全
若当前你负责的App模块是支付类或用户密码的,更加应该引起足够重视,特别是输入键盘。我们可能很多时候默认使用的是系统键盘或第三方键盘,就会出现记住密码等,甚至有些会被同步到输入法的云端。
以上的解决方法就是客户端自定义的软键盘并随机变化布局,同时在每次点击输入框时都进行随机初始化。
页面劫持安全
客户端进入登录或注册页面时,被别人弹出一个页面,实现的原理就是给劫持Activity加入一个标志位FLAG_ACTIVITY_NEW_TASK
,就能使它置于栈顶并呈现给用户。开发者要在关键页面的onPause()
中,及时检测顶部 Activity
应用是不是自身或者是系统应用,如果检测到不是 APP 本身,就弹出告警或者退出。
总结:
道路千万条,安全第一条;
搬砖不规范,同事两行泪。
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!