Android安全测试规范

伴随着Android app的越来越多的应用,关于Android app的安全漏洞也在不断地出现,在面对一个安卓应用时,如何对它进行安全测试,本文将对安全测试方面进行一些介绍。

安装包测试

安装包反编译测试

用例风险:源代码未做混淆使攻击者很轻易反编译出源代码导致代码泄漏风险。

执行步骤:使用反编译工具打开应用,如发现代码内未经过混淆,就说明存在应用可进行反编译,记录漏洞,停止测试。

预期结果:安装包中核心模块与敏感数据经过加密或者混淆

整改建议:建议使用Proguard等工具对源码进行进一步混淆,避免造成源码泄漏。

安装包签名测试

用例风险Android签名机制是一种有效的身份标识,为了保证应用不被恶意修改后重新发布,需要检查应用签名是否有保护机制。

执行步骤

  1. 解压缩安装包.apk文件后,删除META-INF/目录下的xx.RSAxxx.SF文件
  2. 使用自己的私钥对删除过后的apk文件进行重新签名,首先生成自己的私钥
1
`keytool -genkey -v -keystore [keystore路径] -alias [密钥别名] -keyalg RSA -keysize 2048 -validity [有效天数]`
  1. 然后对apk进行二次签名,签名命令格式如下
1
2
3
jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore [keystore名称] [apk文件] [密钥别名] 


  • -sigalg:签名算法名称
  • -digestalg:信息摘要算法
  • -keystore:签名文件

执行签名命令

1
2
3
jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore android.keystore kaoyan.apk  android.keystore


  1. 安装重新签名后的apk文件,查看应用是否具有保护机制阻止程序运行。

预期结果: 更换签名后,触发应用防御机制,应用无法启动或提示

整改建议: 内部代码实现apk二次打包鉴别机制,在程序运行时校验apk签名是否由官方私钥签名而来。

应用权限测试

用例风险:应用权限分配不合理,可能导致用户隐私数据泄露。

执行步骤

  1. 使用反编译工具反编译
  2. 打开源码后,检查应用AndoridManifest.xml文件,将应用权限和业务功能需要权限做对比,检查申请应用权限是否大于业务需要权限,有即存在安全隐患。

预期结果:应用申请合理的系统权限

整改建议:为应用分配合理的系统权限

AllowBackup开启

用例风险:当allowBackup标志值为true时,即可通过adb backupadb restore来备份和恢复应用程序数据,导致应用数据泄露。

执行步骤

  1. 打开AndroidManifest.xml文件;
  2. 检查应用AndoridManifest.xml文件中的配置是否为:android:allowBackup="true",即为allowBackup开启,记录漏洞,停止测试。

预期结果AllowBackup关闭

整改建议:在AndroidManifest.xml文件设置allowBackup属性值为False

备注allowBackup属性未配置时默认为true

debuggable开启

用例风险:当debuggable标志值为true时,即表示是App可调试的,存在安全泄露风险。

执行步骤

  1. 打开解析的AndroidManifest.xml文件;
  2. 检查应用AndoridManifest.xml文件中的配置是否为:android: debuggable="true",即为debuggable开启。

预期结果 debuggable关闭

整改建议AndroidManifest.xml文件设置debuggable属性值,其默认值为false

备注 Debuggable属性未配置时默认为false

弱加密算法审查

用例风险

使用弱加密算法会大大增加黑客攻击的概率,黑客可能会破解隐私数据、猜解密钥、中间人攻击等,造成隐私信息的泄漏,甚至造成财产损失。容易被破解的加密算法被称为弱加密算法,例如可以使用穷举法在有限的时间内破解DES算法。

执行步骤

  1. 使用反编译工具进行反编译
  2. 打开源码后,查找代码中的敏感数据和敏感函数加密代码,是否使用DES弱加密算法,弱加密代码样例:
1
2
3
SecretKeySpec key = new SecretKeySpec(rawKeyData, "DES"); //指定加密方式
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); //设置加密填充模式
cipher.init(Cipher.DECRYPT_MODE, key);
  1. RSA中加密不使用PaddingRSA加密常用的填充模式有三种:
  • RSA_PKCS1_PADDING
  • RSA_PKCS1_OAEP_PADDING
  • RSA_NO_PADDING

使用RSA公钥时通常会绑定一个Padding,原因是为了防止对RSA算法的攻击。风险代码样例如下: 扩展资料:RSA填充模式

