If you only read one Sitecore post this week, make it Sitecore’s security bulletin SC2025-005. Mandiant’s write-up on it went live on September 3rd, and this one is a little different from the usual “apply hotfix X to version Y” bulletin. There’s no DLL to swap. The vulnerable thing is a value sitting in your web.config.
I’ve been doing Sitecore since 2013, and I’ll be honest – back in the 8.x and 9.0 days, copying config straight out of the installation and deployment guides was just… what you did. It was in the official doc, it worked, and you moved on to the next ticket. That habit is exactly what this bulletin is about.
What actually happened
Short version:
- Older Sitecore deployment guides (2017 and earlier, XP 9.0 era) included a sample ASP.NET
machineKeyin their example config. - Some of those sample keys made it into production and never left.
- An attacker who knows the key can craft a
__VIEWSTATEpayload that the server will happily validate and deserialize. That’s remote code execution. - Mandiant caught someone doing exactly this in the wild. The target was
/sitecore/blocked.aspx– a legit Sitecore page that loads without authentication and happens to have a ViewState form on it.
Sitecore is tracking it as CVE-2025-53690. It’s rated 9.0, and CISA has already added it to the Known Exploited Vulnerabilities list with a September 25th deadline for federal agencies. When CISA puts a date on something, I take that as a hint for the rest of us too.
Am I affected?
The products listed are XM, XP, XC and Managed Cloud. If you’re fully on XM Cloud, Sitecore is running the CM for you and you’re not on the list. But let’s be real – a lot of us are mid-migration and still have an XP or XM instance humming along somewhere (sometimes a “temporary” one that’s been there for three years).
The risk sits with any instance that has a hard-coded machineKey in web.config. Single-box installs usually rely on AutoGenerate and never set one. Scaled and multi-instance setups (multiple CDs behind a load balancer, Azure PaaS, Managed Cloud) have to set an explicit key so every node can read the same ViewState and auth cookies. That’s where copied values tend to live.
So the question isn’t “do I have a machineKey”. It’s:
- Is it explicit (not AutoGenerate)?
- Did the value come from a guide, a blog, a starter repo or another project?
- Is the same value used across dev, QA and prod?
If you can’t answer #2 with confidence, treat the key as burned and rotate it.
Step 1: Find every machineKey you’ve got
Here’s the PowerShell I’ve been running against our boxes. It walks your site roots, finds every web.config with a machineKey, and prints a short fingerprint (a truncated SHA-256) instead of the actual key. That way you can paste the output into Teams or a ticket without leaking anything, and you can still spot duplicates.
# Find-MachineKeys.ps1param( [string[]]$Roots = @('C:\inetpub\wwwroot'), # Optional: keys you KNOW are bad (e.g. the sample from an old guide). # They're only ever compared as fingerprints. [string[]]$KnownBadKeys = @())function Get-Fingerprint([string]$value) { if ([string]::IsNullOrWhiteSpace($value)) { return '' } $sha = [System.Security.Cryptography.SHA256]::Create() $hash = $sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($value.Trim().ToUpperInvariant())) ([BitConverter]::ToString($hash) -replace '-', '').Substring(0, 12)}$badPrints = $KnownBadKeys | ForEach-Object { Get-Fingerprint $_ }$results = foreach ($root in $Roots) { Get-ChildItem -Path $root -Filter 'web.config' -Recurse -ErrorAction SilentlyContinue | ForEach-Object { try { [xml]$xml = Get-Content -Path $_.FullName -Raw } catch { return } $mk = $xml.SelectSingleNode('/configuration/system.web/machineKey') if (-not $mk) { return } $encrypted = [bool]$mk.GetAttribute('configProtectionProvider') $vk = $mk.GetAttribute('validationKey') $auto = (-not $encrypted) -and ($vk -eq '' -or $vk -like 'AutoGenerate*') $print = if ($encrypted -or $auto) { '' } else { Get-Fingerprint $vk } [pscustomobject]@{ File = $_.FullName Encrypted = $encrypted AutoGen = $auto Fingerprint = $print KnownBad = ($print -and $badPrints -contains $print) } }}$results | Format-Table -AutoSizeWrite-Host "`nKeys shared by more than one site/environment:" -ForegroundColor Yellow$results | Where-Object Fingerprint | Group-Object Fingerprint | Where-Object Count -gt 1 | ForEach-Object { $_.Group | Select-Object Fingerprint, File } | Format-Table -AutoSize
How to read the output:
- AutoGen = True – you’re in decent shape on that box.
- Encrypted = True – the section is protected. You still need to know where the value came from, but at least it isn’t sitting in plain text.
- KnownBad = True – stop reading this post and go rotate it.
- Same fingerprint in multiple environments – not this CVE exactly, but it means a leaked dev config gives someone your prod key. Fix it while you’re in there.
For the -KnownBadKeys part, Microsoft keeps a list of hashes of publicly disclosed ASP.NET machine keys (plus a checker script) in their GitHub, linked from their February post on this exact attack pattern. Worth running both.
Don’t forget source control. If the key is checked into the repo, it’s effectively public to everyone who has ever had access to that repo:
# from the root of your solution repogit grep -n -i "validationKey=" -- "*.config" "*.xdt"git log -p -S "validationKey" --all -- "*.config" "*.xdt" | Select-String "validationKey"
Transforms (.xdt) are where these love to hide.
Step 2: Generate a new key (properly)
Don’t grab a key from a “machineKey generator” website either. Generate it locally:
# New-MachineKey.ps1function New-HexKey([int]$bytes) { $buffer = New-Object byte[] $bytes $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() $rng.GetBytes($buffer) ($buffer | ForEach-Object { $_.ToString('X2') }) -join ''}$validationKey = New-HexKey 64 # 512 bits for HMACSHA256$decryptionKey = New-HexKey 32 # 256 bits for AES@"<machineKey validationKey="$validationKey" decryptionKey="$decryptionKey" validation="HMACSHA256" decryption="AES" />"@
One key per environment. Dev, QA, UAT and prod should all have different values. In a scaled environment every CD and CM in that environment needs the same value, so push it through your pipeline variables / Key Vault – not a committed file.
Step 3: Don’t leave it in plain text
Mandiant’s guidance includes encrypting plaintext secrets in web.config. On IIS that’s a one-liner with aspnet_regiis:
$aspnetRegiis = "$env:WINDIR\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe"& $aspnetRegiis -pef "system.web/machineKey" "C:\inetpub\wwwroot\YourSite.cd"
Heads up for scaled environments: the default RSA provider encrypts with a key stored on that machine. If you encrypt on one CD and deploy the file to the others, they won’t be able to decrypt it. Either run it on every node or set up a shared, exported RSA key container. Test this on a non-prod environment first. Ask me how I know.
Step 4: Rotate, and plan for the side effects
Changing the machineKey invalidates anything signed or encrypted with the old one:
- Forms auth /
.ASPXAUTHcookies – everyone gets logged out, including content authors - Any in-flight ViewState (people mid-form in the Content Editor will get an error on submit)
- Anything else your customizations protect with
MachineKey.Protect
None of that is a reason to wait. Just give your authors a heads up and do it outside a big publish window.
Step 5: Look for signs you were already hit
Rotating the key closes the door. It doesn’t tell you whether someone already walked through it. Two quick checks.
The Windows Application log – ASP.NET logs failed ViewState validation as Event ID 1316 with event code 4009:
Get-WinEvent -FilterHashtable @{ LogName = 'Application' Id = 1316 StartTime = (Get-Date).AddDays(-90)} -ErrorAction SilentlyContinue |Where-Object { $_.Message -match '4009' -and $_.Message -match 'blocked\.aspx' } |Select-Object TimeCreated, MachineName, @{ n = 'Detail'; e = { ($_.Message -split "`r?`n" | Select-String 'Client IP|Path') -join ' | ' } }
And IIS logs – POSTs to blocked.aspx shouldn’t really be a thing:
Get-ChildItem 'C:\inetpub\logs\LogFiles' -Recurse -Filter '*.log' | Select-String -Pattern 'POST /sitecore/blocked\.aspx' | Select-Object Filename, LineNumber, Line
On Azure PaaS, run the same idea as a query against your App Service HTTP logs in Log Analytics (AppServiceHTTPLogs, filtering CsMethod == "POST" and CsUriStem containing blocked.aspx).
If either of these returns hits, this isn’t a config fix anymore – pull in your security team and go through the indicators in Mandiant’s write-up. They documented the follow-on activity (recon, new local admin accounts, tunnelling, credential dumping), and you want people who do incident response for a living looking at that.
While you’re in there
A few things that cost almost nothing:
- Block
/sitecore/on your CDs at the edge or in IIS. Your CDs don’t need it publicly reachable – this has been a hardening guide item forever, and it’s exactly the kind of thing that turns a bad day into a non-event. - Check
blocked.aspxspecifically is not reachable from the internet on CD. - Make sure your servers are on .NET Framework 4.8 so the Antimalware Scan Interface (AMSI) integration is available. Microsoft calls this out as a mitigation for ViewState injection.
- Go back to the bulletins from earlier this year (SC2025-003 and friends) and make sure those are patched too. 2025 has been a busy year for Sitecore security.
Wrapping up
The scary part of this one isn’t the exploit, it’s how normal the root cause is. Somebody followed the docs. That’s it.
Remember to:
- Run the scan against every environment, including the “temporary” ones
- Rotate any key you can’t trace to a fresh, local generation
- Use a unique key per environment and keep it out of source control
- Hunt your logs before you call it done
Stay safe out there, and go check your machineKey. Seriously.
Resources
- Sitecore Security Bulletin SC2025-005 (KB1003865): https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB1003865
- Mandiant – ViewState Deserialization Zero-Day Vulnerability in Sitecore Products: https://cloud.google.com/blog/topics/threat-intelligence/viewstate-deserialization-zero-day-vulnerability
- Microsoft – Code injection attacks using publicly disclosed ASP.NET machine keys: https://www.microsoft.com/en-us/security/blog/2025/02/06/code-injection-attacks-using-publicly-disclosed-asp-net-machine-keys/




