# CVE-2025-59201 — Windows NCSI `ncsi.dll` Elevation of Privilege (Coerced Registry Write via ETW + Registry DACL)

---

## Summary

| **Product**           | Microsoft Windows — `ncsi.dll` (Network Connection Status Indicator, hosted by the Network List Service `netprofm`) |
|-----------------------|------------------------------------------------------------------|
| **Vendor**            | Microsoft Corporation |
| **Severity**          | Important (MSRC) — CVSS v3.1 **7.8 (High)** |
| **CVSS Vector**       | `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` ([MSRC](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-59201)) |
| **CVE Title**         | Network Connection Status Indicator (NCSI) Elevation of Privilege Vulnerability (MSRC) |
| **Affected Versions** | Windows 11 24H2 (and other supported SKUs) before KB5066835 |
| **Tested Version**    | Windows 11 24H2 — ncsi.dll 10.0.26100.6725 (pre-patch) vs 10.0.26100.6899 (post-patch) |
| **Impact**            | Elevation of Privilege — arbitrary code execution as **NETWORK SERVICE** (MSRC FAQ); SYSTEM reachable from there via the well-known James Forshaw trick |
| **CVE ID**            | CVE-2025-59201 |
| **CWE**               | CWE-284: Improper Access Control (MSRC wording — accurate: the root cause is a registry DACL) |
| **PoC Available**     | Yes — ETW trigger PoC (itm4n blog); blue-team trigger variant (`poc_cve_2025_59201.c`) derived from it |
| **Exploit Available** | No public full exploit (registry-symlink chain documented conceptually by itm4n; no code released) |
| **Patch Available**   | Yes |
| **Patch Date**        | October 14, 2025 — KB5066835 |
| **Discovered**        | @t0zhang (per MSRC acknowledgment) |

---

## Root Cause

This CVE has **two halves**, and the binary diff only shows one of them —
itm4n's central finding.

### Half 1 (the code, visible in the diff)

`ncsi!StoreNcsiIEProxyString` stores a string of the form `"<0|1><input>"`
into the **default value** of
`HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet\ManualProxies`
whenever the system's proxy configuration changes. The `input` part is fully
attacker-influenceable (it comes from proxy settings, see the ETW trigger
below), and the function performed **zero validation** before calling
`RegSetValueExW` — verified decompilation of the pre-patch build
(10.0.26100.6725, `StoreNcsiIEProxyString` @ `0x1800546d0`).

The patch (10.0.26100.6899, same function @ `0x1800549f0`) adds the new
helper `ContainsRelativePathDoubleDot` (@ `0x180053294`, literally
`wcsstr(input, L"..") != NULL`) and gates the write behind it — behind a WIL
CFR flag, `Feature_3499399482`:

```c
if (Feature_3499399482__private_IsEnabled()) {
    if (!ContainsRelativePathDoubleDot(param_1))
        RegSetValueExW(hKey, NULL, 0, REG_SZ, buf, cb);   // '..' strings dropped
} else {
    RegSetValueExW(hKey, NULL, 0, REG_SZ, buf, cb);       // legacy path
}
```

### Half 2 (the actual vulnerability — a registry DACL)

The `..` check looked like a path-traversal fix, but chasing what the
traversal would even buy led itm4n to the real issue: the DACL on
`HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters` granted the
**INTERACTIVE** group permission to **create subkeys**. That enables a
**registry key symbolic link** attack:

1. A low-privilege interactive user creates/replaces the subkey path leading
   to `ManualProxies` with a registry symbolic link pointing at an arbitrary
   HKLM location.
2. The attacker then fires the proxy-change ETW event (below).
3. The Network List Service — running as **NETWORK SERVICE** — follows the
   symlink and performs `RegSetValueExW` of an attacker-chosen string at the
   attacker-chosen HKLM location.

KB5066835 also **changed the DACL** on `Parameters` (the INTERACTIVE
create-subkey right was removed) — that is the real fix; the `ncsi.dll`
`..` check is complementary hardening. Neither half alone tells the whole
story.

### The trigger interface (how any local user reaches the write)

Proxy changes are broadcast as an **ETW event** by the
`Microsoft-Windows-WinINet-Config` provider (GUID
`{5402e5ea-1bdd-4390-82be-e108f1e634f5}`), event ID **5600**, level
INFORMATION, with four fields: `bAutoDetect` (BOOL), `pwszAutoConfigUrl`,
`pwszProxy`, `pwszProxyBypass`. `netprofm`'s `ncsi.dll` consumes it in
`EtwListener::ProcessEvent` and funnels the strings into
`StoreNcsiIEProxyString`. Any local user can `EventRegister` +
`EventWrite` this exact event — no Settings app needed, no special
privileges.

