CYBER-0 initial concept ready
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user