Skip to content

Commit

Permalink
add client-initiated renegotiation detection
Browse files Browse the repository at this point in the history
  • Loading branch information
hph86 committed Feb 21, 2016
1 parent 884b13a commit fc1ae48
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 52 deletions.
6 changes: 4 additions & 2 deletions docs/source/development/rating.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 | |
Expand Down
9 changes: 9 additions & 0 deletions sslscan/module/rating/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
42 changes: 30 additions & 12 deletions sslscan/module/report/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
16 changes: 12 additions & 4 deletions sslscan/module/scan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,16 +737,21 @@ def _connect_internal_ssl(self, protocol_versions=None):

return None

def _connect_openssl(self, protocol_versions=None):
if openssl_enabled == False:
def _connect_openssl(
self,
protocol_versions=None,
ctx_options=None,
methods=None):
if not openssl_enabled:
return None
from sslscan._helper.openssl import convert_versions2methods

if protocol_versions is None:
protocol_versions = self._scanner.get_enabled_versions()

methods = convert_versions2methods(protocol_versions)
methods.reverse()
if methods is None:
methods = convert_versions2methods(protocol_versions)
methods.reverse()

for method in methods:
try:
Expand All @@ -756,6 +761,9 @@ def _connect_openssl(self, protocol_versions=None):
continue

ctx.set_cipher_list("ALL:COMPLEMENT")
if ctx_options:
for option in ctx_options:
ctx.set_options(option)
conn = self._scanner.handler.connect()
conn_ssl = SSL.Connection(ctx, conn._socket)
conn_ssl.set_tlsext_host_name(
Expand Down
105 changes: 71 additions & 34 deletions sslscan/module/scan/server_renegotiation.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
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
from sslscan.module.scan import BaseInfoScan

openssl_enabled = False
version_info = []
try:
from OpenSSL import SSL, _util
from OpenSSL import _util

openssl_enabled = True
if version_pyopenssl:
Expand All @@ -17,18 +21,36 @@
pass


class ServerRenegotiation(BaseScan):
class ServerRenegotiation(BaseInfoScan):
"""
Test if renegotiation is supported by the server.
"""

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)
BaseInfoScan.__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()
Expand All @@ -39,41 +61,56 @@ def run(self):
methods.reverse()

for method in methods:
try:
ctx = SSL.Context(method)
except:
# ToDo:
continue

ctx.set_cipher_list("ALL:COMPLEMENT")
ctx.set_options(_util.lib.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)
conn = self._scanner.handler.connect()
conn_ssl = SSL.Connection(ctx, conn._socket)
conn_ssl.set_tlsext_host_name(
self._scanner.handler.hostname.encode("utf-8")
ctx_options = (_util.lib.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION,)
conn_ssl = self._connect_openssl(
methods=(method,),
ctx_options=ctx_options
)
conn_ssl.set_connect_state()
try:
conn_ssl.do_handshake()
except Exception as e:
# ToDo:
# print(e)
conn_ssl.close()
if conn_ssl is None:
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)

0 comments on commit fc1ae48

Please sign in to comment.