コード例 #1
0
    def _command_resum(self, target):
        """
        Tests the server for session resumption support using session IDs and
        TLS session tickets (RFC 5077).
        """
        NB_THREADS = 5
        MAX_RESUM = 5
        thread_pool = ThreadPool()

        for _ in xrange(MAX_RESUM):  # Test 5 resumptions with session IDs
            thread_pool.add_job(
                (self._resume_with_session_id, (target, ), 'session_id'))
        thread_pool.start(NB_THREADS)

        # Test TLS tickets support while threads are running
        try:
            (ticket_supported,
             ticket_reason) = self._resume_with_session_ticket(target)
            ticket_error = None
        except Exception as e:
            ticket_error = str(e.__class__.__module__) + '.' + \
                            str(e.__class__.__name__) + ' - ' + str(e)

        # Format session ID results
        (txt_resum,
         xml_resum) = self._format_resum_id_results(thread_pool, MAX_RESUM)

        if ticket_error:
            ticket_txt = 'Error: ' + ticket_error
        else:
            ticket_txt = 'Supported' if ticket_supported \
                                     else 'Not Supported - ' + ticket_reason+'.'

        cmd_title = 'Session Resumption'
        txt_result = [self.PLUGIN_TITLE_FORMAT.format(cmd_title)]
        RESUM_FORMAT = '      {0:<27} {1}'

        txt_result.append(
            RESUM_FORMAT.format('With Session IDs:', txt_resum[0]))
        txt_result.extend(txt_resum[1:])
        txt_result.append(
            RESUM_FORMAT.format('With TLS Session Tickets:', ticket_txt))

        # XML output
        xml_resum_ticket_attr = {}
        if ticket_error:
            xml_resum_ticket_attr['error'] = ticket_error
        else:
            xml_resum_ticket_attr['isSupported'] = str(ticket_supported)
            if not ticket_supported:
                xml_resum_ticket_attr['reason'] = ticket_reason

        xml_resum_ticket = Element('sessionResumptionWithTLSTickets',
                                   attrib=xml_resum_ticket_attr)
        xml_result = Element('resum', title=cmd_title)
        xml_result.append(xml_resum)
        xml_result.append(xml_resum_ticket)

        thread_pool.join()
        return PluginBase.PluginResult(txt_result, xml_result)
コード例 #2
0
    def process_task(self, target, command, args):
        output_format = '        {0:<25} {1}'

        ctSSL_initialize(zlib=True)

        ssl_ctx = SSL_CTX.SSL_CTX('tlsv1') # sslv23 hello will fail for specific servers such as post.craigslist.org
        ssl_connect = SSLyzeSSLConnection(self._shared_settings, target,ssl_ctx,
                                          hello_workaround=True)

        try: # Perform the SSL handshake
            ssl_connect.connect()
            compression_status = ssl_connect._ssl.get_current_compression()
        finally:
            ssl_connect.close()
            
        ctSSL_cleanup()

        # Text output
        if compression_status:
            comp_txt = 'Enabled ' +  compression_status
            comp_xml = {'isSupported':'True','type':compression_status.strip('()')}
        else:
            comp_txt = 'Disabled'
            comp_xml = {'isSupported':'False'}
            
        cmd_title = 'Compression'
        txt_result = [self.PLUGIN_TITLE_FORMAT.format(cmd_title)]
        txt_result.append(output_format.format("Compression Support:", comp_txt))

        # XML output
        xml_el = Element('compression', comp_xml)
        xml_result = Element(command, title = cmd_title)
        xml_result.append(xml_el)

        return PluginBase.PluginResult(txt_result, xml_result)
コード例 #3
0
    def process_task(self, target, command, args):
        if self._shared_settings['starttls']:
            raise Exception('Cannot use --hsts with --starttls.')

        hsts_supported = self._get_hsts_header(target)
        if hsts_supported:
            hsts_timeout = hsts_supported
            hsts_supported = True

        # Text output
        cmd_title = 'HTTP Strict Transport Security'
        txt_result = [self.PLUGIN_TITLE_FORMAT(cmd_title)]
        if hsts_supported:
            txt_result.append(
                self.FIELD_FORMAT("OK - HSTS header received:", hsts_timeout))
        else:
            txt_result.append(
                self.FIELD_FORMAT(
                    "NOT SUPPORTED - Server did not send an HSTS header.", ""))

        # XML output
        xml_hsts_attr = {'sentHstsHeader': str(hsts_supported)}
        if hsts_supported:
            xml_hsts_attr['hstsHeaderValue'] = hsts_timeout
        xml_hsts = Element('hsts', attrib=xml_hsts_attr)

        xml_result = Element('hsts', title=cmd_title)
        xml_result.append(xml_hsts)

        return PluginBase.PluginResult(txt_result, xml_result)
コード例 #4
0
    def process_task(self, target, command, args):

        if self._shared_settings['starttls']:
            raise Exception('Cannot use --hsts with --starttls.')

        FIELD_FORMAT = '      {0:<35}{1}'.format

        hsts_supported = self._get_hsts_header(target)
        if hsts_supported:
            hsts_timeout = hsts_supported
            hsts_supported = True

        # Text output
        cmd_title = 'HTTP Strict Transport Security'
        txt_result = [self.PLUGIN_TITLE_FORMAT(cmd_title)]
        if hsts_supported:
            txt_result.append(FIELD_FORMAT("Supported:", hsts_timeout))
        else:
            txt_result.append(FIELD_FORMAT("Not supported.", ""))

        # XML output
        xml_hsts_attr = {'hsts_header_found': str(hsts_supported)}
        if hsts_supported:
            xml_hsts_attr['hsts_header'] = hsts_timeout
        xml_hsts = Element('hsts', attrib=xml_hsts_attr)

        xml_result = Element(self.__class__.__name__,
                             command=command,
                             title=cmd_title)
        xml_result.append(xml_hsts)

        return PluginBase.PluginResult(txt_result, xml_result)
コード例 #5
0
    def process_task(self, target, command, args):

        ctSSL_initialize()
        try:
            (can_reneg, is_secure) = self._test_renegotiation(target)
        finally:
            ctSSL_cleanup()

        # Text output
        reneg_txt = 'Honored' if can_reneg else 'Rejected'
        secure_txt = 'Supported' if is_secure else 'Not supported'
        cmd_title = 'Session Renegotiation'
        txt_result = [self.PLUGIN_TITLE_FORMAT.format(cmd_title)]

        RENEG_FORMAT = '      {0:<35} {1}'
        txt_result.append(
            RENEG_FORMAT.format('Client-initiated Renegotiations:', reneg_txt))
        txt_result.append(
            RENEG_FORMAT.format('Secure Renegotiation: ', secure_txt))

        # XML output
        xml_reneg_attr = {
            'canBeClientInitiated': str(can_reneg),
            'isSecure': str(is_secure)
        }
        xml_reneg = Element('sessionRenegotiation', attrib=xml_reneg_attr)

        xml_result = Element(command, title=cmd_title)
        xml_result.append(xml_reneg)

        return PluginBase.PluginResult(txt_result, xml_result)
コード例 #6
0
    def process_task(self, target, command):
        """
        Connects to the target server and tries to get acceptable CAs for client cert
        """
        (_, _, _, ssl_version) = target
        ssl_conn = create_sslyze_connection(target, self._shared_settings,
                                            ssl_version)

        res = []
        try:  # Perform the SSL handshake
            ssl_conn.connect()

        except ClientCertificateRequested:  # The server asked for a client cert
            res = ssl_conn.get_client_CA_list()

        finally:
            ssl_conn.close()

        text_output = [self.PLUGIN_TITLE_FORMAT(self.CMD_TITLE)]
        if res:
            xml_output = Element(command,
                                 title=self.CMD_TITLE,
                                 isProvided="True")
            for ca in res:
                text_output.append(self.FIELD_FORMAT('', str(ca)))
                ca_xml = Element('ca')
                ca_xml.text = ca
                xml_output.append(ca_xml)
        else:
            xml_output = Element(command,
                                 title=self.CMD_TITLE,
                                 isProvided="False")

        return PluginBase.PluginResult(text_output, xml_output)
コード例 #7
0
    def process_task(self, target, command, args):
        if self._shared_settings['starttls']:
            raise Exception('Cannot use --httpredirect with --starttls.')

        self._target = target
        self._ip = target[1]
        self._timeout = self._shared_settings['timeout']
        (status, redirect_header) = self._get_redirect_header()
        redirect_installed = False
        if redirect_header and status > 0:
            redirect_installed = True

        # Text output
        cmd_title = 'HTTP to HTTPS Redirect'
        txt_result = [self.PLUGIN_TITLE_FORMAT(cmd_title)]
        if redirect_installed:
            txt_result.append(self.FIELD_FORMAT("OK - HTTP to HTTPS header received:", redirect_header))
        else:
            txt_result.append(self.FIELD_FORMAT("NOT INSTALLED - Server did not send an HTTP to HTTPS redirect header.", ""))
            if status < 0:
                txt_result.append(self.FIELD_FORMAT(redirect_header, ""))

        # XML output
        xml_redirect_attr = {'isInstalled': str(redirect_installed)}
        if redirect_installed:
            xml_redirect_attr['location'] = redirect_header
            xml_redirect_attr['status'] = str(status)

        if status < 0:
            xml_redirect_attr['error'] = redirect_header

        xml_result = Element('HTTPredirect', title=cmd_title, attrib=xml_redirect_attr)

        return PluginBase.PluginResult(txt_result, xml_result)
コード例 #8
0
    def _command_resum_rate(self, target):
        """
        Performs 100 session resumptions with the server in order to estimate
        the session resumption rate.
        """
        # Create a thread pool and process the jobs
        NB_THREADS = 20
        MAX_RESUM = 100
        thread_pool = ThreadPool()
        for _ in xrange(MAX_RESUM):
            thread_pool.add_job((self._resume_with_session_id, (target, )))
        thread_pool.start(NB_THREADS)

        # Format session ID results
        (txt_resum,
         xml_resum) = self._format_resum_id_results(thread_pool, MAX_RESUM)

        # Text output
        cmd_title = 'Resumption Rate with Session IDs'
        txt_result = [self.PLUGIN_TITLE_FORMAT(cmd_title) + ' ' + txt_resum[0]]
        txt_result.extend(txt_resum[1:])

        # XML output
        xml_result = Element('resum_rate', title=cmd_title)
        xml_result.append(xml_resum)

        thread_pool.join()
        return PluginBase.PluginResult(txt_result, xml_result)
