Skip to content

Commit

Permalink
Merge pull request #661 from mxsasha/configurable-renegotiation-limit
Browse files Browse the repository at this point in the history
Make sensitivity of is_vulnerable_to_client_renegotiation_dos configurable
  • Loading branch information
nabla-c0d3 authored Dec 27, 2024
2 parents 7fd482c + fca777b commit a0ec707
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 18 deletions.
1 change: 1 addition & 0 deletions sslyze/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
# Classes for setting up scan commands and extra arguments
from sslyze.plugins.scan_commands import ScanCommand
from sslyze.plugins.certificate_info.implementation import CertificateInfoExtraArgument
from sslyze.plugins.session_renegotiation_plugin import SessionRenegotiationExtraArgument

# Classes for scanning the servers
from sslyze.scanner.models import (
Expand Down
51 changes: 38 additions & 13 deletions sslyze/plugins/session_renegotiation_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,52 @@
ScanCommandExtraArgument,
ScanJob,
ScanCommandResult,
ScanCommandWrongUsageError,
ScanCommandCliConnector,
ScanJobResult,
)
from sslyze.server_connectivity import ServerConnectivityInfo, TlsVersionEnum


@dataclass(frozen=True)
class SessionRenegotiationExtraArgument(ScanCommandExtraArgument):
"""Additional configuration for testing a server for client-initiated renegotiation.
Attributes:
client_renegotiation_attempts: The number of attempts to make when testing the client initiated
renegotiation DoS vector. If the server accepts this many attempts,
is_vulnerable_to_client_renegotiation_dos is set. Default: 10.
"""

client_renegotiation_attempts: int


@dataclass(frozen=True)
class SessionRenegotiationScanResult(ScanCommandResult):
"""The result of testing a server for insecure TLS renegotiation and client-initiated renegotiation.
Attributes:
accepts_client_renegotiation: True if the server honors client-initiated renegotiation attempts.
supports_secure_renegotiation: True if the server supports secure renegotiation.
client_renegotiations_success_count: the number of successful client-initiated renegotiation attempts.
"""

supports_secure_renegotiation: bool
is_vulnerable_to_client_renegotiation_dos: bool
client_renegotiations_success_count: int


class SessionRenegotiationScanResultAsJson(BaseModelWithOrmModeAndForbid):
supports_secure_renegotiation: bool
is_vulnerable_to_client_renegotiation_dos: bool
client_renegotiations_success_count: int


class SessionRenegotiationScanAttemptAsJson(ScanCommandAttemptAsJson):
result: Optional[SessionRenegotiationScanResultAsJson]


class _ScanJobResultEnum(Enum):
IS_VULNERABLE_TO_CLIENT_RENEG_DOS = 1
CLIENT_RENEG_RESULT = 1
SUPPORTS_SECURE_RENEG = 2


Expand Down Expand Up @@ -75,21 +90,25 @@ def result_to_console_output(cls, result: SessionRenegotiationScanResult) -> Lis
return result_txt


class SessionRenegotiationImplementation(ScanCommandImplementation[SessionRenegotiationScanResult, None]):
class SessionRenegotiationImplementation(
ScanCommandImplementation[SessionRenegotiationScanResult, SessionRenegotiationExtraArgument]
):
"""Test a server for insecure TLS renegotiation and client-initiated renegotiation."""

cli_connector_cls = _SessionRenegotiationCliConnector

@classmethod
def scan_jobs_for_scan_command(
cls, server_info: ServerConnectivityInfo, extra_arguments: Optional[ScanCommandExtraArgument] = None
cls, server_info: ServerConnectivityInfo, extra_arguments: Optional[SessionRenegotiationExtraArgument] = None
) -> List[ScanJob]:
if extra_arguments:
raise ScanCommandWrongUsageError("This plugin does not take extra arguments")
client_renegotiation_attempts = extra_arguments.client_renegotiation_attempts if extra_arguments else 10

return [
ScanJob(function_to_call=_test_secure_renegotiation, function_arguments=[server_info]),
ScanJob(function_to_call=_test_client_renegotiation, function_arguments=[server_info]),
ScanJob(
function_to_call=_test_client_renegotiation,
function_arguments=[server_info, client_renegotiation_attempts],
),
]

