critical Threat analysis

Cisco Catalyst SD-WAN CVE-2026-20182: KEV Control-Plane Exposure

CISA added Cisco Catalyst SD-WAN CVE-2026-20182 to KEV on 2026-05-14. Cisco lists fixed releases across 20.9, 20.12, 20.15, 20.18, and 26.1 trains; CISA ED 26-03 provides concrete artifact selectors for rogue peering, root SSH, downgrades, and log clearing.

#cisco#sdwan#cisa-kev#zero-day#vulnerability-response
On this page 0% read

    Executive Summary

    CISA added CVE-2026-20182 to KEV on 2026-05-14 with due date 2026-05-17 CISA KEV. Cisco describes an authentication bypass affecting Catalyst SD-WAN Controller and Catalyst SD-WAN Manager that can allow unauthenticated remote administrative access Cisco.

    This article uses Cisco’s fixed-release table and CISA ED 26-03 supplemental artifact selectors for version closure and compromise hunting Cisco, CISA Supplemental Direction.

    Key Facts

    cve: "CVE-2026-20182"
    vendor: "Cisco"
    product: "Catalyst SD-WAN Controller and Catalyst SD-WAN Manager"
    kev_added: "2026-05-14"
    kev_due: "2026-05-17"
    vulnerability: "Authentication bypass to administrative privileges"
    cwe: ["CWE-287"]
    cvss_v31: "10.0 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
    vulnerable_product_scope: "Controller and Manager across on-prem, Cloud-Pro, Cisco-managed cloud, and FedRAMP deployments"
    first_fixed_releases:
      "20.9": "20.9.9.1"
      "20.10": "20.12.7.1"
      "20.11": "20.12.7.1"
      "20.12": ["20.12.5.4", "20.12.6.2", "20.12.7.1"]
      "20.13": "20.15.5.2"
      "20.14": "20.15.5.2"
      "20.15": ["20.15.4.4", "20.15.5.2"]
      "20.16": "20.18.2.2"
      "20.18": "20.18.2.2"
      "26.1": "26.1.1.1"
      "cloud_managed": "20.15.506"
    exploitation_status: "cisa_emergency_directive_and_kev"

    Source Confidence & Evidence Mapping

    • confirmed: CISA KEV lists CVE-2026-20182 as known exploited CISA KEV.
    • confirmed: Cisco lists Catalyst SD-WAN Controller and Manager as vulnerable and provides fixed releases Cisco.
    • confirmed: CISA ED 26-03 and supplemental guidance provide concrete hunt artifacts and selectors for Cisco SD-WAN systems CISA ED 26-03, CISA Supplemental Direction.
    • confirmed: NVD lists CWE-287 and CVSS 10.0 for CVE-2026-20182 NVD.

    Impact Determination

    ClassificationCriteriaRequired evidenceRemediation triggerClosure condition
    Confirmed compromiseExported Cisco SD-WAN artifacts show rogue peering, root SSH, downgrade/reversion, anomalous users, or log clearing on a vulnerable deployment.Fixed-release comparison plus artifact lines from CISA selectors.Preserve vManage/vSmart/vBond artifacts and deploy fresh patched OVA/QCOW2 images when root compromise is identified per CISA guidance.Fixed release is verified and artifact hunt has no unresolved root, rogue peer, downgrade, or log-clearing evidence.
    Presumed exposedCatalyst SD-WAN Controller or Manager runs below Cisco’s first fixed release for its train.Product export, scanner row, or admin UI export with exact release.Keep the control plane in scope until fixed-release proof exists.Version verifier returns a fixed release for the train.
    Potentially exposedCatalyst SD-WAN appears in inventory but release, deployment type, or artifact export is missing.CMDB, scanner, device export, or service evidence.Collect release and artifact bundle evidence.Asset resolves to confirmed compromise, presumed exposed, not exposed, or unknown.
    Not exposedNo Controller/Manager asset exists, or release is at a Cisco fixed release.Negative inventory or fixed-release output.None for this CVE.Evidence is attached to the control-plane asset record.
    UnknownRelease or device artifacts are unavailable.Gap statement naming unavailable assets or exports.Keep SD-WAN management/control-plane assets in scope.Evidence is recovered or the risk owner accepts the named gap.

    Timeline

    • 2026-05-14: Cisco publishes the Catalyst SD-WAN advisory for CVE-2026-20182 Cisco.
    • 2026-05-14: CISA adds CVE-2026-20182 to KEV with due date 2026-05-17 CISA KEV.
    • 2026-05-14: NVD publication timestamp for CVE-2026-20182 NVD.

    What Happened

    Cisco’s advisory scopes the vulnerable products to Catalyst SD-WAN Controller and Manager. CISA’s supplemental direction provides host artifact selectors that are directly usable for triage: downgrade/reversion events, rogue peering, SSH abuse, root login, anomalous users, and log clearing.

    Technical Analysis

    The critical risk is administrative access to the SD-WAN management or control plane. CISA’s guidance names artifacts under /var/volatile/log, /var/log/tmplog, /home/*/.ssh, /etc/ssh/sshd_config, /var/log/wtmp, /var/log/btmp, and /etc/passwd. The scripts below operate on exported artifacts and release inventory to avoid requiring live device credentials.

    Affected Assets and Blast Radius

    asset_selectors:
      - "Cisco Catalyst SD-WAN Controller"
      - "Cisco Catalyst SD-WAN Manager"
      - "vManage"
      - "CVE-2026-20182"
    control_plane_artifacts:
      - "/var/volatile/log/vdebug"
      - "/var/log/tmplog/vdebug"
      - "/var/volatile/log/sw_script_synccdb.log"
      - "/home/vmanage-admin/.ssh/authorized_keys"
      - "/home/root/.ssh/authorized_keys"
      - "/etc/ssh/sshd_config"
      - "/var/log/wtmp"
      - "/var/log/btmp"
      - "/etc/passwd"
    highest_value_audit_targets:
      - "root SSH access"
      - "authorized_keys changes"
      - "rogue control-plane peering"
      - "version downgrade and application reversion"
      - "zero-byte wtmp, lastlog, or bash history"

    Indicators And Detection Selectors

    cves: ["CVE-2026-20182"]
    fixed_releases: ["20.9.9.1", "20.12.5.4", "20.12.6.2", "20.12.7.1", "20.15.4.4", "20.15.5.2", "20.18.2.2", "26.1.1.1", "20.15.506"]
    cisa_artifact_selectors:
      - "master install"
      - "system-reboot-issued"
      - "Starting upgrade confirmation timer"
      - "Waiting for upgrade confirmation from user"
      - "Software upgrade not confirmed"
      - "control-connection-state-change"
      - "peer-type:'vhub'"
      - "remote-color"
      - "Accepted publickey for root"
      - "PermitRootLogin yes"
      - "/usr/sbin/useradd cfgmgr_config_aaa_user"
      - "cat /dev/null > wtmp"
      - "cat /dev/null > lastlog"

    Detection and Hunting

    Script: local repository and exported telemetry scope

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
    LOG_ROOT = os.environ.get("LOG_ROOT", "")
    OUT = Path(os.environ.get("OUT", "hp-cisco-sdwan-cve-2026-20182-kev-scope"))
    SINCE = "2026-05-26T00:00:00Z"
    UNTIL = "2026-05-26T23:59:59Z"
    
    PACKAGES = [
    ]
    VERSIONS = [
    ]
    FILES = [
    ]
    DOMAINS = [
      "www.cisa.gov",
      "sec.cloudapps.cisco.com",
      "nvd.nist.gov",
    ]
    URLS = [
      "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
      "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-sdwan-rpa2-v69WY2SW",
      "https://www.cisa.gov/news-events/directives/supplemental-direction-ed-26-03-hunt-and-hardening-guidance-cisco-sd-wan-systems",
      "https://www.cisa.gov/news-events/directives/ed-26-03-mitigate-vulnerabilities-cisco-sd-wan-systems",
      "https://nvd.nist.gov/vuln/detail/CVE-2026-20182",
    ]
    IPS = [
      "20.9.9.1",
      "20.12.7.1",
      "20.12.5.4",
      "20.12.6.2",
      "20.15.5.2",
      "20.15.4.4",
      "20.18.2.2",
      "26.1.1.1",
    ]
    HASHES = [
    ]
    PROCESS_PATTERNS = [
    ]
    NETWORK_PATTERNS = [
    ]
    
    # Positive signal: repository, lockfile, artifact, process, or network telemetry contains one of the exact incident selectors above.
    # Escalation: any match tied to a production build, CI run, deployed asset, or secret-bearing host moves the asset to presumed exposed.
    
    OUT.mkdir(parents=True, exist_ok=True)
    indicators_file = OUT / "indicators.txt"
    
    # Collect unique indicators
    indicators = set()
    for group in [PACKAGES, VERSIONS, FILES, DOMAINS, URLS, IPS, HASHES, PROCESS_PATTERNS, NETWORK_PATTERNS]:
        for val in group:
            if val:
                indicators.add(val)
    
    with open(indicators_file, "w") as f:
        for ind in sorted(indicators):
            f.write(ind + "\n")
    
    print(f"[+] Written unique selectors to {indicators_file}")
    
    # Walk local directory
    print(f"[+] Scanning directory: {ROOT} for selectors...")
    matches = []
    exclude_dirs = {"node_modules", "vendor", "dist", ".git"}
    for root, dirs, filenames in os.walk(ROOT):
        dirs[:] = [d for d in dirs if d not in exclude_dirs]
        for filename in filenames:
            filepath = Path(root) / filename
            try:
                content = filepath.read_text(errors="ignore")
                for ind in indicators:
                    if ind in content:
                        matches.append(f"{filepath}: found '{ind}'")
            except Exception:
                pass
    
    if matches:
        (OUT / "repository-indicator-matches.txt").write_text("\n".join(matches) + "\n")
        print(f"[!] Found {len(matches)} matches in codebase!")
    
    # Optional Log Scanning
    if LOG_ROOT and os.path.exists(LOG_ROOT):
        print(f"[+] Scanning telemetry log directory: {LOG_ROOT}...")
        log_matches = []
        for root, _, filenames in os.walk(LOG_ROOT):
            for filename in filenames:
                filepath = Path(root) / filename
                try:
                    content = filepath.read_text(errors="ignore")
                    for ind in indicators:
                        if ind in content:
                            log_matches.append(f"{filepath}: found '{ind}'")
                except Exception:
                    pass
        if log_matches:
            (OUT / "exported-telemetry-indicator-matches.txt").write_text("\n".join(log_matches) + "\n")
            print(f"[!] Found {len(log_matches)} matches in logs!")
    
        if PACKAGES:
            registry_dir = OUT / "registry"
            registry_dir.mkdir(exist_ok=True)
    
    print(f"[+] Wrote scope artifacts under {OUT}")

    Patch, Mitigation, and Verification

    #!/usr/bin/env python3
    import csv
    import json
    import os
    import re
    import sys
    from pathlib import Path
    
    ASSET_EXPORT = Path(os.environ.get("ASSET_EXPORT", sys.argv[1] if len(sys.argv) > 1 else "sdwan-assets.csv")).resolve()
    OUT = Path(os.environ.get("OUT", "hp-cisco-sdwan-cve-2026-20182-closure")).resolve()
    CVE = "CVE-2026-20182"
    FIXED = ["20.9.9.1", "20.12.5.4", "20.12.6.2", "20.12.7.1", "20.15.4.4", "20.15.5.2", "20.18.2.2", "26.1.1.1", "20.15.506"]
    SOURCE = "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-sdwan-rpa2-v69WY2SW"
    
    def vt(value):
        return tuple(int(x) for x in re.findall(r"\d+", str(value))[:5])
    
    def ge(left, right):
        l, r = vt(left), vt(right)
        width = max(len(l), len(r), 1)
        return l + (0,) * (width - len(l)) >= r + (0,) * (width - len(r))
    
    def row_iter(path):
        if not path.exists():
            raise SystemExit(f"ASSET_EXPORT not found: {path}")
        if path.suffix.lower() == ".csv":
            with path.open(newline="", encoding="utf-8", errors="ignore") as handle:
                yield from csv.DictReader(handle)
        else:
            data = json.loads(path.read_text(encoding="utf-8", errors="ignore"))
            if isinstance(data, list):
                yield from data
            else:
                for key in ("assets", "devices", "controllers", "managers", "rows"):
                    if isinstance(data.get(key), list):
                        yield from data[key]
    
    def closest_fixed(version):
        candidates = [f for f in FIXED if vt(f)[:2] == vt(version)[:2] or vt(f)[0] == vt(version)[0]]
        return candidates[0] if candidates else ""
    
    OUT.mkdir(parents=True, exist_ok=True)
    results = []
    for idx, row in enumerate(row_iter(ASSET_EXPORT), start=1):
        text = json.dumps(row, sort_keys=True)
        if "Catalyst SD-WAN" not in text and "vManage" not in text and CVE not in text:
            continue
        match = re.search(r"(?<!\d)(20\.\d+\.\d+(?:\.\d+)?|26\.1\.\d+\.\d+)(?!\d)", text)
        version = match.group(1) if match else ""
        fixed_target = closest_fixed(version) if version else ""
        fixed = bool(version and fixed_target and ge(version, fixed_target))
        results.append({"row": idx, "cve": CVE, "source": SOURCE, "detected_release": version, "target_fixed_release": fixed_target, "fixed_release_proven": fixed, "row_data": row})
    
    (OUT / "cisco-sdwan-cve-2026-20182-release-verification.json").write_text(json.dumps(results, indent=2, sort_keys=True), encoding="utf-8")
    
    # Remediation trigger: fixed_release_proven false for any Catalyst SD-WAN Controller or Manager keeps CVE-2026-20182 open.
    print(json.dumps({"out": str(OUT), "checked": len(results), "not_closed": [r for r in results if not r["fixed_release_proven"]]}, indent=2))

    Downstream Abuse Audits

    Script: GitHub organization run, release, secret, and workflow audit

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    if "ORG" not in os.environ:
        print("ERROR: Set ORG environment variable to the GitHub organization to audit", file=sys.stderr)
        sys.exit(1)
    
    ORG = os.environ["ORG"]
    SINCE = "2026-05-26T00:00:00Z"
    UNTIL = "2026-05-26T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-cisco-sdwan-cve-2026-20182-kev-github-audit"))
    
    SELECTORS = [
      "www.cisa.gov",
      "sec.cloudapps.cisco.com",
      "nvd.nist.gov",
      "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
      "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-sdwan-rpa2-v69WY2SW",
      "https://www.cisa.gov/news-events/directives/supplemental-direction-ed-26-03-hunt-and-hardening-guidance-cisco-sd-wan-systems",
      "https://www.cisa.gov/news-events/directives/ed-26-03-mitigate-vulnerabilities-cisco-sd-wan-systems",
      "https://nvd.nist.gov/vuln/detail/CVE-2026-20182",
      "20.9.9.1",
      "20.12.7.1",
      "20.12.5.4",
      "20.12.6.2",
      "20.15.5.2",
      "20.15.4.4",
      "20.18.2.2",
      "26.1.1.1",
    ]
    
    # Positive signal: a workflow run, release, secret, key, package, or workflow change overlaps the exposure window and references an incident selector.
    # Remediation trigger: unauthorized post-exposure write activity or a secret-bearing run matching an incident selector requires token revocation and downstream cloud/registry review.
    
    OUT.mkdir(parents=True, exist_ok=True)
    (OUT / "runs").mkdir(exist_ok=True)
    (OUT / "logs").mkdir(exist_ok=True)
    (OUT / "repos").mkdir(exist_ok=True)
    
    # 1. Write incident-selectors file
    selectors_file = OUT / "incident-selectors.txt"
    with open(selectors_file, "w") as sf:
        for s in SELECTORS:
            if s:
                sf.write(s + "\n")
    
    # 2. Get list of repos
    print(f"[+] Fetching repositories for organization: {ORG}")
    repo_res = subprocess.run(["gh", "repo", "list", ORG, "--limit", "1000", "--json", "nameWithOwner"], capture_output=True, text=True)
    if repo_res.returncode != 0:
        print(f"[-] Failed to fetch repos: {repo_res.stderr}", file=sys.stderr)
        sys.exit(1)
    
    repos = [r["nameWithOwner"] for r in json.loads(repo_res.stdout)]
    
    for repo in repos:
        safe_repo = repo.replace("/", "__")
        print(f"[+] Auditing repository: {repo}")
    
        # Check runs in the window
        runs_res = subprocess.run([
            "gh", "api", f"/repos/{repo}/actions/runs",
            "-f", "per_page=100",
            "-f", f"created=>={SINCE}",
            "--paginate"
        ], capture_output=True, text=True)
    
        if runs_res.returncode == 0:
            try:
                all_runs = json.loads(runs_res.stdout).get("workflow_runs", [])
                filtered_runs = [r for r in all_runs if r["created_at"] <= UNTIL]
    
                if filtered_runs:
                    with open(OUT / "runs" / f"{safe_repo}-runs.jsonl", "w") as rf:
                        for run in filtered_runs:
                            rf.write(json.dumps(run) + "\n")
    
                            # Fetch log dynamically
                            run_id = str(run["id"])
                            log_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--log"], capture_output=True, text=True)
                            if log_res.returncode == 0:
                                (OUT / "logs" / f"{safe_repo}-{run_id}.log").write_text(log_res.stdout)
    
                            # Fetch details
                            view_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--json", "databaseId,workflowName,headSha,event,createdAt,jobs"], capture_output=True, text=True)
                            if view_res.returncode == 0:
                                (OUT / "runs" / f"{safe_repo}-{run_id}.json").write_text(view_res.stdout)
            except Exception as e:
                print(f"[-] Error parsing runs for {repo}: {e}")
    
        # Check releases in window
        subprocess.run(["gh", "api", f"/repos/{repo}/releases", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check repo secrets updated in window
        subprocess.run(["gh", "api", f"/repos/{repo}/actions/secrets", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check deploy keys
        subprocess.run(["gh", "api", f"/repos/{repo}/keys", "-f", "per_page=100", "--paginate"], capture_output=True)
    
    # Scan output directory for any indicator selector matches
    print("[+] Scanning gathered telemetry for indicator matches...")
    subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(selectors_file), str(OUT)], capture_output=False)
    
    print(f"[+] Wrote GitHub audit artifacts under {OUT}")

    Script: cloud OIDC and deployment credential follow-on audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2026-05-26T00:00:00Z"
    UNTIL = "2026-05-26T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-cisco-sdwan-cve-2026-20182-kev-cloud-audit"))
    AWS_REGIONS = os.environ.get("AWS_REGIONS", "us-east-1").split(",")
    
    # Positive signal: token exchange or privileged write activity occurs in the exposure window from GitHub, CI/CD, package registry, or deployment automation identity.
    # Remediation trigger: unexpected write, deploy, IAM, secret, or registry activity tied to an exposed CI/CD path requires trust-policy disablement and credential rotation.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    # 1. AWS CloudTrail Audit
    print("[+] Querying AWS CloudTrail for Web Identity token exchanges...")
    aws_events = []
    for region in AWS_REGIONS:
        res = subprocess.run([
            "aws", "cloudtrail", "lookup-events",
            "--region", region,
            "--start-time", SINCE,
            "--end-time", UNTIL,
            "--lookup-attributes", "AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity",
            "--output", "json"
        ], capture_output=True, text=True)
    
        if res.returncode == 0:
            try:
                events = json.loads(res.stdout).get("Events", [])
                for event in events:
                    ct = json.loads(event.get("CloudTrailEvent", "{}"))
                    ct["region"] = region
                    aws_events.append(ct)
            except Exception as e:
                print(f"[-] Error parsing AWS CloudTrail events: {e}")
    
    if aws_events:
        with open(OUT / "aws-assume-role-with-web-identity.jsonl", "w") as f:
            for ev in aws_events:
                f.write(json.dumps(ev) + "\n")
    
        # Audit follow-on events for returned access keys
        for ev in aws_events:
            access_key = ev.get("responseElements", {}).get("credentials", {}).get("accessKeyId")
            region = ev.get("region", "us-east-1")
            if access_key:
                print(f"[+] Enumerating AWS events for AccessKey: {access_key}")
                f_res = subprocess.run([
                    "aws", "cloudtrail", "lookup-events",
                    "--region", region,
                    "--start-time", SINCE,
                    "--end-time", UNTIL,
                    "--lookup-attributes", f"AttributeKey=AccessKeyId,AttributeValue={access_key}",
                    "--output", "json"
                ], capture_output=True, text=True)
                if f_res.returncode == 0:
                    try:
                        f_events = json.loads(f_res.stdout).get("Events", [])
                        with open(OUT / "aws-follow-on-api-calls.jsonl", "a") as ff:
                            for fe in f_events:
                                ff.write(fe.get("CloudTrailEvent", "{}") + "\n")
                    except Exception as e:
                        print(f"[-] Error writing follow-on events: {e}")
    
    # 2. Azure Activity Log Audit
    print("[+] Querying Azure activity logs...")
    az_res = subprocess.run([
        "az", "monitor", "activity-log", "list",
        "--start-time", SINCE,
        "--end-time", UNTIL,
        "--query", "[?contains(operationName.value, 'write') || contains(operationName.value, 'delete') || contains(operationName.value, 'Microsoft.ManagedIdentity')]",
        "-o", "json"
    ], capture_output=True, text=True)
    
    if az_res.returncode == 0:
        (OUT / "azure-write-delete-activity.json").write_text(az_res.stdout)
    
    # 3. GCP Logging Audit
    print("[+] Querying GCP Cloud Logging...")
    gcp_filter = f'timestamp>="{SINCE}" AND timestamp<="{UNTIL}" AND (protoPayload.methodName="google.sts.v1.SecurityTokenService.ExchangeToken" OR protoPayload.methodName:"GenerateAccessToken" OR protoPayload.methodName:"CreateServiceAccountKey" OR protoPayload.methodName:"SetIamPolicy")'
    gcp_res = subprocess.run([
        "gcloud", "logging", "read", gcp_filter,
        "--format", "json"
    ], capture_output=True, text=True)
    
    if gcp_res.returncode == 0:
        (OUT / "gcp-token-and-iam-activity.json").write_text(gcp_res.stdout)
    
    print(f"[+] Wrote cloud audit artifacts under {OUT}")

    Script: registry metadata and artifact audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2026-05-26T00:00:00Z"
    OUT = Path(os.environ.get("OUT", "hp-cisco-sdwan-cve-2026-20182-kev-registry-audit"))
    PACKAGES = [
    ]
    VERSIONS = [
    ]
    
    # Positive signal: workflow files or extensions reference the affected action/extension names or versions.
    # Remediation trigger: exposed secrets or OIDC federation policies must be immediately rotated.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    with open(OUT / "affected-versions.txt", "w") as av:
        for version in VERSIONS:
            if version:
                av.write(version + "\n")
    
        # 1. Search local workspace files for the affected actions/extensions
        print("[+] Scanning workspace workflows for selectors...")
        for file in Path(".").glob(".github/workflows/**/*.yml"):
            subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(OUT / "affected-versions.txt"), str(file)])
    
        # 2. HOW TO ROTATE EXPOSED GITHUB ACTIONS SECRETS:
        # Overwrite compromised secrets with newly generated credentials:
        # subprocess.run(["gh", "secret", "set", "COMPROMISED_SECRET_NAME", "--body", "my-new-secret-value", "--repo", "my-org/my-repo"])
        # For organization-level secrets:
        # subprocess.run(["gh", "secret", "set", "COMPROMISED_SECRET_NAME", "--org", "my-org", "--visibility", "private"])
        # Revoke compromised OIDC federated trust credentials in AWS/GCP and redeploy the IAM trust policy:
        # subprocess.run(["aws", "iam", "update-assume-role-policy", "--role-name", "my-role-name", "--policy-document", "file://new-clean-trust-policy.json"])
    
    print(f"[+] Wrote registry audit artifacts under {OUT}")

    Sources

    1. CISA Known Exploited Vulnerabilities catalog JSON
    2. Cisco Security Advisory: Cisco Catalyst SD-WAN Controller Authentication Bypass Vulnerability
    3. CISA ED 26-03: Mitigate Vulnerabilities in Cisco SD-WAN Systems
    4. CISA Supplemental Direction ED 26-03: Hunt and Hardening Guidance for Cisco SD-WAN Systems
    5. NVD CVE-2026-20182

    IOC Clipboard

    13 IOCs
    Defang IOCs
    path master install master install
    path system-reboot-issued system-reboot-issued
    path Starting upgrade confirmation timer Starting upgrade confirmation timer
    path Waiting for upgrade confirmation from user Waiting for upgrade confirmation from user
    path Software upgrade not confirmed Software upgrade not confirmed
    path control-connection-state-change control-connection-state-change
    path peer-type:'vhub peer-type:'vhub
    path remote-color remote-color
    path Accepted publickey for root Accepted publickey for root
    path PermitRootLogin yes PermitRootLogin yes
    path /usr/sbin/useradd cfgmgr_config_aaa_user /usr/sbin/useradd cfgmgr_config_aaa_user
    path cat /dev/null > wtmp cat /dev/null > wtmp
    path cat /dev/null > lastlog cat /dev/null > lastlog