コード例 #9
0
    def process_task(self, target, command, args):

        OUT_FORMAT = '        {0:<25} {1}'.format

        sslConn = create_sslyze_connection(target, self._shared_settings)

        try:  # Perform the SSL handshake
            sslConn.connect()
            compName = sslConn.get_current_compression_name()
        except ClientAuthenticationError:  # The server asked for a client cert
            compName = sslConn.get_current_compression_name()
        finally:
            sslConn.close()

        # Text output
        if compName:
            compTxt = 'Enabled ' + compName
            compXml = {'isSupported': 'True', 'type': compName.strip('()')}
        else:
            compTxt = 'Disabled'
            compXml = {'isSupported': 'False'}

        cmdTitle = 'Compression'
        txtOutput = [self.PLUGIN_TITLE_FORMAT(cmdTitle)]
        txtOutput.append(OUT_FORMAT("Compression Support:", compTxt))

        # XML output
        xmlNode = Element('compression', compXml)
        xmlOutput = Element(command, title=cmdTitle)
        xmlOutput.append(xmlNode)

        return PluginBase.PluginResult(txtOutput, xmlOutput)
コード例 #10
0
    def process_task(self, target, command, args):

        (clientReneg, secureReneg) = self._test_renegotiation(target)

        # Text output
        clientTxt = 'VULNERABLE - Server honors client-initiated renegotiations' if clientReneg else 'OK - Rejected'
        secureTxt = 'OK - Supported' if secureReneg else 'VULNERABLE - Secure renegotiation not supported'
        cmdTitle = 'Session Renegotiation'
        txtOutput = [self.PLUGIN_TITLE_FORMAT(cmdTitle)]

        txtOutput.append(
            self.FIELD_FORMAT('Client-initiated Renegotiations:', clientTxt))
        txtOutput.append(self.FIELD_FORMAT('Secure Renegotiation:', secureTxt))

        # XML output
        xmlReneg = Element('sessionRenegotiation',
                           attrib={
                               'canBeClientInitiated': str(clientReneg),
                               'isSecure': str(secureReneg)
                           })

        xmlOutput = Element(command, title=cmdTitle)
        xmlOutput.append(xmlReneg)

        return PluginBase.PluginResult(txtOutput, xmlOutput)
コード例 #11
0
    def process_task(self, target, command, args):

        (clientReneg, secureReneg) = self._test_renegotiation(target)

        # Text output
        clientTxt = 'Honored' if clientReneg else 'Rejected'
        secureTxt = 'Supported' if secureReneg else 'Not supported'
        cmdTitle = 'Session Renegotiation'
        txtOutput = [self.PLUGIN_TITLE_FORMAT(cmdTitle)]

        outFormat = '      {0:<35}{1}'.format
        txtOutput.append(
            outFormat('Client-initiated Renegotiations:', clientTxt))
        txtOutput.append(outFormat('Secure Renegotiation:', secureTxt))

        # XML output
        xmlReneg = Element('sessionRenegotiation',
                           attrib={
                               'canBeClientInitiated': str(clientReneg),
                               'isSecure': str(secureReneg)
                           })

        xmlOutput = Element(command, title=cmdTitle)
        xmlOutput.append(xmlReneg)

        return PluginBase.PluginResult(txtOutput, xmlOutput)
コード例 #12
0
class PluginHSTS(PluginBase.PluginBase):

    interface = PluginBase.PluginInterface(title="PluginHSTS",
                                           description=(''))
    interface.add_command(
        command="hsts",
        help="Checks support for HTTP Strict Transport Security "
        "(HSTS) by collecting any Strict-Transport-Security field present in "
        "the HTTP response sent back by the server.",
        dest=None)

    def process_task(self, target, command, args):

        if self._shared_settings['starttls']:
            raise Exception('Cannot use --hsts with --starttls.')

        output_format = '        {0:<25} {1}'

        hsts_supported = False
        hsts_timeout = ""
        (host, addr, port, sslVersion) = target
        connection = httplib.HTTPSConnection(host)
        try:
            connection.connect()
            connection.request("HEAD", "/", headers={"Connection": "close"})
            response = connection.getresponse()
            headers = response.getheaders()
            for (field, data) in headers:
                if field == 'strict-transport-security':
                    hsts_supported = True
                    hsts_timeout = data

        except httplib.HTTPException as ex:
            print "Error: %s" % ex

        finally:
            connection.close()

        # Text output
        cmd_title = 'HSTS'
        txt_result = [self.PLUGIN_TITLE_FORMAT(cmd_title)]
        if hsts_supported:
            txt_result.append(output_format.format("Supported:", hsts_timeout))
        else:
            txt_result.append(output_format.format("Not supported.", ""))

        # XML output
        xml_hsts_attr = {'hsts_header_found': str(hsts_supported)}
        if hsts_supported:
            xml_hsts_attr['hsts_header'] = hsts_timeout
        xml_hsts = Element('hsts', attrib=xml_hsts_attr)

        xml_result = Element(self.__class__.__name__,
                             command=command,
                             title=cmd_title)
        xml_result.append(xml_hsts)

        return PluginBase.PluginResult(txt_result, xml_result)
コード例 #13
0
class PluginHeartbleed(PluginBase.PluginBase):

    interface = PluginBase.PluginInterface("PluginHeartbleed",  "")
    interface.add_command(
        command="heartbleed",
        help=(
            "Tests the server(s) for the OpenSSL Heartbleed vulnerability (experimental)."))


    def process_task(self, target, command, args):

        OUT_FORMAT = '      {0:<35}{1}'.format
        (host, ip, port, sslVersion) = target

        if sslVersion == SSLV23: # Could not determine the preferred  SSL version - client cert was required ?
            sslVersion = TLSV1 # Default to TLS 1.0
            target = (host, ip, port, sslVersion)

        sslConn = create_sslyze_connection(target, self._shared_settings)
        sslConn.sslVersion = sslVersion # Needed by the heartbleed payload

        # Awful hack #1: replace nassl.sslClient.do_handshake() with a heartbleed
        # checking SSL handshake so that all the SSLyze options
        # (startTLS, proxy, etc.) still work
        sslConn.do_handshake = new.instancemethod(do_handshake_with_heartbleed, sslConn, None)

        heartbleed = None
        try: # Perform the SSL handshake
            sslConn.connect()
        except HeartbleedSent:
            # Awful hack #2: directly read the underlying network socket
            heartbleed = sslConn._sock.recv(16381)
        finally:
            sslConn.close()

        # Text output
        if heartbleed is None:
            raise Exception("Error: connection failed.")
        elif '\x01\x01\x01\x01\x01\x01\x01\x01\x01' in heartbleed:
            # Server replied with our hearbeat payload
            heartbleedTxt = 'VULNERABLE'
            heartbleedXml = 'True'
        else:
            heartbleedTxt = 'NOT vulnerable'
            heartbleedXml = 'False'

        cmdTitle = 'Heartbleed'
        txtOutput = [self.PLUGIN_TITLE_FORMAT(cmdTitle)]
        txtOutput.append(OUT_FORMAT("OpenSSL Heartbleed:", heartbleedTxt))

        # XML output
        xmlOutput = Element(command, title=cmdTitle)
        if heartbleed:
            xmlNode = Element('heartbleed', isVulnerable=heartbleedXml)
            xmlOutput.append(xmlNode)

        return PluginBase.PluginResult(txtOutput, xmlOutput)
コード例 #14
0
 def shorten_url(self, url):
     '''Return a shortened version of a URL passed in
 using bitly
 '''
     try:
         return self.bitly.shorten(url)['url']
     except:
         log.err('[Error]: bitly traceback: {}'.format(
             traceback.format_exc()))
         raise pb.CommandError(u'[Error]: Invalid URL', pm=True)
コード例 #15
0
    def ytSearch(self, args, irc):
        '''yt [search term(s)] -- Search YouTube and return the first result
      '''
        try:
            if not args:
                raise pb.CommandError(u'[Error]: Missing YouTube search terms',
                                      pm=False)

            terms = u'+'.join(args)
            r = requests.get(self.YT_SEARCH.format(terms))

            soup = BeautifulSoup(r.text)
            result = soup.find('div', {
                'class': 'yt-lockup-content'
            }).find('a')['href']
            url = u'https://youtube.com{}'.format(result)
            return u'{} | {}'.format(url, self.youtube_data(url))
        except:
            log.err('[Error]: yt {}'.format(sys.exc_info()[0]))
            raise pb.CommandError(u'[Error]: Could not contact YouTube',
                                  pm=True)
コード例 #16
0
class PluginCompression(PluginBase.PluginBase):

    interface = PluginBase.PluginInterface(title="PluginCompression",
                                           description="")
    interface.add_command(
        command="compression",
        help="Tests the server for Zlib compression support.",
        dest=None)

    def process_task(self, target, command, args):

        OUT_FORMAT = '      {0:<35}{1}'.format

        sslConn = create_sslyze_connection(target, self._shared_settings)

        # Make sure OpenSSL was built with support for compression to avoid false negatives
        if 'zlib compression' not in sslConn.get_available_compression_methods(
        ):
            raise RuntimeError(
                'OpenSSL was not built with support for zlib / compression. Did you build nassl yourself ?'
            )

        try:  # Perform the SSL handshake
            sslConn.connect()
            compName = sslConn.get_current_compression_method()
        except ClientCertificateRequested:  # The server asked for a client cert
            compName = sslConn.get_current_compression_method()
        finally:
            sslConn.close()

        # Text output
        if compName:
            compTxt = 'Supported'
        else:
            compTxt = 'Disabled'

        cmdTitle = 'Compression'
        txtOutput = [self.PLUGIN_TITLE_FORMAT(cmdTitle)]
        txtOutput.append(OUT_FORMAT("DEFLATE Compression:", compTxt))

        # XML output
        xmlOutput = Element(command, title=cmdTitle)
        if compName:
            xmlNode = Element('compressionMethod', type="DEFLATE")
            xmlOutput.append(xmlNode)

        return PluginBase.PluginResult(txtOutput, xmlOutput)
コード例 #17
0
ファイル: Quotes.py プロジェクト: genericpersona/BaneBot
  def joke(self, args, irc):
    '''(joke) -- Random joke from goodbadjokes.com
    '''
    try:
        r = requests.get(self.JOKE_URL)
        if r.status_code != 200:
            log.err('Error]: Status code of {} for joke'.format(r.status_code))
            return

        soup = BeautifulSoup(r.text)
        joke = soup.find('span', {'class': 'joke-content'})
        q = joke.find('dt').text 
        a = joke.find('dd').text

        return '{} {}'.format(q, a)
    except:
        log.err('[Error]: {}'.format(sys.exc_info()[0]))
        raise pb.CommandError('[Error]: Cannot contact goodbadjokes.com', pm=False)