1
2
3
4
5
6
7
8
9
10
11
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"); //选择了NoPadding加密模式
cipher.init(Cipher.DECRYPT_MODE, key);
  1. 使用了不安全的密钥长度,如下演示代码所示密码长度为512bits

    常用的密钥长度有1024bits2048bits等,使用RSA加密时,建议密钥长度大于1024bit

1
2
3
4
5
6
public static KeyPair getRSAKey() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(512); //密码长度设置为512bits
KeyPair key = keyGen.generateKeyPair();
return key;
}
  1. 使用了不安全的加密模式,如下示例代码中AES加密使用了ECB模式

ECB模式是最简单的模式,在其中明文和密文是一一对应的,相同的明文会被加密为相同的密文,这样可以通过观察密文得到明文中重复的组合,并以此为线索来破解密码。

1
2
3
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key);

预期结果:系统未使用包含风险的加密算法

整改建议

  • 使用对称加密算法时避免使用DES算法
  • 使用RSA算法加密时不使用NoPadding
  • 在选择加密模式时避免使用ECB模式
  • 使用RSA加密时,建议密钥长度大于1024bit

数据传输测试

敏感信息明文传输

用例风险:如果在传输过程中未对敏感数据进行加密传输,存在被恶意攻击者通过网络窃听等手段获取网络数据包中的敏感数据的威胁。

执行步骤

  1. 安装应用后,触发应用功能。
  2. 同时开启抓取数据包工具(如Charles),查看数据包中是否明文包含:用户名密码、IP地址、SIM序列号,或其他用户、系统等敏感信息。
  3. 如发现代码内包含以上信息,就说明存在应用中存在敏感数据,记录漏洞,停止测试。

预期结果:传输的数据包中未包含敏感信息

整改建议:确保包含重要敏感信息的数据均已加密的形式或者以https形式传输。

Java层ssl中间人攻击漏洞

用例风险

在密码学和计算机安全领域中,中间人攻击(Man-in-the-middle attack,缩写:MITM)是指攻击者与通讯的两端分别建立独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。

执行步骤

  1. 使用反编译工具打开应用,反编译出应用源码。
  2. 在源码中搜索类似写法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SSLSocketFactory_poc extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public SSLSocketFactory_poc(KeyManager[] keys,KeyStore truststore ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);

TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
if(true)
{}
try {
throw new CertificateException("illegal DN, reject the connection");
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

public void checkServerTrusted(X509Certificate[] chain, String authType) {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
};

sslContext.init(keys, new TrustManager[] { tm }, null);
}
...
}s(), "js2java");
  1. 检测代码中是否实现域名判断逻辑,未实现域名判断逻辑的代码如下:
1
2
3
4
5
6
HostnameVerifier hostnameVerifier = new HostnameVerifier(){
public boolean verify(String hostname, SSLSession session)
{
return true; #未实现任何判断语句,直接返回true
};
};
  1. 检测代码中是否允许所有域名,允许所有域名的代码如下:
1
2
3
SSLSocketFactory sf = new SSLSocketFactory_poc(null,trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); #允许所有域名
sf.setHostnameVerifier(new AllowAllHostnameVerifier());
  1. 如果在使用证书的时候,像示例代码中的写法类似,未进行域名相关判断、允许所有域名的证书,则风险存在。

预期结果:在使用证书的时候进行相关校验

整改建议:建议开发者对SSL证书进行强校验,包括证书是否合法、主机域名是否合法和证书的有效期。

数据存储测试

日志中包含敏感信息

安全风险

如果日志中包含用户信息、业务信息,攻击者可以通过抓取日志,搜集整理大量的有用信息。比如有时研发开发时为了调试方便会添加一些debug日志,如果在打正式发布包时不将这些log去掉那么很容易泄漏敏感信息。

执行步骤

  1. 安装应用后,对应用进行使用。
  2. 同时使用adb logcat | find "com.youku.phone"(包名)" 捕获输出的日志
  3. 还可以使用命令adb logcat | find "com.youku.phone" >C:\Users\Shuqing\Desktop\log.txt将日志保存到指定文件。
  4. 如果输出的日志中包含敏感信息,记录漏洞,停止测试。

预期结果:日志中不包含敏感信息

整改建议:为了防止信息泄漏,不要在日志中输出敏感数据

敏感数据明文存储

安全风险:敏感数据明文存储在手机上增加了信息泄露的风险

执行步骤

  1. 使用软件(如:好压)打开apk安装文件查找是否明文存储用户信息、业务数据、服务信息或其他敏感信息。
  2. 如果存在,记录漏洞,停止测试。

预期结果:文件中未存放用户或系统敏感信息

整改建议:如果一定要在客户端存放系统敏感数据,建议加密后再存储。

安装文件权限检测

安全风险:应用文件被分配了不合理的权限,导致其他应用可以读取和获取文件内容,增加了内容泄露的风险。

执行步骤

  1. 使用adb shell连接设备
  2. 进入应用目录cd /data/data/xxxx(包名)
  3. 执行命令ls -al,查看当前目录下所有文件权限。
  4. r代表只读,w代表写,x代表可执行,d表示是一个目录。
  5. 文件权限为:文件主-组用户-其他用户

预期结果

  • 目录权限为drwxrwx--x,允许多一个执行位x
  • 文件权限最后三位应为空(类似-rw-rw----),即除应用自己以外任何人无法读写;

整改建议

  • 避免使用MODE_WORLD_WRITEABLE(可写)和MODE_WORLD_READABLE(可读)模式创建进程间通信的文件,如果需要与其他进程应用进行数据共享,请考虑使用content provider
  • 避免使用MODE_PRIVATE模式创建内部存储文件,默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容。
  • 避免将密码等敏感数据信息明文存储在文件中;为文件使用合适的权限。

数据库敏感数据泄露

安全风险:敏感数据直接存储在sqlite数据库导致信息泄露的风险。

执行步骤

  1. 使用进入应用安装文件目录/data/data/[package name]/databases/,查找sqlite数据库文件并复制到PC端
  2. 使用DB.Browser.for.SQLite打开sqlite文件。
  3. 查看或检索文件中是否存在用户信息、业务数据、服务系统信息或其他敏感信息。如果存在,记录漏洞,停止测试。

预期结果:客户端数据库文件中不存在敏感数据。

整改建议:如果一定要在客户端存放系统或系统敏感数据,建议加密后再存储。

本地数据库注入/文件遍历检测

安全风险:获取或者篡改app中存储的敏感信息,如手机号、账号、密码等,在业务运行操作时无法保证数据安全。

执行步骤

  • 启动drozer连接测试设备
  • 获取开放的provider:run app.provider.info -a appname
  • 查找可用的URI:run scanner.provider.finduris -a appname
  • 执行SQL注入检测run scanner.provider.injection -a appname
  • 执行文件遍历检测 run scanner.provider.traversal -a appname

预期结果 无法获取到相关数据信息。

整改建议 使用参数化查询防御SQL注入,限制Provider组件的权限,取消不必要的Provider组件接口。

WebView组件安全测试

WebViewAndroid系统提供能显示Web页面的系统控件,例如混合类型的AppH5界面就是使用了WebView组件。

WebView远程代码执行漏洞

安全风险:Webview中接口addJavascriptInterface可通过webview对象向页面javascript导出java本地接口,可能导致任意命令执行。

执行步骤

  1. 使用反编译工具打开应用,反编译出应用源码。
  2. 在源码中搜索类似写法:
1
2
3
settings.setJavaScriptEnabled(true); //设置开启javascript
settings.setJavaScriptCanOpenWindowsAutomatically(true); //设置允许js弹出alert对话框
mWebView.addJavascriptInterface(new JSInvokeClass(), "js2java"); //注入javascript映射
  1. 如果源码存在上面代码,那么该处就可能存在Web组件远程代码执行的风险。
  2. 如果存在该风险,将会在该页面中显示出存在问题的接口。

预期结果:系统使用安全接口调用webview

整改建议

  • 建议禁用危险接口addJavascriptInterface导出Java类及方法,并加强访问的url的域控制;
  • 严格控制导出方法的权限,避免越权操作;

WebView密码明文保存漏洞

安全风险

  • 在使用WebView的过程中开启了 setSavePassword保存密码,当用户在WebView中输入的用户名和密码,则会被明文保存到应用。
  • 用户数据保存到目录的databases/webview.db中。
  • 如果手机被root就可以获取明文保存的密码,造成用户的个人敏感数据泄露。