---

## Complete Call Flow (userspace → vulnerable code)

```
[userspace, any integrity level]
EventRegister({5402e5ea-1bdd-4390-82be-e108f1e634f5})
EventWrite(event ID 5600, level TRACE_LEVEL_INFORMATION,
           { bAutoDetect, pwszAutoConfigUrl, pwszProxy, pwszProxyBypass })
EventUnregister(...)
   (legitimate source: Settings > Network > Proxy page — same provider/event)

[Network List Service — svchost.exe -k LocalService -s netprofm, NETWORK SERVICE]
ETW consumer thread
  └─ ncsi!EtwListener::ProcessEvent            // event 5600 handler
       └─ ncsi!StoreNcsiIEProxyString(input, bool)
            ├─ StringCchPrintfExW(buf, L"%s%s", bool ? L"1" : L"0", input)
            ├─ RegOpenKeyExW(HKLM, ...\NlaSvc\Parameters\Internet\ManualProxies,
            │                0, KEY_SET_VALUE, &hKey)
            │     ← DACL/symlink decides WHERE this really lands  ← ROOT CAUSE
            ├─ input == NULL  → RegDeleteValueW(hKey, NULL)
            └─ else           → RegSetValueExW(hKey, NULL, 0, REG_SZ, buf, cb)
                                   ← pre-patch: no validation
                                   ← post-patch: only if no ".." (Feature_3499399482)
```

### Relevant interfaces

| Layer | Interface |
|---|---|
| Trigger | ETW provider `Microsoft-Windows-WinINet-Config` `{5402e5ea-1bdd-4390-82be-e108f1e634f5}`, event ID **5600** |
| Userspace API | `EventRegister` / `EventWrite` / `EventUnregister` (advapi32.dll) |
| Service | Network List Service (`netprofm`, NETWORK SERVICE), module `ncsi.dll` |
| Vulnerable sink | `ncsi!StoreNcsiIEProxyString` → `RegSetValueExW` on `HKLM\...\NlaSvc\Parameters\Internet\ManualProxies` (default value) |
| Root-cause primitive | Registry key symbolic link under `HKLM\...\NlaSvc\Parameters` (pre-patch DACL: INTERACTIVE = create subkey) |

---

## Exploitation Scenario

1. **Plant the symlink** — as a low-privilege interactive user, create a
   registry key symbolic link so the `ManualProxies` write resolves to an
   attacker-chosen HKLM target (pre-patch DACL allows subkey creation under
   `Parameters`).
2. **Fire the ETW event** — forge the WinINet-Config event 5600 with the
   desired string content (the leading `<0|1>` prefix constrains the first
   character; choose the payload accordingly).
3. **Coerced write** — netprofm writes the string at the symlink target as
   NETWORK SERVICE.
4. **Code execution as NETWORK SERVICE** — weaponise the write (itm4n
   evaluated and rejected registry string redirection `@dll,-id` due to the
   forced leading `0`/`1`; the precise weaponisation target is left as
   future work publicly).