コード例 #18
0
    def process_task(self, target, command, arg):

        # Get the certificate and validate it against all the trust stores
        (cert, verify_result) = self._get_cert(target, TRUST_STORE_PATHS)

        # Results formatting
        # Text output - display each trust store and the validation result
        cmdTitle = 'Multi Trust Store Validation'
        txtOutput = [self.PLUGIN_TITLE_FORMAT(cmdTitle)]
        isCertTrusted = True
        fingerprint = cert.get_SHA1_fingerprint()
        txtOutput.append(
            self.FIELD_FORMAT('Certificate SHA1 Fingerprint:', fingerprint))

        for trustStorePath in TRUST_STORE_PATHS:
            if verify_result[trustStorePath] != 'ok':
                isCertTrusted = False
            txtOutput.append(
                self.FIELD_FORMAT(
                    "Validation w/ '" + os.path.split(trustStorePath)[1] +
                    "': ", verify_result[trustStorePath]))

        # XML output.
        xmlOutput = Element(command, title=cmdTitle)
        trustStoresXml = Element('trustStoreList',
                                 isTrustedByAllTrustStores=str(isCertTrusted))

        for trustStorePath in TRUST_STORE_PATHS:
            # Add the result of each trust store
            trustStoresXml.append(
                Element('trustStore',
                        filePath=os.path.split(trustStorePath)[1],
                        verifyResult=verify_result[trustStorePath]))
        xmlOutput.append(trustStoresXml)

        # Add the certificate
        certXml = Element('certificate', sha1Fingerprint=fingerprint)
        for (key, value) in cert.as_dict().items():
            certXml.append(_keyvalue_pair_to_xml(key, value))

        xmlOutput.append(certXml)

        return PluginBase.PluginResult(txtOutput, xmlOutput)
コード例 #19
0
ファイル: PluginHSTS.py プロジェクト: dud3/sslyze
    def process_task(self, target, command, args):
        if self._shared_settings['starttls']:
            raise Exception('Cannot use --hsts with --starttls.')

        hsts_header = self._get_hsts_header(target)
        hsts_supported = False
        if hsts_header:
            hsts_supported = True

        # Text output
        cmd_title = 'HTTP Strict Transport Security'
        txt_result = [self.PLUGIN_TITLE_FORMAT(cmd_title)]
        if hsts_supported:
            txt_result.append(
                self.FIELD_FORMAT("OK - HSTS header received:", hsts_header))
        else:
            txt_result.append(
                self.FIELD_FORMAT(
                    "NOT SUPPORTED - Server did not send an HSTS header.", ""))

        # XML output
        xml_hsts_attr = {'isSupported': str(hsts_supported)}
        if hsts_supported:
            # Do some light parsing of the HSTS header
            hsts_header_split = hsts_header.split('max-age=')[1].split(';')
            hsts_max_age = hsts_header_split[0].strip()
            hsts_subdomains = False
            if len(hsts_header_split
                   ) > 1 and 'includeSubdomains' in hsts_header_split[1]:
                hsts_subdomains = True

            xml_hsts_attr['maxAge'] = hsts_max_age
            xml_hsts_attr['includeSubdomains'] = str(hsts_subdomains)

        xml_hsts = Element('httpStrictTransportSecurity', attrib=xml_hsts_attr)
        xml_result = Element('hsts', title=cmd_title)
        xml_result.append(xml_hsts)

        return PluginBase.PluginResult(txt_result, xml_result)
コード例 #20
0
ファイル: PluginCompression.py プロジェクト: yutianhot/sslyze
    def process_task(self, target, command, args):

        sslConn = create_sslyze_connection(target, self._shared_settings)

        # Make sure OpenSSL was built with support for compression to avoid false negatives
        if 'zlib compression' not in sslConn.get_available_compression_methods():
            raise RuntimeError('OpenSSL was not built with support for zlib / compression. Did you build nassl yourself ?')

        try: # Perform the SSL handshake
            sslConn.connect()
            compName = sslConn.get_current_compression_method()
        except ClientCertificateRequested: # The server asked for a client cert
            compName = sslConn.get_current_compression_method()
        finally:
            sslConn.close()

        # Text output
        if compName:
            compTxt = 'VULNERABLE - Server supports Deflate compression'
        else:
            compTxt = 'OK - Compression disabled'

        cmdTitle = 'Deflate Compression'
        txtOutput = [self.PLUGIN_TITLE_FORMAT(cmdTitle)]
        txtOutput.append(self.FIELD_FORMAT(compTxt, ""))

        # XML output
        xmlOutput = Element(command, title=cmdTitle)
        if compName:
            xmlNode = Element('compressionMethod', type="DEFLATE", isSupported="True")
            xmlOutput.append(xmlNode)
        else:
            xmlNode = Element('compressionMethod', type="DEFLATE", isSupported="False")
            xmlOutput.append(xmlNode)

        return PluginBase.PluginResult(txtOutput, xmlOutput)
コード例 #21
0
ファイル: PluginCertInfo.py プロジェクト: kyprizel/sslyze
class PluginCertInfo(PluginBase.PluginBase):

    interface = PluginBase.PluginInterface(title="PluginCertInfo",
                                           description='')
    interface.add_command(
        command="certinfo",
        help="Verifies the validity of the server(s) certificate(s) against "
        "various trust stores, checks for support for OCSP stapling, and "
        "prints relevant fields of "
        "the certificate. CERTINFO should be 'basic' or 'full'.",
        dest="certinfo")
    interface.add_option(
        option="ca_file",
        help="Local Certificate Authority file (in PEM format), to verify the "
        "validity of the server(s) certificate(s) against.",
        dest="ca_file")

    TRUST_FORMAT = '{store_name} CA Store ({store_version}):'.format

    def process_task(self, target, command, arg):

        if arg == 'basic':
            txt_output_generator = self._get_basic_text
        elif arg == 'full':
            txt_output_generator = self._get_full_text
        else:
            raise Exception("PluginCertInfo: Unknown command.")

        (host, _, _, _) = target
        thread_pool = ThreadPool()

        if 'ca_file' in self._shared_settings and self._shared_settings[
                'ca_file']:
            AVAILABLE_TRUST_STORES[self._shared_settings['ca_file']] = (
                'Custom --ca_file', 'N/A')

        for (store_path, _) in AVAILABLE_TRUST_STORES.iteritems():
            # Try to connect with each trust store
            thread_pool.add_job((self._get_cert, (target, store_path)))

        # Start processing the jobs
        thread_pool.start(len(AVAILABLE_TRUST_STORES))

        # Store the results as they come
        x509_cert_chain = []
        (verify_dict, verify_dict_error, x509_cert,
         ocsp_response) = ({}, {}, None, None)

        for (job, result) in thread_pool.get_result():
            (_, (_, store_path)) = job
            (x509_cert_chain, verify_str, ocsp_response) = result
            # Store the returned verify string for each trust store
            x509_cert = x509_cert_chain[
                0]  # First cert is always the leaf cert
            store_info = AVAILABLE_TRUST_STORES[store_path]
            verify_dict[store_info] = verify_str

        if x509_cert is None:
            # This means none of the connections were successful. Get out
            for (job, exception) in thread_pool.get_error():
                raise exception

        # Store thread pool errors
        for (job, exception) in thread_pool.get_error():
            (_, (_, store_path)) = job
            error_msg = str(
                exception.__class__.__name__) + ' - ' + str(exception)

            store_info = AVAILABLE_TRUST_STORES[store_path]
            verify_dict_error[store_info] = error_msg

        thread_pool.join()

        # Results formatting
        # Text output - certificate info
        text_output = [self.PLUGIN_TITLE_FORMAT('Certificate - Content')]
        text_output.extend(txt_output_generator(x509_cert))

        # Text output - trust validation
        text_output.extend(
            ['', self.PLUGIN_TITLE_FORMAT('Certificate - Trust')])

        # Hostname validation
        if self._shared_settings['sni']:
            text_output.append(
                self.FIELD_FORMAT("SNI enabled with virtual domain:",
                                  self._shared_settings['sni']))
        # TODO: Use SNI name for validation when --sni was used
        host_val_dict = {
            X509_NAME_MATCHES_SAN: 'OK - Subject Alternative Name matches',
            X509_NAME_MATCHES_CN: 'OK - Common Name matches',
            X509_NAME_MISMATCH: 'FAILED - Certificate does NOT match ' + host
        }
        text_output.append(
            self.FIELD_FORMAT("Hostname Validation:",
                              host_val_dict[x509_cert.matches_hostname(host)]))

        # Path validation that was successful
        for ((store_name, store_version),
             verify_str) in verify_dict.iteritems():
            verify_txt = 'OK - Certificate is trusted' if (verify_str in 'ok') \
                else 'FAILED - Certificate is NOT Trusted: ' + verify_str

            # EV certs - Only Mozilla supported for now
            if (verify_str in 'ok') and ('Mozilla' in store_info):
                if self._is_ev_certificate(x509_cert):
                    verify_txt += ', Extended Validation'

            text_output.append(
                self.FIELD_FORMAT(
                    self.TRUST_FORMAT(store_name=store_name,
                                      store_version=store_version),
                    verify_txt))

        # Path validation that ran into errors
        for ((store_name, store_version),
             error_msg) in verify_dict_error.iteritems():
            verify_txt = 'ERROR: ' + error_msg
            text_output.append(
                self.FIELD_FORMAT(
                    self.TRUST_FORMAT(store_name=store_name,
                                      store_version=store_version),
                    verify_txt))

        # Print the Common Names within the certificate chain
        cns_in_cert_chain = []
        for cert in x509_cert_chain:
            cert_identity = self._extract_subject_cn_or_oun(cert)
            cns_in_cert_chain.append(cert_identity)

        text_output.append(
            self.FIELD_FORMAT('Certificate Chain Received:',
                              str(cns_in_cert_chain)))

        # Text output - OCSP stapling
        text_output.extend(
            ['', self.PLUGIN_TITLE_FORMAT('Certificate - OCSP Stapling')])
        text_output.extend(self._get_ocsp_text(ocsp_response))

        # XML output
        xml_output = Element(command,
                             argument=arg,
                             title='Certificate Information')

        # XML output - certificate chain:  always return the full certificate for each cert in the chain
        cert_chain_xml = Element('certificateChain')

        # First add the leaf certificate
        cert_chain_xml.append(
            self._format_cert_to_xml(x509_cert_chain[0], 'leaf',
                                     self._shared_settings['sni']))

        # Then add every other cert in the chain
        for cert in x509_cert_chain[1:]:
            cert_chain_xml.append(
                self._format_cert_to_xml(cert, 'intermediate',
                                         self._shared_settings['sni']))

        xml_output.append(cert_chain_xml)

        # XML output - trust
        trust_validation_xml = Element('certificateValidation')

        # Hostname validation
        is_hostname_valid = 'False' if (x509_cert.matches_hostname(host)
                                        == X509_NAME_MISMATCH) else 'True'
        host_validation_xml = Element(
            'hostnameValidation',
            serverHostname=host,
            certificateMatchesServerHostname=is_hostname_valid)
        trust_validation_xml.append(host_validation_xml)

        # Path validation - OK
        for ((store_name, store_version),
             verify_str) in verify_dict.iteritems():
            path_attrib_xml = {
                'usingTrustStore': store_name,
                'trustStoreVersion': store_version,
                'validationResult': verify_str
            }

            # EV certs - Only Mozilla supported for now
            if (verify_str in 'ok') and ('Mozilla' in store_info):
                path_attrib_xml['isExtendedValidationCertificate'] = str(
                    self._is_ev_certificate(x509_cert))

            trust_validation_xml.append(
                Element('pathValidation', attrib=path_attrib_xml))

        # Path validation - Errors
        for ((store_name, store_version),
             error_msg) in verify_dict_error.iteritems():
            path_attrib_xml = {
                'usingTrustStore': store_name,
                'trustStoreVersion': store_version,
                'error': error_msg
            }

            trust_validation_xml.append(
                Element('pathValidation', attrib=path_attrib_xml))

        xml_output.append(trust_validation_xml)

        # XML output - OCSP Stapling
        if ocsp_response is None:
            ocsp_attr_xml = {'isSupported': 'False'}
            ocsp_xml = Element('ocspStapling', attrib=ocsp_attr_xml)
        else:
            ocsp_attr_xml = {'isSupported': 'True'}
            ocsp_xml = Element('ocspStapling', attrib=ocsp_attr_xml)

            ocsp_resp_attr_xml = {
                'isTrustedByMozillaCAStore':
                str(ocsp_response.verify(MOZILLA_STORE_PATH))
            }
            ocsp_resp_xmp = Element('ocspResponse', attrib=ocsp_resp_attr_xml)
            for (key, value) in ocsp_response.as_dict().items():
                ocsp_resp_xmp.append(_keyvalue_pair_to_xml(key, value))
            ocsp_xml.append(ocsp_resp_xmp)

        xml_output.append(ocsp_xml)

        return PluginBase.PluginResult(text_output, xml_output)

    # FORMATTING FUNCTIONS
    @staticmethod
    def _format_cert_to_xml(x509_cert, x509_cert_position_in_chain_txt,
                            sni_txt):
        cert_attrib_xml = {'sha1Fingerprint': x509_cert.get_SHA1_fingerprint()}

        if x509_cert_position_in_chain_txt:
            cert_attrib_xml['position'] = x509_cert_position_in_chain_txt

        if sni_txt:
            cert_attrib_xml['suppliedServerNameIndication'] = sni_txt
        cert_xml = Element('certificate', attrib=cert_attrib_xml)

        cert_as_pem_xml = Element('asPEM')
        cert_as_pem_xml.text = x509_cert.as_pem().strip()
        cert_xml.append(cert_as_pem_xml)

        for (key, value) in x509_cert.as_dict().items():

            # Sanitize OpenSSL's output
            if 'subjectPublicKeyInfo' in key:
                # Remove the bit suffix so the element is just a number for the key size
                if 'publicKeySize' in value.keys():
                    value['publicKeySize'] = value['publicKeySize'].split(
                        ' bit')[0]

            # Add the XML element
            cert_xml.append(_keyvalue_pair_to_xml(key, value))
        return cert_xml

    def _get_ocsp_text(self, ocsp_resp):

        if ocsp_resp is None:
            return [
                self.FIELD_FORMAT(
                    'NOT SUPPORTED - Server did not send back an OCSP response.',
                    '')
            ]

        ocsp_resp_dict = ocsp_resp.as_dict()
        ocsp_trust_txt = 'OK - Response is trusted' if ocsp_resp.verify(MOZILLA_STORE_PATH) \
            else 'FAILED - Response is NOT trusted'

        ocsp_resp_txt = [
            self.FIELD_FORMAT('OCSP Response Status:',
                              ocsp_resp_dict['responseStatus']),
            self.FIELD_FORMAT('Validation w/ Mozilla\'s CA Store:',
                              ocsp_trust_txt),
            self.FIELD_FORMAT('Responder Id:', ocsp_resp_dict['responderID'])
        ]

        if 'successful' not in ocsp_resp_dict['responseStatus']:
            return ocsp_resp_txt

        ocsp_resp_txt.extend([
            self.FIELD_FORMAT('Cert Status:',
                              ocsp_resp_dict['responses'][0]['certStatus']),
            self.FIELD_FORMAT(
                'Cert Serial Number:',
                ocsp_resp_dict['responses'][0]['certID']['serialNumber']),
            self.FIELD_FORMAT('This Update:',
                              ocsp_resp_dict['responses'][0]['thisUpdate']),
            self.FIELD_FORMAT('Next Update:',
                              ocsp_resp_dict['responses'][0]['nextUpdate'])
        ])

        return ocsp_resp_txt

    @staticmethod
    def _is_ev_certificate(cert):
        cert_dict = cert.as_dict()
        try:
            policy = cert_dict['extensions']['X509v3 Certificate Policies'][
                'Policy']
            if policy[0] in MOZILLA_EV_OIDS:
                return True
        except:
            return False
        return False

    @staticmethod
    def _get_full_text(cert):
        return [cert.as_text()]

    @staticmethod
    def _extract_subject_cn_or_oun(cert):
        try:  # Extract the CN if there's one
            cert_name = cert.as_dict()['subject']['commonName']
        except KeyError:
            # If no common name, display the organizational unit instead
            try:
                cert_name = cert.as_dict()['subject']['organizationalUnitName']
            except KeyError:
                # Give up
                cert_name = 'No Common Name'

        return cert_name

    def _get_basic_text(self, cert):
        cert_dict = cert.as_dict()

        try:  # Extract the CN if there's one
            common_name = cert_dict['subject']['commonName']
        except KeyError:
            common_name = 'None'

        try:  # Extract the CN from the issuer if there's one
            issuer_name = cert_dict['issuer']['commonName']
        except KeyError:
            issuer_name = str(cert_dict['issuer'])

        text_output = [
            self.FIELD_FORMAT("SHA1 Fingerprint:",
                              cert.get_SHA1_fingerprint()),
            self.FIELD_FORMAT("Common Name:", common_name),
            self.FIELD_FORMAT("Issuer:", issuer_name),
            self.FIELD_FORMAT("Serial Number:", cert_dict['serialNumber']),
            self.FIELD_FORMAT("Not Before:",
                              cert_dict['validity']['notBefore']),
            self.FIELD_FORMAT("Not After:", cert_dict['validity']['notAfter']),
            self.FIELD_FORMAT("Signature Algorithm:",
                              cert_dict['signatureAlgorithm']),
            self.FIELD_FORMAT(
                "Public Key Algorithm:",
                cert_dict['subjectPublicKeyInfo']['publicKeyAlgorithm']),
            self.FIELD_FORMAT(
                "Key Size:",
                cert_dict['subjectPublicKeyInfo']['publicKeySize'])
        ]

        try:  # Print the Public key exponent if there's one; EC public keys don't have one for example
            text_output.append(
                self.FIELD_FORMAT(
                    "Exponent:", "{0} (0x{0:x})".format(
                        int(cert_dict['subjectPublicKeyInfo']['publicKey']
                            ['exponent']))))
        except KeyError:
            pass

        try:  # Print the SAN extension if there's one
            text_output.append(
                self.FIELD_FORMAT(
                    'X509v3 Subject Alternative Name:', cert_dict['extensions']
                    ['X509v3 Subject Alternative Name']))
        except KeyError:
            pass

        return text_output

    def _get_cert(self, target, store_path):
        """
        Connects to the target server and uses the supplied trust store to
        validate the server's certificate. Returns the server's certificate and
        OCSP response.
        """
        (_, _, _, ssl_version) = target
        ssl_conn = create_sslyze_connection(target,
                                            self._shared_settings,
                                            ssl_version,
                                            sslVerifyLocations=store_path)

        # Enable OCSP stapling
        ssl_conn.set_tlsext_status_ocsp()

        try:  # Perform the SSL handshake
            ssl_conn.connect()

            ocsp_resp = ssl_conn.get_tlsext_status_ocsp_resp()
            x509_cert_chain = ssl_conn.get_peer_cert_chain()
            (_, verify_str) = ssl_conn.get_certificate_chain_verify_result()

        except ClientCertificateRequested:  # The server asked for a client cert
            # We can get the server cert anyway
            ocsp_resp = ssl_conn.get_tlsext_status_ocsp_resp()
            x509_cert_chain = ssl_conn.get_peer_cert_chain()
            (_, verify_str) = ssl_conn.get_certificate_chain_verify_result()

        finally:
            ssl_conn.close()

        return x509_cert_chain, verify_str, ocsp_resp
