CYBER-0 initial concept ready

This commit is contained in:
Ole Valente
2026-09-22 00:00:45 +02:00
parent 29eecd4f70
commit edb6cde069
70 changed files with 1976 additions and 3140 deletions
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Normalize ZAP baseline alerts and TLS preflight findings with ASVS mappings."""
import argparse
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from jsonschema import Draft202012Validator, FormatChecker
RISK = {"0": "info", "1": "low", "2": "medium", "3": "high", "4": "critical"}
def standards(mapping: dict[str, list[str]], finding_id: str) -> list[dict[str, str]]:
return [
{"framework": "OWASP ASVS", "version": "5.0", "control": control}
for control in mapping.get(finding_id, [])
]
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("zap_json", type=Path)
parser.add_argument("tls_json", type=Path)
parser.add_argument("output", type=Path)
parser.add_argument("--target", required=True)
parser.add_argument("--mapping", type=Path, default=Path("methodologies/zap/asvs-mapping.json"))
parser.add_argument("--schema", type=Path, default=Path("schemas/test-report.schema.json"))
args = parser.parse_args()
zap = json.loads(args.zap_json.read_text(encoding="utf-8"))
tls = json.loads(args.tls_json.read_text(encoding="utf-8"))
mapping = json.loads(args.mapping.read_text(encoding="utf-8"))
results = []
for site in zap.get("site", []):
for alert in site.get("alerts", []):
finding_id = str(alert.get("pluginid", alert.get("alertRef", "ZAP-UNKNOWN")))
instances = alert.get("instances", [])
observed = "; ".join(str(item.get("uri", "")) for item in instances[:5])
results.append(
{
"id": f"ZAP-{finding_id}",
"title": str(alert.get("alert", "ZAP finding")),
"description": str(alert.get("desc", "")),
"status": "failed",
"severity": RISK.get(str(alert.get("riskcode", "0")), "info"),
"category": "Web application security",
"expected": "No ZAP alert",
"observed": observed or str(alert.get("evidence", "")),
"remediation": str(alert.get("solution", "Review and remediate the finding.")),
"standards": standards(mapping, finding_id),
"evidence": [{"type": "text", "name": "ZAP alert", "value": observed or str(alert)}],
}
)
for finding in tls.get("findings", []):
finding_id = str(finding["id"])
results.append(
{
"id": finding_id,
"title": str(finding["title"]),
"description": str(finding.get("description", "")),
"status": "failed",
"severity": str(finding.get("severity", "medium")),
"category": "TLS configuration",
"expected": "Current TLS protocol, cipher, and certificate configuration",
"observed": str(finding.get("evidence", ""))[-2000:],
"remediation": str(finding.get("remediation", "")),
"standards": standards(mapping, finding_id),
"evidence": [{"type": "text", "name": "TLS preflight", "value": str(finding.get("evidence", ""))[-2000:]}],
}
)
counts = dict.fromkeys(("passed", "failed", "errors", "skipped", "review"), 0)
counts["failed"] = len(results)
now = datetime.now(timezone.utc).isoformat()
report = {
"schema_version": "1.0.0",
"run": {
"id": os.environ.get("CI_PIPELINE_ID", now),
"started_at": now,
"source": "gitlab" if os.environ.get("CI") else "local",
"pipeline_url": os.environ.get("CI_PIPELINE_URL", ""),
"commit_sha": os.environ.get("CI_COMMIT_SHA", ""),
},
"project": {
"id": os.environ.get("TEST_PROJECT_ID", "demo-ubuntu-weak"),
"name": os.environ.get("TEST_PROJECT_NAME", "Ubuntu Weak Target Demonstration"),
"environment": os.environ.get("TEST_ENVIRONMENT", "test"),
"customer": os.environ.get("TEST_CUSTOMER", "Internal"),
"location": os.environ.get("TEST_LOCATION", "testserv"),
},
"tool": {"id": "zaproxy", "name": "OWASP ZAP with TLS preflight", "adapter_version": "1.0.0"},
"target": {"id": "demo-web", "type": "web_application", "address": args.target, "groups": ["web_applications"]},
"summary": {"total": len(results), **counts, "score": 0},
"results": results,
"raw_artifacts": [str(args.zap_json), str(args.tls_json)],
}
schema = json.loads(args.schema.read_text(encoding="utf-8"))
Draft202012Validator(schema, format_checker=FormatChecker()).validate(report)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
print(f"Normalized {len(results)} web findings")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+96
View File
@@ -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())