(八)netty的SSL renegotiation攻擊漏洞

爲了知足安全規範,從http改形成https(見(四)啓用HTTPS),然而啓用https後就能夠高枕無憂了嗎?綠盟告訴你:固然不,TLS Client-initiated 重協商攻擊(CVE-2011-1473)瞭解一下。html

1. 漏洞nginx

報告是這樣的:apache

詳細描述    該漏洞存在於SSL renegotiation的過程當中。對於使用SSL重協商功能的服務都會受其影響。特別的,renegotiation被用於瀏覽器到服務器之間的驗證。雖然目前能夠在不啓用renegotiation進程的狀況下使用HTTPS,但不少服務器的默認設置均啓用了renegotiation功能。該漏洞只須要一臺普通電腦和DSL鏈接便可輕易攻破SSL服務器。而對於大型服務器集羣來講,則須要20臺電腦和120Kbps的網絡鏈接便可實現。SSL是銀行、網上電子郵件服務和其餘用於服務器和用戶之間保護私人數據並安全通訊必不可少的功能。因此本次拒絕服務漏洞影響範圍很是廣危害很是大。
解決辦法    使用SSL開啓重協商的服務都會受該漏洞影響.

Apache解決辦法:
   升級到Apache 2.2.15之後版本

IIS解決辦法:
    IIS 5.0啓用SSL服務時,也會受影響。能夠升級IIS 6.0到更高的版本。

Lighttpd解決辦法:
    建議升級到lighttpd 1.4.30或者更高,並設置ssl.disable-client-renegotiation = "enable"。  
    http://download.lighttpd.net/lighttpd/releases-1.4.x/
 
Nginx解決辦法:
    0.7.x升級到nginx 0.7.64 
    0.8.x升級到 0.8.23 以及更高版本。
    http://nginx.org/en/download.html

Tomcat解決辦法:
 1、使用NIO connector代替BIO connector,由於NIO不支持重協商,參考以下配置:
<Connector protocol="org.apache.coyote.http11.Http11NioProtocol">
(可能會影響Tomcat性能);
 2、配置Nginx反向代理,在Nginx中修復OpenSSL相關問題。
    參考連接:
    https://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html
    https://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html
    http://tomcat.apache.org/security-7.html#Not_a_vulnerability_in_Tomcat
    https://tomcat.apache.org/tomcat-6.0-doc/config/http.html#Connector_Comparison

Squid解決辦法:
    升級到3.5.24以及之後版本
    http://www.squid-cache.org/Versions/

其它服務解決方案請聯繫各應用廠商確認關閉重協商的方法。

然而個人http server用的是netty(netty既支持jdk ssl,也支持open ssl,open ssl的安全性和性能都比jdk ssl來的高,能夠參考博文)。windows

解決方案中並沒有想要的信息,僅有一個用nginx增長反向代理,這種方式並沒有法讓我感到滿意,網上搜了一堆信息,但並無找到想要的答案,所以只能從源碼找起了。瀏覽器

2. 找http servertomcat

方式是直接從啓動入口,經過find usages往上找。安全

@Override
    public void start() throws WebServerException {
        if (this.nettyContext == null) {
            try {
                this.nettyContext = startHttpServer();
            }
            catch (Exception ex) {
                if (findBindException(ex) != null) {
                    SocketAddress address = this.httpServer.options().getAddress();
                    if (address instanceof InetSocketAddress) {
                        throw new PortInUseException(
                                ((InetSocketAddress) address).getPort());
                    }
                }
                throw new WebServerException("Unable to start Netty", ex);
            }
            NettyWebServer.logger.info("Netty started on port(s): " + getPort());
            startDaemonAwaitThread(this.nettyContext);
        }
    }
NettyWebServer
@Override
    public WebServer getWebServer(HttpHandler httpHandler) {
        HttpServer httpServer = createHttpServer();
        ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(
                httpHandler);
        return new NettyWebServer(httpServer, handlerAdapter, this.lifecycleTimeout);
    }
NettyReactiveWebServerFactory
private HttpServer createHttpServer() {
        return HttpServer.builder().options((options) -> {
            options.listenAddress(getListenAddress());
            if (getSsl() != null && getSsl().isEnabled()) {
                SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(
                        getSsl(), getSslStoreProvider());
                sslServerCustomizer.customize(options);
            }
            if (getCompression() != null && getCompression().getEnabled()) {
                CompressionCustomizer compressionCustomizer = new CompressionCustomizer(
                        getCompression());
                compressionCustomizer.customize(options);
            }
            applyCustomizers(options);
        }).build();
    }
