Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sensitivity of is_vulnerable_to_client_renegotiation_dos configurable #661

Merged
merged 2 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading