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 (
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.
- port¶
The server’s TLS port number.
- 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.
- 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.
- 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.
- 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.
- 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.
- network_timeout¶
The timeout (in seconds) to be used when attempting to establish a connection to the server.
- network_max_retries¶
The number of retries SSLyze will perform when attempting to establish a connection to the server.
- 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, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)¶
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.
- key_path¶
Path to the file containing the client’s private key.
- key_password¶
The password to decrypt the private key.
- 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¶
- 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.
- connectivity_result¶
The result of connectivity testing; only set if SSLyze was able to connect to the server.
- 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.
- 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, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)¶
- class sslyze.ServerScanStatusEnum(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)¶
- 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.
- 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.
- 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.
- Parameters:
status (
ScanCommandAttemptStatusEnum
) –error_reason (
Optional
[ScanCommandErrorReasonEnum
]) –error_trace (
Optional
[TracebackException
]) –result (
Optional
[TypeVar
(_ScanCommandResultTypeVar
)]) –