コード例 #22
0
ファイル: PluginCertInfo.py プロジェクト: kyprizel/sslyze
    def process_task(self, target, command, arg):

        if arg == 'basic':
            txt_output_generator = self._get_basic_text
        elif arg == 'full':
            txt_output_generator = self._get_full_text
        else:
            raise Exception("PluginCertInfo: Unknown command.")

        (host, _, _, _) = target
        thread_pool = ThreadPool()

        if 'ca_file' in self._shared_settings and self._shared_settings[
                'ca_file']:
            AVAILABLE_TRUST_STORES[self._shared_settings['ca_file']] = (
                'Custom --ca_file', 'N/A')

        for (store_path, _) in AVAILABLE_TRUST_STORES.iteritems():
            # Try to connect with each trust store
            thread_pool.add_job((self._get_cert, (target, store_path)))

        # Start processing the jobs
        thread_pool.start(len(AVAILABLE_TRUST_STORES))

        # Store the results as they come
        x509_cert_chain = []
        (verify_dict, verify_dict_error, x509_cert,
         ocsp_response) = ({}, {}, None, None)

        for (job, result) in thread_pool.get_result():
            (_, (_, store_path)) = job
            (x509_cert_chain, verify_str, ocsp_response) = result
            # Store the returned verify string for each trust store
            x509_cert = x509_cert_chain[
                0]  # First cert is always the leaf cert
            store_info = AVAILABLE_TRUST_STORES[store_path]
            verify_dict[store_info] = verify_str

        if x509_cert is None:
            # This means none of the connections were successful. Get out
            for (job, exception) in thread_pool.get_error():
                raise exception

        # Store thread pool errors
        for (job, exception) in thread_pool.get_error():
            (_, (_, store_path)) = job
            error_msg = str(
                exception.__class__.__name__) + ' - ' + str(exception)

            store_info = AVAILABLE_TRUST_STORES[store_path]
            verify_dict_error[store_info] = error_msg

        thread_pool.join()

        # Results formatting
        # Text output - certificate info
        text_output = [self.PLUGIN_TITLE_FORMAT('Certificate - Content')]
        text_output.extend(txt_output_generator(x509_cert))

        # Text output - trust validation
        text_output.extend(
            ['', self.PLUGIN_TITLE_FORMAT('Certificate - Trust')])

        # Hostname validation
        if self._shared_settings['sni']:
            text_output.append(
                self.FIELD_FORMAT("SNI enabled with virtual domain:",
                                  self._shared_settings['sni']))
        # TODO: Use SNI name for validation when --sni was used
        host_val_dict = {
            X509_NAME_MATCHES_SAN: 'OK - Subject Alternative Name matches',
            X509_NAME_MATCHES_CN: 'OK - Common Name matches',
            X509_NAME_MISMATCH: 'FAILED - Certificate does NOT match ' + host
        }
        text_output.append(
            self.FIELD_FORMAT("Hostname Validation:",
                              host_val_dict[x509_cert.matches_hostname(host)]))

        # Path validation that was successful
        for ((store_name, store_version),
             verify_str) in verify_dict.iteritems():
            verify_txt = 'OK - Certificate is trusted' if (verify_str in 'ok') \
                else 'FAILED - Certificate is NOT Trusted: ' + verify_str

            # EV certs - Only Mozilla supported for now
            if (verify_str in 'ok') and ('Mozilla' in store_info):
                if self._is_ev_certificate(x509_cert):
                    verify_txt += ', Extended Validation'

            text_output.append(
                self.FIELD_FORMAT(
                    self.TRUST_FORMAT(store_name=store_name,
                                      store_version=store_version),
                    verify_txt))

        # Path validation that ran into errors
        for ((store_name, store_version),
             error_msg) in verify_dict_error.iteritems():
            verify_txt = 'ERROR: ' + error_msg
            text_output.append(
                self.FIELD_FORMAT(
                    self.TRUST_FORMAT(store_name=store_name,
                                      store_version=store_version),
                    verify_txt))

        # Print the Common Names within the certificate chain
        cns_in_cert_chain = []
        for cert in x509_cert_chain:
            cert_identity = self._extract_subject_cn_or_oun(cert)
            cns_in_cert_chain.append(cert_identity)

        text_output.append(
            self.FIELD_FORMAT('Certificate Chain Received:',
                              str(cns_in_cert_chain)))

        # Text output - OCSP stapling
        text_output.extend(
            ['', self.PLUGIN_TITLE_FORMAT('Certificate - OCSP Stapling')])
        text_output.extend(self._get_ocsp_text(ocsp_response))

        # XML output
        xml_output = Element(command,
                             argument=arg,
                             title='Certificate Information')

        # XML output - certificate chain:  always return the full certificate for each cert in the chain
        cert_chain_xml = Element('certificateChain')

        # First add the leaf certificate
        cert_chain_xml.append(
            self._format_cert_to_xml(x509_cert_chain[0], 'leaf',
                                     self._shared_settings['sni']))

        # Then add every other cert in the chain
        for cert in x509_cert_chain[1:]:
            cert_chain_xml.append(
                self._format_cert_to_xml(cert, 'intermediate',
                                         self._shared_settings['sni']))

        xml_output.append(cert_chain_xml)

        # XML output - trust
        trust_validation_xml = Element('certificateValidation')

        # Hostname validation
        is_hostname_valid = 'False' if (x509_cert.matches_hostname(host)
                                        == X509_NAME_MISMATCH) else 'True'
        host_validation_xml = Element(
            'hostnameValidation',
            serverHostname=host,
            certificateMatchesServerHostname=is_hostname_valid)
        trust_validation_xml.append(host_validation_xml)

        # Path validation - OK
        for ((store_name, store_version),
             verify_str) in verify_dict.iteritems():
            path_attrib_xml = {
                'usingTrustStore': store_name,
                'trustStoreVersion': store_version,
                'validationResult': verify_str
            }

            # EV certs - Only Mozilla supported for now
            if (verify_str in 'ok') and ('Mozilla' in store_info):
                path_attrib_xml['isExtendedValidationCertificate'] = str(
                    self._is_ev_certificate(x509_cert))

            trust_validation_xml.append(
                Element('pathValidation', attrib=path_attrib_xml))

        # Path validation - Errors
        for ((store_name, store_version),
             error_msg) in verify_dict_error.iteritems():
            path_attrib_xml = {
                'usingTrustStore': store_name,
                'trustStoreVersion': store_version,
                'error': error_msg
            }

            trust_validation_xml.append(
                Element('pathValidation', attrib=path_attrib_xml))

        xml_output.append(trust_validation_xml)

        # XML output - OCSP Stapling
        if ocsp_response is None:
            ocsp_attr_xml = {'isSupported': 'False'}
            ocsp_xml = Element('ocspStapling', attrib=ocsp_attr_xml)
        else:
            ocsp_attr_xml = {'isSupported': 'True'}
            ocsp_xml = Element('ocspStapling', attrib=ocsp_attr_xml)

            ocsp_resp_attr_xml = {
                'isTrustedByMozillaCAStore':
                str(ocsp_response.verify(MOZILLA_STORE_PATH))
            }
            ocsp_resp_xmp = Element('ocspResponse', attrib=ocsp_resp_attr_xml)
            for (key, value) in ocsp_response.as_dict().items():
                ocsp_resp_xmp.append(_keyvalue_pair_to_xml(key, value))
            ocsp_xml.append(ocsp_resp_xmp)

        xml_output.append(ocsp_xml)

        return PluginBase.PluginResult(text_output, xml_output)
コード例 #23
0
class PluginSessionResumption(PluginBase.PluginBase):

    interface = PluginBase.PluginInterface(
        title="PluginSessionResumption",
        description=("Analyzes the target server's SSL session "
                     "resumption capabilities."))
    interface.add_command(
        command="resum",
        help=("Tests the server(s) for session resumption support using "
              "session IDs and TLS session tickets (RFC 5077)."))
    interface.add_command(
        command="resum_rate",
        help=("Performs 100 session resumptions with the server(s), "
              "in order to estimate the session resumption rate."),
        aggressive=True)

    def process_task(self, target, command, args):

        if command == 'resum':
            result = self._command_resum(target)
        elif command == 'resum_rate':
            result = self._command_resum_rate(target)
        else:
            raise Exception("PluginSessionResumption: Unknown command.")

        return result

    def _command_resum_rate(self, target):
        """
        Performs 100 session resumptions with the server in order to estimate
        the session resumption rate.
        """
        # Create a thread pool and process the jobs
        NB_THREADS = 20
        MAX_RESUM = 100
        thread_pool = ThreadPool()
        for _ in xrange(MAX_RESUM):
            thread_pool.add_job((self._resume_with_session_id, (target, )))
        thread_pool.start(NB_THREADS)

        # Format session ID results
        (txt_resum,
         xml_resum) = self._format_resum_id_results(thread_pool, MAX_RESUM)

        # Text output
        cmd_title = 'Resumption Rate with Session IDs'
        txt_result = [self.PLUGIN_TITLE_FORMAT(cmd_title) + ' ' + txt_resum[0]]
        txt_result.extend(txt_resum[1:])

        # XML output
        xml_result = Element('resum_rate', title=cmd_title)
        xml_result.append(xml_resum)

        thread_pool.join()
        return PluginBase.PluginResult(txt_result, xml_result)

    def _command_resum(self, target):
        """
        Tests the server for session resumption support using session IDs and
        TLS session tickets (RFC 5077).
        """
        NB_THREADS = 5
        MAX_RESUM = 5
        thread_pool = ThreadPool()

        for _ in xrange(MAX_RESUM):  # Test 5 resumptions with session IDs
            thread_pool.add_job(
                (self._resume_with_session_id, (target, ), 'session_id'))
        thread_pool.start(NB_THREADS)

        # Test TLS tickets support while threads are running
        try:
            (ticket_supported,
             ticket_reason) = self._resume_with_session_ticket(target)
            ticket_error = None
        except Exception as e:
            ticket_error = str(e.__class__.__name__) + ' - ' + str(e)

        # Format session ID results
        (txt_resum,
         xml_resum) = self._format_resum_id_results(thread_pool, MAX_RESUM)

        if ticket_error:
            ticket_txt = 'ERROR: ' + ticket_error
        else:
            ticket_txt = 'OK - Supported' if ticket_supported \
                                     else 'NOT SUPPORTED - ' + ticket_reason+'.'

        cmd_title = 'Session Resumption'
        txt_result = [self.PLUGIN_TITLE_FORMAT(cmd_title)]
        RESUM_FORMAT = '      {0:<35}{1}'.format

        txt_result.append(RESUM_FORMAT('With Session IDs:', txt_resum[0]))
        txt_result.extend(txt_resum[1:])
        txt_result.append(RESUM_FORMAT('With TLS Session Tickets:',
                                       ticket_txt))

        # XML output
        xml_resum_ticket_attr = {}
        if ticket_error:
            xml_resum_ticket_attr['error'] = ticket_error
        else:
            xml_resum_ticket_attr['isSupported'] = str(ticket_supported)
            if not ticket_supported:
                xml_resum_ticket_attr['reason'] = ticket_reason

        xml_resum_ticket = Element('sessionResumptionWithTLSTickets',
                                   attrib=xml_resum_ticket_attr)
        xml_result = Element('resum', title=cmd_title)
        xml_result.append(xml_resum)
        xml_result.append(xml_resum_ticket)

        thread_pool.join()
        return PluginBase.PluginResult(txt_result, xml_result)

    @staticmethod
    def _format_resum_id_results(thread_pool, MAX_RESUM):
        # Count successful/failed resumptions
        nb_resum = 0
        for completed_job in thread_pool.get_result():
            (job, (is_supported, reason_str)) = completed_job
            if is_supported:
                nb_resum += 1

        # Count errors and store error messages
        error_list = []
        for failed_job in thread_pool.get_error():
            (job, exception) = failed_job
            error_msg = str(
                exception.__class__.__name__) + ' - ' + str(exception)
            error_list.append(error_msg)
        nb_error = len(error_list)

        nb_failed = MAX_RESUM - nb_error - nb_resum

        # Text output
        SESSID_FORMAT = '{4} ({0} successful, {1} failed, {2} errors, {3} total attempts).{5}'.format
        sessid_try = ''
        if nb_resum == MAX_RESUM:
            sessid_stat = 'OK - Supported'
        elif nb_failed == MAX_RESUM:
            sessid_stat = 'NOT SUPPORTED'
        elif nb_error == MAX_RESUM:
            sessid_stat = 'ERROR'
        else:
            sessid_stat = 'PARTIALLY SUPPORTED'
            sessid_try = ' Try --resum_rate.'
        sessid_txt = SESSID_FORMAT(str(nb_resum), str(nb_failed),
                                   str(nb_error), str(MAX_RESUM), sessid_stat,
                                   sessid_try)

        ERRORS_FORMAT = '        ERROR #{0}: {1}'.format
        txt_result = [sessid_txt]
        # Add error messages
        if error_list:
            i = 0
            for error_msg in error_list:
                i += 1
                txt_result.append(ERRORS_FORMAT(str(i), error_msg))

        # XML output
        sessid_xml = str(nb_resum == MAX_RESUM)
        xml_resum_id_attr = {
            'totalAttempts': str(MAX_RESUM),
            'errors': str(nb_error),
            'isSupported': sessid_xml,
            'successfulAttempts': str(nb_resum),
            'failedAttempts': str(nb_failed)
        }
        xml_resum_id = Element('sessionResumptionWithSessionIDs',
                               attrib=xml_resum_id_attr)
        # Add errors
        if error_list:
            for error_msg in error_list:
                xml_resum_error = Element('error')
                xml_resum_error.text = error_msg
                xml_resum_id.append(xml_resum_error)

        return txt_result, xml_resum_id

    def _resume_with_session_id(self, target):
        """
        Performs one session resumption using Session IDs.
        """

        session1 = self._resume_ssl_session(target)
        try:  # Recover the session ID
            session1_id = self._extract_session_id(session1)
        except IndexError:
            return False, 'Session ID not assigned'

        if session1_id == '':
            return False, 'Session ID empty'

        # Try to resume that SSL session
        session2 = self._resume_ssl_session(target, session1)
        try:  # Recover the session ID
            session2_id = self._extract_session_id(session2)
        except IndexError:
            return False, 'Session ID not assigned'

        # Finally, compare the two Session IDs
        if session1_id != session2_id:
            return False, 'Session ID assigned but not accepted'

        return True, ''

    def _resume_with_session_ticket(self, target):
        """
        Performs one session resumption using TLS Session Tickets.
        """

        # Connect to the server and keep the SSL session
        session1 = self._resume_ssl_session(target, tlsTicket=True)
        try:  # Recover the TLS ticket
            session1_tls_ticket = self._extract_tls_session_ticket(session1)
        except IndexError:
            return False, 'TLS ticket not assigned'

        # Try to resume that session using the TLS ticket
        session2 = self._resume_ssl_session(target, session1, tlsTicket=True)
        try:  # Recover the TLS ticket
            session2_tls_ticket = self._extract_tls_session_ticket(session2)
        except IndexError:
            return False, 'TLS ticket not assigned'

        # Finally, compare the two TLS Tickets
        if session1_tls_ticket != session2_tls_ticket:
            return False, 'TLS ticket assigned but not accepted'

        return True, ''

    @staticmethod
    def _extract_session_id(ssl_session):
        """
        Extracts the SSL session ID from a SSL session object or raises IndexError
        if the session ID was not set.
        """
        session_string = ((ssl_session.as_text()).split("Session-ID:"))[1]
        session_id = (session_string.split("Session-ID-ctx:"))[0].strip()
        return session_id

    @staticmethod
    def _extract_tls_session_ticket(ssl_session):
        """
        Extracts the TLS session ticket from a SSL session object or raises
        IndexError if the ticket was not set.
        """
        session_string = ((
            ssl_session.as_text()).split("TLS session ticket:"))[1]
        session_tls_ticket = (session_string.split("Compression:"))[0]
        return session_tls_ticket

    def _resume_ssl_session(self, target, sslSession=None, tlsTicket=False):
        """
        Connect to the server and returns the session object that was assigned
        for that connection.
        If ssl_session is given, tries to resume that session.
        """
        sslConn = create_sslyze_connection(target, self._shared_settings)
        if not tlsTicket:
            # Need to disable TLS tickets to test session IDs, according to rfc5077:
            # If a ticket is presented by the client, the server MUST NOT attempt
            # to use the Session ID in the ClientHello for stateful session resumption
            sslConn.set_options(SSL_OP_NO_TICKET)  # Turning off TLS tickets.

        if sslSession:
            sslConn.set_session(sslSession)

        try:  # Perform the SSL handshake
            sslConn.connect()
            newSession = sslConn.get_session()  # Get session data
        finally:
            sslConn.close()

        return newSession
