Android SSL通信安全

一、漏洞介绍

1、描述

        中间人攻击时指攻击者与通信的两端建立连接,使得通信双方以为自己在使用安全加密连接进行通信,其实,攻击者已经拦截获取了整个会话的内容,同时,攻击者还可以修改插入新的内容。

        Android HTTPS中间人攻击漏洞源于:1没有对SSL证书进行校验;2没有对域名进行校验;3. 证书颁发机构(Certification Authority)被攻击导致私钥泄露等。攻击者可通过中间人攻击,盗取账户密码明文、聊天内容、通讯地址、电话号码以及信用卡支付信息等敏感信息,甚至通过中间人劫持将原有信息替换成恶意链接或恶意代码程序,以达到远程控制、恶意扣费等攻击意图。

2、影响范围

        所有的Android系统。

3、漏洞原理

        由于客户端没有校验服务端的证书,因此攻击者就能与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。

4、漏洞攻击位置

        X509TrustManager、HostnameVerifer、setHostnameVerifier(X509HostnameVeriier hostnameVerifier)

 

5、漏洞促发条件

l  自定义的X509TrustManager不校验证书;

l  实现的自定义HostnameVerifier不校验域名接受任意域名;

l  使用setHostnameVerifier (ALLOW_ALL_HOSTNAME_VERIFIER);

l  忽略webview证书继续加载

二、样例解析

1、自定义X509TrustManager没有校验证书

2、实现的自定义HostnameVerifier不校验域名接受任意域名

3、使用setHostnameVerifier (ALLOW_ALL_HOSTNAME_VERIFIER)

4、忽略webview证书继续加载

三、漏洞利用

        中间人攻击

四、修复建议

1. 建议使用setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER)

2. 对浏览器app,对SSL证书进行强校验(签名CA是否合法、证书是否是自签名、主机域名是否匹配、证书是否过期等),参考谷歌官方关于SSL的安全建议[4]

3.对于非浏览器的app来说,可以申请CA证书,或者在客户端中添加证书,参考[5]

4.调用WebViewClient类的onReceivedSslError方法,不应该调用proceed()方法忽略证书错误,应该采用默认的处理方法handler.cancel(),停止加载问题页面[6]、[7]

4.一个SSLSocketFactory的例子:

 

java public class SecureSocketFactory extends SSLSocketFactory {
 
    private static final String LOG_TAG = "SecureSocketFactory";
 
    private final SSLContext sslCtx;
    private final X509Certificate[] acceptedIssuers;
 
    /**
     * Instantiate a new secured factory pertaining to the passed store. Be sure to initialize the
     * store with the password using {@link java.security.KeyStore#load(java.io.InputStream,
     * char[])} method.
     *
     * @param store The key store holding the certificate details
     * @param alias The alias of the certificate to use
     */
    public SecureSocketFactory(KeyStore store, String alias)
            throws
            CertificateException,
            NoSuchAlgorithmException,
            KeyManagementException,
            KeyStoreException,
            UnrecoverableKeyException {
 
        super(store);
 
        // Loading the CA certificate from store.
        final Certificate rootca = store.getCertificate(alias);
 
        // Turn it to X509 format.
        InputStream is = new ByteArrayInputStream(rootca.getEncoded());
        X509Certificate x509ca = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
        AsyncHttpClient.silentCloseInputStream(is);
 
        if (null == x509ca) {
            throw new CertificateException("Embedded SSL certificate has expired.");
        }
 
        // Check the CA's validity.
        x509ca.checkValidity();
 
        // Accepted CA is only the one installed in the store.
        acceptedIssuers = new X509Certificate[]{x509ca};
 
        sslCtx = SSLContext.getInstance("TLS");
        sslCtx.init(
                null,
                new TrustManager[]{
                        new X509TrustManager() {
                            @Override
                            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                            }
 
                            @Override
                            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                                Exception error = null;
 
                                if (null == chain || 0 == chain.length) {
                                    error = new CertificateException("Certificate chain is invalid.");
                                } else if (null == authType || 0 == authType.length()) {
                                    error = new CertificateException("Authentication type is invalid.");
                                } else {
                                    Log.i(LOG_TAG, "Chain includes " + chain.length + " certificates.");
                                    try {
                                        for (X509Certificate cert : chain) {
                                            Log.i(LOG_TAG, "Server Certificate Details:");
                                            Log.i(LOG_TAG, "---------------------------");
                                            Log.i(LOG_TAG, "IssuerDN: " + cert.getIssuerDN().toString());
                                            Log.i(LOG_TAG, "SubjectDN: " + cert.getSubjectDN().toString());
                                            Log.i(LOG_TAG, "Serial Number: " + cert.getSerialNumber());
                                            Log.i(LOG_TAG, "Version: " + cert.getVersion());
                                            Log.i(LOG_TAG, "Not before: " + cert.getNotBefore().toString());
                                            Log.i(LOG_TAG, "Not after: " + cert.getNotAfter().toString());
                                            Log.i(LOG_TAG, "---------------------------");
 
                                            // Make sure that it hasn't expired.
                                            cert.checkValidity();
 
                                            // Verify the certificate's public key chain.
                                            cert.verify(rootca.getPublicKey());
                                        }
                                    } catch (InvalidKeyException e) {
                                        error = e;
                                    } catch (NoSuchAlgorithmException e) {
                                        error = e;
                                    } catch (NoSuchProviderException e) {
                                        error = e;
                                    } catch (SignatureException e) {
                                        error = e;
                                    }
                                }
                                if (null != error) {
                                    Log.e(LOG_TAG, "Certificate error", error);
                                    throw new CertificateException(error);
                                }
                            }
 
                            @Override
                            public X509Certificate[] getAcceptedIssuers() {
                                return acceptedIssuers;
                            }
                        }
                },
                null
        );
 
        setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
    }
 
    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
            throws IOException {
 
        injectHostname(socket, host);
        Socket sslSocket = sslCtx.getSocketFactory().createSocket(socket, host, port, autoClose);
 
        // throw an exception if the hostname does not match the certificate
        getHostnameVerifier().verify(host, (SSLSocket) sslSocket);
 
        return sslSocket;
    }
 
    @Override
    public Socket createSocket() throws IOException {
        return sslCtx.getSocketFactory().createSocket();
    }
 
    /**
     * Pre-ICS Android had a bug resolving HTTPS addresses. This workaround fixes that bug.
     *
     * @param socket The socket to alter
     * @param host   Hostname to connect to
     * @see <a href="https://code.google.com/p/android/issues/detail?id=13117#c14">https://code.google.com/p/android/issues/detail?id=13117#c14</a>
     */
    private void injectHostname(Socket socket, String host) {
        try {
            if (Integer.valueOf(Build.VERSION.SDK) >= 4) {
                Field field = InetAddress.class.getDeclaredField("hostName");
                field.setAccessible(true);
                field.set(socket.getInetAddress(), host);
            }
        } catch (Exception ignored) {
        }
    }
 
 
} 

  

五、参考资料

[1] http://drops.wooyun.org/tips/3296

[2] http://jaq.alibaba.com/blog.htm?id=60

[3] http://www.wooyun.org/bugs/wooyun-2014-079358

[4] https://developer.android.com/training/articles/security-ssl.html

[5] http://pingguohe.net/2016/02/26/Android-App-secure-ssl.html

[6] http://wolfeye.baidu.com/blog/webview-ignore-ssl-error/

[7] http://developer.android.com/reference/android/webkit/SslErrorHandler.html

 

posted @ 2016-04-02 11:25  w踏雪w  阅读(790)  评论(0)    收藏  举报