Android 开发中的若干安全问题

来源:互联网 发布:苹果免费数据恢复软件 编辑:程序博客网 时间:2024/06/02 01:03


1.    本app内部使用的activity一定要设置为非公开

                  不准备对外公开的activity一定要设置为非公开,以防止被人非法调用

[html] view plaincopyprint?
  1. <activity  
  2. android:name=".PrivateActivity"  
  3. android:label="@string/app_name"  
  4. android:exported="false" />  

                 同时,一定要注意的是, 非公开的Activity不能设置intent-filter

                 因为,如果假设在同一机器上,有另外一个app有同样的intent-filter的话, 调用该Activity的intent会唤醒android的选择画面, 让你选择使用那个app接受该intent。这样就会事实上绕过了非公开的设置。

2.    不要指定taskAffinity

Android中的activity全都归属于task管理 , 简单说来task是一种stack的数据结构, 先入后出。

一般来说, 如果不指明归属于什么task, 同一个app内部的所有Activity都会存续在一个task中,task的名字就是app的packageName。

因为在同一个andorid设备中,不会有两个同packageName的app存在,所以能保证Activity不被攻击。

但是如果你指明taskAffinity,比如如下

[html] view plaincopyprint?
  1. <application android:icon="@drawable/icon" android:label="@string/app_name">   
  2.         <activity android:name=".Activity1"   
  3.                   android:taskAffinity="com.winuxxan.task"   
  4.                   android:label="@string/app_name">   
  5.         </activity>   
  6.         <activity android:name=".Activity2">   
  7.             <intent-filter>   
  8.                 <action android:name="android.intent.action.MAIN" />   
  9.                 <category android:name="android.intent.category.LAUNCHER" />   
  10.             </intent-filter>   
  11.         </activity>   
  12.     </application>  

那此时,恶意软件中的Activity如果也声明为同样的taskAffinity,那他的Activity就会启动到你的task中,就会有机会拿到你的intent

3.    不要指定LaunchMode(默认standard模式)

             Android中Activity的LaunchMode分成 以下四种

           Standard:   这种方式打开的Activity不会被当作rootActivity,会生成一个新的Activity的instance,会和打开者在同一个task内

             singleTop:      和standard基本一样,唯一的区别在于如果当前task第一个Activity就是该Activity的话,就不会生成新的instance

           singleTask:系统会创建一个新task(如果没有启动应用)和一个activity新实例在新task根部,然后,如果activity实例已经存在单独的task中,系统会调用已经存在activity的 onNewIntent()方法,而不是存在新实例,仅有一个activity实例同时存在

            singleInstance: 和singleTask相似,除了系统不会让其他的activities运行在所有持有的task实例中,这个activity是独立的,并且task中的成员只有它,任何其他activities运行这个activity都将打开一个独立的task。

               所有发送给root Activity(根Activiy)的intent都会在android中留下履历。所以一般来说严禁用singleTask或者singleInstance来启动画面。

               然而,即使用了standard来打开画面,也可能会出问题,比如如果调用者的Activity是用singleInstance模式打开,即使用standard模式打开被调用Activity,因为调用者的Activitytask是不能有其他task的, 所以android会被迫生成一个新的task,并且把被调用者塞进去,最后被调用者就成了rootActivity。

程序如下:

AndroidManifest.xml 

[html] view plaincopyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.    package="org.jssec.android.activity.privateactivity"  
  4.    android:versionCode="1"  
  5. android:versionName="1.0" >  
  6. <uses-sdk android:minSdkVersion="8" />  
  7.    <application  
  8.      android:icon="@drawable/ic_launcher"  
  9.      android:label="@string/app_name" >  
  10.      <!—root Activity以”singleInstance”模式启动 -->  
  11.      <!—不设置taskAffinity-->  
  12.    <activity  
  13.      android:name=".PrivateUserActivity"  
  14.       android:label="@string/app_name"  
  15.       android:launchMode="singleInstance" >  
  16.     <intent-filter>  
  17.      <action android:name="android.intent.action.MAIN" />  
  18.      <category android:name="android.intent.category.LAUNCHER" />  
  19.     </intent-filter>  
  20. </activity>  
  21. <!-- 非公開Activity -->  
  22. <!—启动模式为”standard” -->  
  23. <!—不设置taskAffinity-->  
  24.    <activity  
  25.       android:name=".PrivateActivity"  
  26.       android:label="@string/app_name"  
  27.      android:exported="false" />  
  28. </application>  
  29. </manifest>  