@classmethod
Expand All @@ -104,11 +123,13 @@ def result_for_completed_scan_jobs(
result_enum, value = job.get_result()
results_dict[result_enum] = value

is_vulnerable_to_client_renegotiation_dos, client_renegotiations_success_count = results_dict[
_ScanJobResultEnum.CLIENT_RENEG_RESULT
]
return SessionRenegotiationScanResult(
is_vulnerable_to_client_renegotiation_dos=results_dict[
_ScanJobResultEnum.IS_VULNERABLE_TO_CLIENT_RENEG_DOS
],
is_vulnerable_to_client_renegotiation_dos=is_vulnerable_to_client_renegotiation_dos,
supports_secure_renegotiation=results_dict[_ScanJobResultEnum.SUPPORTS_SECURE_RENEG],
client_renegotiations_success_count=client_renegotiations_success_count,
)


Expand Down Expand Up @@ -147,9 +168,12 @@ def _test_secure_renegotiation(server_info: ServerConnectivityInfo) -> Tuple[_Sc
return _ScanJobResultEnum.SUPPORTS_SECURE_RENEG, supports_secure_renegotiation


def _test_client_renegotiation(server_info: ServerConnectivityInfo) -> Tuple[_ScanJobResultEnum, bool]:
def _test_client_renegotiation(
server_info: ServerConnectivityInfo, client_renegotiation_attempts: int
) -> Tuple[_ScanJobResultEnum, Tuple[bool, int]]:
"""Check whether the server honors session renegotiation requests."""
# Try with TLS 1.2 even if the server supports TLS 1.3 or higher as there is no reneg with TLS 1.3
client_renegotiations_success_count = 0
if server_info.tls_probing_result.highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_3.value:
tls_version_to_use = TlsVersionEnum.TLS_1_2
downgraded_from_tls_1_3 = True
Expand Down Expand Up @@ -180,8 +204,9 @@ def _test_client_renegotiation(server_info: ServerConnectivityInfo) -> Tuple[_Sc
try:
# Do a reneg multiple times in a row to be 100% sure that the server has no mitigations in place
# https://github.com/nabla-c0d3/sslyze/issues/473
for i in range(10):
for i in range(client_renegotiation_attempts):
ssl_connection.ssl_client.do_renegotiate()
client_renegotiations_success_count += 1
accepts_client_renegotiation = True

# Errors caused by a server rejecting the renegotiation
Expand Down Expand Up @@ -230,4 +255,4 @@ def _test_client_renegotiation(server_info: ServerConnectivityInfo) -> Tuple[_Sc
finally:
ssl_connection.close()

return _ScanJobResultEnum.IS_VULNERABLE_TO_CLIENT_RENEG_DOS, accepts_client_renegotiation
return _ScanJobResultEnum.CLIENT_RENEG_RESULT, (accepts_client_renegotiation, client_renegotiations_success_count)
6 changes: 5 additions & 1 deletion sslyze/scanner/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
from sslyze.plugins.openssl_cipher_suites.implementation import CipherSuitesScanResult
from sslyze.plugins.robot.implementation import RobotScanResult
from sslyze.plugins.scan_commands import ScanCommand, ScanCommandsRepository
from sslyze.plugins.session_renegotiation_plugin import SessionRenegotiationScanResult
from sslyze.plugins.session_renegotiation_plugin import (
SessionRenegotiationScanResult,
SessionRenegotiationExtraArgument,
)
from sslyze.plugins.session_resumption.implementation import (
SessionResumptionSupportScanResult,
SessionResumptionSupportExtraArgument,
Expand All @@ -33,6 +36,7 @@ class ScanCommandsExtraArguments:
# Field is present if extra arguments were provided for the corresponding scan command
certificate_info: Optional[CertificateInfoExtraArgument] = None
session_resumption: Optional[SessionResumptionSupportExtraArgument] = None
session_renegotiation: Optional[SessionRenegotiationExtraArgument] = None


@dataclass(frozen=True)
Expand Down
8 changes: 5 additions & 3 deletions tests/json_tests/sslyze_output.json
Original file line number Diff line number Diff line change
Expand Up @@ -8232,7 +8232,8 @@
"error_trace": null,
"result": {
"supports_secure_renegotiation": true,
"is_vulnerable_to_client_renegotiation_dos": false
"is_vulnerable_to_client_renegotiation_dos": false,
"client_renegotiations_success_count": 0
}
},
"session_resumption": {
Expand Down Expand Up @@ -17403,7 +17404,8 @@
"error_trace": null,
"result": {
"supports_secure_renegotiation": true,
"is_vulnerable_to_client_renegotiation_dos": false
"is_vulnerable_to_client_renegotiation_dos": false,
"client_renegotiations_success_count": 0
}
},
"session_resumption": {
Expand Down Expand Up @@ -17545,4 +17547,4 @@
"date_scans_completed": "2024-02-24T18:51:11.055270",
"sslyze_version": "6.0.0b0",
"sslyze_url": "https://github.com/nabla-c0d3/sslyze"
}
}
12 changes: 11 additions & 1 deletion tests/plugins_tests/test_session_renegotiation_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
SessionRenegotiationImplementation,
SessionRenegotiationScanResult,
SessionRenegotiationScanResultAsJson,
SessionRenegotiationExtraArgument,
)

from sslyze.server_setting import (
Expand Down Expand Up @@ -40,17 +41,26 @@ def test_renegotiation_good(self) -> None:
@can_only_run_on_linux_64
def test_renegotiation_is_vulnerable_to_client_renegotiation_dos(self) -> None:
# Given a server that is vulnerable to client renegotiation DOS
expected_renegotiations_success_count = 3

with LegacyOpenSslServer() as server:
server_location = ServerNetworkLocation(
hostname=server.hostname, ip_address=server.ip_address, port=server.port
)
extra_arg = SessionRenegotiationExtraArgument(
client_renegotiation_attempts=expected_renegotiations_success_count
)
server_info = check_connectivity_to_server_and_return_info(server_location)

# When testing for insecure reneg, it succeeds
result: SessionRenegotiationScanResult = SessionRenegotiationImplementation.scan_server(server_info)
result: SessionRenegotiationScanResult = SessionRenegotiationImplementation.scan_server(
server_info,
extra_arguments=extra_arg,
)

# And the server is reported as vulnerable
assert result.is_vulnerable_to_client_renegotiation_dos
assert result.client_renegotiations_success_count == expected_renegotiations_success_count

# And a CLI output can be generated
assert SessionRenegotiationImplementation.cli_connector_cls.result_to_console_output(result)
Expand Down

0 comments on commit a0ec707

Please sign in to comment.