1、场景
项目进行等保测试,App扫描出了几项漏洞需要修复:
2、问题分析
根据描述给的建议,应该还是项目使用了https环境部署,App中有访问后台接口,但是客户端没有对服务端证书进行完整性校验暴露出的问题。App中主要是使用okhttp 框架进行接口访问,对应证书校验这块确实没有实现。
3、问题处理
参考Android App 安全的HTTPS 通信 - 简书 文章, 其实主要应该就是在代码中设置对应的校验逻辑即可:
自定义的逻辑:
try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); // uwca.crt 打包在 asset 中,该证书可以从https://itconnect.uw.edu/security/securing-computer/install/safari-os-x/下载 InputStream caInput = new BufferedInputStream(mContext.getAssets().open("xxx.cer")); Certificate ca; try { ca = cf.generateCertificate(caInput); Log.i("Longer", "ca=" + ((X509Certificate) ca).getSubjectDN()); Log.i("Longer", "key=" + ((X509Certificate) ca).getPublicKey()); } 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("TLSv1","AndroidOpenSSL"); context.init(null, tmf.getTrustManagers(), null); HostnameVerifier hnv = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { //示例 if("yourhostname".equals(hostname)){ return true; } else { HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); boolean verify = hv.verify(hostname, session); System.out.println("=====verify="+verify); return verify; } } }; URL url = new URL("https://xxx.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); urlConnection.setSSLSocketFactory(context.getSocketFactory()); urlConnection.setHostnameVerifier(hnv); InputStream in = urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out); //使用okhttp 来访问 Request request = new Request.Builder().url("https://xxx..com/").get().build(); // 获取X509TrustManager X509TrustManager x509TrustManager = null; for (javax.net.ssl.TrustManager tm : tmf.getTrustManagers()) { if (tm instanceof X509TrustManager) { x509TrustManager = (X509TrustManager)tm; } } OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.newBuilder().sslSocketFactory(context.getSocketFactory(), x509TrustManager) .hostnameVerifier(hnv); Call call= okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println("response is "+response.body()); } }); } catch (CertificateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); }
其中打包到App中的这个证书,不要使用后台服务器上的pem 公钥证书,而是要使用CA 根证书,不然依然会报错:
在调试完之后,我发现自己的代码接口中都是使用了默认的OKHttp 接口访问方式,并没有进行特殊设置自定义的,这种默认的设置都是会进行校验的,理论上说不存在这种漏洞,后面发现代码中有一个没有使用的类:
package com.hnac.utils; import java.security.KeyStore; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.Arrays; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; public class SSLSocketClient { //获取这个SSLSocketFactory public static SSLSocketFactory getSSLSocketFactory() { try { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, getTrustManager(), new SecureRandom()); return sslContext.getSocketFactory(); } catch (Exception e) { throw new RuntimeException(e); } } //获取TrustManager private static TrustManager[] getTrustManager() { return new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; } } }; } //获取HostnameVerifier public static HostnameVerifier getHostnameVerifier() { return (s, sslSession) -> true; } public static X509TrustManager getX509TrustManager() { X509TrustManager trustManager = null; try { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init((KeyStore) null); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); } trustManager = (X509TrustManager) trustManagers[0]; } catch (Exception e) { e.printStackTrace(); } return trustManager; } }
这个应该是之前为了解决每次证书校验报错,找到的一个直接信任所有,不进行证书校验而增加的,代码虽然没用,但是可以漏洞扫描工具扫到这个问题,所以这个问题估计删除掉这个就可以解决了,等待后续验证。