执行步骤

  1. 使用反编译工具打开应用,反编译出应用源码。
  2. 在源码中搜索类似写法:
1
2
mWebView.getSettings().setSavePassword(true); //设置开启保存密码
mWebView.loadUrl("http://www.example.com");
  1. 则说明风险存在,记录漏洞,停止测试。

预期结果:在调用setSavePassword时设定setSavePassword(false)

整改建议:使用WebView.getSettings().setSavePassword(false)来禁止保存密码

WebView组件忽略SSL证书验证错误漏洞

安全风险

Android WebView组件加载网页发生证书认证错误时,会调用WebViewClient类的onReceivedSslError方法,如果该方法实现调用了handler.proceed()来忽略该证书错误,则会受到中间人攻击的威胁,可能导致隐私泄露。

执行步骤

  1. 使用反编译工具打开应用,反编译出应用源码。
  2. 在源码中搜索类似写法:
1
2
3
4
5
6
7
8
9
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new JsBridge(mContext), JS_OBJECT);
mWebView.loadUrl("http://www.example.org/tests/addjsif/");
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); //忽略证书错误
}
});
  1. 如上所示代码在onReceivedSslError处理时没有进行cancel,还是使用了proceed(),忽略掉了发生的SSL异常。则说明风险存在。记录漏洞,停止测试。

预期结果:正确的处理SSL错误,避免证书错误的风险。

整改建议:当发生证书认证错误时,采用默认的处理方法handler.cancel(),停止加载问题页面。

Broadcast组件安全测试

空广播造成Broadcast组件拒绝服务

安全风险:攻击者可以发送恶意的消息,控制Receiver执行恶意动作或者造成信息泄露。

执行步骤

  1. 使用工具Drozer扫描暴露的broadcast组件run app.broadcast.info -a xxxx -i和相关action信息
  2. 尝试向应用程序的receiver组件发送空值,run app.broadcast.send --action xxx,查看是否能够造成应用程序崩溃,形成拒绝服务。

预期结果 系统为Broadcast组件分配了适当权限。

整改建议 AndroidManifest.xml文件的各receiver标签中,设置android:exported="false"BroadcastReceiver代码中增加消息异常处理机制。

未指定接收组件造成信息泄露

安全风险

应用程序在广播包含敏感信息的消息时,由于未指定具体的接收组件,攻击者可能仿冒receiver来接受来自应用程序的消息,从而窃取敏感信息。

执行步骤

  1. 反编译apk获取源代码,在源代码中搜索定位发送广播消息的位置,例如搜索sendBroadcast()
  2. 查看在新建Intent时,是否显式指定了接收该广播的组件名称,以及要发送的广播中是否包含敏感信息。 示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ServerService extends Service {
// ...
private void d() {
// ...
Intent v1 = new Intent();//未显式指定接收组建名称
v1.setAction("com.sample.action.server_running");
v1.putExtra("local_ip", v0.h);
v1.putExtra("port", v0.i);
v1.putExtra("code", v0.g);
v1.putExtra("connected", v0.s);
v1.putExtra("pwd_predefined", v0.r);
if (!TextUtils.isEmpty(v0.t)) {
v1.putExtra("connected_usr", v0.t);
}
}
this.sendBroadcast(v1);
}

3、 如果应用程序未指定接收组件并且广播中包含类似password等信息,则存在信息泄露的风险。

预期结果 :显式的制定接收组建、并且分配了合适的权限

整改建议

  1. 通常采用的安全方式有设置指定action与包名
1
2
3
intent.setAction("allow.package.recv.action"); //设置指定的action
intent.setPackage("allow.package.recv.packagename"); //设置指定的包名
sendBroadcast(intent); //发送广播

如果指定特殊的receiver接收可以指定component

1
2
intent.setComponent(newComponentName("allow.package.recv.packagename", "allow.package.recv.classname"));
sendBroadcast(intent);
  1. 广播如果是APP内部使用,使用LocalBroadcastManager,使广播的Intent仅在该进程内部,而不会让同一APP的其他进程或者其他APP接收到。

  2. 避免使用如下API:

  • sendStickyBroadcast
  • sendStickyBroadcastAsUser
  • sendStickyOrderedBroadcast
  • sendStickyOrderedBroadcastAsUser

Android SDK文档中也明确说明了存在安全问题, 如果必须使用,广播中不应包含敏感信息,另外需要设置接收权限:

1
2
3
4
5
6
7
//设置广播权限
sendBroadcast(intent,"broadcast.permission");
//向特定用户发送广播
this.sendBroadcastAsUser(i, null,"broadcast.permission");
this.sendOrderedBroadcastAsUser(i, null, "broadcast.permission", null, null, 0, null, null);
this.sendOrderedBroadcast(i, "broadcast.permission");
this.sendOrderedBroadcast(i, "broadcast.permission", null, null, 0, null, null);

同时在AndroidManifest.xml中如下配置:

1
2
<uses-permission android:name="broadcast.permission" /> 
<permission android:name="broadcast.permission" android:protectionLevel="signature" />

android:protectionLevelsignature,防止其他APP能够非常容易的窃取权限。

Broadcast组件越权漏洞

安全风险:攻击者可以发送恶意的消息,控制Receiver执行恶意动作或者造成信息泄露。

执行步骤

  1. 反编译后检索registerReceiver(),查找动态广播接收器。也可以使用命令:
1
run app.broadcast.info -a com.xxxx -i
  1. 同时留意android:exported="true"权限的组件。
  2. 在源代码中搜索receiver,找到应用程序定义的在接收到消息时的各项参数以及各种处理逻辑。
  3. 查看业务逻辑寻找是否能够直接调用Broadcast组件,是否越权进行操作。

预期结果:合理分配Broadcast组件权限

整改建议

  • AndroidManifest.xml文件的receiver标签中设android:exported="false"
  • 或者在AndroidManifest.xml中,申明一个私有权限,级别为signature
  • 私有广播接收器设置exported='false',并且不配置intent-filter,对接收来的广播进行验证;

Activity组件安全测试

绕过认证调用activity

安全风险:攻击者可以绕过认证阶段,直接调用后续activity组件。

执行步骤

  1. 反编译查看配置文件AndroidManifest.xmlactivity组件(关注配置了intent-filter的及未设置export=“false”的组件)。
  2. 可使用工具Drozer扫描暴露的Activity:run app.activity.info -a packagename
  3. 执行命令run app.activity.start --component 包名 Activity名
  4. 查看在未经登录的情况下,登录之后的Activity能否被正常显示,如果可以则会形成越权、信息泄露等风险。

预期结果 设定正确的activity权限,避免造成越权或信息泄露。

整改建议

  • app内使用的私有Activity不应配置intent-filter,如果配置了intent-filter需设置exported属性为false
  • 谨慎处理接收的intent以及其携带的信息,当Activity返回数据时候需注意目标Activity是否有泄露信息的风险;

隐式启动intent包含敏感数据

安全风险

APP创建Intent传递数据到其他Activity,如果创建Activity时通过addFlags设置了FLAG_ACTIVITY_NEW_TASKActivity会在另一个Task中打开,

这种情况很可能被其他的Activity劫持读取到Intent内容,跨TaskActivity通过Intent传递敏感信息是不安全的,会导致intent中的敏感数据泄露。

执行步骤

  1. 使用反编译工具打开应用,反编译出应用源码。
  2. 在源码中查找以下示例源码(主要是FLAG_ACTIVITY_NEW_TASK标签):
1
2
3
4
5
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //设置了FLAG_ACTIVITY_NEW_TASK
startActivity(intent);
Intent i = new Intent("com.xiaomi.mipush.RECEIVE_MESSAGE");
startActivity(i);
  1. 如果FLAG_ACTIVITY_NEW_TASK标签就存在该风险,记录漏洞,停止测试

预期结果:不包含FLAG_ACTIVITY_NEW_TASK标志的Intent启动Activity

整改建议:避免使用包含FLAG_ACTIVITY_NEW_TASK标志的Intent启动Activity

Content Provider组件测试

Provider组件导致信息泄露

安全风险: 攻击者可以利用开放的Provider Content获取系统敏感资源

执行步骤

  1. 查看AndroidManifest.xml文件,定位各Provider,尤其是设置了android:exported="true"的。
  2. 可使用工具Drozer扫描:run scanner.provider.finduris -a com.mwr.example.sieve
  3. 如果存在Accessible content URIs说明存在注入风险。

预期结果:系统为Content Provider组件分配合适的权限,不存在信息泄露。

