#!/usr/bin/env python3
"""Scope KNX CVE-2023-4346 exposure from local inventories and exported logs."""
from __future__ import annotations
import json, os, sys
from pathlib import Path
CVE_IDS=['CVE-2023-4346']
VENDOR='KNX Association'
PRODUCT='KNX Protocol Connection Authorization Option 1'
AFFECTED_VERSION_SELECTORS=['knx-connection-authorization-option-1 affected implementation / version 0 selector']
FIXED_OR_MITIGATION_SELECTORS=['fixed: enable vendor-recommended KNX security options such as KNX IP Secure/Data Secure or remove exposed vulnerable configuration; no public semver fixed release identified']
SOURCE_SELECTORS=['https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json', 'https://www.cisa.gov/news-events/ics-advisories/icsa-23-236-01', 'https://nvd.nist.gov/vuln/detail/CVE-2023-4346', 'https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-4346']
DOMAINS=[]; HASHES=[]; URLS=[]; FILES=[]; NETWORK_PATTERNS=[]
PROCESS_PATTERNS=['KNX Protocol Connection Authorization Option 1', 'KNX Association', 'CVE-2023-4346']
PRODUCT_TELEMETRY_PATTERNS=['KNX', 'Connection Authorization Option 1', 'BCU key', 'purge all devices', 'KNX IP Secure', 'KNX Data Secure', 'CVE-2023-4346', 'device lockout', 'ETS project']
INDICATORS=list(dict.fromkeys(CVE_IDS+[VENDOR,PRODUCT]+AFFECTED_VERSION_SELECTORS+FIXED_OR_MITIGATION_SELECTORS+SOURCE_SELECTORS+PRODUCT_TELEMETRY_PATTERNS))
OUT=os.environ.get('OUT','hp-knx-protocol-cve-2023-4346-kev-scope')
TEXT_SUFFIXES={'.csv','.json','.jsonl','.txt','.log','.yaml','.yml','.xml','.conf','.ini','.md'}
EXCLUDED_DIR_NAMES={'.git','node_modules','vendor','dist','__pycache__','.venv'}
def read_text(path: Path)->str:
try: return path.read_text(encoding='utf-8', errors='ignore')
except Exception: return ''
def iter_files(root: Path):
if root.is_file(): yield root; return
for p in root.rglob('*'):
if p.is_file() and not any(part in EXCLUDED_DIR_NAMES for part in p.parts):
if not p.suffix or p.suffix.lower() in TEXT_SUFFIXES: yield p
def scan(root: Path):
matches=[]; lowered=[s.lower() for s in INDICATORS if s]
for path in iter_files(root):
low=read_text(path).lower(); hits=sorted({INDICATORS[i] for i,s in enumerate(lowered) if s in low})
if hits: matches.append({'path':str(path),'hits':hits})
return matches
def main(argv=None):
argv=argv or sys.argv[1:]; root=Path(argv[0]) if argv else Path('.')
matches=scan(root); out_dir=Path(OUT); out_dir.mkdir(parents=True, exist_ok=True)
report={'cves':CVE_IDS,'vendor':VENDOR,'product':PRODUCT,'affected_version_selectors':AFFECTED_VERSION_SELECTORS,'fixed_or_mitigation_selectors':FIXED_OR_MITIGATION_SELECTORS,'match_count':len(matches),'matches':matches}
(out_dir/'scope_report.json').write_text(json.dumps(report,indent=2),encoding='utf-8')
print(json.dumps({'cves':CVE_IDS,'match_count':len(matches),'report':str(out_dir/'scope_report.json')},indent=2))
return 2 if matches else 0
if __name__=='__main__': raise SystemExit(main())