Android 应用常见漏洞以及修复方案

 

 

 

      

1.1私有文件全局可读,造成信息泄露

漏洞产生原因:

应用开发者在将应用或者用户的相关信息直接明文,或者简单加密(可逆的)保存在应用私有目录下,并没有设置权限,造成全局可读,从而泄露相关隐私信息。

漏洞案例

百度云:http://www.wooyun.org/bugs/wooyun-2010-0101468

漏洞修复:

改变文件的权限,移除全局可读、可写的权限,只能应用自己可读可写。

1.2应用安装/加载,签名绕过漏洞

漏洞产生原因

安装APK、或者应用在动态加载运行DexJAR或者APK时,并没有对DEXJAR或者APK进行签名校验,导致恶意代码可以替换原有的文件,从而使恶意的APKJAR或者DEX被加载执行;

漏洞案例

百度定位sdk,加载绕过漏洞

 

漏洞修复

1)对APK继续安装或者加载前,使用APK签名校验方案说明继续APK签名校验;

1)将jardex存放在应用私有目录,同时在加载执行JAR/DEX时,进行RSA签名校验;

2jardex等文件下载更新,使用插件SO库等第三方文件安全更新方案进行安全更新下载;

其他说明

在进行签名校验之前,禁止加载执行JAR\APK\DEX的任何方法,包括newInstace()即构造方法。

1.3WebView远程命令执行漏洞

漏洞产生原因

1)在API level>=17的系统上通过webview.addJavascriptInterface()方法,添加高危接口使得JS可以调用获取并执行,如命令执行接口、获取用户敏感信息等等,但是该JS交互接口,并没有对允许加载调用的url进行限制,导致攻击者可以远程调用高危接口;

2Android API Level16及其之前的版本存在远程代码执行安全漏洞,该漏洞来源于Android系统没有对JS可调用的方法进行限制。而应用一旦使用WebView,没有移除系统默认的JS交互接口(searchBoxJavaBridge_),并且开启JS执行能力时,便存在远程命令执行漏洞。攻击者可以通过“JS代码访问Java方法对象来突破限制,通过反射执行任意Java对象的方法。简单的说就是通过Android系统给WebView加入一个JavaScript桥接接口(searchBoxJavaBridge)JavaScript通过调用这个接口可以直接操作本地的JAVA接口,并且通过反射可以调用系统中任意方法。该漏洞最早公布于CVE-2012-66361】,其描述了WebView中导致的远程代码执行安全漏洞。

Android API Level 17之后,Android系统规定被JS对象调用的方法必须使用@JavascriptInterface关键字申请,虽然做了限制,但是很多开发者还是将很多更能强大的API申明给JS对象调用,导致可以为恶意JS调用执行。

漏洞演示1

漏洞演示2

mWebView = new WebView(this);

mWebView.getSettings().setJavaScriptEnabled(true);

mWebView.loadUrl("file:///android_asset/www/index.html");

攻击的index.html代码(远程发送短信):

 

漏洞案例

http://www.wooyun.org/bugs/wooyun-2010-0195499

百度手机助手:http://www.wooyun.org/bugs/wooyun-2010-0194107

北京移动:http://www.wooyun.org/bugs/wooyun-2010-0193589

漏洞修复:

使用安全编码库中的方案:安全webview,方案详情见:Android应用安全编码方案使用

其他说明:

暂无


1.4SQL注入漏洞

漏洞产生的原因:

APP或者网站页面没有对输入的SQL语句进行过滤,从而造成攻击者把SQL命令插入到Web表单、appSQL查询语句以及页面请求的查询字符串中,最终达到欺骗服务器执行恶意SQL的命令。具体来说,他是利用现有应用程序,将(恶意)SQL命令插入到后台数据库引擎执行的能力从而得到数据库的相关信息,造成数据库的信息泄露。

漏洞案例:

乐视网APPhttp://www.wooyun.org/bugs/wooyun-2013-042356

万达集团APPhttp://www.wooyun.org/bugs/wooyun-2010-0111868

漏洞修复:

1)对用户输入进行校验,可以通过正则表达式或限制长度,或者对单引号和双“-”进行转换。

2)不要动态拼装SQL,使用参数化的SQL或者直接使用存储过程进行数据查询存取。

3)不要使用管理员权限连接数据库,为每个数据库建立单独的连接以及,增删改查的权限;

4)不要把机密信息明文存放,加密或者hash掉密码和敏感的信息。

5)应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误消息进行包装,把异常信息放在独立的表中。

其他说明:

1.5WebView File域同源策略绕过,造成应用本地信息泄露

漏洞产生原因:

应用开发者在WebView中开启了File协议(默认是允许的),并且在访问file协议时,没有禁用JavaScript脚本的执行,在这种情况下浏览器会访问自己的私有文件,包括Cookie,浏览记录等等,但是无法通过JS代码发送;在上面的基础上,如果WebView中开启了setAllowFileAccessFromFileURLs(true)或者setAllowUniversalAccessFromFileURLs(true)方法,则会通过JS代码发送浏览器的隐私数据。setAllowFileAccessFromFileURLs可以设置是否允许通过file url加载的JS读取其他的本地文件;setAllowUniversalAccessFromFileURLs可以设置是否允许通过file url加载的JS访问其他的源,包括其他文件和httphttps等其他的源。这两个方法在Android4.1(API 16)以前是默认开启的,4.1及其以后系统上关闭了。另外JS代码访问本地文件是通过XMLHttpRequest对象进行的,所以XMLHttpRequest对象不仅可以请求xmlhttp请求,还可以访问file协议,读取本地文件。但是在4.1及其以后的系统上可以使用软连接绕过同源浏览器策略,窃取应用内私有目录文件。

漏洞演示:

漏洞版本1(此种情况可以通过JS代码发送浏览器的私有数据)

 

webView.getSettings().setJavaScriptEnabled(true);  

webView.addJavascriptInterface(new JSInterface(), "jsInterface");  

webView.getSettings().setAllowFileAccessFromFileURLs(true); 

 

漏洞版本2

 

webView.getSettings().setJavaScriptEnabled(true);  

webView.addJavascriptInterface(new JSInterface(), "jsInterface");  

//攻击者发送(恶意HTML)file文件的url,让webview加载,恶意的HTML代码如下。

//恶意Html中包含的JS功能就是,8秒后读取所在Html页面的内容

public final static String HTML =

  

  
        "" +

  

  
                "Wait a few seconds." +

  

  
                ""+

  

  
                "";

 

//攻击者APP

//删除A.html,并创建软连接A.html指向浏览器内的私有Cookie文件;

cmdexec("mkdir " + MY_TMP_DIR);

  

  
cmdexec("echo \"" + HTML + "\" > " + HTML_PATH);

  

  
cmdexec("chmod -R 777 " + MY_TMP_DIR);

  

  
Thread.sleep(1000);

  

  
invokeVulnAPP("file://" + HTML_PATH);

  

  
Thread.sleep(6000);

  

  
cmdexec("rm " + HTML_PATH);

  

  
cmdexec("ln -s " + "/data/data/应用内私有文件" + " " + HTML_PATH);

//当webview再次加载原html界面时,A.html(页面已经删除)指向本应用内的私有文件了,而上面的JS恶意代码还是在系统的JS引擎中的等待执行的,8秒后,JS恶意代码再去执行时,就会绕过同源策略,读取指向的应用内私有文件,从而完成隐私文件的窃取。

 

漏洞案例:

360手机浏览器:http://www.wooyun.org/bugs/wooyun-2013-037836

赶集网APPhttp://www.wooyun.org/bugs/wooyun-2010-0119312

苏宁易购:http://www.wooyun.org/bugs/wooyun-2010-0107132

漏洞修复:

使用安全编码库中的方案:安全webview,方案详情见:Android应用安全编码方案使用

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

漏洞产生原因:

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

漏洞演示:

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(); // Ignore SSL certificate errors

     }

});

漏洞案例

漏洞修复

使用安全编码库中的方案:安全webview,方案详情见:Android应用安全编码方案使用

1.7Fragment注入漏洞

漏洞产生原因

AndroidPreferenceActivity可以接受外部的Intent的参数进行动态加载Fragment。如果一个Activity继承了PreferenceActivity,并且export=true,那么攻击者可以通过构造Intent调用这个Activity,从而突破限制让动态加载敏感的或者涉及到安全的Fragment

/frameworks/base/core/java/android/preference/PreferenceActivity.java漏洞代码

mSinglePane = hidingHeaders || !onIsMultiPane();

String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);

Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);

int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);

int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);

获取到上面中的参数initialFragmentinitialArguments后,在switchToHeaderInner里完成Fragment的动态加载,

private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {

        getFragmentManager().popBackStack(BACK_STACK_PREFS,

                FragmentManager.POP_BACK_STACK_INCLUSIVE);

        Fragment f = Fragment.instantiate(this, fragmentName, args);

        FragmentTransaction transaction = getFragmentManager().beginTransaction();

        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);

        transaction.replace(com.android.internal.R.id.prefs, f);

        transaction.commitAllowingStateLoss();

    }

到此为止,我们可以通过设置Intentextral,实现动态修改PreferencesActivity的初次显示的Fragment

漏洞演示

SettingAPP几乎每个Android设备都有的。Setting是以system_uid方式签名,所以具备行使system的权力。它的主界面com.android.settings.Settings就是继承自PreferenceActivity,而且肯定是exported。我们以此作为入口,尝试寻找Setting里有哪些重要的Fragment,并尝试把它加载进来,主要目的是希望可以跳过某些需要用户交互的限制。比如说ChooseLockPassword$ChooseLockPasswordFragment这个Fragment,这个类主要是负责锁屏界面的密码设定和修改。同时,这个类会根据之前传入的initialArguments做不同的逻辑,关键代码如下所示:

Intent intent = getActivity().getIntent();  

           final boolean confirmCredentials = intent.getBooleanExtra("confirm_credentials", true);  

           if (savedInstanceState == null) {  

               updateStage(Stage.Introduction);  

               if (confirmCredentials) {  

                   mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,  

                           null, null);  

               }  

           } else {  

               mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);  

               final String state = savedInstanceState.getString(KEY_UI_STAGE);  

               if (state != null) {  

                   mUiStage = Stage.valueOf(state);  

                   updateStage(mUiStage);  

               }  

           }  

如果传入的参数当中,key"confirm_credentials"true,就会调起旧密码验证的流程。如果为false,就可以跳过旧密码验证而直接进入密码修改的流程。测试代码如下所示:

Intent intent = new Intent();  

        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);  

        intent.setClassName("com.android.settings", "com.android.settings.Settings");  

        intent.putExtra(":android:show_fragment", "com.android.settings.ChooseLockPassword$ChooseLockPasswordFragment");  

        intent.putExtra("confirm_credentials", false);  

        startActivity(intent);

运行测试Demo,成功打开设置新PINFragment界面(不需要知道原来的PIN密码),输入密码后可以覆盖原来的旧密码。

 

漏洞案例

暂无

漏洞修复

Android4.4及以上版本进行了修复,修复方式如下:

private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {

        getFragmentManager().popBackStack(BACK_STACK_PREFS,

                FragmentManager.POP_BACK_STACK_INCLUSIVE);

        if (!isValidFragment(fragmentName)) {      //修复代码

            throw new IllegalArgumentException("Invalid fragment for this activity: "

                    + fragmentName);

        }

        Fragment f = Fragment.instantiate(this, fragmentName, args);

        FragmentTransaction transaction = getFragmentManager().beginTransaction();

        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);

        transaction.replace(com.android.internal.R.id.prefs, f);

        transaction.commitAllowingStateLoss();

    }

protected boolean isValidFragment(String fragmentName) {

        if (getApplicationInfo().targetSdkVersion  >= android.os.Build.VERSION_CODES.KITKAT) {

            throw new RuntimeException(

                    "Subclasses of PreferenceActivity must override isValidFragment(String)"

                    + " to verify that the Fragment class is valid! " + this.getClass().getName()

                    + " has not checked if fragment " + fragmentName + " is valid.");

        } else {

            return true;

        }

    }

简单来说,就是在外部要调用PreferenceActivity的子类进行动态加载Fragment时,调用isValidFragment方法来判断要加载的Fragment是否合法。所以PreferenceActivity的子类必须要覆盖重写isValidFragment方法,因为该方法在4.4一下返回true4.4及其以上抛出异常(不动态加载Fragment,并不会触发调用isValidFragment方法)

Android4.4以下需要手动判断Fragment是否合法。

综上,通用的修复方案如下:

public final class MyPreferenceActivity extends PreferenceActivity {

         private boolean doValidcheck(String fragmentName) throws IllegalArgumentException{

                   //TODO 做合法性检查

                   return true;

         }

         //添加上这个方法,以使2.x~4.3的代码在4.4上可以正常运行

         protected boolean isValidFragment(String fragmentName) {

                   return doValidcheck(fragmentName);

         }

         @Override

         protected void onCreate(Bundle savedInstanceState) {

                   //在onCreate前就做合法性判断

                   String fragmentname = getIntent().getStringExtra(":android:show_fragment");

                   if(doValidcheck(fragmentname)){

super.onCreate(savedInstanceState);

              //检查合法,可以加载Fragment

}

         }

}

其他说明

1.8HttpHttps通信空验证、没有校验或者允许所有服务器证书(中间人劫持漏洞)

漏洞产生原因

App或者其他客户端在跟服务器进行httpHttps通信时,没有验证验证服务器的证书,因此攻击者就能与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容(中间人攻击)。该攻击方式类似于“WebView组件忽略SSL证书验证错误漏洞,但又有不同,前者是客户端和服务器进行通信,后者是WebView组件访问Https服务器。

漏洞演示

漏洞方式一:

public class HttpsTestActivity extends Activity {

  private TextView text;

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        text=(TextView)findViewById(R.id.textView1);

        GetHttps();

    }

    private void GetHttps(){

    String https = "https://www.google.com.hk";

        try{

           SSLContext sc = SSLContext.getInstance("TLS");

            sc.init(null, new TrustManager[]{new MyTrustManager()}, new SecureRandom());

            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            HttpsURLConnection.setDefaultHostnameVerifier(new MyHostnameVerifier());

            HttpsURLConnection conn = (HttpsURLConnection)new URL(https).openConnection();

            conn.setDoOutput(true);

            conn.setDoInput(true);

            conn.connect();

            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));

            StringBuffer sb = new StringBuffer();

            String line;

            while ((line = br.readLine()) != null)

                sb.append(line);               

            text.setText(sb.toString());

           }catch(Exception e){

                Log.e(this.getClass().getName(), e.getMessage());

           }     

     }

      private class MyHostnameVerifier implements HostnameVerifier{

            @Override

            public boolean verify(String hostname, SSLSession session) {

                    // TODO Auto-generated method stub

                    return true;

            }

       }

       private class MyTrustManager implements X509TrustManager{

            @Override

            public void checkClientTrusted(X509Certificate[] chain, String authType)

                            throws CertificateException {

                    // TODO Auto-generated method stub 

            }

            @Override

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

                            throws CertificateException {

                    // TODO Auto-generated method stub   

            }

            @Override

            public X509Certificate[] getAcceptedIssuers() {

                    // TODO Auto-generated method stub

                    return null;

            }       

      }  

}

