Running a Scan in Python¶
Overview¶
SSLyze’s Python API can be used to run scans and process results in an automated fashion.
Every SSLyze class has typing annotations, which allows IDEs such as VS Code and PyCharms to auto-import modules and auto-complete field names. Make sure to leverage this typing information as it will make it significantly easier to use SSLyze’s Python API.
To run a scan against a server, the scan can be described via the ServerScanRequest
class, which contains
information about the server to scan(hostname, port, etc.):
try:
all_scan_requests = [
ServerScanRequest(server_location=ServerNetworkLocation(hostname="cloudflare.com")),
ServerScanRequest(server_location=ServerNetworkLocation(hostname="google.com")),
]
except ServerHostnameCouldNotBeResolved:
# Handle bad input ie. invalid hostnames
print("Error resolving the supplied hostnames")
return
More details can optionally be supplied to the ServerScanRequest
, including:
Server settings via the
server_location
argument, for example to use an HTTP proxy, or scan a specific IP address.Network settings via the
network_configuration
argument, for example to configure a client certificate, or scan a non-HTTP server.A specific of specific TLS checks to run (Heartbleed, cipher suites, etc.), via the scan_commands argument. By default, all the checks will be enabled.
Every type of TLS check that SSLyze can run against a server (supported cipher suites, Heartbleed, etc.) is
represented by a ScanCommand
. Once a ScanCommand
is run against a server, it returns a “result” object with
attributes containing the results of the scan command.
All the available ScanCommands
and corresponding results are described in Appendix: Scan Commands.
Then, to start the scan, pass the list of ServerScanRequest
to Scanner.queue_scans()
:
scanner = Scanner()
scanner.queue_scans(all_scan_requests)
The Scanner
class, uses a pool of workers to run the scans concurrently, but without DOS-ing the servers.
Lastly, the results can be retrieved using the Scanner.get_results()
method, which returns an iterable of
ServerScanResult
. Each result is returned as soon as the server scan was completed:
for server_scan_result in scanner.get_results():
print(f"\n\n****Results for {server_scan_result.server_location.hostname}****")
Full Example¶
A full example of running a scan on a couple servers follow:
def main() -> None:
print("=> Starting the scans")
date_scans_started = datetime.utcnow()
# First create the scan requests for each server that we want to scan
try:
all_scan_requests = [
ServerScanRequest(server_location=ServerNetworkLocation(hostname="cloudflare.com")),
ServerScanRequest(server_location=ServerNetworkLocation(hostname="google.com")),
]
except ServerHostnameCouldNotBeResolved:
# Handle bad input ie. invalid hostnames
print("Error resolving the supplied hostnames")
return
# Then queue all the scans
scanner = Scanner()
scanner.queue_scans(all_scan_requests)
# And retrieve and process the results for each server
all_server_scan_results = []
for server_scan_result in scanner.get_results():
all_server_scan_results.append(server_scan_result)
print(f"\n\n****Results for {server_scan_result.server_location.hostname}****")
# Were we able to connect to the server and run the scan?
if server_scan_result.scan_status == ServerScanStatusEnum.ERROR_NO_CONNECTIVITY:
# No we weren't
print(
f"\nError: Could not connect to {server_scan_result.server_location.hostname}:"
f" {server_scan_result.connectivity_error_trace}"
)
continue
# Since we were able to run the scan, scan_result is populated
assert server_scan_result.scan_result
# Process the result of the SSL 2.0 scan command
ssl2_attempt = server_scan_result.scan_result.ssl_2_0_cipher_suites
if ssl2_attempt.status == ScanCommandAttemptStatusEnum.ERROR:
# An error happened when this scan command was run
_print_failed_scan_command_attempt(ssl2_attempt)
elif ssl2_attempt.status == ScanCommandAttemptStatusEnum.COMPLETED:
# This scan command was run successfully
ssl2_result = ssl2_attempt.result
assert ssl2_result
print("\nAccepted cipher suites for SSL 2.0:")
for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
print(f"* {accepted_cipher_suite.cipher_suite.name}")
# Process the result of the TLS 1.3 scan command
tls1_3_attempt = server_scan_result.scan_result.tls_1_3_cipher_suites
if tls1_3_attempt.status == ScanCommandAttemptStatusEnum.ERROR:
_print_failed_scan_command_attempt(ssl2_attempt)
elif tls1_3_attempt.status == ScanCommandAttemptStatusEnum.COMPLETED:
tls1_3_result = tls1_3_attempt.result
assert tls1_3_result
print("\nAccepted cipher suites for TLS 1.3:")
for accepted_cipher_suite in tls1_3_result.accepted_cipher_suites:
print(f"* {accepted_cipher_suite.cipher_suite.name}")
# Process the result of the certificate info scan command
certinfo_attempt = server_scan_result.scan_result.certificate_info
if certinfo_attempt.status == ScanCommandAttemptStatusEnum.ERROR:
_print_failed_scan_command_attempt(certinfo_attempt)
elif certinfo_attempt.status == ScanCommandAttemptStatusEnum.COMPLETED:
certinfo_result = certinfo_attempt.result
assert certinfo_result
print("\nLeaf certificates deployed:")
for cert_deployment in certinfo_result.certificate_deployments:
leaf_cert = cert_deployment.received_certificate_chain[0]
print(
f"{leaf_cert.public_key().__class__.__name__}: {leaf_cert.subject.rfc4514_string()}"
f" (Serial: {leaf_cert.serial_number})"
)
# etc... Other scan command results to process are in server_scan_result.scan_result
# Lastly, save the all the results to a JSON file
json_file_out = Path("api_sample_results.json")
print(f"\n\n=> Saving scan results to {json_file_out}")
example_json_result_output(json_file_out, all_server_scan_results, date_scans_started, datetime.utcnow())
# And ensure we are able to parse them
print(f"\n\n=> Parsing scan results from {json_file_out}")
example_json_result_parsing(json_file_out)
Classes for Starting a Scan¶
- class sslyze.ServerScanRequest(server_location, network_configuration=None, scan_commands=<factory>, scan_commands_extra_arguments=<factory>, uuid=<factory>)¶
A request to scan a specific server.
- Parameters
server_location (
ServerNetworkLocation
) – The server to scan.network_configuration (
Optional
[ServerNetworkConfiguration
]) – An optional network configuration. If not supplied, a default configuration will be used.scan_commands (
Set
[ScanCommand
]) – An optional list of scan commands to run against the server. If not supplied, all available scan commands will be run.scan_commands_extra_arguments (
ScanCommandsExtraArguments
) – An optional list of extra arguments specific to some scan commands. If not supplied, no extra arguments will be set.uuid (
UUID
) –
- class sslyze.ServerNetworkLocation(hostname, port=443, ip_address=None, http_proxy_settings=None)¶
All the information needed to connect to a server.
- hostname¶
The server’s hostname.
- Type
str
- port¶
The server’s TLS port number.
- Type
int
- connection_type¶
How sslyze should connect to the server: either directly, or via an HTTP proxy.
- ip_address¶
The server’s IP address; only set if sslyze is connecting directly to the server. If no IP address is supplied and connection_type is set to DIRECT, sslyze will automatically lookup one IP address for the supplied hostname.
- Type
Optional[str]
- http_proxy_settings¶
The HTTP proxy configuration to use in order to tunnel the scans through a proxy; only set if sslyze is connecting to the server via an HTTP proxy. The proxy will be responsible for looking up the server’s IP address and connecting to it.
- Type
Optional[sslyze.server_setting.HttpProxySettings]
- Parameters
hostname (
str
) –port (
int
) –ip_address (
Optional
[str
]) –http_proxy_settings (
Optional
[HttpProxySettings
]) –
- class sslyze.Scanner(per_server_concurrent_connections_limit=None, concurrent_server_scans_limit=None, observers=None)¶
- Parameters
per_server_concurrent_connections_limit (
Optional
[int
]) –concurrent_server_scans_limit (
Optional
[int
]) –observers (
Optional
[Sequence
[ScannerObserver
]]) –
Additional settings: StartTLS, SNI, etc.¶
- class sslyze.ServerNetworkConfiguration(tls_server_name_indication, tls_opportunistic_encryption=None, tls_client_auth_credentials=None, xmpp_to_hostname=None, network_timeout=5, network_max_retries=3)¶
Additional network settings to provide fine-grained control on how to connect to a specific server.
- tls_server_name_indication¶
The hostname to set within the Server Name Indication TLS extension.
- Type
str
- tls_wrapped_protocol¶
The protocol wrapped in TLS that the server expects. It allows SSLyze to figure out how to establish a (Start)TLS connection to the server and what kind of “hello” message (SMTP, XMPP, etc.) to send to the server after the handshake was completed. If not supplied, standard TLS will be used.
- tls_client_auth_credentials¶
The client certificate and private key needed to perform mutual authentication with the server. If not supplied, SSLyze will attempt to connect to the server without performing client authentication.
- Type
Optional[sslyze.server_setting.ClientAuthenticationCredentials]
- xmpp_to_hostname¶
The hostname to set within the to attribute of the XMPP stream. If not supplied, the server’s hostname will be used. Should only be set if the supplied tls_wrapped_protocol is an XMPP protocol.
- Type
Optional[str]
- network_timeout¶
The timeout (in seconds) to be used when attempting to establish a connection to the server.
- Type
int
- network_max_retries¶
The number of retries SSLyze will perform when attempting to establish a connection to the server.
- Type
int
- Parameters
tls_server_name_indication (
str
) –tls_opportunistic_encryption (
Optional
[ProtocolWithOpportunisticTlsEnum
]) –tls_client_auth_credentials (
Optional
[ClientAuthenticationCredentials
]) –xmpp_to_hostname (
Optional
[str
]) –network_timeout (
int
) –network_max_retries (
int
) –
- class sslyze.ProtocolWithOpportunisticTlsEnum(value)¶
The list of plaintext protocols supported by SSLyze for opportunistic TLS upgrade (such as STARTTLS).
This allows SSLyze to figure out how to complete an SSL/TLS handshake with the server.
- SMTP = 'SMTP'¶
- XMPP = 'XMPP'¶
- XMPP_SERVER = 'XMPP_SERVER'¶
- FTP = 'FTP'¶
- POP3 = 'POP3'¶
- LDAP = 'LDAP'¶
- IMAP = 'IMAP'¶
- RDP = 'RDP'¶
- POSTGRES = 'POSTGRES'¶
- classmethod from_default_port(port)¶
Given a port number, return the protocol that uses this port number by default.
- Parameters
port (
int
) –- Return type
Optional
[ProtocolWithOpportunisticTlsEnum
]
Enabling SSL/TLS client authentication¶
- class sslyze.ClientAuthenticationCredentials(certificate_chain_path, key_path, key_password='', key_type=OpenSslFileTypeEnum.PEM)¶
Everything needed by a client to perform SSL/TLS client authentication with the server.
- certificate_chain_path¶
Path to the file containing the client’s certificate.
- Type
pathlib.Path
- key_path¶
Path to the file containing the client’s private key.
- Type
pathlib.Path
- key_password¶
The password to decrypt the private key.
- Type
str
- key_type¶
The format of the key file.
- Parameters
certificate_chain_path (
Path
) –key_path (
Path
) –key_password (
str
) –key_type (
OpenSslFileTypeEnum
) –
Classes for Processing Scan Results¶
- class sslyze.ServerScanResult(uuid, server_location, network_configuration, connectivity_status, connectivity_error_trace, connectivity_result, scan_status, scan_result)¶
The result of scanning a server.
- uuid¶
- Type
uuid.UUID
- server_location¶
- network_configuration¶
- connectivity_status¶
Whether SSLyze was able to connect to the server, or not.
- connectivity_error_trace¶
The connectivity error; only set if SSLyze was NOT able to connect to the server.
- Type
Optional[traceback.TracebackException]
- connectivity_result¶
The result of connectivity testing; only set if SSLyze was able to connect to the server.
- Type
- scan_status¶
Whether SSLyze was able to complete the scan, or not.
- scan_result¶
The result of the scan; only set if SSLyze was able to complete the scan.
- Type
- Parameters
uuid (
UUID
) –server_location (
ServerNetworkLocation
) –network_configuration (
ServerNetworkConfiguration
) –connectivity_status (
ServerConnectivityStatusEnum
) –connectivity_error_trace (
Optional
[TracebackException
]) –connectivity_result (
Optional
[ServerTlsProbingResult
]) –scan_status (
ServerScanStatusEnum
) –scan_result (
Optional
[AllScanCommandsAttempts
]) –
- class sslyze.ServerConnectivityStatusEnum(value)¶
An enumeration.
- class sslyze.ServerScanStatusEnum(value)¶
An enumeration.
- class sslyze.ServerTlsProbingResult(highest_tls_version_supported, cipher_suite_supported, client_auth_requirement, supports_ecdh_key_exchange)¶
Additional details about the server, detected via connectivity testing.
- Parameters
highest_tls_version_supported (
TlsVersionEnum
) –cipher_suite_supported (
str
) –client_auth_requirement (
ClientAuthRequirementEnum
) –supports_ecdh_key_exchange (
bool
) –
- class sslyze.AllScanCommandsAttempts(certificate_info, ssl_2_0_cipher_suites, ssl_3_0_cipher_suites, tls_1_0_cipher_suites, tls_1_1_cipher_suites, tls_1_2_cipher_suites, tls_1_3_cipher_suites, tls_compression, tls_1_3_early_data, openssl_ccs_injection, tls_fallback_scsv, heartbleed, robot, session_renegotiation, session_resumption, elliptic_curves, http_headers)¶
The result of every scan command supported by SSLyze.
- Parameters
certificate_info (
CertificateInfoScanAttempt
) –ssl_2_0_cipher_suites (
CipherSuitesScanAttempt
) –ssl_3_0_cipher_suites (
CipherSuitesScanAttempt
) –tls_1_0_cipher_suites (
CipherSuitesScanAttempt
) –tls_1_1_cipher_suites (
CipherSuitesScanAttempt
) –tls_1_2_cipher_suites (
CipherSuitesScanAttempt
) –tls_1_3_cipher_suites (
CipherSuitesScanAttempt
) –tls_compression (
CompressionScanAttempt
) –tls_1_3_early_data (
EarlyDataScanAttempt
) –openssl_ccs_injection (
OpenSslCcsInjectionScanAttempt
) –tls_fallback_scsv (
FallbackScsvScanAttempt
) –heartbleed (
HeartbleedScanAttempt
) –robot (
RobotScanAttempt
) –session_renegotiation (
SessionRenegotiationScanAttempt
) –session_resumption (
SessionResumptionSupportScanAttempt
) –elliptic_curves (
SupportedEllipticCurvesScanAttempt
) –http_headers (
HttpHeadersScanAttempt
) –
- class sslyze.ScanCommandAttempt(status, error_reason, error_trace, result)¶
The result of a single scan command.
- status¶
Whether this specific scan command was ran successfully.
- Type
sslyze.scanner.scan_command_attempt.ScanCommandAttemptStatusEnum
- error_reason¶
The reason why the scan command failed; None if the scan command succeeded.
- error_trace¶
The exception trace of when the scan command failed; None if the scan command succeeded.
- Type
Optional[traceback.TracebackException]
- result¶
The actual result of the scan command; None if the scan command failed. The type of this attribute is the “ScanResult” object corresponding to the scan command.
- Type
Optional[sslyze.scanner.scan_command_attempt._ScanCommandResultTypeVar]
- Parameters
status (
ScanCommandAttemptStatusEnum
) –error_reason (
Optional
[ScanCommandErrorReasonEnum
]) –error_trace (
Optional
[TracebackException
]) –result (
Optional
[TypeVar
(_ScanCommandResultTypeVar
)]) –