非公开Activity的代码如下:

[java] view plaincopyprint?
  1. package org.jssec.android.activity.privateactivity;  
  2. import android.app.Activity;  
  3. import android.content.Intent;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6. import android.widget.Toast;  
  7.     public class PrivateActivity extends Activity {  
  8.         @Override  
  9.         public void onCreate(Bundle savedInstanceState) {  
  10.             super.onCreate(savedInstanceState);  
  11.             setContentView(R.layout.private_activity);  
  12.             String param = getIntent().getStringExtra("PARAM");  
  13.             Toast.makeText(this, String.format("「%s」取得。", param),  
  14.                     Toast.LENGTH_LONG).show();  
  15.         }  
  16.   
  17.         public void onReturnResultClick(View view) {  
  18.         Intent intent = new Intent();  
  19.         intent.putExtra("RESULT", 机密数据");  
  20.         setResult(RESULT_OK, intent);  
  21.         finish();  
  22.         }  
  23.     }  

调用非公开Activity者,以standard模式打开

[java] view plaincopyprint?
  1. package org.jssec.android.activity.privateactivity;  
  2. import android.app.Activity;  
  3. import android.content.Intent;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6. import android.widget.Toast;  
  7. public class PrivateUserActivity extends Activity {  
  8.         private static final int REQUEST_CODE = 1;  
  9.   
  10.         @Override  
  11.         public void onCreate(Bundle savedInstanceState) {  
  12.             super.onCreate(savedInstanceState);  
  13.             setContentView(R.layout.user_activity);  
  14.         }  
  15.   
  16.         public void onUseActivityClick(View view) {  
  17.             // 用standard模式启动非公开Activity  
  18.             Intent intent = new Intent();  
  19.             intent.setClass(this, PrivateActivity.class);  
  20.             intent.putExtra("PARAM""机密数据");  
  21.             startActivityForResult(intent, REQUEST_CODE);  
  22.         }  
  23.   
  24.         @Override  
  25.         public void onActivityResult(int requestCode, int resultCode,  
  26.                 Intent data) {  
  27.             super.onActivityResult(requestCode, resultCode, data);  
  28.             if (resultCode != RESULT_OK)  
  29.                 return;  
  30.             switch (requestCode) {  
  31.             case REQUEST_CODE:  
  32.                 String result = data.getStringExtra("RESULT");  
  33.                 break;  
  34.             }  
  35.         }  
  36.     }  

4.    发给Activity的intent不要设定为FLAG_ACTIVITY_NEW_TASK

就算上面的Activity的lauchMode设置完善了, 在打开intent的时候还是能指定打开模式。

比如在intent中指明用FLAG_ACTIVITY_NEW_TASK模式的话,发现该activity不存在的话,就会强制新建一个task。如果同时设置了FLAG_ACTIVITY_MULTIPLE_TASK+ FLAG_ACTIVITY_NEW_TASK,就无论如何都会生成新的task,该Activity就会变成rootActiviy,并且intent会被留成履历

5.    Intent中数据的加密

Activity中数据的传递都依靠intent, 很容易被攻击, 所以 就算同一个app内部传递数据, 最好还是要加密, 加密算法很多

6.    明确ActivityName发送Intent

   明确Activity发送Intent,能够避免被恶意软件截取。

 同一app内部的发送


[java] view plaincopyprint?
  1. Intent intent = new Intent(this, PictureActivity.class);  
  2. intent.putExtra("BARCODE", barcode);  
  3. startActivity(intent);  

不同app内部的发送

[java] view plaincopyprint?
  1. Intent intent = new Intent();  
  2. intent.setClassName(  
  3. "org.jssec.android.activity.publicactivity",  
  4. "org.jssec.android.activity.publicactivity.PublicActivity");  
  5. startActivity(intent);  

                             但是,要注意的是! 

                           不是指明了packageName和ActivityName就能避免所有的问题, 

                            如果有一个恶意软件故意做成和你发送目标同packageName, 同ActivityName, 此时的intent就会被截取

7.    跨app接受Intent时,要明确对方的身份

接受到别的app发来的intent时,要能确定对方的身份。

一个好方法是比对对方的app的hashcode。

当前,前提是调用者要用startActivityForResult(),因为只有这个方法,被调用者才能得到调用者的packageName

代码如下:

被调用的Activity

[java] view plaincopyprint?
  1.   package org.jssec.android.activity.exclusiveactivity;  
  2.     import org.jssec.android.shared.PkgCertWhitelists;  
  3.     import org.jssec.android.shared.Utils;  
  4.     import android.app.Activity;  
  5.     import android.content.Context;  
  6.     import android.content.Intent;  
  7.     import android.os.Bundle;  
  8.     import android.view.View;  
  9.     import android.widget.Toast;  
  10. public class ExclusiveActivity extends Activity {  
  11.         // hashcode的白名单  
  12.         private static PkgCertWhitelists sWhitelists = null;  
  13.   
  14.         private static void buildWhitelists(Context context) {  
  15.             boolean isdebug = Utils.isDebuggable(context);  
  16.             sWhitelists = new PkgCertWhitelists();  
  17.             sWhitelists  
  18.                     .add("org.jssec.android.activity.exclusiveuser", isdebug ?  
  19.                     "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"  
  20.                             :  
  21.                             "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A");  
  22.         }  
  23.   
  24.         private static boolean checkPartner(Context context, String pkgname) {  
  25.             if (sWhitelists == null)  
  26.                 buildWhitelists(context);  
  27.             return sWhitelists.test(context, pkgname);  
  28.         }  
  29.   
  30.         @Override  
  31.     public void onCreate(Bundle savedInstanceState) {  
  32.     super.onCreate(savedInstanceState);  
  33.     setContentView(R.layout.main);  
  34.     // check白名单  
  35.     if (!checkPartner(this, getCallingPackage())) {  
  36.     Toast.makeText(this"不是白名单内部的。", Toast.LENGTH_LONG).show();  
  37.     finish();  
  38.     return;  
  39.     }  
  40.     }  
  41.   
  42.         public void onReturnResultClick(View view) {  
  43.             Intent intent = new Intent();  
  44.             intent.putExtra("RESULT""机密数据");  
  45.             setResult(RESULT_OK, intent);  
  46.             finish();  
  47.         }  
  48.     }   
[java] view plaincopyprint?
  1.   
PkgCertWhitelists.java
[java] view plaincopyprint?
  1. package org.jssec.android.shared;  
  2. import java.util.HashMap;  
  3. import java.util.Map;  
  4. import android.content.Context;  
  5. public class PkgCertWhitelists {  
  6.         private Map<String, String> mWhitelists = new HashMap<String, String>();  
  7.   
  8.         public boolean add(String pkgname, String sha256) {  
  9.             if (pkgname == null)  
  10.                 return false;  
  11.             if (sha256 == null)  
  12.                 return false;  
  13.             sha256 = sha256.replaceAll(" """);  
  14.             if (sha256.length() != 64)  
  15.                 return false;    
  16.             sha256 = sha256.toUpperCase();  
  17.             if (sha256.replaceAll("[0-9A-F]+""").length() != 0)  
  18.                 return false;   
  19.             mWhitelists.put(pkgname, sha256);  
  20.             return true;  
  21.         }  
  22.   
  23.         public boolean test(Context ctx, String pkgname) {  
  24.             String correctHash = mWhitelists.get(pkgname);  
  25.             return PkgCert.test(ctx, pkgname, correctHash);  
  26.         }  
  27.     }  
PkgCert.java
[java] view plaincopyprint?
  1. package org.jssec.android.shared;  
  2. import java.security.MessageDigest;  
  3. import java.security.NoSuchAlgorithmException;  
  4. import android.content.Context;  
  5. import android.content.pm.PackageInfo;  
  6. import android.content.pm.PackageManager;  
  7. import android.content.pm.PackageManager.NameNotFoundException;  
  8. import android.content.pm.Signature;  
  9. public class PkgCert {  
  10.         public static boolean test(Context ctx, String pkgname,  
  11.                 String correctHash) {  
  12.             if (correctHash == null)  
  13.                 return false;  
  14.             correctHash = correctHash.replaceAll(" """);  
  15.             return correctHash.equals(hash(ctx, pkgname));  
  16.         }  
  17.   
  18.         public static String hash(Context ctx, String pkgname) {  
  19.             if (pkgname == null)  
  20.                 return null;  
  21.             try {  
  22.                 PackageManager pm = ctx.getPackageManager();  
  23.                 PackageInfo pkginfo = pm.getPackageInfo(pkgname,  
  24.                         PackageManager.GET_SIGNATURES);  
  25.                 if (pkginfo.signatures.length != 1)  
  26.                     return null;   
  27.                 Signature sig = pkginfo.signatures[0];  
  28.                 byte[] cert = sig.toByteArray();  
  29.                 byte[] sha256 = computeSha256(cert);  
  30.                 return byte2hex(sha256);  
  31.             } catch (NameNotFoundException e) {  
  32.                 return null;  
  33.             }  
  34.         }  
  35.   
  36.         private static byte[] computeSha256(byte[] data) {  
  37.             try {  
  38.                 return MessageDigest.getInstance("SHA-256").digest(data);  
  39.             } catch (NoSuchAlgorithmException e) {  
  40.                 return null;  
  41.             }  
  42.         }  
  43.   
  44.         private static String byte2hex(byte[] data) {  
  45.             if (data == null)  
  46.                 return null;  
  47.             final StringBuilder hexadecimal = new StringBuilder();  
  48.             for (final byte b : data) {  
  49.                 hexadecimal.append(String.format("%02X", b));  
  50.             }  
  51.             return hexadecimal.toString();  
  52.         }  
  53.     }  

8.    所有根Activity中的intent都能被所有app共享

 

所有的app,只要按照如下样子,就能取出这台手机上所有task上所有根Activity接受到的intent

AndroidManifest.xml

[html] view plaincopyprint?
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     package="org.jssec.android.intent.maliciousactivity"  
  3.     android:versionCode="1"  
  4.     android:versionName="1.0" >  
  5.   
  6.     <uses-sdk  
  7.         android:minSdkVersion="8"  
  8.         android:targetSdkVersion="15" />  
  9.   
  10.     <application  
  11.         android:icon="@drawable/ic_launcher"  
  12.         android:label="@string/app_name"  
  13.         android:theme="@style/AppTheme" >  
  14.         <activity  
  15.             android:name=".MaliciousActivity"  
  16.             android:label="@string/title_activity_main" >  
  17.             <intent-filter>  
  18.                 <action android:name="android.intent.action.MAIN" />  
  19.   
  20.                 <category android:name="android.intent.category.LAUNCHER" />  
  21.             </intent-filter>  
  22.         </activity>  
  23.     </application>  
  24.   
  25.     <uses-permission android:name="android.permission.GET_TASKS" />  
  26.   
  27. </manifest>  

MaliciousActivity.java

[java] view plaincopyprint?
  1. package org.jssec.android.intent.maliciousactivity;  
  2. import java.util.List;  
  3. import android.app.Activity;  
  4. import android.app.ActivityManager;  
  5. import android.content.Intent;  
  6. import android.os.Bundle;  
  7. import android.util.Log;  
  8. public class MaliciousActivity extends Activity {  
  9.         @Override  
  10.         public void onCreate(Bundle savedInstanceState) {  
  11.             super.onCreate(savedInstanceState);  
  12.             setContentView(R.layout.malicious_activity);  
  13.             ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);  
  14.             List<ActivityManager.RecentTaskInfo> list = activityManager  
  15.                     .getRecentTasks(100, ActivityManager.RECENT_WITH_EXCLUDED);  
  16.             for (ActivityManager.RecentTaskInfo r : list) {  
  17.                 Intent intent = r.baseIntent;  
  18.                 Log.v("baseIntent", intent.toString());  
  19.             }  
  20.         }  
  21.     }  

9.    Intent数据遗漏到LogCat的可能性

如果像如下代码,那Intent中发送的数据就会被自动写入LogCat


[java] view plaincopyprint?
  1. Uri uri = Uri.parse("mailto:test@gmail.com");  
  2. Intent intent = new Intent(Intent.ACTION_SENDTO, uri);  
  3. startActivity(intent);  

如果像如下,就能避免

[java] view plaincopyprint?
  1. Uri uri = Uri.parse("mailto:");  
  2. Intent intent = new Intent(Intent.ACTION_SENDTO, uri);  
  3. intent.putExtra(Intent.EXTRA_EMAIL, new String[] {"test@gmail.com"});  
  4. startActivity(intent);  
0 0