上面这段代码,虽然定义了Host域校验和TrustManager,但是并没有实现HostnameVerifier. Verify方法以及X509TrustManager. checkServerTrusted方法,这两方法的作用是:校验要访问的服务器域名跟证书中的授权域名是否一致,校验服务器证书是否是合法的CA证书。Https通信的安全保障就是这两个方法,哪一个方法出现问题,Https都可能会被中间人劫持。另外在进行Https通信时这两个方法必须被实现,否则会报异常,无法完成通信。

漏洞方式二:

/*其他代码类似,不管证书校验方法是否实现*/

HostnameVerifier.setHostnameVerifier (ALLOW_ALL_HOSTNAME_VERIFIER);

上面这段代码,不校验客户端访问的服务器域名是否跟服务器证书中一致,客户端一律允许访问。在进行Https通信时,HostnameVerifier.setHostnameVerifier方法必须执行实现,如果否则会出错

漏洞方式三:

// Open SSLSocket directly to gmail.com

SocketFactory sf = SSLSocketFactory.getDefault();

SSLSocket socket = (SSLSocket) sf.createSocket("gmail.com", 443);

InputStream input = socket.getInputStream();

socket.close();

没有验证服务器正式是否可以通过socket访问gmail.com的域名就开始直接使用Socket。因为SSLSocket默认情况下是没有校验服务器域名的合法性的,并且是允许连接的。

漏洞案例

暂无。

漏洞修复

1)所有产品使用Https,并且严格校验Https的证书,公司的证书是合法签发的证书,不需要走自定义证书校验流程,系统默认就会校验,不要主动忽略证书错误都行;

2)如果是自定义证书,请参考如下修复方案:

对于漏洞一和漏洞二,修复方式就是在实现HostnameVerifier. Verify方法以及X509TrustManager. checkServerTrusted方法,严格校验服务器的证书是否来自CA认证,如果是自定义的证书,则需要添加进信任列表;

将自签名证书添加进信任列表代码如下:

// Load CAs from an InputStream

// (could be from a resource or ByteArrayInputStream or ...)

CertificateFactory cf = CertificateFactory.getInstance("X.509");

// From https://www.washington.edu/itconnect/security/ca/load-der.crt

InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));

Certificate ca;

try {

    ca = cf.generateCertificate(caInput);

    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());

} finally {

    caInput.close();

}

// Create a KeyStore containing our trusted CAs

String keyStoreType = KeyStore.getDefaultType();

KeyStore keyStore = KeyStore.getInstance(keyStoreType);

keyStore.load(null, null);

keyStore.setCertificateEntry("ca", ca);

// Create a TrustManager that trusts the CAs in our KeyStore

String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();

TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);

tmf.init(keyStore);

// Create an SSLContext that uses our TrustManager

SSLContext context = SSLContext.getInstance("TLS");

context.init(null, tmf.getTrustManagers(), null);

 

// Tell the URLConnection to use a SocketFactory from our SSLContext

URL url = new URL("https://certs.cac.washington.edu/CAtest/");

HttpsURLConnection urlConnection =

    (HttpsURLConnection)url.openConnection();

urlConnection.setSSLSocketFactory(context.getSocketFactory());

InputStream in = urlConnection.getInputStream();

copyInputStreamToOutputStream(in, System.out);

HostnameVerifier. Verify的修复代码如下:

// Create an HostnameVerifier that hardwires the expected hostname.

// Note that is different than the URL's hostname:

// example.com versus example.org

HostnameVerifier hostnameVerifier = new HostnameVerifier() {

    @Override

    public boolean verify(String hostname, SSLSession session) {

        HostnameVerifier hv =

            HttpsURLConnection.getDefaultHostnameVerifier();

        return hv.verify("example.com", session);

    }

};

// Tell the URLConnection to use our HostnameVerifier

URL url = new URL("https://example.org/");

HttpsURLConnection urlConnection =

    (HttpsURLConnection)url.openConnection();

urlConnection.setHostnameVerifier(hostnameVerifier);

InputStream in = urlConnection.getInputStream();

copyInputStreamToOutputStream(in, System.out);

漏洞方式三的SSLScoket修复方式如下:

// Open SSLSocket directly to gmail.com

SocketFactory sf = SSLSocketFactory.getDefault();

SSLSocket socket = (SSLSocket) sf.createSocket("gmail.com", 443);

HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();

SSLSession s = socket.getSession();

 

// Verify that the certicate hostname is for mail.google.com

// This is due to lack of SNI support in the current SSLSocket.

if (!hv.verify("mail.google.com", s)) {

    throw new SSLHandshakeException("Expected mail.google.com, "

                                    "found " + s.getPeerPrincipal());

}

 

// At this point SSLSocket performed certificate verificaiton and

