Android应用安全解决方案
- 2019 年 10 月 5 日
- 筆記
Apk安全解决方案
背景
公司为政府做的App开发完了,需要上一些手段保证安全。这样客户才放心嘛。 防止第三方反编译篡改应用,防止数据隐私泄露,防止二次打包欺骗用户。
Apk需要加固
腾讯乐固
代码混淆及日志关闭
采用proguard
# Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -optimizationpasses 5 # 指定代码的压缩级别 -dontusemixedcaseclassnames # 是否使用大小写混合 -dontpreverify # 混淆时是否做预校验 -verbose # 混淆时是否记录日志 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* # 混淆时所采用的算法 -keep public class * extends android.app.Fragment -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.preference.Preference -keep public class * extends android.content.ContentProvider -keep public class * extends android.support.v4.** -keep public class * extends android.support.annotation.** -keep public class * extends android.support.v7.** -keepclasseswithmembernames class * { # 保持 native 方法不被混淆 native <methods>; } -keepclasseswithmembers class * { # 保持自定义控件类不被混淆 public <init>(android.content.Context, android.util.AttributeSet); } -keepclasseswithmembers class * {# 保持自定义控件类不被混淆 public <init>(android.content.Context, android.util.AttributeSet, int); } -keepclassmembers class * extends android.app.Activity { # 保持自定义控件类不被混淆 public void *(android.view.View); } -keepclassmembers enum * { # 保持枚举 enum 类不被混淆 public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆 public static final android.os.Parcelable$Creator *; } #过滤注解 -keepattributes *Annotation* -keep class * extends java.lang.annotation.Annotation { *; } -keep interface * extends java.lang.annotation.Annotation { *; } #过滤泛型 -keepattributes Signature ##fastjson -keep public class * implements java.io.Serializable { public *; } -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } -dontwarn android.support.** -dontwarn com.alibaba.fastjson.** -dontskipnonpubliclibraryclassmembers -dontskipnonpubliclibraryclasses -keep class com.alibaba.fastjson.** { *; } -keepclassmembers class * { public <methods>; } ##Gson -keepattributes Signature -keepattributes *Annotation* -keep class sun.misc.Unsafe { *; } -keep class com.google.gson.stream.** { *; } # Application classes that will be serialized/deserialized over Gson 下面替换成自己的实体类 -keep class com.kuaijiajin.julynovel.bean.** { *; } #close log -assumenosideeffects class android.util.Log { public static boolean isLoggable(java.lang.String,int); public static int v(...); public static int i(...); public static int w(...); public static int d(...); public static int e(...); } #webview -keepclassmembers class fqcn.of.javascript.interface.for.webview { public *; } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); public boolean *(android.webkit.WebView, java.lang.String); } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.webView, jav.lang.String); } #Butter Knife -keep class butterknife.** { *; } -dontwarn butterknife.internal.** -keep class **$$ViewBinder { *; } -keepclasseswithmembernames class * { @butterknife.* <fields>; } -keepclasseswithmembernames class * { @butterknife.* <methods>; } #Retrofit -dontwarn retrofit2.** -keep class retrofit2.** { *; } -keepattributes Signature -keepattributes Exceptions #Rxjava RxAndroid -dontwarn sun.misc.** -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { long producerIndex; long consumerIndex; } -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { rx.internal.util.atomic.LinkedQueueNode producerNode; } -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { rx.internal.util.atomic.LinkedQueueNode consumerNode; } #Glide -keep public class * implements com.bumptech.glide.module.GlideModule -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { **[] $VALUES; public *; } # Okhttp3的混淆配置 # JSR 305 annotations are for embedding nullability information. -dontwarn javax.annotation.** # A resource is loaded with a relative path so the package of this class must be preserved. -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. -dontwarn org.codehaus.mojo.animal_sniffer.* # OkHttp platform used only on JVM and when Conscrypt dependency is available. -dontwarn okhttp3.internal.platform.ConscryptPlatform #自定义混淆(部分使用了反射的地方) -keep class com.kuaijiajin.julynovel.util.BottomNavigationViewUtils
Janus签名机制漏洞
打包时选择v1和v2签名
应用签名未校验
增加签名证书的校验代码,降低App被二次打包的几率。
/** * 检测签名 */ private boolean checkSignature() { Context context = WXApplication.getInstance(); try { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); Signature[] signatures = packageInfo.signatures; if (signatures != null) { for (Signature signature : packageInfo.signatures) { //获取MD5或者SHA1 MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(signature.toByteArray()); String currentSignature = bytesToHexString(md.digest()).toUpperCase(); if ("YOUR SIGENATURE".equals(currentSignature)) { return true; } } } else { LogUtil.i("signatures ==null"); } } catch (NameNotFoundException e) { e.printStackTrace(); LogUtil.e(e); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); LogUtil.e(e); } return false; } /** * byte转16进制String * * @param src 数据源 * @return string */ public String bytesToHexString(byte[] src) { StringBuilder stringBuilder = new StringBuilder(); if (src == null || src.length <= 0) { return ""; } for (byte by : src) { int v = by & 0xFF; String hv = Integer.toHexString(v); if (hv.length() < 2) { stringBuilder.append(0); } stringBuilder.append(hv); } return stringBuilder.toString(); }
Webview明文存储密码风险
解决方案
WebView.getSettings().setSavePassword(false)
Webview File同源策略绕过漏洞
将不必要导出的组件设置为不导出,并显式设置所注册组件的“android:exported”属性为false 如果需要导出组件,禁止使用File域 WebView.getSettings.setAllowFileAccess(false);
应用数据任意备份风险
AndroidManifest.xml内关闭数据允许备份
application android:allowBackup=false
敏感函数调用风险
审核包含敏感行为的函数调用,确保其使用是必要且限制于授权用户的。
restartPackage - 关闭进程
随机数不安全使用漏洞
禁止在生成随机数之前调用setSeed()方法设置随机种子或调用SecureRandom类的构造函数SecureRandom(byte[] seed),建议通过/dev/urandom或者/dev/random获取的熵值来初始化伪随机数生成器。
URL信息检测
在移动应用的程序代码内部,可能存在大量开发人员或其他工作人员无意识留下的信息内容。URL信息检测就是通过检测移动应用程序代码内部所存在的URL地址信息,尽可能呈现出应用中所有的URL信息,便于应用开发者查看并评估其安全性。移动应用发布包中的URL地址信息,可能会被盗取并恶意利用在正式服务器上进行攻击,攻击安全薄弱的测试服务器以获取服务器安全漏洞或者逻辑漏洞。 解决方案 1、核查并评估所有的URL信息,判断是否存在涉及内部业务等敏感信息的URL地址,进行删除; 2、尽量不要将与客户端业务相关的URL信息以硬编码的方式写在应用客户端中,建议以动态的方式生成所需要请求的URL
残留账户密码信息检测
- 核查所有残留的账户和密码信息,删除与业务无关的账户和密码。
- 尽量不要将与客户端业务相关的账户密码信息以硬编码的方式写在应用客户端中。 截屏攻击风险 开发者审查应用中显示或者输入关键信息的界面,在此类Activity创建时设置WindowManager.LayoutParams.FLAG_SECURE属性,该属性能防止屏幕被截图和录制
public class DemoActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); setContentView(R.layout.main); } }
未移除有风险的Webview系统隐藏接口漏洞
开发者显式移除有风险的Webview系统隐藏接口。 以下为修复代码示例: 在使用Webview加载页面之前,执行
webView.removeJavascriptInterface("searchBoxJavaBridge_"); webView.removeJavascriptInterface("accessibility"); webView.removeJavascriptInterface("accessibilityTraversal");
Root设备运行风险
开发者应在应用启动时增加对应用运行环境的检测,当发现运行设备为Root设备时,应禁止应用启动。
@Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); if(checkRootPathSU() || checkRootWhichSU()){ ToastUtils.showLong(getResources().getString(R.string.illegal_root)); finish(); return; } if(!checkSignature()){ ToastUtils.showLong(getResources().getString(R.string.illegal_apk_signatures)); finish(); return; } } //通过检测指定目录下是否存在su程序来检测运行环境是否为Root设备 //当CheckRootPathSU返回值为true时,禁止应用启动 public static boolean checkRootPathSU() { File f=null; final String kSuSearchPaths[] = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/","/vendor/bin/"}; try{ for(int i=0;i<kSuSearchPaths.length;i++) { f=new File(kSuSearchPaths[i]+"su"); if(f!=null&&f.exists()) { return true; } } }catch(Exception e) { e.printStackTrace(); } return false; } //通过which命令检测系统PATH变量指定的路径下是否存在su程序来检测运行环境是否为Root设备 //当CheckRootWhichSU返回值为true时,禁止应用启动 public static boolean checkRootWhichSU() { String[] strCmd = new String[] {"/system/xbin/which","su"}; ArrayList<String> execResult = executeCommand(strCmd); if (execResult != null){ return true; }else{ return false; } } public static ArrayList<String> executeCommand(String[] shellCmd){ String line = null; ArrayList<String> fullResponse = new ArrayList<String>(); Process localProcess = null; try { localProcess = Runtime.getRuntime().exec(shellCmd); } catch (Exception e) { return null; } BufferedWriter out = new BufferedWriter(new OutputStreamWriter(localProcess.getOutputStream())); BufferedReader in = new BufferedReader(new InputStreamReader(localProcess.getInputStream())); try { while ((line = in.readLine()) != null) { fullResponse.add(line); } } catch (Exception e) { e.printStackTrace(); } return fullResponse; }
不安全的浏览器调用漏洞
开发者需要在应用调用外部浏览器时对引擎版本进行检测,当发现调用Chrome V8引擎并且版本低于4.2时停止调用外部浏览器并且提示用户对调用的系统浏览器进行升级或者修改系统默认的浏览器。