漏洞产生原因:
应用开发者在将应用或者用户的相关信息直接明文,或者简单加密(可逆的)保存在应用私有目录下,并没有设置权限,造成全局可读,从而泄露相关隐私信息。
漏洞案例:
百度云:http://www.wooyun.org/bugs/wooyun-2010-0101468
漏洞修复:
改变文件的权限,移除全局可读、可写的权限,只能应用自己可读可写。
漏洞产生原因:
安装APK、或者应用在动态加载运行Dex、JAR或者APK时,并没有对DEX、JAR或者APK进行签名校验,导致恶意代码可以替换原有的文件,从而使恶意的APK、JAR或者DEX被加载执行;
漏洞案例:
百度定位sdk,加载绕过漏洞
漏洞修复:
1)对APK继续安装或者加载前,使用APK签名校验方案说明继续APK签名校验;
1)将jar、dex存放在应用私有目录,同时在加载执行JAR/DEX时,进行RSA签名校验;
2)jar、dex等文件下载更新,使用插件SO库等第三方文件安全更新方案进行安全更新下载;
其他说明:
在进行签名校验之前,禁止加载执行JAR\APK\DEX的任何方法,包括newInstace()即构造方法。
漏洞产生原因:
1)在API level>=17的系统上通过webview.addJavascriptInterface()方法,添加高危接口使得JS可以调用获取并执行,如命令执行接口、获取用户敏感信息等等,但是该JS交互接口,并没有对允许加载调用的url进行限制,导致攻击者可以远程调用高危接口;
2)Android API Level16及其之前的版本存在远程代码执行安全漏洞,该漏洞来源于Android系统没有对JS可调用的方法进行限制。而应用一旦使用WebView,没有移除系统默认的JS交互接口(searchBoxJavaBridge_),并且开启JS执行能力时,便存在远程命令执行漏洞。攻击者可以通过“JS代码访问Java方法对象”来突破限制,通过反射执行任意Java对象的方法。简单的说就是通过Android系统给WebView加入一个JavaScript桥接接口(searchBoxJavaBridge),JavaScript通过调用这个接口可以直接操作本地的JAVA接口,并且通过反射可以调用系统中任意方法。该漏洞最早公布于CVE-2012-6636【1】,其描述了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应用安全编码方案使用
其他说明:
暂无
漏洞产生的原因:
APP或者网站页面没有对输入的SQL语句进行过滤,从而造成攻击者把SQL命令插入到Web表单、app的SQL查询语句以及页面请求的查询字符串中,最终达到欺骗服务器执行恶意SQL的命令。具体来说,他是利用现有应用程序,将(恶意)的SQL命令插入到后台数据库引擎执行的能力从而得到数据库的相关信息,造成数据库的信息泄露。
漏洞案例:
乐视网APP:http://www.wooyun.org/bugs/wooyun-2013-042356
万达集团APP:http://www.wooyun.org/bugs/wooyun-2010-0111868
漏洞修复:
1)对用户输入进行校验,可以通过正则表达式或限制长度,或者对单引号和双“-”进行转换。
2)不要动态拼装SQL,使用参数化的SQL或者直接使用存储过程进行数据查询存取。
3)不要使用管理员权限连接数据库,为每个数据库建立单独的连接以及,增删改查的权限;
4)不要把机密信息明文存放,加密或者hash掉密码和敏感的信息。
5)应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误消息进行包装,把异常信息放在独立的表中。
其他说明:
漏洞产生原因:
应用开发者在WebView中开启了File协议(默认是允许的),并且在访问file协议时,没有禁用JavaScript脚本的执行,在这种情况下浏览器会访问自己的私有文件,包括Cookie,浏览记录等等,但是无法通过JS代码发送;在上面的基础上,如果WebView中开启了setAllowFileAccessFromFileURLs(true)或者setAllowUniversalAccessFromFileURLs(true)方法,则会通过JS代码发送浏览器的隐私数据。setAllowFileAccessFromFileURLs可以设置是否允许通过file url加载的JS读取其他的本地文件;setAllowUniversalAccessFromFileURLs可以设置是否允许通过file url加载的JS访问其他的源,包括其他文件和http,https等其他的源。这两个方法在Android4.1(API 16)以前是默认开启的,在4.1及其以后系统上关闭了。另外JS代码访问本地文件是通过XMLHttpRequest对象进行的,所以XMLHttpRequest对象不仅可以请求xml,http请求,还可以访问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
赶集网APP:http://www.wooyun.org/bugs/wooyun-2010-0119312
苏宁易购:http://www.wooyun.org/bugs/wooyun-2010-0107132
漏洞修复:
使用安全编码库中的方案:安全webview,方案详情见:Android应用安全编码方案使用
漏洞产生原因:
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应用安全编码方案使用
漏洞产生原因:
Android的PreferenceActivity可以接受外部的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); |
获取到上面中的参数initialFragment和initialArguments后,在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(); } |
到此为止,我们可以通过设置Intent的extral,实现动态修改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,成功打开设置新PIN的Fragment界面(不需要知道原来的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一下返回true,4.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 } } } |
其他说明:
漏洞产生原因:
App或者其他客户端在跟服务器进行http或Https通信时,没有验证验证服务器的证书,因此攻击者就能与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容(中间人攻击)。该攻击方式类似于“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(); |
其他说明:
为了检测互联网设备和应用程序中的TLS和SSL加密漏洞,Google开源了Nogotofail检测工具。对于SSL漏洞可以通过该工具进行检测。
漏洞产生原因:
AndroidMannifest.xml文件android:allowBackup=true;一旦一个APP设置了该属性true,并且用户手机打开了USB调试模式,攻击者可以使用adb backup和adb restore来进行对应用数据的备份和恢复,该数据包含应用下的私有数据,如Cookie,登陆密码,聊天记录等等。对于支付金融类应用,攻击者可通过此来进行恶意支付、盗取存款等;因此为了安全起见,开发者务必将allowBackup标志值设置为false来关闭应用程序的备份和恢复功能,以免造成信息泄露和财产损失。
漏洞演示:
AndroidManifest.xml文件中默认设置allowBackup=true的,所以要自己设定allowBackup=false;
漏洞案例:
暂无
漏洞修复:
在application标签下设置allowBackup=false
其他说明:
该备份风险来源于adb restore命令,攻击者可以通过adb命令来恢复应用中的私有数据。
漏洞产生原因:
应用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没有设置具体的类名或者增加权限限制,都会弹窗让用户选择。
漏洞产生原因:
使用sendbroadcast、sendorderbroadcast以及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
漏洞修复:
1、Receiver的修复
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发送的广播,不应该包含敏感信息;
4、Ordered Broadcast建议设置接收权限receiverPermission,避免恶意应用设置高优先级抢收此广播后并执行abortBroadcast()方法。
其他说明:
参考文章:
1、http://dalufan.com/2015/01/12/android-BroadCast-Security/
2、http://www.vogella.com/tutorials/AndroidBroadcastReceiver/article.html#broadcast-receiver
漏洞产生原因:
1、敏感、隐私信息直接或者弱加密存放在SD卡等公有目录上;
2、通过DexClassLoader从SD卡上加载APK、JAR进行执行(加载时,会先从APK或者JAR文件中释放出Dex文件至应用私有目录处在加载,在Android4.1.2之前可以释放到公有目录下,4.1.2之后则不可以释放到公共目录下),没有对加载的APK、JAR进行签名校验,直接释放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
漏洞产生原因:
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
漏洞产生原因:
URI中包含用户敏感信息,致攻击者可以通过逆向查看到敏感信息,从而可能导致信息泄露
漏洞演示:
暂未查到该漏洞示例以及逆向窃取URI中的敏感
漏洞案例:
漏洞修复:
对URI中的敏感数据做加密处理。
其他说明:
漏洞产生原因:
在使用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/
漏洞产生原因: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()获取调用者的包名,并进行签名校验
漏洞产生原因:
漏洞之开放端口:应用开发者在APP内实现了一个跟外部通信的Server(Http Server,Socket Server等),该Server会监听外部某个端口,该Server会响应外部请求,并根据外部请求做一系列动作。由于Server端缺乏对网络调用者身份或者本地调用者pid、permission等细粒度的安全检查机制,在实现不当的情况下,可以突破Android的沙箱限制,以被攻击应用的权限执行命令,通常出现比较严重的漏洞。
漏洞之不安全Binder通信:Binder是Android系统跨进程调用的轻量级IPC组件,但是很多APP使用Binder时未对客户端进行身份效验,导致信息泄露或者本地提权,如命令执行等等。
漏洞案例:
高德地图:http://10.114.31.221/static/bugs/wooyun-2015-0114241.html
百度输入法,bindier泄露用户账号,包含budss等:
百度“虫洞”(大量百度系应用)
漏洞修复:
1、移除私自搭建的Server,通过Binder进行异步通信;
2、使用安全编码方案,在Binder的接口中对调用者进行身份校验和判断;应用IPC通信安全
漏洞产生原因:
应用开发者使用粘贴板存储敏感信息时,或者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进行。
其他说明:
漏洞产生原因:
应用APP中使用PendingIntent传递Intent对象给响应组件时(Activity、Service和Receiver),使用的是隐示发送方式,没有使用显示发送,导致攻击者可以劫持和嗅探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被劫持。
漏洞产生原因:ContentProvider是Android APP中进行数据存储的组件,如果设置对外,并且没有添加权限设置,造成风险就是攻击者可以窃取应用的数据
漏洞演示:
漏洞案例:
漏洞修复:
如ContentProvider没有必要对外,则手动关闭设置 provider export=false;如果provider export=true,需要添加android:permission限制,该permission限制的是读、写,如果没有添加permission,则需要分别添加readPermission和WritePermission进行限制,并且该签名最好是签名级别的权限限制。
漏洞产生原因:应用中包含公司内网信息,内网信息泄露后有助于攻击者了解公司的网络架构
漏洞修复:
移除内网信息
漏洞产生原因:
因为ZIP压缩包文件中允许存在“../”的字符串,攻击者可以利用多个“../”在解压时改变ZIP包中某个文件的存放位置,覆盖掉应用原有的文件。如果被覆盖掉的文件是动态链接so、dex或者odex文件,轻则产生本地拒绝服务漏洞,影响应用的可用性,重则可能造成任意代码执行漏洞,危害用户的设备安全和信息安全。比如近段时间发现的“寄生兽”漏洞、海豚浏览器远程命令执行漏洞、三星默认输入法远程代码执行漏洞等都与ZIP文件目录遍历有关。
在Linux/Unix系统中“../”代表的是向上级目录跳转,有些程序在当前工作目录中处理到诸如用“../../../../../../../../../../../etc/hosts”表示的文件,会跳转出当前工作目录,跳转到到其他目录中。
Java代码在解压ZIP文件时,会使用到ZipEntry类的getName()方法,如果ZIP文件中包含“../”的字符串,该方法返回值里面原样返回,如果没有过滤掉getName()返回值中的“../”字符串,继续解压缩操作,就会在其他目录中创建解压的文件。
漏洞案例:
盘古爆出来的“ZipperDown漏洞”,包含:微博、手百、QQ音乐等等;
漏洞修复:
使用安全编码库中的方案:ZIPFile读取Zip文件的安全,方案详情见:Android应用安全编码方案使用
漏洞产生原因:
应用中使用Runtime.getRuntime().exec(command);函数进行命令执行,并且exec参数接受外部用户输入,导致攻击者可以注入命令参数,进行任意命令执行。其中常见的外部参数输入点包括:socket、短信、粘贴板、Intent、AIDL接口这5部分。
漏洞案例:
漏洞修复:
1)移除外部参数传入exec的代码逻辑;
2)执行的命令参数需要在代码中写死,禁止接受外部输入的参数传入到exec函数中进行任意命令执行;
漏洞产生原因:
应用将用户隐私信息(联系人、电话、短信等),直接通过http明文发送。
漏洞案例:
漏洞修复:
1)使用https,并且禁止忽略SSL证书校验;
2)禁止采集非业务功能逻辑内的用户隐私数据;
漏洞产生原因:
通过加载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写死在代码里;