// we have performed hostname verification, so it is safe to proceed.

 

// ... use socket ...

socket.close();

其他说明

为了检测互联网设备和应用程序中的TLSSSL加密漏洞,Google开源了Nogotofail检测工具。对于SSL漏洞可以通过该工具进行检测。

1.9app备份风险

漏洞产生原因

AndroidMannifest.xml文件android:allowBackup=true;一旦一个APP设置了该属性true,并且用户手机打开了USB调试模式,攻击者可以使用adb backupadb restore来进行对应用数据的备份和恢复,该数据包含应用下的私有数据,如Cookie,登陆密码,聊天记录等等。对于支付金融类应用,攻击者可通过此来进行恶意支付、盗取存款等;因此为了安全起见,开发者务必将allowBackup标志值设置为false来关闭应用程序的备份和恢复功能,以免造成信息泄露和财产损失。

漏洞演示

AndroidManifest.xml文件中默认设置allowBackup=true的,所以要自己设定allowBackup=false

漏洞案例

暂无

漏洞修复

application标签下设置allowBackup=false

其他说明

该备份风险来源于adb restore命令,攻击者可以通过adb命令来恢复应用中的私有数据。

1.10Intent泄露用户敏感数据

漏洞产生原因

应用A在创建Intent传递数据给其他Activity时,数据中包含敏感信息,并且没有通过类名的方式,指定具体的接受者,而是通过action方式传递数据和接受者,导致Intent中的敏感数据有可能被其他恶意Activity读取到(恶意APP通过伪造activity接受相同的action)。应用A在发送Intent数据时,会弹出窗口,让用户选择哪一个应用接受这个Intent数据,如果用户选择了恶意APP,那么隐私数据接回泄露。

漏洞演示

Intent intent = new Intent();

  

  
intent.setAction("com.daizy.appsecurity");

  

  
intent.putExtra("key", "2222222");

  

  
  //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

   startActivity(intent);

漏洞案例:

暂无

漏洞修复

通过类名来制定具体的Intent数据处理Activity,或者发送Intent数据增加权限限制。

其他说明

网上说通过addFlags设置了FLAG_ACTIVITY_NEW_TASK,这会让Activity运行在一个新的Task中,新的Task中启动的Activity,很有可能被第三方的Activity劫持并获取到Intent内容。进过测试,如论是增加FLAG_ACTIVITY_NEW_TASK参数,还是内部activity接收的action,只要Intent没有设置具体的类名或者增加权限限制,都会弹窗让用户选择。

1.11广播信息泄露风险

漏洞产生原因

使用sendbroadcastsendorderbroadcast以及sendstickybroadcast时,没有控制好权限和隐私数据,导致攻击者可以伪造Receiver进行监听,从而窃取到敏感、隐私数据,甚至串改和终止广播。

漏洞演示

隐式意图发送敏感信息:

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);

}

接收POC

public class BcReceiv extends BroadcastReceiver {

  @Override

  public void onReceive(Context context, Intent intent){

 

    String s = null;

    if (intent.getAction().equals("com.sample.action.server_running")){

      String pwd = intent.getStringExtra("connected");

      s = "Airdroid  => [" + pwd + "]/" + intent.getExtras();

    }

    Toast.makeText(context, String.format("%s Received", s),

                   Toast.LENGTH_SHORT).show();

  }

}

 

漏洞案例

WooYun: 百度云盘手机版钓鱼、信息泄露和代码执行高危漏洞三合一

http://www.wooyun.org/bugs/wooyun-2013-039801

WooYun: phone手机系统重启漏洞

http://www.wooyun.org/bugs/wooyun-2010-0511

 

漏洞修复

1Receiver的修复

1)确定receiver组件是否需要接收外部的action,如果不需要则通过export=‘false’属性,私有广播接收器,并且不配置intent-filter(私有广播依然可以接受同UID的广播)

2)如果Receiver需要被接受外部的Action,则在Receiver标签中定义permission,规定该Receiver接收action时所需要的权限;

3)如果确实是需要接收任意第三方广播,对广播接收器里的Intent所包含的数据进行验证,判断是否包含恶意以及相关非法内容。

4)内部APP之间的Receiver,增加标签:protectionLevel='signature'验证其是否真是内部app

2、发送广播的漏洞修复

1)如果确定只是应用内部Receiver接受,则通过LocalBroadcastManager.sendBroadcast(intent)发送;

2)如果不是应用内接受,则使用显示意图指定广播接收器,后者通过setPackage指定包名

3)如果无法指定显示指定,则在发送广播时,增加权限限制,规定接受该广播所需要的权限。

3、通过stickybroadcast发送的广播,不应该包含敏感信息;

4Ordered Broadcast建议设置接收权限receiverPermission,避免恶意应用设置高优先级抢收此广播后并执行abortBroadcast()方法。

其他说明

参考文章:

1http://dalufan.com/2015/01/12/android-BroadCast-Security/

2http://www.vogella.com/tutorials/AndroidBroadcastReceiver/article.html#broadcast-receiver

1.12外部存储使用风险

漏洞产生原因