NettyReactiveWebServerFactory
@Override
    public void customize(HttpServerOptions.Builder builder) {
        SslContextBuilder sslBuilder = SslContextBuilder
                .forServer(getKeyManagerFactory(this.ssl, this.sslStoreProvider))
                .trustManager(getTrustManagerFactory(this.ssl, this.sslStoreProvider));
        if (this.ssl.getEnabledProtocols() != null) {
            sslBuilder.protocols(this.ssl.getEnabledProtocols());
        }
        if (this.ssl.getCiphers() != null) {
            sslBuilder = sslBuilder.ciphers(Arrays.asList(this.ssl.getCiphers()));
        }
        if (this.ssl.getClientAuth() == Ssl.ClientAuth.NEED) {
            sslBuilder = sslBuilder.clientAuth(ClientAuth.REQUIRE);
        }
        else if (this.ssl.getClientAuth() == Ssl.ClientAuth.WANT) {
            sslBuilder = sslBuilder.clientAuth(ClientAuth.OPTIONAL);
        }
        try {
            builder.sslContext(sslBuilder.build());
        }
        catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
SslServerCustomizer
public SslContext build() throws SSLException {
        if (forServer) {
            return SslContext.newServerContextInternal(provider, sslContextProvider, trustCertCollection,
                trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
                ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls,
                enableOcsp);
        } else {
            return SslContext.newClientContextInternal(provider, sslContextProvider, trustCertCollection,
                trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
                ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, enableOcsp);
        }
    }
SslContextBuilder
static SslContext newServerContextInternal(
            SslProvider provider,
            Provider sslContextProvider,
            X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
            X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
            Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
            long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
            boolean enableOcsp) throws SSLException {

        if (provider == null) {
            provider = defaultServerProvider();
        }

        switch (provider) {
        case JDK:
            if (enableOcsp) {
                throw new IllegalArgumentException("OCSP is not supported with this SslProvider: " + provider);
            }
            return new JdkSslServerContext(sslContextProvider,
                    trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
                    keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
                    clientAuth, protocols, startTls);
        case OPENSSL:
            verifyNullSslContextProvider(provider, sslContextProvider);
            return new OpenSslServerContext(
                    trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
                    keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
                    clientAuth, protocols, startTls, enableOcsp);
        case OPENSSL_REFCNT:
            verifyNullSslContextProvider(provider, sslContextProvider);
            return new ReferenceCountedOpenSslServerContext(
                    trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
                    keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
                    clientAuth, protocols, startTls, enableOcsp);
        default:
            throw new Error(provider.toString());
        }
    }
SslContext

找到這一步會發現provider爲null,而後調用defaultServerProvider()方法裏服務器

private static SslProvider defaultProvider() {
        if (OpenSsl.isAvailable()) {
            return SslProvider.OPENSSL;
        } else {
            return SslProvider.JDK;
        }
    }
SslContext
/**
     * Returns {@code true} if and only if
     * <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and its OpenSSL support
     * are available.
     */
    public static boolean isAvailable() {
        return UNAVAILABILITY_CAUSE == null;
    }
OpenSsl

發現,源碼會先找open ssl,若是找不到則啓用jdk ssl。找open ssl的源碼是這樣的:網絡

static {
        Throwable cause = null;

        if (SystemPropertyUtil.getBoolean("io.netty.handler.ssl.noOpenSsl", false)) {
            cause = new UnsupportedOperationException(
                    "OpenSSL was explicit disabled with -Dio.netty.handler.ssl.noOpenSsl=true");

            logger.debug(
                    "netty-tcnative explicit disabled; " +
                            OpenSslEngine.class.getSimpleName() + " will be unavailable.", cause);
        } else {
            // Test if netty-tcnative is in the classpath first.
            try {
                Class.forName("io.netty.internal.tcnative.SSL", false, OpenSsl.class.getClassLoader());
            } catch (ClassNotFoundException t) {
                cause = t;
                logger.debug(
                        "netty-tcnative not in the classpath; " +
                                OpenSslEngine.class.getSimpleName() + " will be unavailable.");
            }

            // If in the classpath, try to load the native library and initialize netty-tcnative.
            if (cause == null) {
                try {
                    // The JNI library was not already loaded. Load it now.
                    loadTcNative();
                } catch (Throwable t) {
                    cause = t;
                    logger.debug(
                            "Failed to load netty-tcnative; " +
                                    OpenSslEngine.class.getSimpleName() + " will be unavailable, unless the " +
                                    "application has already loaded the symbols by some other means. " +
                                    "See http://netty.io/wiki/forked-tomcat-native.html for more information.", t);
                }

                try {
                    String engine = SystemPropertyUtil.get("io.netty.handler.ssl.openssl.engine", null);
                    if (engine == null) {
                        logger.debug("Initialize netty-tcnative using engine: 'default'");
                    } else {
                        logger.debug("Initialize netty-tcnative using engine: '{}'", engine);
                    }
                    initializeTcNative(engine);

                    // The library was initialized successfully. If loading the library failed above,
                    // reset the cause now since it appears that the library was loaded by some other
                    // means.
                    cause = null;
                } catch (Throwable t) {
                    if (cause == null) {
                        cause = t;
                    }
                    logger.debug(
                            "Failed to initialize netty-tcnative; " +
                                    OpenSslEngine.class.getSimpleName() + " will be unavailable. " +
                                    "See http://netty.io/wiki/forked-tomcat-native.html for more information.", t);
                }
            }
        }

        UNAVAILABILITY_CAUSE = cause;

        if (cause == null) {
            logger.debug("netty-tcnative using native library: {}", SSL.versionString());

            final List<String> defaultCiphers = new ArrayList<String>();
            final Set<String> availableOpenSslCipherSuites = new LinkedHashSet<String>(128);
            boolean supportsKeyManagerFactory = false;
            boolean useKeyManagerFactory = false;
            boolean supportsHostNameValidation = false;
            try {
                final long sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
                long certBio = 0;
                SelfSignedCertificate cert = null;
                try {
                    SSLContext.setCipherSuite(sslCtx, "ALL");
                    final long ssl = SSL.newSSL(sslCtx, true);
                    try {
                        for (String c: SSL.getCiphers(ssl)) {
                            // Filter out bad input.
                            if (c == null || c.isEmpty() || availableOpenSslCipherSuites.contains(c)) {
                                continue;
                            }
                            availableOpenSslCipherSuites.add(c);
                        }

                        try {
                            SSL.setHostNameValidation(ssl, 0, "netty.io");
                            supportsHostNameValidation = true;
                        } catch (Throwable ignore) {
                            logger.debug("Hostname Verification not supported.");
                        }
                        try {
                            cert = new SelfSignedCertificate();
                            certBio = ReferenceCountedOpenSslContext.toBIO(ByteBufAllocator.DEFAULT, cert.cert());
                            SSL.setCertificateChainBio(ssl, certBio, false);
                            supportsKeyManagerFactory = true;
                            try {
                                useKeyManagerFactory = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                                    @Override
                                    public Boolean run() {
                                        return SystemPropertyUtil.getBoolean(
                                                "io.netty.handler.ssl.openssl.useKeyManagerFactory", true);
                                    }
                                });
                            } catch (Throwable ignore) {
                                logger.debug("Failed to get useKeyManagerFactory system property.");
                            }
                        } catch (Throwable ignore) {
                            logger.debug("KeyManagerFactory not supported.");
                        }
                    } finally {
                        SSL.freeSSL(ssl);
                        if (certBio != 0) {
                            SSL.freeBIO(certBio);
                        }
                        if (cert != null) {
                            cert.delete();
                        }
                    }
                } finally {
                    SSLContext.free(sslCtx);
                }
            } catch (Exception e) {
                logger.warn("Failed to get the list of available OpenSSL cipher suites.", e);
            }
            AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.unmodifiableSet(availableOpenSslCipherSuites);
            final Set<String> availableJavaCipherSuites = new LinkedHashSet<String>(
                    AVAILABLE_OPENSSL_CIPHER_SUITES.size() * 2);
            for (String cipher: AVAILABLE_OPENSSL_CIPHER_SUITES) {
                // Included converted but also openssl cipher name
                availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "TLS"));
                availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "SSL"));
            }

            addIfSupported(availableJavaCipherSuites, defaultCiphers, DEFAULT_CIPHER_SUITES);
            useFallbackCiphersIfDefaultIsEmpty(defaultCiphers, availableJavaCipherSuites);
            DEFAULT_CIPHERS = Collections.unmodifiableList(defaultCiphers);

            AVAILABLE_JAVA_CIPHER_SUITES = Collections.unmodifiableSet(availableJavaCipherSuites);

            final Set<String> availableCipherSuites = new LinkedHashSet<String>(
                    AVAILABLE_OPENSSL_CIPHER_SUITES.size() + AVAILABLE_JAVA_CIPHER_SUITES.size());
            availableCipherSuites.addAll(AVAILABLE_OPENSSL_CIPHER_SUITES);
            availableCipherSuites.addAll(AVAILABLE_JAVA_CIPHER_SUITES);

            AVAILABLE_CIPHER_SUITES = availableCipherSuites;
            SUPPORTS_KEYMANAGER_FACTORY = supportsKeyManagerFactory;
            SUPPORTS_HOSTNAME_VALIDATION = supportsHostNameValidation;
            USE_KEYMANAGER_FACTORY = useKeyManagerFactory;

            Set<String> protocols = new LinkedHashSet<String>(6);
            // Seems like there is no way to explicitly disable SSLv2Hello in openssl so it is always enabled
            protocols.add(PROTOCOL_SSL_V2_HELLO);
            if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV2, SSL.SSL_OP_NO_SSLv2)) {
                protocols.add(PROTOCOL_SSL_V2);
            }
            if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV3, SSL.SSL_OP_NO_SSLv3)) {
                protocols.add(PROTOCOL_SSL_V3);
            }
            if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1, SSL.SSL_OP_NO_TLSv1)) {
                protocols.add(PROTOCOL_TLS_V1);
            }
            if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_1, SSL.SSL_OP_NO_TLSv1_1)) {
                protocols.add(PROTOCOL_TLS_V1_1);
            }
            if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_OP_NO_TLSv1_2)) {
                protocols.add(PROTOCOL_TLS_V1_2);
            }

            SUPPORTED_PROTOCOLS_SET = Collections.unmodifiableSet(protocols);
            SUPPORTS_OCSP = doesSupportOcsp();

            if (logger.isDebugEnabled()) {
                logger.debug("Supported protocols (OpenSSL): {} ", SUPPORTED_PROTOCOLS_SET);
                logger.debug("Default cipher suites (OpenSSL): {}", DEFAULT_CIPHERS);
            }
        } else {
            DEFAULT_CIPHERS = Collections.emptyList();
            AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet();
            AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet();
            AVAILABLE_CIPHER_SUITES = Collections.emptySet();
            SUPPORTS_KEYMANAGER_FACTORY = false;
            SUPPORTS_HOSTNAME_VALIDATION = false;
            USE_KEYMANAGER_FACTORY = false;
            SUPPORTED_PROTOCOLS_SET = Collections.emptySet();
            SUPPORTS_OCSP = false;
        }
    }
