Files
ansible-testing/methodologies/zap/scripts/tls-probe.py
T

96 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Collect focused TLS evidence that ZAP baseline does not enumerate."""
import argparse
import json
import subprocess
from pathlib import Path
from urllib.parse import urlparse
def openssl_handshake(host: str, port: int, option: str, cipher: str | None = None) -> tuple[bool, str]:
command = ["openssl", "s_client", "-connect", f"{host}:{port}", "-servername", host, option, "-brief"]
if cipher:
command.extend(["-cipher", cipher])
result = subprocess.run(command, input="", text=True, capture_output=True, timeout=20, check=False)
evidence = (result.stdout + result.stderr).strip()
return result.returncode == 0 and "Protocol version" in evidence, evidence[-2000:]
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("url")
parser.add_argument("output", type=Path)
args = parser.parse_args()
parsed = urlparse(args.url)
host = parsed.hostname
port = parsed.port or 443
if not host:
parser.error("URL must include a hostname")
findings = []
for option, label in (("-tls1", "TLS 1.0"), ("-tls1_1", "TLS 1.1")):
accepted, evidence = openssl_handshake(host, port, option, "AES128-SHA:@SECLEVEL=0")
if accepted:
findings.append(
{
"id": "TLS-OLD-PROTOCOL",
"title": f"Server accepts {label}",
"severity": "high",
"description": "The endpoint accepts an obsolete TLS protocol.",
"remediation": "Allow only TLS 1.2 and TLS 1.3.",
"evidence": evidence,
}
)
weak_cipher, cipher_evidence = openssl_handshake(host, port, "-tls1_2", "AES128-SHA:@SECLEVEL=0")
if weak_cipher:
findings.append(
{
"id": "TLS-WEAK-CIPHER",
"title": "Server accepts TLS_RSA_WITH_AES_128_CBC_SHA",
"severity": "medium",
"description": "The endpoint accepts a legacy RSA/CBC cipher suite.",
"remediation": "Use forward-secret AEAD cipher suites.",
"evidence": cipher_evidence,
}
)
verification = subprocess.run(
[
"openssl",
"s_client",
"-connect",
f"{host}:{port}",
"-servername",
host,
"-verify_return_error",
"-brief",
],
input="",
text=True,
capture_output=True,
timeout=20,
check=False,
)
verification_evidence = (verification.stdout + verification.stderr).strip()
if verification.returncode != 0 and "certificate verify failed" in verification_evidence.lower():
findings.append(
{
"id": "TLS-SELF-SIGNED",
"title": "TLS certificate is not publicly trusted",
"severity": "medium",
"description": "Default certificate verification rejected the endpoint certificate.",
"remediation": "Install a certificate issued by a trusted CA for the environment.",
"evidence": verification_evidence[-2000:],
}
)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps({"target": args.url, "findings": findings}, indent=2) + "\n", encoding="utf-8")
print(f"Collected {len(findings)} TLS findings")
return 0
if __name__ == "__main__":
raise SystemExit(main())