1、敏感、隐私信息直接或者弱加密存放在SD卡等公有目录上;

2、通过DexClassLoaderSD卡上加载APKJAR进行执行(加载时,会先从APK或者JAR文件中释放出Dex文件至应用私有目录处在加载,在Android4.1.2之前可以释放到公有目录下,4.1.2之后则不可以释放到公共目录下),没有对加载的APKJAR进行签名校验,直接释放Dex文件进行加载执行。

漏洞演示

String sdPath = Environment.getExternalStorageDirectory().toString()+ File.separator + "repll.jar";

DexClassLoader cl = new DexClassLoader(sdPath, this.getFilesDir().getPath(), null, getClassLoader());

漏洞案例

QQ游戏Android客户端漏洞导致任意代码执行和密码泄漏

http://www.wooyun.org/bugs/wooyun-2010-09299

 

漏洞修复

1、对于敏感、私有信息,如账号,隐私相关信息采用加密(RSA非对称加密)或者进行脱敏打码后,保存在应用内私有目录;

2、在私有目录下对APK进行动态加载,并且在加载执行前进行RSA签名校验

其他说明

参考列表:

http://jaq.alibaba.com/blog.htm?id=63

1.13app调试风险

漏洞产生原因

Android应用程序在Manifest.xml文件中设置了android:debuggable="true",意思为该APK允许被外部调试。导致攻击者可以远程调试APK,从而分析程序的实现逻辑。

漏洞演示

 

漏洞案例

漏洞修复

android:debuggable="false",并且在APK里面增加签名判断的过程,防止攻击者反编译APK,更改该属性。

其他说明

APK 设置了android:debuggable="true",表明可以以debug模式运行,然后再代码调试器中(Android Studio或者Eclipse)导入smali代码,进行远程调试。另外即使设置了参数=false,攻击者可以通过反编译修改,所以需要增加签名检查。(即使增加签名检查,攻击者可以串改程序逻辑,逆向和破解难度,取决于攻防手段和双方水平)

参考列表:

http://www.mamicode.com/info-detail-1204762.html

1.14Uri用户敏感信息泄露

漏洞产生原因

URI中包含用户敏感信息,致攻击者可以通过逆向查看到敏感信息,从而可能导致信息泄露

漏洞演示

暂未查到该漏洞示例以及逆向窃取URI中的敏感

漏洞案例

漏洞修复

URI中的敏感数据做加密处理。

其他说明

1.15WebView用户密码明文存储

漏洞产生原因:

在使用WebView的过程中忽略了WebView.setSavePassword这个方法默认值是true,当用户通过WebView.load方法加载url时,如果用户在webview中输入了用户名和密码时并且不设置setSavePassword(false),就会弹出一个对话框询问是否保存密码,一旦用户选择了是,则会被明文保存到应用的数据目录下databases/webview.db中。如果手机被root后就,恶意代码就可以获取到明文保存的密码,造成用户的个人敏感数据泄露。

漏洞演示:

//mWebView.getSettings().setSavePassword(true);

mWebView.loadUrl("http://www.example.com");

漏洞案例:

微信APK在打开微博url时,如果用户输入了账号信息,则会被明文。

http://www.wooyun.org/bugs/wooyun-2013-020246

漏洞修复:

Android API 18之前使用WebView.getSettings().setSavePassword(false);API 18及其以后该方法被google已经官方废除,不存在该问题。

其他说明:

参考列表:

https://developer.android.com/reference/android/webkit/WebSettings.html#setSavePassword(boolean)

https://jaq.alibaba.com/blog.htm?id=89

http://wolfeye.baidu.com/blog/webview-setsavepassword/

1.16权限提升漏洞

漏洞产生原因APP组件或者方法可以被外部调用或通过socket+aidl+剪切板等接受外部参数,在处理输入参数时,没有进行校验和过滤,导致攻击者可以以受害者”APP权限或者系统权限执行相关方法。

漏洞演示

组件对外导出

组件接受外部参数

Intent pollution = this.getIntent();

 

private Class getLaunchClass(Intent pullution){

    String fin = pullution.getStringExtra("Attack_class_name");

    Class res = null;

    try {

        res = Class.forName(fin);

    } catch (ClassNotFoundException e){

        e.printStackTrace();

    }

    return res;

}

// 方法1

if (pollution.getBooleanExtra("Attack_class",false)){

    Class attack = getLaunchClass(pollution);

    if (null != attack){

  v1.setClass(this,attack);

        startActivity(v1);

   

    }

}

//方法2

if (pollution.getBooleanExtra("Attack_class",false)) {

    Intent v2 = new Intent(this, getLaunchClass(pollution));

    startActivity(v2);

}

 

//方法3

if (pollution.getBooleanExtra("Attack_class",false)){

    String packagename = pollution.getStringExtra("packagename");

    String classname = pollution.getStringExtra("classname");

    ComponentName componentname = new ComponentName(packagename,classname);

    Intent v3 = new Intent();

    v3.setComponent(componentname);

    startActivity(v3);

}

 

//方法4

if (pollution.getBooleanExtra("Attack_class",false)){

    Intent v5 = new Intent();

    ComponentName componetname = new ComponentName(this,pollution.getStringExtra("classname"));

    v5.setComponent(componetname);

    startActivity(v5);

}

//方法5

if(pollution.getBooleanExtra("Attack_class",false)){

    Intent v6 = new Intent();

    v6.setClassName(pollution.getStringExtra("packagename"),pollution.getStringExtra("classname"));

    startActivity(v6);

}
 

此外通过socket+aidl+剪切板接受参数并进行起调任意组件也会导致该问题

漏洞案例

搜狗app权限提升(涉及搜索输入法\搜狗号码通\搜狗搜索\搜狗地图)

手百、百度地图任意组件起调

任意第三方应用起调设置密码组件:

漏洞修复

1、组件设置导出为false

2、对Intent接受对数据进行严格过滤建议采用白名单对形式,如Intent白名单拦截

3、起调组件的时候采用startActivityForResult的方式,被调用组件采用getCallingPackage()getCallingActivity()获取调用者的包名,并进行签名校验

1.17应用IPC通信安全

漏洞产生原因:

漏洞之开放端口:应用开发者在APP内实现了一个跟外部通信的Server(Http Server,Socket Server),该Server会监听外部某个端口,该Server会响应外部请求,并根据外部请求做一系列动作。由于Server端缺乏对网络调用者身份或者本地调用者pidpermission等细粒度的安全检查机制,在实现不当的情况下,可以突破Android的沙箱限制,以被攻击应用的权限执行命令,通常出现比较严重的漏洞。

漏洞之不安全Binder通信:BinderAndroid系统跨进程调用的轻量级IPC组件,但是很多APP使用Binder时未对客户端进行身份效验,导致信息泄露或者本地提权,如命令执行等等。

漏洞案例:

高德地图:http://10.114.31.221/static/bugs/wooyun-2015-0114241.html

百度输入法,bindier泄露用户账号,包含budss等:

 

百度虫洞”(大量百度系应用)

漏洞修复:

1、移除私自搭建的Server,通过Binder进行异步通信;

2、使用安全编码方案,在Binder的接口中对调用者进行身份校验和判断;应用IPC通信安全

1.18剪贴板敏感信息泄露风险

漏洞产生原因:

应用开发者使用粘贴板存储敏感信息时,或者APP用户使用拷贝功能拷贝敏感信息时,如账号,密码等内容时,会被窃听。在Android中粘贴板中数据- ClipData是多个应用共享的,所以慎用此功能!此外如果在type属性=password的控件中的密码内容是无法进行拷贝的。

漏洞演示:

//漏洞代码

ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

ClipData clip1 = ClipData.newPlainText("simple text", "Hello, World!");

Uri copyUri = Uri.parse("content://com.example.contacts/copy/lastname");

ClipData clip2 = ClipData.newUri(getContentResolver(), "URI", copyUri);

Intent appIntent = new Intent(this, OtherActivity.class);

ClipData clip3 = ClipData.newIntent("Intent", appIntent);

clipboard.setPrimaryClip(clip1);

clipboard.setPrimaryClip(clip2);

clipboard.setPrimaryClip(clip3);

//窃听代码

ClipboardManager cm = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);

  

  
ClipData cd2 = cm.getPrimaryClip();

  

  
String str = cd2.getItemAt(0).getText().toString();

  

  
Log.i(TAG, "粘贴板内容:" + str);

漏洞案例:

 

漏洞修复:

不用使用粘贴板进行数据的传递,使用Bundle进行。

其他说明:

1.19PendingIntent包含隐式Intent风险

漏洞产生原因

       应用APP中使用PendingIntent传递Intent对象给响应组件时(ActivityServiceReceiver),使用的是隐示发送方式,没有使用显示发送,导致攻击者可以劫持和嗅探intent,导致intent内容泄露。

漏洞演示

Intent intent = new Intent("com.daizy.appsecurity");

  

  
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);

  

  
Notification notice=new Notification.Builder(this).setContentTitle("通知").setContentText("test").setContentIntent(pi).build();

  

  
NotificationManager manager=(NotificationManager)getSystemService(this.NOTIFICATION_SERVICE);

  

  
manager.notify(0,notice);

漏洞案例

 

漏洞修复:

使用PendingIntent时,建议使用显示Intent,明确Intent的处理对象,避免Intent被劫持。

1.20ContentProvider组件对外暴露,并且未设置权限

漏洞产生原因ContentProviderAndroid APP中进行数据存储的组件,如果设置对外,并且没有添加权限设置,造成风险就是攻击者可以窃取应用的数据

漏洞演示

 

漏洞案例

 

漏洞修复

ContentProvider没有必要对外,则手动关闭设置 provider export=false;如果provider export=true,需要添加android:permission限制,该permission限制的是读、写,如果没有添加permission,则需要分别添加readPermissionWritePermission进行限制,并且该签名最好是签名级别的权限限制。

1.21内网信息泄露

漏洞产生原因:应用中包含公司内网信息,内网信息泄露后有助于攻击者了解公司的网络架构

漏洞修复

