美文网首页
Android App 安全的HTTPS 通信

Android App 安全的HTTPS 通信

作者: 紫虹载雪 | 来源:发表于2019-05-17 15:57 被阅读0次

    参考原文@ 我们不生产代码, 只是Bug 的搬运工

    摘要: 漏洞描述 对于数字证书相关概念、Android 里 https 通信代码就不再复述了,直接讲问题。缺少相应的安全校验很容易导致中间人攻击,而漏洞的形式主要有以下3种: 自定义X509TrustManager。

    漏洞描述

    对于数字证书相关概念、Android 里 https 通信代码就不再复述了,直接讲问题。缺少相应的安全校验很容易导致中间人攻击,而漏洞的形式主要有以下3种:

    自定义X509TrustManager。在使用HttpsURLConnection发起 HTTPS 请求的时候,提供了一个自定义的X509TrustManager,未实现安全校验逻辑,下面片段就是常见的容易犯错的代码片段。如果不提供自定义的X509TrustManager,代码运行起来可能会报异常(原因下文解释),初学者就很容易在不明真相的情况下提供了一个自定义的X509TrustManager,却忘记正确地实现相应的方法。本文重点介绍这种场景的处理方式。

    TrustManager tm=newX509TrustManager(){public void checkClientTrusted(X509Certificate[] chain, String authType)            throws CertificateException {//do nothing,接受任意客户端证书}public void checkServerTrusted(X509Certificate[] chain, String authType)            throws CertificateException {//do nothing,接受任意服务端证书}publicX509Certificate[]getAcceptedIssuers(){returnnull;}};sslContext.init(null,newTrustManager[]{tm},null);

    自定义了HostnameVerifier。在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。如果回调内实现不恰当,默认接受所有域名,则有安全风险。代码示例。

    HostnameVerifier hnv=newHostnameVerifier(){@Overridepublic boolean verify(String hostname, SSLSession session) {// Always return true,接受任意域名服务器returntrue;}};HttpsURLConnection.setDefaultHostnameVerifier(hnv);

    信任所有主机名。

    SSLSocketFactory sf=newMySSLSocketFactory(trustStore);sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

    修复方案

    分而治之,针对不同的漏洞点分别描述,这里就讲的修复方案主要是针对非浏览器App,非浏览器 App 的服务端通信对象比较固定,一般都是自家服务器,可以做很多特定场景的定制化校验。如果是浏览器 App,校验策略就有更通用一些。

    自定义X509TrustManager。前面说到,当发起 HTTPS 请求时,可能抛起一个异常,以下面这段代码为例(来自官方文档):

    try{URL url=newURL("https://certs.cac.washington.edu/CAtest/");URLConnection urlConnection=url.openConnection();InputStream in=urlConnection.getInputStream();copyInputStreamToOutputStream(in,System.out);}catch(MalformedURLExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}

    private void copyInputStreamToOutputStream(InputStream in, PrintStream out) throws IOException {byte[]buffer=newbyte[1024];intc=0;while((c=in.read(buffer))!=-1){out.write(buffer,0,c);}}

    它会抛出一个SSLHandshakeException的异常。

    javax.net.ssl.SSLHandshakeException:java.security.cert.CertPathValidatorException:Trust anchorforcertification path not found.at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:322)at com.android.okhttp.Connection.upgradeToTls(Connection.java:201)at com.android.okhttp.Connection.connect(Connection.java:155)at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:276)at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:211)at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:382)at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:332)at com.android.okhttp.internal.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:199)at com.android.okhttp.internal.http.DelegatingHttpsURLConnection.getInputStream(DelegatingHttpsURLConnection.java:210)at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:25)at me.longerian.abcandroid.datetimepicker.TestDateTimePickerActivity$1.run(TestDateTimePickerActivity.java:236)Caused by:java.security.cert.CertificateException:java.security.cert.CertPathValidatorException:Trust anchorforcertification path not found.at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:318)at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:219)at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:114)at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:550)at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:318)...10moreCaused by:java.security.cert.CertPathValidatorException:Trust anchorforcertification path not found....16more

    Android 手机有一套共享证书的机制,如果目标 URL 服务器下发的证书不在已信任的证书列表里,或者该证书是自签名的,不是由权威机构颁发,那么会出异常。对于我们这种非浏览器 app 来说,如果提示用户去下载安装证书,可能会显得比较诡异。幸好还可以通过自定义的验证机制让证书通过验证。验证的思路有两种:

    方案1

    不论是权威机构颁发的证书还是自签名的,打包一份到 app 内部,比如存放在 asset 里。通过这份内置的证书初始化一个KeyStore,然后用这个KeyStore去引导生成的TrustManager来提供验证,具体代码如下:

    try{CertificateFactory cf=CertificateFactory.getInstance("X.509");// uwca.crt 打包在 asset 中,该证书可以从https://itconnect.uw.edu/security/securing-computer/install/safari-os-x/下载InputStream caInput=newBufferedInputStream(getAssets().open("uwca.crt"));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 CAsString 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 KeyStoreString tmfAlgorithm=TrustManagerFactory.getDefaultAlgorithm();TrustManagerFactory tmf=TrustManagerFactory.getInstance(tmfAlgorithm);tmf.init(keyStore);// Create an SSLContext that uses our TrustManagerSSLContext context=SSLContext.getInstance("TLSv1","AndroidOpenSSL");context.init(null,tmf.getTrustManagers(),null);URL url=newURL("https://certs.cac.washington.edu/CAtest/");HttpsURLConnection urlConnection=(HttpsURLConnection)url.openConnection();urlConnection.setSSLSocketFactory(context.getSocketFactory());InputStream in=urlConnection.getInputStream();copyInputStreamToOutputStream(in,System.out);}catch(CertificateExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}catch(NoSuchAlgorithmExceptione){e.printStackTrace();}catch(KeyStoreExceptione){e.printStackTrace();}catch(KeyManagementExceptione){e.printStackTrace();}catch(NoSuchProviderExceptione){e.printStackTrace();}

    这样就可以得到正确的输出内容:

    <html><head><title>UW Services CA Test Page</title></head><body><h2>UW Services CA test page</h2><p><b>QUESTION</b>:Did you arrive here without any security alerts or warnings?</p><ul><p><li><b>YES</b>- This test page uses a certificate issued by theUW Services Certificate Authority. If you reached this pagewithout any alerts or warnings from your browser, youhave successfully installed the UW Services CA Certificateinto your browser.<p><li><b>NO</b>- If your browser warned you about the validity of thistest page's security certificate, or the certificateauthority is unrecognized, you may not have successfullyinstalled the UW Services CA Certificate.<p></ul><form action="https://www.washington.edu/computing/ca/" method=get><input type=submit value="Return to the Install Page"></form></body></html>

    如果你用上述同样的代码访问 https://www.taobao.com/ 或者 https://www.baidu.com/ ,则会抛出那个SSLHandshakeException异常,也就是说对于特定证书生成的TrustManager,只能验证与特定服务器建立安全链接,这样就提高了安全性。如之前提到的,对于非浏览器 app 来说,这是可以接受的。

    方案2

    同方案1,打包一份到证书到 app 内部,但不通过KeyStore去引导生成的TrustManager,而是干脆直接自定义一个TrustManager,自己实现校验逻辑;校验逻辑主要包括:

    服务器证书是否过期

    证书签名是否合法

    try{CertificateFactory cf=CertificateFactory.getInstance("X.509");// uwca.crt 打包在 asset 中,该证书可以从https://itconnect.uw.edu/security/securing-computer/install/safari-os-x/下载InputStream caInput=newBufferedInputStream(getAssets().open("uwca.crt"));finalCertificate ca;try{ca=cf.generateCertificate(caInput);Log.i("Longer","ca="+((X509Certificate)ca).getSubjectDN());Log.i("Longer","key="+((X509Certificate)ca).getPublicKey());}finally{caInput.close();}// Create an SSLContext that uses our TrustManagerSSLContext context=SSLContext.getInstance("TLSv1","AndroidOpenSSL");context.init(null,newTrustManager[]{newX509TrustManager(){@Overridepublic void checkClientTrusted(X509Certificate[] chain,                      String authType)                      throws CertificateException {}@Overridepublic void checkServerTrusted(X509Certificate[] chain,                      String authType)                      throws CertificateException {for(X509Certificate cert:chain){// Make sure that it hasn't expired.cert.checkValidity();// Verify the certificate's public key chain.try{cert.verify(((X509Certificate)ca).getPublicKey());}catch(NoSuchAlgorithmExceptione){e.printStackTrace();}catch(InvalidKeyExceptione){e.printStackTrace();}catch(NoSuchProviderExceptione){e.printStackTrace();}catch(SignatureExceptione){e.printStackTrace();}}}@OverridepublicX509Certificate[]getAcceptedIssuers(){returnnewX509Certificate[0];}}},null);URL url=newURL("https://certs.cac.washington.edu/CAtest/");HttpsURLConnection urlConnection=(HttpsURLConnection)url.openConnection();urlConnection.setSSLSocketFactory(context.getSocketFactory());InputStream in=urlConnection.getInputStream();copyInputStreamToOutputStream(in,System.out);}catch(CertificateExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}catch(NoSuchAlgorithmExceptione){e.printStackTrace();}catch(KeyManagementExceptione){e.printStackTrace();}catch(NoSuchProviderExceptione){e.printStackTrace();}

    同样上述代码只能访问 certs.cac.washington.edu 相关域名地址,如果访问 https://www.taobao.com/ 或者 https://www.baidu.com/ ,则会在cert.verify(((X509Certificate) ca).getPublicKey());处抛异常,导致连接失败。

    自定义HostnameVerifier,简单的话就是根据域名进行字符串匹配校验;业务复杂的话,还可以结合配置中心、白名单、黑名单、正则匹配等多级别动态校验;总体来说逻辑还是比较简单的,反正只要正确地实现那个方法。

    HostnameVerifier hnv=newHostnameVerifier(){@Overridepublic boolean verify(String hostname, SSLSession session) {//示例if("yourhostname".equals(hostname)){returntrue;}else{HostnameVerifier hv=HttpsURLConnection.getDefaultHostnameVerifier();returnhv.verify(hostname,session);}}};

    主机名验证策略改成严格模式

    SSLSocketFactory sf=newMySSLSocketFactory(trustStore);sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

    参考资料

    窃听风暴: Android平台https嗅探劫持漏洞

    Android证书信任问题与大表哥

    Android HTTPS中间人劫持漏洞浅析

    数字证书及其在安全测试中的应用

    wooyun-2014-080117

    WooYun-2014-79358

    SSL证书百科

    Security with HTTPS and SSL

    为你的安卓应用实现自签名的 SSL 证书

    Android HTTPS SSL双向验证

    DRD19. Properly verify server certificate on SSL/TLS

    版权声明:本文内容由互联网用户自发贡献,版权归作者所有,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:yqgroup@service.aliyun.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

    相关文章

      网友评论

          本文标题:Android App 安全的HTTPS 通信

          本文链接:https://www.haomeiwen.com/subject/husoaqtx.html