high Threat analysis

Windows Shell CVE-2026-32202 KEV: Zero-Click NTLM Coercion

CVE-2026-32202 is an actively exploited Windows Shell protection-mechanism failure that Akamai traced to an incomplete patch for an APT28 LNK exploit chain, allowing zero-click NTLM authentication coercion when Explorer renders a malicious shortcut.

#microsoft#windows#zero-day#cisa-kev#credential-theft
On this page 0% read

    Executive Summary

    CVE-2026-32202 is a Windows Shell protection-mechanism failure that turns normal folder rendering into a credential-exposure path. Akamai found it while analyzing Microsoft’s patch for an APT28-linked LNK exploit chain: the February fix blocked the earlier remote code execution and SmartScreen bypass path, but Windows Explorer could still resolve a UNC path while extracting shortcut resources, causing a victim host to authenticate to an attacker-controlled SMB server without a click Akamai.

    CISA added CVE-2026-32202 to the Known Exploited Vulnerabilities catalog on 2026-04-28, with a federal remediation due date of 2026-05-12 CISA. The NVD entry also marks the CVE as present in CISA KEV and links to Microsoft and Akamai advisories NVD.

    The immediate risk is Net-NTLMv2 capture and relay. A mailbox, archive share, developer download folder, ticket attachment, or synced cloud directory that contains a crafted .lnk can trigger outbound authentication as soon as Explorer previews or renders the folder. Treat exposure as credential theft until SMB egress, NTLM relay paths, and affected accounts are ruled out.

    Key Facts

    cve: "CVE-2026-32202"
    vendor: "Microsoft"
    product: "Windows Shell"
    vulnerability_name: "Microsoft Windows Protection Mechanism Failure Vulnerability"
    kev_added: "2026-04-28"
    kev_due_date: "2026-05-12"
    exploitation_status: "CISA KEV / active exploitation reported"
    root_cause_summary: "incomplete patch left UNC path resolution before trust verification"
    primary_trigger: "Explorer renders a malicious LNK / Shell Link target containing a UNC path"
    credential_at_risk: "Net-NTLMv2 authentication material"
    related_chain:
      - "CVE-2026-21510"
      - "CVE-2026-21513"
    attacker_context:
      - "APT28-linked LNK exploitation chain reported by Akamai and CERT-UA context"
    affected_assets:
      - "Windows endpoints"
      - "Windows servers where users browse attacker-controlled folders or attachments"
      - "VDI and jump hosts with outbound SMB permitted"
    last_verified: "2026-05-27"

    Source Confidence & Evidence Mapping

    • confirmed: Akamai reports that CVE-2026-32202 resulted from an incomplete patch for CVE-2026-21510, leaving zero-click authentication coercion through Windows Shell resource resolution Akamai.
    • confirmed: Akamai states the earlier exploit chain was detected in January 2026 and that CVE-2026-21513 and CVE-2026-21510 were used in the same LNK file Akamai.
    • confirmed: CISA’s KEV alert URL for 2026-04-28 identifies CVE-2026-32202 as one of two added exploited vulnerabilities CISA.
    • confirmed: NVD lists CVE-2026-32202 and notes its presence in CISA’s Known Exploited Vulnerabilities catalog NVD.
    • likely: Environments allowing outbound SMB from workstations or VDI to the internet have higher blast radius because captured NTLM material can be relayed or cracked.
    • unclear: Public sources do not enumerate all campaigns using CVE-2026-32202 after KEV listing.

    Impact Determination

    ClassificationCriteriaRequired evidenceRequired actionClosure condition
    Confirmed compromiseEndpoint opened or rendered a malicious LNK path and initiated SMB/NTLM authentication to an untrusted host.Sysmon process/file events, Windows Security NTLM events, firewall/proxy logs, SMB server logs, EDR file collection, and the .lnk artifact.Isolate the endpoint, collect the LNK, block outbound SMB, reset affected account credentials, and investigate relay targets.Patch is installed, no outbound SMB persists, account tokens/passwords are rotated, and relay-sensitive services are hardened.
    Presumed exposedUser received or stored suspicious .lnk files, browsed attacker-controlled archive/share content, or endpoint allowed outbound SMB during the exposure window.Mail gateway attachments, downloaded archives, filesystem inventory, firewall policy, and Windows event logs.Hunt for LNK UNC targets and NTLM outbound events.No malicious LNK, no outbound SMB, and no suspicious account use are found.
    Potentially exposedWindows endpoint/server fleet status is unknown or missing April 2026 cumulative updates.Patch inventory and asset records.Prioritize KEV remediation and run the fleet audit below.Assets are patched or removed from exposure.
    Not exposedPatched systems have outbound SMB blocked and no suspicious LNK/NTLM telemetry.Patch compliance export, egress firewall evidence, negative endpoint hunts.Maintain controls and monitor for LNK attachments.Evidence is archived with query timestamps.
    UnknownEndpoint logs, firewall logs, or patch inventory are unavailable.Gap statement identifying missing telemetry.Apply conservative credential rotation for high-value accounts that used affected hosts.Missing data is recovered or risk is accepted.

    Timeline

    • 2025-12: CERT-UA context cited by Akamai describes APT28 activity using weaponized LNK files against Ukraine and European targets Akamai.
    • 2026-01: Akamai detects the exploit chain later associated with CVE-2026-21510 and CVE-2026-21513 Akamai.
    • 2026-02: Microsoft patches the original chain, but Akamai finds the remaining authentication coercion path Akamai.
    • 2026-04-23: Akamai publishes technical analysis of the incomplete patch and CVE-2026-32202 Akamai.
    • 2026-04-28: CISA adds CVE-2026-32202 to KEV CISA.
    • 2026-05-27: This site adds a standalone response article because the CVE was not previously covered locally.

    What Happened

    The original LNK exploit chain used Windows Shell namespace parsing to load a Control Panel component from a UNC path. Microsoft’s patch added trust verification later in the ShellExecute path. Akamai found an earlier branch still caused Windows to resolve the UNC path while Explorer extracted the icon or UI resource for the shortcut. That resolution triggers SMB and an automatic NTLM authentication handshake before the later trust verification can help Akamai.

    The result is zero-click credential coercion. A user does not need to execute the shortcut; viewing a folder containing the malicious LNK can be enough for the host to offer Net-NTLMv2 material to an attacker-controlled server.

    Technical Analysis

    Trigger Path

    A malicious .lnk embeds shell item data that references a UNC path for a Control Panel-related object. During folder rendering, Windows Explorer asks Shell32 to resolve resources such as icons. Akamai traces the early call path to CControlPanelFolder::GetUIObjectOf and PathFileExistsW, which can resolve the remote path before SmartScreen or Mark-of-the-Web trust checks block later execution Akamai.

    Primary Security Impact

    The exposed credential material can support NTLM relay or offline cracking. The practical blast radius depends on whether outbound SMB is permitted, whether SMB signing and EPA are enforced on internal services, and whether the affected user has privileged access.

    Why KEV Priority Is Higher Than CVSS

    CVE-2026-32202 may appear less urgent if reviewed only through base severity. KEV status changes the decision: exploitation is confirmed, the trigger requires low user interaction in normal file-browsing workflows, and the result can be a credential pivot rather than a single-host crash.

    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-windows-shell-cve-2026-32202-kev-scope"))
    SINCE = "2026-05-27T00:00:00Z"
    UNTIL = "2026-05-27T23:59:59Z"
    
    PACKAGES = [
    ]
    VERSIONS = [
    ]
    FILES = [
    ]
    DOMAINS = [
      "www.akamai.com",
      "www.cisa.gov",
      "nvd.nist.gov",
    ]
    URLS = [
      "https://www.akamai.com/blog/security-research/2026/apr/incomplete-patch-apt28s-zero-day-cve-2026-32202",
      "https://www.cisa.gov/news-events/alerts/2026/04/28/cisa-adds-two-known-exploited-vulnerabilities-catalog",
      "https://nvd.nist.gov/vuln/detail/CVE-2026-32202",
    ]
    IPS = [
    ]
    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}")

    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-27T00:00:00Z"
    UNTIL = "2026-05-27T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-windows-shell-cve-2026-32202-kev-github-audit"))
    
    SELECTORS = [
      "www.akamai.com",
      "www.cisa.gov",
      "nvd.nist.gov",
      "https://www.akamai.com/blog/security-research/2026/apr/incomplete-patch-apt28s-zero-day-cve-2026-32202",
      "https://www.cisa.gov/news-events/alerts/2026/04/28/cisa-adds-two-known-exploited-vulnerabilities-catalog",
      "https://nvd.nist.gov/vuln/detail/CVE-2026-32202",
    ]
    
    # 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-27T00:00:00Z"
    UNTIL = "2026-05-27T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-windows-shell-cve-2026-32202-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-27T00:00:00Z"
    OUT = Path(os.environ.get("OUT", "hp-windows-shell-cve-2026-32202-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}")

    Remediation & Credential Rotation Plan

    Containment

    1. Apply Microsoft’s April 2026 or later security updates that address CVE-2026-32202.
    2. Block outbound SMB and WebDAV from endpoints to the internet. Workstations should not initiate TCP/445 to arbitrary external hosts.
    3. Quarantine suspicious .lnk files and preserve original attachments, archives, and directory listings for evidence.

    Eradication

    1. Remove malicious LNK artifacts only after hashing and preserving them.
    2. Audit mailbox, browser download, synced cloud, and shared-drive folders for suspicious shortcuts.
    3. Reset passwords for users whose hosts authenticated to untrusted SMB endpoints.

    Recovery

    1. Enforce SMB signing and Extended Protection for Authentication on relay-sensitive services.
    2. Reduce NTLM use where possible and monitor exceptions.
    3. Add mail and web gateway controls for .lnk inside archives and disk images.

    Closure Gates

    • Patch inventory confirms remediation for all affected Windows assets.
    • No endpoint has outbound SMB to untrusted networks.
    • All suspicious .lnk files are collected and removed.
    • Accounts tied to confirmed outbound NTLM coercion are rotated and reviewed for post-exposure activity.

    Sources

    1. Akamai: A Shortcut to Coercion: Incomplete Patch of APT28’s Zero-Day Leads to CVE-2026-32202
    2. CISA: CISA Adds Two Known Exploited Vulnerabilities to Catalog
    3. NVD: CVE-2026-32202
    4. Microsoft Security Update Guide: CVE-2026-32202
    5. MCNC: Active Exploitation of Windows Shell Zero-Day CVE-2026-32202

    Indicators of Compromise

    6 IOCs
    Defang IOCs
    domain www.akamai.com www[.]akamai[.]com
    domain www.cisa.gov www[.]cisa[.]gov
    domain nvd.nist.gov nvd[.]nist[.]gov
    url https://www.akamai.com/blog/security-research/2026/apr/incomplete-patch-apt28s-zero-day-cve-2026-32202 hxxps://www[.]akamai[.]com/blog/security-research/2026/apr/incomplete-patch-apt28s-zero-day-cve-2026-32202
    url https://www.cisa.gov/news-events/alerts/2026/04/28/cisa-adds-two-known-exploited-vulnerabilities-catalog hxxps://www[.]cisa[.]gov/news-events/alerts/2026/04/28/cisa-adds-two-known-exploited-vulnerabilities-catalog
    url https://nvd.nist.gov/vuln/detail/CVE-2026-32202 hxxps://nvd[.]nist[.]gov/vuln/detail/CVE-2026-32202