OpenSsl

最終發現是因爲io.netty.internal.tcnative.SSL的類找不到。session

直到如今ssl啓動的順序思路也比較清晰了,若是系統支持open ssl則啓用open ssl,若是不支持則用jdk ssl。那麼咱們改造的方式也就相應的有2種:

1. 改造jdk ssl,禁用SSL renegotiation

2. 找到相對應的包,支持open ssl

我的傾向第二種,緣由就是open ssl的性能和安全性比jdk ssl更加好。針對第一種,翻遍相關資料,均是對jdk1.8如下的,對jdk1.8以上的均沒有說明(jdk1.8如下的參考這個)。

3. 支持open ssl

經過上面的源碼,能夠看到是io.netty.internal.tcnative.SSL找不到,那麼咱們就只要增長相對應的包就行了。

找了下maven倉庫,有如下包可用:

在pom中增長對應的依賴

使用了netty-tcnative,會發現這種方式須要增長平臺支持,否則就會報如下錯誤

Failed to load any of the given libraries: [netty_tcnative_windows_x86_64, netty_tcnative_x86_64, netty_tcnative]

載入的包愈來愈多

使用netty-tcnative-boringssl-static則沒有任何問題(boringssl是google對openssl的一個fork,果真google強大)。

重啓後用openssl s_client -connect進行鏈接,以後輸入R進行驗證:

[root@devlop gateway]# openssl s_client -connect localhost:443
CONNECTED(00000003)
depth=3 C = US, O = "The Go Daddy Group, Inc.", OU = Go Daddy Class 2 Certification Authority
verify return:1******中間省略******
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-SHA
    Session-ID: 5C384430A5E50FB18848259D71396E94320454D6828439347F9791D43F32BC63
    Session-ID-ctx: 
    Master-Key: 3D7FCF9A38CA624D9B22A79A05CCBBACDED4ED01DF5AD3E1CBF19BE9AEFD9CD08A6FECFC215ECEECF829C004C3D7F2F1
    Key-Arg   : None
    Krb5 Principal: None
    PSK identity: None
    PSK identity hint: None
    Start Time: 1547191344
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---

===改造前===

R
RENEGOTIATING
depth=3 C = US, O = "The Go Daddy Group, Inc.", OU = Go Daddy Class 2 Certification Authority
verify return:1
depth=2 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", CN = Go Daddy Root Certificate Authority - G2
verify return:1
depth=1 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", OU = http://certs.godaddy.com/repository/, CN = Go Daddy Secure Certificate Authority - G2
verify return:1
depth=0 C = CN, L = \E6\9D\AD\E5\B7\9E\E5\B8\82, O = \E4\B8\AD\E7\A7\BB\EF\BC\88\E6\9D\AD\E5\B7\9E\EF\BC\89\E4\BF\A1\E6\81\AF\E6\8A\80\E6\9C\AF\E6\9C\89\E9\99\90\E5\85\AC\E5\8F\B8, CN = *****
verify return:1
read:errno=0

===改造後===

R

RENEGOTIATING
140137592752032:error:1409444C:SSL routines:ssl3_read_bytes:tlsv1 alert no renegotiation:s3_pkt.c:1493:SSL alert number 100
140137592752032:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:659:

解決問題。

相關文章
相關標籤/搜索