コード例 #24
0
    def process_task(self, target, command, arg):

        (_, _, _, sslVersion) = target

        # Get the server's cert chain
        sslConn = create_sslyze_connection(target, self._shared_settings,
                                           sslVersion)
        try:  # Perform the SSL handshake
            sslConn.connect()
            certChain = sslConn.get_peer_cert_chain()
        except ClientCertificateRequested:  # The server asked for a client cert
            # We can get the server cert chain anyway
            certChain = sslConn.get_peer_cert_chain()
        finally:
            sslConn.close()

        outputXml = Element(command, title=self.CMD_TITLE)
        outputTxt = [self.PLUGIN_TITLE_FORMAT(self.CMD_TITLE)]

        # Is this cert chain affected ?
        leafNotAfter = datetime.datetime.strptime(
            certChain[0].as_dict()['validity']['notAfter'],
            "%b %d %H:%M:%S %Y %Z")
        if leafNotAfter.year < 2016:
            # Not affected - the certificate expires before 2016
            outputTxt.append(
                self.FIELD_FORMAT('OK - Leaf certificate expires before 2016.',
                                  ''))
            outputXml.append(
                Element('chromeSha1Deprecation', isServerAffected=str(False)))

        else:
            certsWithSha1 = []
            for cert in certChain:
                if self._is_root_cert(cert):
                    # Ignore root certs as they are unaffected
                    continue

                if "sha1" in cert.as_dict()['signatureAlgorithm']:
                    certsWithSha1.append(cert)

            if certsWithSha1 == []:
                # Not affected - no certificates used SHA-1 in the chain
                outputTxt.append(
                    self.FIELD_FORMAT(
                        'OK - Certificate chain does not contain any SHA-1 certificate.',
                        ''))
                outputXml.append(
                    Element('chromeSha1Deprecation',
                            isServerAffected=str(False)))

            else:
                # Server is affected
                leafCertNotAfter = certChain[0].as_dict(
                )['validity']['notAfter']
                outputXml2 = Element('chromeSha1Deprecation',
                                     isServerAffected=str(True),
                                     leafCertificateNotAfter=leafCertNotAfter)
                chrome39Txt = 'OK'
                chrome40Txt = 'OK'

                if leafNotAfter.year == 2016 and leafNotAfter.month < 6:
                    chrome41Txt = self.CHROME_MINOR_ERROR_TXT

                elif leafNotAfter.year == 2016 and leafNotAfter.month >= 6:
                    chrome40Txt = self.CHROME_MINOR_ERROR_TXT
                    chrome41Txt = self.CHROME_MINOR_ERROR_TXT

                else:
                    # Certificate expires in 2017
                    chrome39Txt = self.CHROME_MINOR_ERROR_TXT
                    chrome40Txt = self.CHROME_NEUTRAL_TXT
                    chrome41Txt = self.CHROME_INSECURE_TXT

                # Text output
                certsWithSha1Txt = [
                    '"{0}"'.format(
                        PluginCertInfo._extract_subject_CN_or_OUN(cert))
                    for cert in certsWithSha1
                ]
                outputTxt.append(
                    self.FIELD_FORMAT("Chrome 39 behavior:", chrome39Txt))
                outputTxt.append(
                    self.FIELD_FORMAT("Chrome 40 behavior:", chrome40Txt))
                outputTxt.append(
                    self.FIELD_FORMAT("Chrome 41 behavior:", chrome41Txt))
                outputTxt.append(
                    self.FIELD_FORMAT("Leaf certificate notAfter field:",
                                      leafCertNotAfter))
                outputTxt.append(
                    self.FIELD_FORMAT("SHA1-signed certificates:",
                                      certsWithSha1Txt))

                # XML output
                affectedCertsXml = Element('sha1SignedCertificates')
                for cert in certsWithSha1:
                    affectedCertsXml.append(
                        PluginCertInfo._format_cert_to_xml(
                            cert, '', self._shared_settings['sni']))
                outputXml2.append(affectedCertsXml)

                outputXml2.append(
                    Element('chrome39',
                            behavior=chrome39Txt,
                            isAffected=str(False)
                            if chrome39Txt is 'OK' else str(True)))
                outputXml2.append(
                    Element('chrome40',
                            behavior=chrome40Txt,
                            isAffected=str(False)
                            if chrome40Txt is 'OK' else str(True)))
                outputXml2.append(
                    Element('chrome41',
                            behavior=chrome41Txt,
                            isAffected=str(True)))
                outputXml.append(outputXml2)

        return PluginBase.PluginResult(outputTxt, outputXml)
コード例 #25
0
class PluginChromeSha1Deprecation(PluginBase.PluginBase):

    interface = PluginBase.PluginInterface(title="PluginChromeSha1Deprecation",
                                           description=(''))
    interface.add_command(
        command="chrome_sha1",
        help=
        "Determines if the server will be affected by Google Chrome's SHA-1 deprecation plans. See "
        "http://googleonlinesecurity.blogspot.com/2014/09/gradually-sunsetting-sha-1.html for more information"
    )

    CMD_TITLE = "Google Chrome SHA-1 Deprecation Status"

    # Chrome icon descriptions
    CHROME_MINOR_ERROR_TXT = 'AFFECTED - SHA1-signed certificate(s) will trigger the "Secure, but minor errors" icon.'
    CHROME_NEUTRAL_TXT = 'AFFECTED - SHA1-signed certificate(s) will trigger the "Neutral, lacking security" icon.'
    CHROME_INSECURE_TXT = 'AFFECTED - SHA1-signed certificate(s) will trigger the "Affirmatively insecure" icon.'

    def process_task(self, target, command, arg):

        (_, _, _, sslVersion) = target

        # Get the server's cert chain
        sslConn = create_sslyze_connection(target, self._shared_settings,
                                           sslVersion)
        try:  # Perform the SSL handshake
            sslConn.connect()
            certChain = sslConn.get_peer_cert_chain()
        except ClientCertificateRequested:  # The server asked for a client cert
            # We can get the server cert chain anyway
            certChain = sslConn.get_peer_cert_chain()
        finally:
            sslConn.close()

        outputXml = Element(command, title=self.CMD_TITLE)
        outputTxt = [self.PLUGIN_TITLE_FORMAT(self.CMD_TITLE)]

        # Is this cert chain affected ?
        leafNotAfter = datetime.datetime.strptime(
            certChain[0].as_dict()['validity']['notAfter'],
            "%b %d %H:%M:%S %Y %Z")
        if leafNotAfter.year < 2016:
            # Not affected - the certificate expires before 2016
            outputTxt.append(
                self.FIELD_FORMAT('OK - Leaf certificate expires before 2016.',
                                  ''))
            outputXml.append(
                Element('chromeSha1Deprecation', isServerAffected=str(False)))

        else:
            certsWithSha1 = []
            for cert in certChain:
                if self._is_root_cert(cert):
                    # Ignore root certs as they are unaffected
                    continue

                if "sha1" in cert.as_dict()['signatureAlgorithm']:
                    certsWithSha1.append(cert)

            if certsWithSha1 == []:
                # Not affected - no certificates used SHA-1 in the chain
                outputTxt.append(
                    self.FIELD_FORMAT(
                        'OK - Certificate chain does not contain any SHA-1 certificate.',
                        ''))
                outputXml.append(
                    Element('chromeSha1Deprecation',
                            isServerAffected=str(False)))

            else:
                # Server is affected
                leafCertNotAfter = certChain[0].as_dict(
                )['validity']['notAfter']
                outputXml2 = Element('chromeSha1Deprecation',
                                     isServerAffected=str(True),
                                     leafCertificateNotAfter=leafCertNotAfter)
                chrome39Txt = 'OK'
                chrome40Txt = 'OK'

                if leafNotAfter.year == 2016 and leafNotAfter.month < 6:
                    chrome41Txt = self.CHROME_MINOR_ERROR_TXT

                elif leafNotAfter.year == 2016 and leafNotAfter.month >= 6:
                    chrome40Txt = self.CHROME_MINOR_ERROR_TXT
                    chrome41Txt = self.CHROME_MINOR_ERROR_TXT

                else:
                    # Certificate expires in 2017
                    chrome39Txt = self.CHROME_MINOR_ERROR_TXT
                    chrome40Txt = self.CHROME_NEUTRAL_TXT
                    chrome41Txt = self.CHROME_INSECURE_TXT

                # Text output
                certsWithSha1Txt = [
                    '"{0}"'.format(
                        PluginCertInfo._extract_subject_CN_or_OUN(cert))
                    for cert in certsWithSha1
                ]
                outputTxt.append(
                    self.FIELD_FORMAT("Chrome 39 behavior:", chrome39Txt))
                outputTxt.append(
                    self.FIELD_FORMAT("Chrome 40 behavior:", chrome40Txt))
                outputTxt.append(
                    self.FIELD_FORMAT("Chrome 41 behavior:", chrome41Txt))
                outputTxt.append(
                    self.FIELD_FORMAT("Leaf certificate notAfter field:",
                                      leafCertNotAfter))
                outputTxt.append(
                    self.FIELD_FORMAT("SHA1-signed certificates:",
                                      certsWithSha1Txt))

                # XML output
                affectedCertsXml = Element('sha1SignedCertificates')
                for cert in certsWithSha1:
                    affectedCertsXml.append(
                        PluginCertInfo._format_cert_to_xml(
                            cert, '', self._shared_settings['sni']))
                outputXml2.append(affectedCertsXml)

                outputXml2.append(
                    Element('chrome39',
                            behavior=chrome39Txt,
                            isAffected=str(False)
                            if chrome39Txt is 'OK' else str(True)))
                outputXml2.append(
                    Element('chrome40',
                            behavior=chrome40Txt,
                            isAffected=str(False)
                            if chrome40Txt is 'OK' else str(True)))
                outputXml2.append(
                    Element('chrome41',
                            behavior=chrome41Txt,
                            isAffected=str(True)))
                outputXml.append(outputXml2)

        return PluginBase.PluginResult(outputTxt, outputXml)

    @staticmethod
    def _is_root_cert(cert):
        # Root certificates are not affected by the deprecation of SHA1
        # However a properly configured server should not send the CA cert in the chain so I'm not using this for now
        if not ROOT_CERTS:
            #Parse the Mozilla Store into roots
            f = open(MOZILLA_STORE_PATH, 'r')
            f_contents = "\n".join(f.readlines())
            root_certs = f_contents.split("-----BEGIN CERTIFICATE-----")
            for r in root_certs:
                if not r.strip():
                    continue
                r = r.replace("-----END CERTIFICATE-----", "")
                r = r.replace("\n", "")
                r = r.replace("\r", "")
                d = base64.b64decode(r)
                ROOT_CERTS.append(hashlib.sha1(d).hexdigest())
        return cert.get_SHA1_fingerprint() in ROOT_CERTS