移除内网信息

1.22Android路径穿越漏洞,造成任意代码执行

漏洞产生原因

       因为ZIP压缩包文件中允许存在“../”的字符串,攻击者可以利用多个“../”在解压时改变ZIP包中某个文件的存放位置,覆盖掉应用原有的文件。如果被覆盖掉的文件是动态链接sodex或者odex文件,轻则产生本地拒绝服务漏洞,影响应用的可用性,重则可能造成任意代码执行漏洞,危害用户的设备安全和信息安全。比如近段时间发现的寄生兽漏洞、海豚浏览器远程命令执行漏洞、三星默认输入法远程代码执行漏洞等都与ZIP文件目录遍历有关。

       Linux/Unix系统中“../”代表的是向上级目录跳转,有些程序在当前工作目录中处理到诸如用“../../../../../../../../../../../etc/hosts”表示的文件,会跳转出当前工作目录,跳转到到其他目录中。 
Java代码在解压ZIP文件时,会使用到ZipEntry类的getName()方法,如果ZIP文件中包含“../”的字符串,该方法返回值里面原样返回,如果没有过滤掉getName()返回值中的“../”字符串,继续解压缩操作,就会在其他目录中创建解压的文件。

漏洞案例:

      盘古爆出来的“ZipperDown漏洞,包含:微博、手百、QQ音乐等等;

漏洞修复

     使用安全编码库中的方案:ZIPFile读取Zip文件的安全,方案详情见:Android应用安全编码方案使用

1.23命令注入执行

漏洞产生原因

       应用中使用Runtime.getRuntime().exec(command);函数进行命令执行,并且exec参数接受外部用户输入,导致攻击者可以注入命令参数,进行任意命令执行。其中常见的外部参数输入点包括:socket、短信、粘贴板、IntentAIDL接口这5部分。

漏洞案例:

      

漏洞修复

     1)移除外部参数传入exec的代码逻辑;

     2)执行的命令参数需要在代码中写死,禁止接受外部输入的参数传入到exec函数中进行任意命令执行;

1.24隐私信息Http明文传输

漏洞产生原因

       应用将用户隐私信息(联系人、电话、短信等),直接通过http明文发送。

漏洞案例:

      

漏洞修复

     1)使用https,并且禁止忽略SSL证书校验;

     2)禁止采集非业务功能逻辑内的用户隐私数据;

 

1.24密钥劫持

漏洞产生原因

       通过加载SD卡上的配置文件,从中获取密钥更新下载url,恶意应用可替换该配置文件并下发恶意的密钥,从而绕过检查如:更新包校验等。

漏洞演示:  

//sdcard上读取密钥下载url配置文件
public void getRsaUrl() {

    FileInputStream v1 = null;

    File v0 = new File(Environment.getExternalStorageDirectory(),"clientupdate_server.cfg");

    if(!v0.exists()){

        return;

    }

    Properties v3 = new Properties();

    FileInputStream v2 = null;

    try {

        v1 = new FileInputStream(v0);

    }catch (Exception e) {

        e.printStackTrace();

    }

  

    try {

        v3.load(v1);

        if(v3.getProperty("server") != null) {

            this.url = String.valueOf(v3.getProperty("server"));

            Log.i(TAG,"设置的server" + this.url);

        }

        if (null == this.url){

            return;

        }

  

    } catch (Exception e) {

        e.printStackTrace();

    }

}

 

//从根据sdcard上的配置文件中的url进行密钥下载和更新
public void downloadPublicKey() {

    if (null == this.url) {

        return;

    }

    try {

        URL cloudRsa = new URL(this.url);

        HttpURLConnection urlConn = (HttpURLConnection) cloudRsa.openConnection();

        urlConn.setRequestMethod("GET");

        urlConn.connect();

        int responseCode = urlConn.getResponseCode();

        if(responseCode == HttpURLConnection.HTTP_OK) {

  

            InputStream v1_3 = ((HttpURLConnection)urlConn).getInputStream();

            StringBuilder v2 = new StringBuilder();

            byte[] v3 = new byte[1024];

            while(true) {

                int v4 = v1_3.read(v3);

                if(v4 == -1) {

                    break;

                }

  

                v2.append(new String(v3, 0, v4, "utf-8"));

            }

  

            this.key = v2.toString();

            v1_3.close();

        }

  

  

    } catch (Exception e){

        e.printStackTrace();

    }

}
// 使用获取的密钥进行rsa加解密
public String RsaDecode(String input) {

    byte[] result = null;

    if(null == this.key) {

        return null;

    }

    byte[] publicBytes = this.key.getBytes();

    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);

    try {

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        PublicKey pubKey = keyFactory.generatePublic(keySpec);

        Cipher RsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

        // 解密

 RsaCipher.init(Cipher.DECRYPT_MODE,pubKey);

  

        result = RsaCipher.doFinal(input.getBytes());

  

    }catch (Exception e) {

        e.printStackTrace();

    }

    return result.toString();

}

漏洞修复

     1)将配置文件放在私有目录下或将云端url写死在代码里;

 

你可能感兴趣的:(android)