5. **NETWORK SERVICE → SYSTEM** — the classic James Forshaw technique
   (abusing the service's impersonation privileges, e.g. RoguePotato-style).

Note: itm4n released only the ETW trigger PoC; no full public exploit exists.
The PoC in this package is the safe trigger variant (unique marker string,
self-verifying registry read-back).

---

## Detection & Hunting (blue team)

### High-signal telemetry

| Source | Indicator |
|---|---|
| Sysmon **Event 12** (Registry key create/delete) | Keys created **under `HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters`** by **non-SYSTEM, non-service processes** — symlink planting (the smoking gun; legitimate interactive users have no business creating keys there) |
| Sysmon **Event 13** (Registry value set) | `netprofm` svchost (`svchost.exe -k LocalService -s netprofm`) setting the `ManualProxies` default value where **Details contains `..`** — exploitation of the string-validation half; also any write landing outside `...\NlaSvc\...` (symlink followed) |
| Sysmon **Event 14** (Registry key/value rename) | Renames under `NlaSvc\Parameters` — symlink setup variants |
| ETW | `Microsoft-Windows-WinINet-Config` event 5600 fired by processes that are NOT the Settings app / known network tooling (hunt via ETW consumer tooling; low fidelity, high signal) |
| Registry ACL audit | Baseline-diff the DACL of `HKLM\...\NlaSvc\Parameters` — pre-patch it grants INTERACTIVE create-subkey; any re-appearance of that ACE on patched fleets is a regression/IoC |

### Sigma rule (starter)

```yaml
title: CVE-2025-59201 NCSI Registry Symlink - Key Created Under NlaSvc Parameters
id: 5b2c9a10-5920-41c2-9a10-020255920100
status: experimental
logsource:
  product: windows
  category: registry_key
detection:
  selection:
    TargetObject|contains: '\Services\NlaSvc\Parameters'
  filter_services:
    Image|endswith:
      - '\svchost.exe'
      - '\System'
  filter_system:
    User|contains:
      - 'SYSTEM'
      - 'NETWORK SERVICE'
      - 'LOCAL SERVICE'
  condition: selection and not (filter_services or filter_system)
level: high
```

```yaml
title: CVE-2025-59201 NCSI ManualProxies Write With Path Traversal
id: 5b2c9a10-5920-41c2-9a10-020255920101
status: experimental
logsource:
  product: windows
  category: registry_set
detection:
  selection:
    TargetObject|contains: '\NlaSvc\Parameters\Internet\ManualProxies'
    Details|contains: '..'
  condition: selection
level: critical
```

### YARA (PoC marker — ETW constants)

```yara
rule CVE_2025_59201_PoC_marker {
    meta:
        description = "Detects NCSI ETW-trigger PoC tooling (provider GUID + event constants)"
        reference = "https://itm4n.github.io/cve-2025-59201-ncsi-eop/"
    strings:
        $guid   = { EA E5 02 54 DD 1B 90 43 82 BE E1 08 F1 E6 34 F5 }  // provider GUID
        $eid    = { E0 15 00 00 }                                      // event ID 5600 (LE)
        $api1   = "EventRegister" ascii
        $api2   = "EventWrite" ascii
        $marker = "cve_2025_59201_marker" wide
    condition:
        uint16(0) == 0x5A4D and ($guid and 1 of ($api*)) and ($eid or $marker)
}
```

### Quick hunt (PowerShell)

```powershell
# Is the DACL still vulnerable? (pre-patch shows INTERACTIVE: CreateSubKey)
(Get-Acl 'HKLM:\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters').Access |
  Where-Object { $_.IdentityReference -match 'INTERACTIVE' }
```

---

## Remediation

1. **Apply KB5066835** (October 2025) or later; verify
   `ncsi.dll ≥ 10.0.26100.6899` **and** the tightened DACL on
   `HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters` (the DLL
   version alone does not tell you the ACL half is fixed).
2. **Alert on subkey creation under `NlaSvc\Parameters`** by anything that
   isn't a service/system process (Sigma above) — near-zero false positives.
3. **Hunt** for `ManualProxies` values containing `..` in historical
   registry telemetry.

---

## Timeline

| Date | Event |
|---|---|
| 2025-10-14 | Microsoft October 2025 Patch Tuesday (KB5066835), CVE-2025-59201 assigned (credited to @t0zhang) |
| 2025-10 | itm4n publishes the analysis: DACL root cause + ETW trigger PoC |
| 2026-07-22 | This analysis: blue-team package; fix **verified** via ghidriff + headless Ghidra decompilation (`ContainsRelativePathDoubleDot` gate behind `Feature_3499399482` in `StoreNcsiIEProxyString`) |

---

## References

- [itm4n — CVE-2025-59201: NCSI Elevation of Privilege](https://itm4n.github.io/cve-2025-59201-ncsi-eop/)
- [MSRC — CVE-2025-59201](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-59201)
- [Microsoft — NCSI overview](https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/internet-explorer-edge-open-connect-corporate-public-network)
- [OnlyFm252 diff report — ncsi_dll-kb5066835](/data/patch_diffs/ncsi_dll-kb5066835.md)

---

<sub>Analysis: patch verified 2026-07-22 via ghidriff (VersionTrackingDiff) of
ncsi.dll 10.0.26100.6725 vs 10.0.26100.6899 (Windows 11 24H2) plus headless
Ghidra decompilation of `StoreNcsiIEProxyString` (pre @ 0x1800546d0 / post @
0x1800549f0) and the added `ContainsRelativePathDoubleDot` (@ 0x180053294).
Registry DACL root cause and ETW trigger per itm4n's public analysis; the
full registry-symlink exploit chain is documented conceptually but no public
code exists. The `poc_cve_2025_59201.c` trigger is a pure-C port of itm4n's
ETW PoC (builds with `cl.exe poc_cve_2025_59201.c /link advapi32.lib
kernel32.lib`).</sub>