コード例 #26
0
class PluginSessionRenegotiation(PluginBase.PluginBase):

    interface = PluginBase.PluginInterface("PluginSessionRenegotiation",  "")
    interface.add_command(
        command="reneg",
        help=(
            "Tests the server(s) for client-initiated "
            'renegotiation and secure renegotiation support.'))


    def process_task(self, target, command, args):

        (clientReneg, secureReneg) = self._test_renegotiation(target)

        # Text output
        clientTxt = 'VULNERABLE - Server honors client-initiated renegotiations' if clientReneg else 'OK - Rejected'
        secureTxt = 'OK - Supported' if secureReneg else 'VULNERABLE - Secure renegotiation not supported'
        cmdTitle = 'Session Renegotiation'
        txtOutput = [self.PLUGIN_TITLE_FORMAT(cmdTitle)]

        txtOutput.append(self.FIELD_FORMAT('Client-initiated Renegotiations:', clientTxt))
        txtOutput.append(self.FIELD_FORMAT('Secure Renegotiation:', secureTxt))

        # XML output
        xmlReneg = Element('sessionRenegotiation',
                           attrib = {'canBeClientInitiated' : str(clientReneg),
                                     'isSecure' : str(secureReneg)})

        xmlOutput = Element(command, title=cmdTitle)
        xmlOutput.append(xmlReneg)

        return PluginBase.PluginResult(txtOutput, xmlOutput)


    def _test_renegotiation(self, target):
        """
        Checks whether the server honors session renegotiation requests and
        whether it supports secure renegotiation.
        """
        sslConn = create_sslyze_connection(target, self._shared_settings)

        try: # Perform the SSL handshake
            sslConn.connect()
            secureReneg = sslConn.get_secure_renegotiation_support()

            try: # Let's try to renegotiate
                sslConn.do_renegotiate()
                clientReneg = True

            # Errors caused by a server rejecting the renegotiation
            except socket.error as e:
                if 'connection was forcibly closed' in str(e.args):
                    clientReneg = False
                elif 'reset by peer' in str(e.args):
                    clientReneg = False
                else:
                    raise
            #except socket.timeout as e:
            #    result_reneg = 'Rejected (timeout)'
            except OpenSSLError as e:
                if 'handshake failure' in str(e.args):
                    clientReneg = False
                elif 'no renegotiation' in str(e.args):
                    clientReneg = False
                else:
                    raise

            # Should be last as socket errors are also IOError
            except IOError as e:
                if 'Nassl SSL handshake failed' in str(e.args):
                    clientReneg = False
                else:
                    raise

        finally:
            sslConn.close()

        return (clientReneg, secureReneg)
コード例 #27
0
class PluginMultipleTrustStores(PluginBase.PluginBase):
    interface = PluginBase.PluginInterface(title="PluginMultipleTrustStores",
                                           description=(''))
    interface.add_command(
        command="truststores",
        help=
        "Verifies the validity of target server's certificate chain against "
        "all the trust stores available as PEM files within ./plugins/data/.",
        dest=None)

    FIELD_FORMAT = '      {0:<40}{1}'.format

    def process_task(self, target, command, arg):

        # Get the certificate and validate it against all the trust stores
        (cert, verify_result) = self._get_cert(target, TRUST_STORE_PATHS)

        # Results formatting
        # Text output - display each trust store and the validation result
        cmdTitle = 'Multi Trust Store Validation'
        txtOutput = [self.PLUGIN_TITLE_FORMAT(cmdTitle)]
        isCertTrusted = True
        fingerprint = cert.get_SHA1_fingerprint()
        txtOutput.append(
            self.FIELD_FORMAT('Certificate SHA1 Fingerprint:', fingerprint))

        for trustStorePath in TRUST_STORE_PATHS:
            if verify_result[trustStorePath] != 'ok':
                isCertTrusted = False
            txtOutput.append(
                self.FIELD_FORMAT(
                    "Validation w/ '" + os.path.split(trustStorePath)[1] +
                    "': ", verify_result[trustStorePath]))

        # XML output.
        xmlOutput = Element(command, title=cmdTitle)
        trustStoresXml = Element('trustStoreList',
                                 isTrustedByAllTrustStores=str(isCertTrusted))

        for trustStorePath in TRUST_STORE_PATHS:
            # Add the result of each trust store
            trustStoresXml.append(
                Element('trustStore',
                        filePath=os.path.split(trustStorePath)[1],
                        verifyResult=verify_result[trustStorePath]))
        xmlOutput.append(trustStoresXml)

        # Add the certificate
        certXml = Element('certificate', sha1Fingerprint=fingerprint)
        for (key, value) in cert.as_dict().items():
            certXml.append(_keyvalue_pair_to_xml(key, value))

        xmlOutput.append(certXml)

        return PluginBase.PluginResult(txtOutput, xmlOutput)

    def _get_cert(self, target, trustStoreList):
        """
        Connects to the target server and returns the server's certificate
        Also performs verification against multiple trust stores.
        """
        verifyResults = {}
        for trustStorePath in trustStoreList:

            (host, ip, port, sslVersion) = target
            sslConn = create_sslyze_connection(
                target,
                self._shared_settings,
                sslVersion,
                sslVerifyLocations=trustStorePath)

            try:
                # Perform the SSL handshake
                sslConn.connect()
                x509Cert = sslConn.get_peer_certificate()
                (verifyCode,
                 verifyStr) = sslConn.get_certificate_chain_verify_result()

            except ClientCertificateError:
                # The server asked for a client cert
                # We can get the server cert anyway
                x509Cert = sslConn.get_peer_certificate()
                (verifyCode,
                 verifyStr) = sslConn.get_certificate_chain_verify_result()

            finally:
                sslConn.close()

            verifyResults[trustStorePath] = verifyStr

        return (x509Cert, verifyResults)
コード例 #28
0
    def process_task(self, target, command, args):

        MAX_THREADS = 30

        if command in ['sslv2', 'sslv3', 'tlsv1', 'tlsv1_1', 'tlsv1_2']:
            ssl_version = command
        else:
            raise Exception("PluginOpenSSLCipherSuites: Unknown command.")

        # Get the list of available cipher suites for the given ssl version
        ctSSL_initialize(multithreading=True)
        ctx = SSL_CTX.SSL_CTX(ssl_version)
        ctx.set_cipher_list('ALL:NULL:@STRENGTH')
        ssl = SSL.SSL(ctx)
        cipher_list = ssl.get_cipher_list()

        # Create a thread pool
        NB_THREADS = min(len(cipher_list),
                         MAX_THREADS)  # One thread per cipher
        thread_pool = ThreadPool()

        # Scan for every available cipher suite
        for cipher in cipher_list:
            thread_pool.add_job(
                (self._test_ciphersuite, (target, ssl_version, cipher)))

        # Scan for the preferred cipher suite
        thread_pool.add_job((self._pref_ciphersuite, (target, ssl_version)))

        # Start processing the jobs
        thread_pool.start(NB_THREADS)

        result_dicts = {
            'preferredCipherSuite': {},
            'acceptedCipherSuites': {},
            'rejectedCipherSuites': {},
            'errors': {}
        }

        # Store the results as they come
        for completed_job in thread_pool.get_result():
            (job, result) = completed_job
            if result is not None:
                (result_type, ssl_cipher, keysize, msg) = result
                (result_dicts[result_type])[ssl_cipher] = (msg, keysize)

        # Store thread pool errors
        for failed_job in thread_pool.get_error():
            (job, exception) = failed_job
            ssl_cipher = str(job[1][2])
            error_msg = str(exception.__class__.__module__) + '.' \
                        + str(exception.__class__.__name__) + ' - ' + str(exception)
            result_dicts['errors'][ssl_cipher] = (error_msg, None)

        thread_pool.join()
        ctSSL_cleanup()

        # Generate results
        return PluginBase.PluginResult(
            self._generate_txt_result(result_dicts, command),
            self._generate_xml_result(result_dicts, command))