整改建议

  • AndroidManifest.xml文件的各provider标签中,设置android:exported="false"
  • 设置minSdkVersion不低于9
  • 内部app通过content provider交换数据设置protectionLevel=“signature”验证签名,仅授予那些和本程序应用了相同密钥来签名的程序
  • 公开的content provider确保不存储敏感数据;

文件遍历漏洞

安全风险

APP的实现中定义了一个可以访问本地文件的Content Provider组件,默认的android:exported="true",该Provider实现了openFile()接口

通过此接口可以访问内部存储app_webview目录下的数据,由于后台未能对目标文件地址进行有效判断,可以通过"../"实现目录跨越,导致对任意私有数据的访问。

执行步骤

  1. 使用drozer命令扫描run scanner.provider.traversal -a com.mwr.example.sieve
  2. 扫描结果中存在Vulnerable Providers说明存在文件遍历风险。

预期结果:不存在文件遍历漏洞。

整改建议:系统对在调用文件参数时添加防御。

Service组件测试

Service组件越权漏洞

安全风险:攻击者可以发送恶意的消息,控制Service执行恶意动作或者造成信息泄露。

执行步骤

  1. 使用drozer命令 run app.service.info -a xxxx查看service组件暴露。
  2. 通过定位的service,找到应用程序定义的在接收到消息时的各项参数以及各种处理逻辑。
  3. 查看业务逻辑寻找是否能够直接调用Service组件,能否能进行越权操作。如果可以风险存在,停止测试,记录漏洞。

预期结果 系统为Service组件分配了适当权限

整改建议

  • AndroidManifest.xml文件的各receiver标签中,设置android:exported="false"
  • 或者在AndroidManifest.xml中,申明一个私有权限,级别为signature
  • 只被应用本身使用的service应设置为私有;
  • 尽量不发送敏感信息,在service接收到的数据需需谨慎处理,对调用的接口做校验;

空广播造成Service组件拒绝服务

安全风险:攻击者可以发送恶意的消息,控制Receiver执行恶意动作或者造成信息泄露。

执行步骤

  1. 查看AndroidManifest.xml文件,定位各Receiver,尤其是设置了android:exported="true"的。
  2. 尝试调用服务组件,run app.service.start --action 服务名 --component 包名 服务名,查看是否能够造成应用程序拒绝服务。

预期结果:系统为Service组件分配了适当权限

整改建议
AndroidManifest.xml文件的各组件标签中,设置android:exported="false"
组件接收消息代码中增加消息异常处理机制。

备注:其他类型的拒绝服务攻击参考SEC_AN_ PLUS_11.1 intent应用本地拒绝服务漏洞。

intent应用本地拒绝服务漏洞

安全风险

Android系统中提供了Intent机制来协助应用间的交互与通讯,例如:应用A发出一个intent信息,系统根据intent的描述,负责找到可以解析该intent消息的应用B

B应用负责接收intent的组件,在解析intent数据时,会通过IntentgetXXXExtra()函数,如果解析为空数据、异常、或是畸形数据,就可能会导致程序崩溃。

执行步骤

  1. 攻击者向Intent传入自定义的序列化对象,被攻击者在组件里解析该序列化数据,可能出现出现找不到类出现ClassNotFoundException异常而崩溃。攻击代码如下:
    1
    2
    3
    4
    5
    Intent intent = new Intent();
    intent.setAction("serializable_action");
    intent.setClassName("com.my.test", "com.my.test.MainActivity"); // 指定攻击目标的包名和Activity入口
    intent.putExtra("serializable_obj",XXX); //此处是传入畸形数据
    startActivity(intent);
  1. 数组越界IndexOutOfBoundsException异常导致的拒绝服务,如果程序没有对getIntegerArrayListExtra()等获取到的数据数组元素大小的判断,从而导致数组访问越界而导致应用崩溃;
    攻击应用代码片段:
1
2
3
4
5
6
Intent intent = new Intent();
intent.setClassName("com.alibaba.jaq.pocforrefuseservice", "com.alibaba.jaq.pocforrefuseservice.MainActivity");
ArrayList<Integer> user_id = new ArrayList<Integer>(); //定义数组
intent.putExtra("user_id", user_id);
startActivity(intent);
)

预期结果:在使用Intent获取数据时对异常做了充分的处理。

整改建议

建议处理通过Intent.getXXXExtra()获取的数据时进行以下判断,以及用try catch方式进行捕获所有异常,以防止应用出现拒绝服务漏洞:

  • 空指针异常;
  • 类型转换异常;
  • 数组越界访问异常;
  • 类未定义异常;
  • 其他异常;

开放网络服务安全测试

安全风险

Android应用通常使用PF_UNIX、PF_INET、PF_NETLINK等不同域名的socket来进行本地进程间通信或者远程网络通信,这些socket暴漏了潜在的本地或远程攻击面,历史上也出现过不少利用socket进行拒绝服务、root提权或者远程命令执行的案例。

特别是PF_INET类型的网络socket,可以通过网络与Android应用通信,其原本用于linux环境下开放网络服务,由于缺乏对网络调用者身份或者本地调用者的安全检查机制,在实现不当的情况下,可以突破Android的沙箱限制,对被攻击的应用执行命令,导致比较严重的漏洞。

执行步骤

  1. 使用反编译工具打开应用,反编译出应用源码。
  2. 检测对收到socket数据是否进行处理,代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//定义读取socket命令
public static String readCMDFromSocket(InputStream in) {
int MAX_BUFFER_BYTES = 2048;
String msg = "";
byte[] tempbuffer = new byte[MAX_BUFFER_BYTES];
try {
int numReadedBytes = in.read(tempbuffer, 0, tempbuffer.length);
if( numReadedBytes > -1 )
msg = new String(tempbuffer, 0, numReadedBytes, "utf-8");
tempbuffer = null;
} catch (Exception e) {
e.printStackTrace();
}
return msg;
}
...
//处理socket信息
public void handlemsg()
{
...
msg = readCMDFromSocket(in)
if ("exec" == msg)
{
//没有任何的socket命令校验
...
...
}
...
}
  1. 如果出现类似以上代码,未对接收到的socket和内容做任何校验检查,则风险存在。

预期结果socket数据内容进行校验。

整改建议 直接传递命令或者间接处理敏感信息时,避免使用socket实现。

运行其它可执行程序风险

安全风险

APP中使用了有运行其他程序的代码逻辑,如果执行的代码是第三方库中,可能会存在未知的恶意行为,如果是程序自身代码,若调用逻辑有缺陷可能会导致执行其他恶意的第三方程序,攻击者可能会利用该缺陷执行恶意代码。

执行步骤

  1. 使用反编译工具打开应用,反编译出应用源码。
  2. 在源码中查找使用Runtime.getRuntime().exec执行第三方程序的代码样例:
1
2
3
4
5
6
7
8
9
10
try {
Process p1 = Runtime.getRuntime().exec(
new String[] { "/system/bin/ls", "-l" },
new String[] { "a=1", "b=2" });
Runtime.getRuntime().load(
"/data/data/com.baidu.seclab/lib/libtest.so");
Runtime.getRuntime().loadLibrary("test");
} catch (IOException e) {
e.printStackTrace();
}
  1. 发现使用Runtime.getRuntime().exec执行第三方程序后,且检测到调用逻辑中存在缺陷,则风险存在。停止测试,记录漏洞。

预期结果 合理使用Runtime.getRuntime().exec等函数,防止恶意调用。

整改建议 合理设置程序逻辑防止恶意调用,如果该行为是非期望行为,移除相关代码。

数据的完整性进行校验

安全风险

App向服务器提交的数据易被中间人篡改,对用户数据的完整性造成影响,如用户信息被破解利用等问题。

执行步骤

  1. 使用Charles代理工具连接设备代理,启动app,正常操作app;
  2. 在app上对提交的数据进行修改,重新提交,查看这些参数的值有无变化;
  3. 对获取数据包参数进行修改并重放,查看是否可正常返回;
  4. 若数据正常返回,没有提示数据错误,说明app请求参数未进行完整性校验。

预期结果:请求数据中包含完整性校验字段;

整改建议:添加完整性校验逻辑。

键盘劫持测试

安全风险

攻击者可以通过劫持键盘窃取用户输入数据,可能带来用户账号密码、敏感数据等泄露的风险,特别是银行金融类App。

执行步骤

  1. 打开应用,选择一处输入点进行输入
  2. 观察应用程序是否打开自带键盘,如果使用系统键盘输入,则问题存在。记录漏洞,停止测试。

预期结果:App在输入时使用自带键盘

整改建议:在App内集成自带键盘,并采用随机分布式键盘。

参考资料