From a888314d9e10bba3b2b3086fc14f5535d0b1b94b Mon Sep 17 00:00:00 2001 From: Hanno Heinrichs Date: Sat, 13 Feb 2016 11:33:53 +0100 Subject: [PATCH] add client-initiated renegotiation detection --- docs/source/development/rating.rst | 6 +- sslscan/module/rating/builtin.py | 9 +++ sslscan/module/report/terminal.py | 42 ++++++++---- sslscan/module/scan/server_renegotiation.py | 75 +++++++++++++++++---- 4 files changed, 106 insertions(+), 26 deletions(-) diff --git a/docs/source/development/rating.rst b/docs/source/development/rating.rst index 6ccce45..593171a 100644 --- a/docs/source/development/rating.rst +++ b/docs/source/development/rating.rst @@ -46,10 +46,12 @@ Rules +-----------------------------------------------------+-----------+----------------------------+ | **Renegotiation** | +-----------------------------------------------------+-----------+----------------------------+ -| server.renegotiation.support | | | -+-----------------------------------------------------+-----------+----------------------------+ | server.renegotiation.secure | | | +-----------------------------------------------------+-----------+----------------------------+ +| server.renegotiation.ci_secure | | | ++-----------------------------------------------------+-----------+----------------------------+ +| server.renegotiation.ci_insecure | | | ++-----------------------------------------------------+-----------+----------------------------+ | **Session** | +-----------------------------------------------------+-----------+----------------------------+ | server.session.compression | Boolean | | diff --git a/sslscan/module/rating/builtin.py b/sslscan/module/rating/builtin.py index acda803..2632ae7 100644 --- a/sslscan/module/rating/builtin.py +++ b/sslscan/module/rating/builtin.py @@ -73,6 +73,15 @@ def __init__(self, **kwargs): ) ) + self.add_rule( + RatingRule( + "server.renegotiation.ci_insecure", + rules=[ + lambda v, i, kb: 6 if v else None, + ] + ) + ) + self.add_rule( RatingRule( "server.renegotiation.secure", diff --git a/sslscan/module/report/terminal.py b/sslscan/module/report/terminal.py index 7769f34..bad0d70 100644 --- a/sslscan/module/report/terminal.py +++ b/sslscan/module/report/terminal.py @@ -271,35 +271,53 @@ def _print_server_ciphers(self, kb): print("") def _print_host_renegotiation(self, kb): - if kb.get("server.renegotiation.support") is None: + if kb.get("server.renegotiation.secure") is None: return - reneg_support = kb.get("server.renegotiation.support") + print("TLS renegotiation:") + + reneg_secure = kb.get("server.renegotiation.secure") rating_renegotiation = self._rating.rate( - "server.renegotiation.support", - reneg_support + "server.renegotiation.secure", + reneg_secure ) - print("TLS renegotiation:") print( - " Supported: {1}{0}{2}".format( - "yes" if reneg_support else "no", + " Secure renegotiation support: {1}{0}{2}".format( + "yes" if reneg_secure else "no", helper.rating2color(self.color, rating_renegotiation), self.color.RESET ) ) - reneg_secure = kb.get("server.renegotiation.secure") + reneg_support = kb.get("server.renegotiation.ci_secure") rating_renegotiation = self._rating.rate( - "server.renegotiation.secure", - reneg_secure + "server.renegotiation.ci_secure", + reneg_support ) print( - " Secure: {1}{0}{2}".format( - "yes" if reneg_secure else "no", + " Client-initiated renegotiation (secure): {1}{0}{2}".format( + "yes" if reneg_support else "no", helper.rating2color(self.color, rating_renegotiation), self.color.RESET ) ) + + reneg_support = kb.get("server.renegotiation.ci_insecure") + # Remove check when insecure, client-initiated renegotiations are + # checked properly (see server_renegotiation.py). + if reneg_support is not None: + rating_renegotiation = self._rating.rate( + "server.renegotiation.ci_insecure", + reneg_support + ) + print( + " Client-initiated renegotiation (insecure): {1}{0}{2}".format( + "yes" if reneg_support else "no", + helper.rating2color(self.color, rating_renegotiation), + self.color.RESET + ) + ) + print("") def _print_server_alpn(self, kb): diff --git a/sslscan/module/scan/server_renegotiation.py b/sslscan/module/scan/server_renegotiation.py index 0b9fecd..9ab4002 100644 --- a/sslscan/module/scan/server_renegotiation.py +++ b/sslscan/module/scan/server_renegotiation.py @@ -1,5 +1,9 @@ from sslscan import modules -from sslscan._helper.openssl import version_openssl, version_pyopenssl, convert_versions2methods +from sslscan._helper.openssl import ( + version_openssl, + version_pyopenssl, + convert_versions2methods +) from sslscan.module import STATUS_OK, STATUS_ERROR from sslscan.module.scan import BaseScan @@ -25,11 +29,29 @@ class ServerRenegotiation(BaseScan): name = "server.renegotiation" alias = ("renegotiation",) status = STATUS_OK if openssl_enabled else STATUS_ERROR - status_messages = ["OpenSSL is {}".format("available" if openssl_enabled else "missing")] + version_info + status_messages = [ + "OpenSSL is {}".format("available" if openssl_enabled else "missing") + ] + version_info def __init__(self, **kwargs): BaseScan.__init__(self, **kwargs) + def _supports_client_initiated_renegotiation(self, conn_ssl): + tr = _util.lib.SSL_total_renegotiations(conn_ssl._ssl) + if _util.lib.SSL_renegotiate(conn_ssl._ssl) != 1: + return False + if _util.lib.SSL_do_handshake(conn_ssl._ssl) != 1: + return False + + # Check that + # - renegotiation counter has increased and + # - no renegotiations are pending + if (_util.lib.SSL_total_renegotiations(conn_ssl._ssl) == tr + 1 and + _util.lib.SSL_renegotiate_pending(conn_ssl._ssl) == 0): + return True + else: + return False + def run(self): kb = self._scanner.get_knowledge_base() @@ -55,25 +77,54 @@ def run(self): conn_ssl.set_connect_state() try: conn_ssl.do_handshake() - except Exception as e: + except Exception: # ToDo: # print(e) conn_ssl.close() continue - kb.set("server.renegotiation.support", False) - if _util.lib.SSL_get_secure_renegotiation_support(conn_ssl._ssl) == 1: + # Does server signal support for secure renegotiation? + if (_util.lib.SSL_get_secure_renegotiation_support(conn_ssl._ssl) + == 1): kb.set("server.renegotiation.secure", True) - kb.set("server.renegotiation.support", True) else: kb.set("server.renegotiation.secure", False) - kb.set("server.renegotiation.support", False) - cipher_status = _util.lib.SSL_do_handshake(conn_ssl._ssl) - if cipher_status == 1: - if _util.lib.SSL_get_state(conn_ssl._ssl) == SSL.SSL_ST_OK: - kb.set("server.renegotiation.support", True) - conn_ssl.close() + # Setting ci_secure to False because pySSLScan can detect this + # feature in all cases and set True if needed. + kb.set("server.renegotiation.ci_secure", False) + + # Setting ci_insecure to None because pySSLScan lacks support for + # checking insecure, client-initiated renegotitations if the server + # provides secure renegotiation support. None prevents misleading + # output in this case. + # If the server does not provide secure renegotiation support, any + # client-initiated renegotiation must be considered insecure. In + # this case, True and False are set correctly for ci_insecure. + kb.set("server.renegotiation.ci_insecure", None) + # Does the server accept client-initiated renegotiatons? + if self._supports_client_initiated_renegotiation(conn_ssl): + if kb.get("server.renegotiation.secure"): + # Renegotiation was performed securely, we do not know + # about insecure renegotiations yet. + kb.set("server.renegotiation.ci_secure", True) + else: + # Renegotiation was performed insecurely, no need to check + # any further. + kb.set("server.renegotiation.ci_insecure", True) + else: + if not kb.get("server.renegotiation.secure"): + # Server does not support secure renegotitation and client- + # initiated renegotiation attempt failed. Thus, the server + # does not allow client-initiated insecure renegotiations + # either. + kb.set("server.renegotiation.ci_insecure", False) + + # TODO: Implement insecure, client-initiated renegotiation (i.e. + # without RFC5746-compliance) for scenarios where the server + # provides secure renegotiation support. + + conn_ssl.close() modules.register(ServerRenegotiation)