admin / Synapse-NetscanXi
publicNetwork Scanning, Vulnerability and Compliance Application
Synapse-NetscanXi / NetscanXiVersion13 / app / compliance.py
16144 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | """ Compliance & credential-exposure analysis (v6). Two responsibilities, both DEFENSIVE: 1. weak_auth_findings(host) Detect *credential-exposure* and weak-authentication risk on a host from what the scan already saw: plaintext login services, exposed admin/database ports, unauthenticated APIs, etc. This reports RISK. It never captures, transmits, brute-forces, or stores anyone's credentials. 2. assess_host(host) Cross-reference each host's findings against the supported regulatory / audit frameworks - PCI DSS, GDPR, ISO 27001, SOC 2, HIPAA, Cyber Essentials, and (v8-r3) NIST CSF and NCSC CAF - returning per-framework pass/fail control mappings so the dashboard can show where a host would put an organisation out of compliance. Both are pure functions over the parsed host dict (see parser.py), so they are trivially unit-testable and add no new scan capability on their own. """ # --------------------------------------------------------------------------- # Service knowledge base # --------------------------------------------------------------------------- # Services that authenticate users but do so over CLEARTEXT (no transport # encryption) -> credentials are exposed on the wire. CLEARTEXT_AUTH_SERVICES = { 21: ("FTP", "ftp"), 23: ("Telnet", "telnet"), 25: ("SMTP (no STARTTLS)", "smtp"), 80: ("HTTP", "http"), 110: ("POP3", "pop3"), 143: ("IMAP", "imap"), 389: ("LDAP (cleartext)", "ldap"), 512: ("rexec", "exec"), 513: ("rlogin", "login"), 514: ("rsh", "shell"), 1433:("MSSQL", "ms-sql-s"), 3306:("MySQL", "mysql"), 5432:("PostgreSQL", "postgresql"), 5900:("VNC", "vnc"), 6379:("Redis", "redis"), 11211:("Memcached", "memcached"), 27017:("MongoDB", "mongodb"), } # Service ports that expose remote administration / data stores. Exposure to # the network is itself a finding even when encrypted. ADMIN_DATASTORE_PORTS = { 22: "SSH (remote admin)", 1433: "MSSQL database", 3306: "MySQL database", 3389: "RDP (remote desktop)", 5432: "PostgreSQL database", 5900: "VNC remote desktop", 6379: "Redis (often no auth)", 9200: "Elasticsearch (often no auth)", 11211:"Memcached (no auth)", 27017:"MongoDB (often no auth)", 2375: "Docker API (unauthenticated)", } # Datastores/APIs that are unauthenticated by default - exposure == open access. DEFAULT_NO_AUTH_PORTS = { 2375: "Docker Engine API", 6379: "Redis", 9200: "Elasticsearch", 11211:"Memcached", 27017:"MongoDB", } SEV_ORDER = {"critical": 3, "high": 2, "warn": 1, "info": 0} def _ports(host): """Yield (portnum:int, detail dict) for each open port on the host.""" for pd in host.get("port_details", []): try: yield int(pd.get("port")), pd except (TypeError, ValueError): continue def _has_tls(detail): """Best-effort: did the scan see TLS/SSL on this port?""" svc = (detail.get("service") or "").lower() extra = (detail.get("extrainfo") or "").lower() if "ssl" in svc or "tls" in svc or "https" in svc: return True if "ssl" in extra or "tls" in extra: return True for s in detail.get("scripts", []): if "ssl" in (s.get("id", "").lower()): return True return False # --------------------------------------------------------------------------- # 1) Credential-exposure / weak-auth detection (defensive) # --------------------------------------------------------------------------- def weak_auth_findings(host): """Return a list of credential-exposure findings for a host. Each finding: {id, title, severity, port, detail, remediation} """ findings = [] for portnum, detail in _ports(host): svc = (detail.get("service") or "").lower() # Cleartext authentication service -> credentials exposed on the wire. if portnum in CLEARTEXT_AUTH_SERVICES and not _has_tls(detail): name, _ = CLEARTEXT_AUTH_SERVICES[portnum] # HTTP is only a credential issue if something logs in over it; we # flag it as a warning rather than critical. sev = "warn" if portnum == 80 else "high" findings.append({ "id": f"cleartext-{portnum}", "title": f"Cleartext authentication: {name}", "severity": sev, "port": f"{portnum}/{detail.get('protocol','tcp')}", "detail": f"{name} accepts logins without transport encryption; " "credentials can be captured by anyone on-path.", "remediation": f"Disable {name} or require TLS (e.g. FTPS/SMTPS/" "LDAPS/HTTPS) and rotate any exposed credentials.", }) # Default-no-auth datastore/API exposed to the network. if portnum in DEFAULT_NO_AUTH_PORTS: name = DEFAULT_NO_AUTH_PORTS[portnum] findings.append({ "id": f"noauth-{portnum}", "title": f"Unauthenticated service exposed: {name}", "severity": "critical", "port": f"{portnum}/{detail.get('protocol','tcp')}", "detail": f"{name} is reachable on the network and is commonly " "deployed without authentication, allowing data access " "or takeover.", "remediation": f"Bind {name} to localhost, enable authentication, " "and firewall the port.", }) # Remote admin / database surface exposed. elif portnum in ADMIN_DATASTORE_PORTS: name = ADMIN_DATASTORE_PORTS[portnum] findings.append({ "id": f"admin-exposed-{portnum}", "title": f"Remote admin/data surface exposed: {name}", "severity": "warn", "port": f"{portnum}/{detail.get('protocol','tcp')}", "detail": f"{name} is reachable on the network; ensure strong " "authentication, MFA where possible, and IP allow-lists.", "remediation": "Restrict by firewall/VPN, enforce strong " "credentials + MFA, and monitor access.", }) # De-dupe by id (a port could match more than one rule path). uniq, seen = [], set() for f in sorted(findings, key=lambda x: -SEV_ORDER.get(x["severity"], 0)): if f["id"] in seen: continue seen.add(f["id"]) uniq.append(f) return uniq # --------------------------------------------------------------------------- # 2) Regulatory compliance cross-reference # --------------------------------------------------------------------------- # Map a generic "issue kind" to the specific control it violates in each # framework. Keeps the mapping auditable in one place. FRAMEWORKS = ("PCI DSS", "GDPR", "ISO 27001", "SOC 2", "HIPAA", "Cyber Essentials", "NIST CSF", "NCSC CAF") # issue kind -> {framework: control-reference} CONTROL_MAP = { "cleartext_auth": { "PCI DSS": "4.2.1 / 8.3.1 - strong crypto for auth over open networks", "GDPR": "Art. 32 - encryption of personal data in transit", "ISO 27001": "A.8.24 / A.5.14 - cryptography & secure transfer", "SOC 2": "CC6.7 - encryption of data in transit", "HIPAA": "164.312(e)(1) - transmission security", "Cyber Essentials": "Secure Configuration - disable insecure/cleartext services", "NIST CSF": "PR.DS-02 - data-in-transit is protected (encryption)", "NCSC CAF": "B3.c Stored/transmitted data - protect data in transit with encryption", }, "unauth_service": { "PCI DSS": "7.2 / 8.2 - restrict access & identify users", "GDPR": "Art. 32 - access control to personal data", "ISO 27001": "A.5.15 / A.8.3 - access control", "SOC 2": "CC6.1 - logical access controls", "HIPAA": "164.312(a)(1) - access control", "Cyber Essentials": "User Access Control - no unauthenticated access to services", "NIST CSF": "PR.AA-01/05 - identities authenticated; access enforced", "NCSC CAF": "B2.a Identity verification & authentication - no unauthenticated access", }, "admin_exposed": { "PCI DSS": "1.3 / 8.3 - restrict inbound admin access", "GDPR": "Art. 32 - security of processing", "ISO 27001": "A.8.20 / A.8.22 - network security & segregation", "SOC 2": "CC6.6 - perimeter / boundary protection", "HIPAA": "164.312(a)(1) - access control", "Cyber Essentials": "Firewalls - restrict inbound admin/remote-access services", "NIST CSF": "PR.IR-01 - networks protected; PR.AA-05 least-privilege access", "NCSC CAF": "B4.a/B2.c Secure config & privileged access - restrict remote admin", }, "unencrypted_web": { "PCI DSS": "4.2.1 - encrypt transmission of cardholder data", "GDPR": "Art. 32 - encryption in transit", "ISO 27001": "A.8.24 - use of cryptography", "SOC 2": "CC6.7 - encryption in transit", "HIPAA": "164.312(e)(1) - transmission security", "Cyber Essentials": "Secure Configuration - enforce HTTPS on web services", "NIST CSF": "PR.DS-02 - data-in-transit protected (enforce TLS)", "NCSC CAF": "B3.c Data in transit - enforce encrypted (HTTPS) transport", }, "known_vuln": { "PCI DSS": "6.3.1 / 11.3 - manage vulnerabilities & patch", "GDPR": "Art. 32(1)(d) - regular testing of security", "ISO 27001": "A.8.8 - management of technical vulnerabilities", "SOC 2": "CC7.1 - vulnerability detection & remediation", "HIPAA": "164.308(a)(1)(ii)(A) - risk analysis / management", "Cyber Essentials": "Security Update Management - patch known vulnerabilities", "NIST CSF": "ID.RA-01 / PR.PS-02 - identify vulnerabilities; manage patches", "NCSC CAF": "B4.d Vulnerability management - identify & remediate known flaws", }, "kev_vuln": { "PCI DSS": "6.3.1 - critical/known-exploited vulnerabilities", "GDPR": "Art. 32 - state-of-the-art security measures", "ISO 27001": "A.8.8 - technical vulnerability management", "SOC 2": "CC7.1 - timely remediation of known issues", "HIPAA": "164.308(a)(1) - security management process", "Cyber Essentials": "Security Update Management - apply critical patches within 14 days", "NIST CSF": "ID.RA-06 / RS.MA - prioritise & remediate known-exploited vulns", "NCSC CAF": "B4.d Vulnerability management - urgent remediation of exploited flaws", }, "expiring_cert": { "PCI DSS": "4.2.1 - maintain valid strong cryptography", "GDPR": "Art. 32 - ensure ongoing confidentiality/integrity", "ISO 27001": "A.8.24 - key & certificate lifecycle management", "SOC 2": "CC6.7 - maintain encryption controls", "HIPAA": "164.312(e)(1) - transmission security", "Cyber Essentials": "Secure Configuration - maintain valid TLS certificates", "NIST CSF": "PR.DS-02 / PR.PS-01 - manage certificate & crypto lifecycle", "NCSC CAF": "B3.c Data in transit - maintain valid certificates & strong crypto", }, "ad_weakness": { "PCI DSS": "8.3 / 2.2 - secure authentication & hardened configuration", "GDPR": "Art. 32 - security of processing", "ISO 27001": "A.5.15 / A.8.5 - access control & secure authentication", "SOC 2": "CC6.1 - logical access controls", "HIPAA": "164.312(a)(1) - access control", "Cyber Essentials": "User Access Control - harden directory authentication", "NIST CSF": "PR.AA-01/03 - manage identities & harden authentication", "NCSC CAF": "B2.a/B2.c Identity & access control - harden directory authentication", }, } def _issue_kinds(host, weak): """Derive the set of generic issue kinds present on a host.""" kinds = {} # kind -> list of human evidence strings def add(kind, evidence): kinds.setdefault(kind, []) if evidence not in kinds[kind]: kinds[kind].append(evidence) for f in weak: if f["id"].startswith("cleartext-"): add("cleartext_auth", f"{f['title']} ({f['port']})") elif f["id"].startswith("noauth-"): add("unauth_service", f"{f['title']} ({f['port']})") elif f["id"].startswith("admin-exposed-"): add("admin_exposed", f"{f['title']} ({f['port']})") # Unencrypted web specifically (port 80 without TLS). for portnum, detail in _ports(host): if portnum == 80 and not _has_tls(detail): add("unencrypted_web", "HTTP without TLS (80/tcp)") # Vulnerabilities -> patch-management controls. for v in host.get("vulns", []): if v.get("kev"): add("kev_vuln", f"Known-exploited: {v.get('id','')}") else: add("known_vuln", f"{v.get('id','')}") # v7: expiring/expired/weak TLS certificates (from cert tracking). for c in host.get("certificates", []): if c.get("status") in ("expired", "expiring", "weak"): label = {"expired": "Expired", "expiring": "Expiring soon", "weak": "Weak"}.get(c["status"], c["status"]) add("expiring_cert", f"{label} cert on {c.get('port','?')} " f"({c.get('subject') or c.get('issuer') or 'unknown'})") # v7: Active Directory / Domain Controller hardening findings. for f in host.get("ad_findings", []): if f.get("severity") in ("warn", "high", "critical"): add("ad_weakness", f.get("title", "AD/DC weakness")) return kinds def assess_host(host, weak=None): """Cross-reference a host against all frameworks. Returns: { "status": "compliant" | "at_risk", "frameworks": { "PCI DSS": {"status": "fail"|"pass", "controls": [ {control, kind, evidence}... ]}, ... one per framework ... }, "issue_count": int, } """ if weak is None: weak = weak_auth_findings(host) kinds = _issue_kinds(host, weak) frameworks = {} for fw in FRAMEWORKS: controls = [] for kind, evidence in kinds.items(): ref = CONTROL_MAP.get(kind, {}).get(fw) if ref: controls.append({ "kind": kind, "control": ref, "evidence": evidence, }) frameworks[fw] = { "status": "fail" if controls else "pass", "controls": controls, } any_fail = any(f["status"] == "fail" for f in frameworks.values()) return { "status": "at_risk" if any_fail else "compliant", "frameworks": frameworks, "issue_count": sum(len(v) for v in kinds.values()), } def summarize(hosts): """Site-wide compliance roll-up across all hosts for the dashboard.""" per_fw = {fw: {"pass": 0, "fail": 0, "failing_hosts": []} for fw in FRAMEWORKS} at_risk_hosts = 0 # v9: passive-only hosts carry no compliance assessment (no active probe # data), so they are excluded from the framework roll-up. assessed = 0 for h in hosts: comp = h.get("compliance") or assess_host(h) if not comp or "frameworks" not in comp: continue assessed += 1 if comp["status"] == "at_risk": at_risk_hosts += 1 for fw, data in comp["frameworks"].items(): if data["status"] == "fail": per_fw[fw]["fail"] += 1 per_fw[fw]["failing_hosts"].append(h.get("ip", "")) else: per_fw[fw]["pass"] += 1 return { "frameworks": per_fw, "hosts_total": assessed, "hosts_at_risk": at_risk_hosts, "hosts_compliant": assessed - at_risk_hosts, } |