Android应用安全测试是一门从攻击者视角审视移动端防护能力的实践学科。与功能测试关注业务流程是否正确不同,安全测试更关心敏感数据是否泄露、关键逻辑是否可被绕过、系统组件是否越权调用。一个缺乏安全测试的App,即便界面精美、性能流畅,也可能在反编译之后暴露出支付签名算法或后台接口鉴权方式,进而被批量薅羊毛甚至窃取用户隐私。

逆向分析与包体防护检测
逆向分析是Android安全测试的起点。攻击者通常会使用ApkTool、jadx等工具将APK反编译为Smali代码或近似Java源码,从而阅读登录校验、加密逻辑和业务规则。测试人员也应主动执行这一过程,确认发布包是否容易被还原。如果直接能搜到明文密钥或接口地址,说明打包阶段未做基本防护。
在防护层面,开启R8或ProGuard混淆是最低成本的手段。通过在build.gradle中配置minifyEnabled true以及合理的混淆字典,可以让反编译后的类名变为a、b、c,大幅增加阅读成本。但混淆并不能隐藏硬编码字符串,因此密钥、URL等仍需放在服务端或使用NDK native层存储。
以下是一段使用Gradle开启混淆的基础配置示例,展示如何在发行构建中强制压缩与混淆:
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
除了混淆,还应检测是否启用了root检测与签名校验。很多App只在Java层调用System.exit来拒绝root手机,但攻击者用Frida钩子可轻松绕过,因此建议把关键校验放进native并配合服务端风控,才能形成有效防线。
组件暴露与权限越权风险
Android四大组件(Activity、Service、BroadcastReceiver、ContentProvider)若错误声明了android:exported="true",且未做权限保护,就会被其他应用调起并越权执行操作。例如一个未校验调用者的Webview Activity允许通过Intent传入远程URL,就可能被用于钓鱼或窃取本地文件。
测试时可以使用drozer框架对设备上的应用进行组件枚举。命令run app.activity.info -a 包名能列出所有可导出的Activity,再配合run app.activity.start尝试拉起,验证是否存在未授权访问。对于ContentProvider,则要检查query和insert是否泄露用户数据库。
在代码层面,应避免在AndroidManifest.xml中随意暴露组件。如果组件仅内部使用,应显式设置exported=false;若必须对外,则通过自定义权限permission限制调用方。如下片段展示了如何给Service添加签名级权限:
<permission
android:name="com.example.app.permission.SECURE_SERVICE"
android:protectionLevel="signature" />
<service
android:name=".SecureService"
android:exported="true"
android:permission="com.example.app.permission.SECURE_SERVICE" />
此外,广播接收器若使用全局无序广播且未做来源验证,容易被伪造消息触发。建议采用本地广播LocalBroadcastManager或显式指定包名发送,降低被外部应用劫持的概率。
数据传输与本地存储安全
移动端网络请求若使用明文HTTP,或在HTTPS下忽略证书校验,都会让中间人攻击变得轻而易举。测试人员可在手机上配置Charles或Burp的代理证书,若App依旧能正常通信且数据明文可见,说明存在证书固定(SSL Pinning)缺失的问题。
针对这一问题,除了服务端强制HTTPS,客户端还应实现证书锁定。以OkHttp为例,可通过CertificatePinner绑定域名与公钥哈希,使伪造证书无法建立连接。同时,所有敏感字段如密码、token不应出现在URL参数中,而应放在POST体或Header并配合加密。
本地存储方面,SharedPreferences默认以明文XML存于私有目录,root设备可直接读取。账号信息应优先存放于EncryptedSharedPreferences或Android Keystore生成的密钥加密后写入文件。以下示例展示Keystore生成密钥并加密一段数据的核心步骤:
KeyGenerator keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGen.init(new KeyGenParameterSpec.Builder("my_key_alias",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
SecretKey key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal("sensitive_data".getBytes());
最后,日志输出也是常见疏忽。很多开发者在发版时忘记关闭Log.d,导致token或接口结构泄露。应统一使用自定义日志类,在release构建中把级别调到ERROR以上,或借助R8的assumenosideeffects彻底剔除日志调用,从打包层面消除隐患。
Android_App_Security安全测试渗透测试修改时间:2026-08-18 00:20:29