コード例 #29
0
class PluginOpenSSLCipherSuites(PluginBase.PluginBase):

    interface = PluginBase.PluginInterface(
        "PluginOpenSSLCipherSuites",
        "Scans the target server for supported OpenSSL cipher suites.")
    interface.add_command(
        command="sslv2",
        help="Lists the SSL 2.0 OpenSSL cipher suites supported by the server.",
        dest=None)
    interface.add_command(
        command="sslv3",
        help="Lists the SSL 3.0 OpenSSL cipher suites supported by the server.",
        dest=None)
    interface.add_command(
        command="tlsv1",
        help="Lists the TLS 1.0 OpenSSL cipher suites supported by the server.",
        dest=None)
    interface.add_command(
        command="tlsv1_1",
        help="Lists the TLS 1.1 OpenSSL cipher suites supported by the server.",
        dest=None)
    interface.add_command(
        command="tlsv1_2",
        help="Lists the TLS 1.2 OpenSSL cipher suites supported by the server.",
        dest=None)
    interface.add_option(
        option='http_get',
        help="Option - For each cipher suite, sends an HTTP GET request after "
        "completing the SSL handshake and returns the HTTP status code.",
        dest=None)
    interface.add_option(
        option='hide_rejected_ciphers',
        help="Option - Hides the (usually long) list of cipher suites that were"
        " rejected by the server.",
        dest=None)

    def process_task(self, target, command, args):

        MAX_THREADS = 30

        if command in ['sslv2', 'sslv3', 'tlsv1', 'tlsv1_1', 'tlsv1_2']:
            ssl_version = command
        else:
            raise Exception("PluginOpenSSLCipherSuites: Unknown command.")

        # Get the list of available cipher suites for the given ssl version
        ctSSL_initialize(multithreading=True)
        ctx = SSL_CTX.SSL_CTX(ssl_version)
        ctx.set_cipher_list('ALL:NULL:@STRENGTH')
        ssl = SSL.SSL(ctx)
        cipher_list = ssl.get_cipher_list()

        # Create a thread pool
        NB_THREADS = min(len(cipher_list),
                         MAX_THREADS)  # One thread per cipher
        thread_pool = ThreadPool()

        # Scan for every available cipher suite
        for cipher in cipher_list:
            thread_pool.add_job(
                (self._test_ciphersuite, (target, ssl_version, cipher)))

        # Scan for the preferred cipher suite
        thread_pool.add_job((self._pref_ciphersuite, (target, ssl_version)))

        # Start processing the jobs
        thread_pool.start(NB_THREADS)

        result_dicts = {
            'preferredCipherSuite': {},
            'acceptedCipherSuites': {},
            'rejectedCipherSuites': {},
            'errors': {}
        }

        # Store the results as they come
        for completed_job in thread_pool.get_result():
            (job, result) = completed_job
            if result is not None:
                (result_type, ssl_cipher, keysize, msg) = result
                (result_dicts[result_type])[ssl_cipher] = (msg, keysize)

        # Store thread pool errors
        for failed_job in thread_pool.get_error():
            (job, exception) = failed_job
            ssl_cipher = str(job[1][2])
            error_msg = str(exception.__class__.__module__) + '.' \
                        + str(exception.__class__.__name__) + ' - ' + str(exception)
            result_dicts['errors'][ssl_cipher] = (error_msg, None)

        thread_pool.join()
        ctSSL_cleanup()

        # Generate results
        return PluginBase.PluginResult(
            self._generate_txt_result(result_dicts, command),
            self._generate_xml_result(result_dicts, command))

# == INTERNAL FUNCTIONS ==

# FORMATTING FUNCTIONS

    def _generate_txt_result(self, result_dicts, ssl_version):

        cipher_format = '        {0:<32}{1:<35}'
        title_format = '      {0:<32} '
        keysize_format = '{0:<25}{1:<14}'
        title_txt = self.PLUGIN_TITLE_FORMAT.format(ssl_version.upper() +
                                                    ' Cipher Suites')
        txt_result = [title_txt]

        txt_titles = [('preferredCipherSuite', 'Preferred Cipher Suite:'),
                      ('acceptedCipherSuites', 'Accepted Cipher Suite(s):'),
                      ('errors', 'Undefined - An unexpected error happened:'),
                      ('rejectedCipherSuites', 'Rejected Cipher Suite(s):')]

        if self._shared_settings['hide_rejected_ciphers']:
            txt_titles.pop(3)
            txt_result.append('')
            txt_result.append(
                title_format.format('Rejected Cipher Suite(s): Hidden'))

        for (result_type, result_title) in txt_titles:

            # Sort the cipher suites by results
            result_list = sorted(result_dicts[result_type].iteritems(),
                                 key=lambda (k, v): (v, k),
                                 reverse=True)

            # Add a new line and title
            txt_result.append('')
            if len(result_list) == 0:  # No ciphers
                txt_result.append(title_format.format(result_title + ' None'))
            else:
                txt_result.append(title_format.format(result_title))

                # Add one line for each ciphers
                for (cipher_txt, (msg, keysize)) in result_list:
                    if keysize:
                        cipher_txt = keysize_format.format(cipher_txt, keysize)

                    txt_result.append(cipher_format.format(cipher_txt, msg))

        return txt_result

    def _generate_xml_result(self, result_dicts, command):

        xml_result = Element(command, title=command.upper() + ' Cipher Suites')

        for (result_type, result_dict) in result_dicts.items():
            xml_dict = Element(result_type)

            # Sort the cipher suites by name to make the XML diff-able
            result_list = sorted(result_dict.items(),
                                 key=lambda (k, v): (k, v),
                                 reverse=False)

            # Add one element for each ciphers
            for (ssl_cipher, (msg, keysize)) in result_list:
                cipher_xml_attr = {'name': ssl_cipher, 'connectionStatus': msg}
                if keysize:
                    cipher_xml_attr['keySize'] = keysize
                cipher_xml = Element('cipherSuite', attrib=cipher_xml_attr)

                xml_dict.append(cipher_xml)

            xml_result.append(xml_dict)

        return xml_result

# SSL FUNCTIONS

    def _test_ciphersuite(self, target, ssl_version, ssl_cipher):
        """
        Initiates a SSL handshake with the server, using the SSL version and 
        cipher suite specified.
        """
        ssl_ctx = SSL_CTX.SSL_CTX(ssl_version)
        ssl_ctx.set_verify(constants.SSL_VERIFY_NONE)
        ssl_ctx.set_cipher_list(ssl_cipher)

        # ssl_connect can be an HTTPS connection or an SMTP STARTTLS connection
        ssl_connect = SSLyzeSSLConnection(self._shared_settings, target,
                                          ssl_ctx)

        try:  # Perform the SSL handshake
            ssl_connect.connect()

        except SSLHandshakeRejected as e:
            return ('rejectedCipherSuites', ssl_cipher, None, str(e))

        else:
            ssl_cipher = ssl_connect._ssl.get_current_cipher()
            if 'ADH' in ssl_cipher or 'AECDH' in ssl_cipher:
                keysize = 'Anon'  # Anonymous, let s not care about the key size
            else:
                keysize = str(
                    ssl_connect._ssl.get_current_cipher_bits()) + ' bits'

            status_msg = ssl_connect.post_handshake_check()
            return ('acceptedCipherSuites', ssl_cipher, keysize, status_msg)

        finally:
            ssl_connect.close()

        return

    def _pref_ciphersuite(self, target, ssl_version):
        """
        Initiates a SSL handshake with the server, using the SSL version and cipher
        suite specified.
        """
        ssl_ctx = SSL_CTX.SSL_CTX(ssl_version)
        ssl_ctx.set_verify(constants.SSL_VERIFY_NONE)
        # ssl_connect can be an HTTPS connection or an SMTP STARTTLS connection
        ssl_connect = SSLyzeSSLConnection(self._shared_settings,
                                          target,
                                          ssl_ctx,
                                          hello_workaround=True)

        try:  # Perform the SSL handshake
            ssl_connect.connect()

            ssl_cipher = ssl_connect._ssl.get_current_cipher()
            if 'ADH' in ssl_cipher or 'AECDH' in ssl_cipher:
                keysize = 'Anon'  # Anonymous, let s not care about the key size
            else:
                keysize = str(
                    ssl_connect._ssl.get_current_cipher_bits()) + ' bits'

            status_msg = ssl_connect.post_handshake_check()
            return ('preferredCipherSuite', ssl_cipher, keysize, status_msg)

        except:
            return None

        finally:
            ssl_connect.close()

        return
コード例 #30
0
    def process_task(self, target, command, arg):

        try:  # Get the certificate and the result of the cert validation
            (cert, certVerifyStr, ocspResp) = self._get_cert(target)
        except:
            raise

        trustedCert = True if 'ok' in certVerifyStr else False

        # Results formatting
        # Text output - certificate
        txt_result = [self.PLUGIN_TITLE_FORMAT('Certificate')]

        if arg == 'basic':
            cert_txt = self._get_basic_text(cert)
        elif arg == 'full':
            cert_txt = [cert.as_text()]
        else:
            raise Exception("PluginCertInfo: Unknown command.")

        fingerprint = cert.get_SHA1_fingerprint()

        # Cert chain validation
        trust_txt = 'Certificate is Trusted' if trustedCert \
            else 'Certificate is NOT Trusted: ' + certVerifyStr

        is_ev = self._is_ev_certificate(cert)
        if is_ev:
            trust_txt = trust_txt + ' - Extended Validation'

    # Hostname validation
        txt_result.append(
            self.FIELD_FORMAT("Validation w/ Mozilla's CA Store:", trust_txt))

        # TODO: Use SNI name when --sni was used
        is_host_valid = self._is_hostname_valid(cert, target)
        host_txt = 'OK - ' + is_host_valid + ' Matches' if is_host_valid \
                                         else 'MISMATCH'

        txt_result.append(self.FIELD_FORMAT("Hostname Validation:", host_txt))
        txt_result.append(self.FIELD_FORMAT('SHA1 Fingerprint:', fingerprint))
        txt_result.append('')
        txt_result.extend(cert_txt)

        # Text output - OCSP stapling
        txt_result.append('')
        txt_result.append(self.PLUGIN_TITLE_FORMAT('OCSP Stapling'))
        txt_result.extend(self._get_ocsp_text(ocspResp))

        # XML output: always return the full certificate
        host_xml = True if is_host_valid \
                        else False

        xml_result = Element(command, argument=arg, title='Certificate')
        trust_xml_attr = {
            'isTrustedByMozillaCAStore': str(trustedCert),
            'sha1Fingerprint': fingerprint,
            'isExtendedValidation': str(is_ev),
            'hasMatchingHostname': str(host_xml)
        }
        if certVerifyStr:
            trust_xml_attr['reasonWhyNotTrusted'] = certVerifyStr

        trust_xml = Element('certificate', attrib=trust_xml_attr)

        # Add certificate in PEM format
        PEMcert_xml = Element('asPEM')
        PEMcert_xml.text = cert.as_pem().strip()
        trust_xml.append(PEMcert_xml)

        for (key, value) in cert.as_dict().items():
            trust_xml.append(_keyvalue_pair_to_xml(key, value))

        xml_result.append(trust_xml)

        # XML output: OCSP Stapling
        if ocspResp is None:
            oscpAttr = {'error': 'Server did not send back an OCSP response'}
            ocspXml = Element('ocspStapling', attrib=oscpAttr)
        else:
            oscpAttr = {
                'isTrustedByMozillaCAStore':
                str(ocspResp.verify(MOZILLA_CA_STORE))
            }
            ocspXml = Element('ocspResponse', attrib=oscpAttr)

            for (key, value) in ocspResp.as_dict().items():
                ocspXml.append(_keyvalue_pair_to_xml(key, value))

        xml_result.append(ocspXml)

        return PluginBase.PluginResult(txt_result, xml_result)