Tech 19 min read

KnowledgeDeliver CVE-2026-5426: shared machineKey, ViewState RCE, Cobalt Strike

IkesanContents

TL;DR

What happened KnowledgeDeliver LMS installations before February 24, 2026 share a common ASP.NET machineKey. Anyone with the key can forge ViewState and achieve RCE (CVE-2026-5426)

What to do Regenerate a unique machineKey per instance. Key rotation alone does not remove post-compromise artifacts: Godzilla web shell, tampered JS, Cobalt Strike on endpoints

What to check Windows Application log Event ID 1316 (Event code 4009) and child processes spawned by w3wp.exe (cmd.exe, powershell.exe)


Mandiant disclosed CVE-2026-5426 in Digital Knowledge’s LMS product KnowledgeDeliver: the ASP.NET machineKey was identical across multiple deployments.
The vulnerability was exploited as a zero-day in the second half of 2025, before a patch was available.

The attack did not stop at server compromise.
Attackers used ViewState deserialization to achieve code execution inside IIS, ran Godzilla (BLUEBEAM) in w3wp.exe memory, tampered with the web application’s JavaScript, and delivered Cobalt Strike Beacon to end-user machines through the LMS interface.

The February 24 boundary in web.config

Mandiant’s disclosure specifies the impact conditions.
KnowledgeDeliver installations from before February 24, 2026 are affected if the vendor-supplied machineKey in web.config was never replaced by the organization and the application is reachable by an attacker.

machineKey is the key pair ASP.NET uses to sign and encrypt data such as ViewState. It consists of validationKey and decryptionKey.
Each deployment is supposed to generate unique values, but KnowledgeDeliver shipped a web.config with hardcoded keys.
Obtaining web.config from one installation gives an attacker a valid key for every other installation that has not changed it.

Installations updated after February 24, 2026 and those still on the old deployment need different verification steps.
For on-premises or custom LMS deployments, checking whether the hardcoded values remain in the actual web.config is more reliable than checking the vendor’s “latest version” label.

ViewState deserialization internals

ViewState is the mechanism ASP.NET Web Forms uses to persist page state.
The server serializes control state with ObjectStateFormatter, Base64-encodes the result, and embeds it in an HTML hidden field named __VIEWSTATE.
The browser sends this value back on every postback, and the server decrypts, validates, and deserializes it to restore page state.

The two keys in machineKey serve different purposes:

KeyPurposeAlgorithm examples
validationKeyHMAC signature to detect tamperingHMACSHA256, HMACSHA512
decryptionKeyEncrypts the ViewState bodyAES-128, AES-256, 3DES

In the normal flow, the server Base64-decodes the posted __VIEWSTATE, decrypts it with decryptionKey, verifies the MAC with validationKey, and deserializes with ObjectStateFormatter to restore page state.

ObjectStateFormatter is a binary formatter in the System.Web.UI namespace that uses a token-based serialization format.
It starts with 0xFF 0x01 magic bytes, followed by repeating sequences of type ID, field length, and value.
Besides primitive types (Int32, String, etc.), there is a generic type token (ID 0x02) that resolves arbitrary .NET types via Type.GetType().
This generic token is the root cause: it allows instantiation of arbitrary classes during deserialization.

The <machineKey> element in web.config specifies algorithms through validation and decryption attributes.
The default since .NET 4.5 is validation="HMACSHA256" / decryption="AES", but older configurations may use SHA1 / 3DES.
Since the attacker reads both the keys and algorithm settings from web.config, one file is all they need to generate a working payload.

ysoserial.net and gadget chains

When the attacker knows the machineKey, they can craft a malicious ViewState that passes MAC verification.
ysoserial.net is a payload generation tool for .NET deserialization attacks. Its --plugin viewstate option directly generates ViewState-formatted payloads.

ysoserial.exe -p viewstate \
  -g TypeConfuseDelegate \
  -c "cmd /c whoami > C:\inetpub\wwwroot\test.txt" \
  --validationalg="SHA1" \
  --validationkey="<hex_key>" \
  --decryptionalg="AES" \
  --decryptionkey="<hex_key>" \
  --path="/KnowledgeDeliver/Default.aspx" \
  --apppath="/KnowledgeDeliver"

--path and --apppath matter because ASP.NET includes the page’s virtual path in its MAC calculation.
If the URL path does not match the target application, the server rejects the MAC, so the attacker needs the URL structure beforehand.

A gadget chain is an object graph that exploits the chained invocation of constructors and property setters during deserialization to ultimately reach Process.Start() or Assembly.Load().
Two representative chains used in this case are worth detailing.

TypeConfuseDelegate exploits the comparer callback that fires when SortedSet<T> is deserialized.
The _invocationList of a Comparison<string> delegate is overwritten via MulticastDelegate to point to Process.Start(string).
When SortedSet attempts to sort its elements during deserialization, the comparer invokes Process.Start, and the argument string is executed as an OS command.

flowchart TD
    A["Deserialize<br/>SortedSet&lt;string&gt;"] --> B["Internal Sort() fires"]
    B --> C["Calls registered<br/>Comparer delegate"]
    C --> D["Reads MulticastDelegate<br/>_invocationList"]
    D --> E["Actual target:<br/>Process.Start()"]
    E --> F["Argument string<br/>executed as OS command"]

TextFormattingRunProperties is a class in System.Windows.Markup (WPF) that parses XAML markup during deserialization.
By embedding an ObjectDataProvider in XAML with MethodName set to Start and ObjectInstance set to Process, the process launches at XAML parse time.
This chain works on any Windows Server environment where the WPF assembly (PresentationFramework.dll) is present in the GAC.

Both chains are output in ObjectStateFormatter format and signed/encrypted with the known validationKey and decryptionKey.
The moment MAC verification passes, ObjectStateFormatter deserialization runs, the gadget chain fires, and code execution is achieved inside w3wp.exe.

flowchart TD
    A["Attacker:<br/>obtain machineKey from web.config"] --> B["Generate gadget chain<br/>with ysoserial.net"]
    B --> C["Sign and encrypt<br/>with validationKey/decryptionKey"]
    C --> D["POST as __VIEWSTATE<br/>to KnowledgeDeliver"]
    D --> E["ASP.NET:<br/>Base64 decode → decrypt"]
    E --> F["MAC verification passes<br/>(path match)"]
    F --> G["ObjectStateFormatter<br/>deserializes"]
    G --> H["Gadget chain fires<br/>(TypeConfuseDelegate or<br/>TextFormattingRunProperties)"]
    H --> I["Arbitrary code execution<br/>inside w3wp.exe"]

Microsoft reported in February 2025 that they had identified over 3,000 publicly disclosed machineKey values across code repositories and documentation.
The common pattern is a hardcoded value from a Stack Overflow answer or sample code ending up in production web.config files. KnowledgeDeliver differs in that the vendor’s own distribution included the shared key, but the underlying structure — the same key used across multiple environments — is identical.

CVSS score discrepancy

The Hacker News reported CVE-2026-5426 with a CVSS score of 7.5.
NVD was still “Awaiting Enrichment” at the time of writing, with a CISA-ADP-sourced CVSS v3.1 of 9.1.
Mandiant’s GitHub disclosure describes a vector that works out to roughly CVSS 9.0, and NIST has not yet published its own analysis.

The gap between 7.5 and 9.1 comes down to the Attack Complexity rating.
7.5 assumes “some precondition is needed to obtain the machineKey”; 9.1 assumes “the attacker can obtain the key beforehand because it is a product default.” For KnowledgeDeliver, the latter interpretation better matches reality — obtaining web.config from any other installation of the same product yields the key, making Attack Complexity Low.

Vulnerability management tools that wait for NVD’s NIST-side CVSS can pick up the CVE and CWE but will be late on severity rating.
This is the same pattern described in NIST NVD’s enrichment changes: a CVE exists but NVD analysis has not yet landed.

Priority here should be based on confirmed exploitation rather than CVSS score.
Mandiant confirmed it through investigation, and Google Cloud published a Threat Intelligence article covering post-compromise behavior.
If the LMS is reachable from the internet, start checking before the CVSS score is finalized.

Godzilla (BLUEBEAM) in-memory operation

The first payload deployed via ViewState deserialization is BLUEBEAM — the .NET variant of the Godzilla web shell.
Godzilla is a web shell framework by developer BeichenDream, supporting Java, PHP, ASP.NET (C#), and ASP (VBScript).
Since KnowledgeDeliver is an ASP.NET application, the C# payload was used.

Fileless execution

Traditional web shells persist by writing .aspx or .ashx files to disk.
BLUEBEAM writes no files.
It uses the code execution obtained via ViewState deserialization to call Assembly.Load(byte[]) in the CLR (Common Language Runtime).
This API loads a .NET assembly from a byte array directly into memory, with no DLL file written to disk.

The execution path:

  1. The gadget chain launches an initial loader via Process.Start("powershell", "-enc <Base64>")
  2. The loader fetches the BLUEBEAM assembly bytes over HTTP
  3. Assembly.Load(byte[]) loads the assembly into memory
  4. The loaded assembly’s entry point registers itself as an IIS HTTP module
  5. From this point, it hooks into the IIS request pipeline and intercepts requests to specific paths/parameters for C2 (Command and Control) communication

Module registration uses HttpApplication.RegisterModule() or DynamicModuleUtility.RegisterModule().
The module lives within the application domain of the IIS worker process w3wp.exe and survives until the process restarts.
Conversely, an application pool recycle (default: every 1,740 minutes or when memory thresholds are exceeded) kills it.
The attacker either has a mechanism to re-deploy after recycling or completes their objectives within the recycle interval.

C2 communication protocol

Godzilla’s C2 communication uses HTTP POST with an encrypted command in the request body.
Encryption is AES-128-CBC; the key is the first 16 bytes of the MD5 hash of a passphrase set by the attacker at deployment time.

The communication format:

[Request]
POST /KnowledgeDeliver/api/handler HTTP/1.1
Content-Type: application/x-www-form-urlencoded

data=<Base64(AES-CBC-Encrypt(payload))>

[Payload structure]
+--------+--------+------------------+
| 2 byte | 4 byte | N byte           |
| cmd_id | length | serialized_args  |
+--------+--------+------------------+

cmd_id maps to module functions: file listing, file download, arbitrary command execution, assembly loading, and so on.
Responses are encrypted with the same AES key.
Externally, the traffic is indistinguishable from normal HTTP POST requests.
Network monitoring might catch it only when Content-Length and response size ratios are abnormally large (e.g., during file exfiltration).

Module architecture

Godzilla extends functionality through modules.
The attacker’s GUI client POSTs module .NET assembly bytes, and the server-side BLUEBEAM loads them with Assembly.Load(byte[]) and calls Invoke().
Modules also leave no files on disk.

Standard modules:

ModuleFunctionDetection surface
CmdShellCommand execution via cmd.exe /ccmd.exe appears as a child process of w3wp.exe
FileManagerFile CRUD operationsCaught by filesystem audit logs (Event 4663)
AssemblyLoaderLoad arbitrary .NET assembliesScanned by AMSI in enabled environments
DatabaseManagerDB operations using connection stringsCaught by DB query logs
ScreenCaptureDesktop screenshot captureFails on servers without GUI sessions

When CmdShell is used, a w3wp.execmd.exe parent-child relationship appears.
This is the primary detection indicator discussed below.

JS tampering to Cobalt Strike delivery

After establishing code execution on the server via BLUEBEAM, the attacker tampered with the web application’s JavaScript.

Permission escalation

The attacker first used icacls to grant Everyone full access to the web application directory:

icacls C:\inetpub\wwwroot\KnowledgeDeliver /grant Everyone:F /T

IIS content directories normally grant read-only access to IIS_IUSRS/IUSR and limited write access to the application pool identity.
The w3wp.exe worker process typically runs as IIS AppPool\<PoolName>, a virtual account without write access to the entire web root.
The icacls command opens full access to Everyone, making files writable from any process.

This icacls call itself goes through the CmdShell module, creating a w3wp.execmd.exeicacls.exe process chain.
With SeSecurityPrivilege auditing enabled, it is also recorded in Security Event 4670 (Permissions on an object were changed).

Injected code structure

The tampered file was a shared library JS loaded on every page.
The injected code had two functions.

The first was a fake security alert dialog.
It inserted a <div> overlay into the DOM, displaying text urging the user to install a “security authentication plugin” with a “Download” button.
The dialog used CSS position:fixed; z-index:99999 to cover the entire page, with a loop that re-displayed it seconds after the close button was pressed.
The intent was to make users believe they could not use the LMS without installing the “plugin.”

The second was an external script loader that dynamically injected a <script> tag pointing to an attacker-controlled domain.
This script performed environment fingerprinting (OS, browser, user agent) and displayed the download link only to matching endpoints.
According to Mandiant, the external domain used a Let’s Encrypt certificate, so the connection was HTTPS.

Cobalt Strike Beacon delivery and behavior

When LMS users opened the page, the fake alert appeared. Those who followed the instructions and downloaded/ran the “plugin” got Cobalt Strike Beacon installed on their machine.

Cobalt Strike is a C2 framework originally developed for penetration testing. Beacon is its agent component.
Beacon supports HTTPS, DNS, and SMB channels for C2 communication; HTTPS was used in this case.

Beacon’s lifecycle:

  1. The downloaded EXE (or DLL sideloading configuration) acts as a stager — an intermediate loader that fetches the main payload. It retrieves the full Beacon payload (~200-300KB) from the C2 server
  2. The stager writes the payload into an RWX memory region allocated via VirtualAlloc() and jumps to it
  3. By default, Beacon spawns a sacrificial process such as rundll32.exe or dllhost.exe, injects itself into it, and terminates the original process. Injection techniques include CreateRemoteThread, QueueUserAPC, and NtMapViewOfSection
  4. At the configured sleep interval (typically ~60 seconds), Beacon sends an HTTPS GET to the C2 server and receives tasks in the response. The sleep interval includes jitter (random variation, typically 10-30%)

A configuration file called a malleable C2 profile allows fine-grained customization of communication patterns.
It defines HTTP GET paths, headers, cookie formats, and response transformations (Base64, XOR, prepend/append), making traffic look like legitimate CDN or API communication.
For example, a path like /api/v2/status with a JSON-formatted response can pass URL filtering and SSL inspection.

Mandiant noted that the Beacon payload in this case was encrypted using the compromised organization’s name as the key.
Specifically, the SHA256 hash of the organization name string was computed, and its first 16 bytes were used as the AES-128 key to encrypt the Beacon payload inside the stager.
If the stager is obtained by another analysis environment, the payload cannot be decrypted without knowing the correct organization name.
This anti-analysis technique prevents Beacon configuration (C2 domains, communication patterns) from being exposed during sandbox analysis or VirusTotal uploads.
Analysts with knowledge of the compromised organization’s name can decrypt it, so it does not impede incident response, but it blocks third-party threat intelligence.

flowchart TD
    A["Obtain shared machineKey"] --> B["Forge malicious<br/>ViewState"]
    B --> C["POST __VIEWSTATE<br/>to KnowledgeDeliver"]
    C --> D["ASP.NET deserializes<br/>→ code execution"]
    D --> E["BLUEBEAM starts<br/>inside w3wp.exe"]
    E --> F["CmdShell runs icacls<br/>→ web dir permissions opened"]
    F --> G["Inject fake alert and<br/>external loader into library JS"]
    G --> H["LMS users see<br/>fake security alert"]
    H --> I["Download and run stager"]
    I --> J["Fetch Beacon payload from C2<br/>(encrypted with org name)"]
    J --> K["Process injection<br/>into sacrificial process"]
    K --> L["HTTPS C2 beaconing begins"]

The pattern of pivoting from web application compromise to end-user endpoint infection is structurally similar to the Magento PolyShell RCE case.
The entry points differ, but the shared structure — achieving arbitrary code execution on a public-facing web app, then tampering with web root or JavaScript to attack visitors — is the same.

Hunting from Event ID 1316

The hunting starting point identified by Mandiant is Windows Application log Event ID 1316.
The source is ASP.NET 4.0.30319.0, and it records ViewState validation failures as Event code 4009.

Two failure patterns

The first — integrity check failure (MAC verification failure) — indicates an attempt where the attacker used the wrong key to craft the ViewState.
The event message contains “Validation of viewstate MAC failed.”
Since the key did not match, deserialization did not proceed, and no actual compromise occurred at this stage.
However, this event almost never fires from legitimate user activity.
It can occur when session affinity breaks behind a load balancer or during a cache period right after a machineKey change, but if it appears continuously, it is either scanning or an attack attempt.

The second — invalid ViewState — indicates an error at the deserialization stage after MAC verification passed.
The message contains “Invalid viewstate.”
This can mean either that the key was correct but the payload format had issues (gadget chain version mismatch, etc.) or that deserialization succeeded, code executed, and then threw an error.
This one warrants immediate investigation.

Log correlation procedure

Logs alone cannot confirm successful execution.
Mandiant decrypted the payload strings in the event log using the server’s machineKey and recovered BLUEBEAM-related code.
When Event ID 1316 is found, correlate the following four log sources for the same time window:

IIS W3C access logs (C:\inetpub\logs\LogFiles\W3SVC<n>\)
Identify POST requests coinciding with Event ID 1316.
Look for entries where cs-method is POST, cs-uri-stem matches a KnowledgeDeliver page path, and cs-bytes (request size) is larger than normal.
Normal ViewState is a few KB; payloads containing gadget chains can be tens of KB.
c-ip (source IP) identifies the attack origin.

Windows Application log
In addition to Event ID 1316, check Event ID 1309 (Event code 3005: Unhandled exception).
If the gadget chain throws during execution, the stack trace may contain type names such as TypeConfuseDelegate or TextFormattingRunProperties.

Process creation events (Sysmon Event ID 1 or Security Event 4688)
Look for child processes spawned by w3wp.exe.
In normal IIS operation, w3wp.exe almost never directly invokes cmd.exe /c, whoami, powershell.exe, or icacls.exe.

Example Sysmon query for environments with Sysmon installed:

<QueryList>
  <Query Id="0" Path="Microsoft-Windows-Sysmon/Operational">
    <Select Path="Microsoft-Windows-Sysmon/Operational">
      *[System[(EventID=1)]]
      and
      *[EventData[Data[@Name='ParentImage'] and (Data='C:\Windows\System32\inetsrv\w3wp.exe')]]
    </Select>
  </Query>
</QueryList>

Example KQL query for Microsoft Defender for Endpoint:

DeviceProcessEvents
| where InitiatingProcessFileName == "w3wp.exe"
| where FileName in ("cmd.exe", "powershell.exe", "whoami.exe", "icacls.exe", "net.exe", "certutil.exe")
| project Timestamp, DeviceName, FileName, ProcessCommandLine, InitiatingProcessCommandLine

Filesystem change events
Use Sysmon Event ID 11 (FileCreate) or Event ID 2 (File creation time changed) to detect .js file modifications under the web root.
If files in the web root are modified by the w3wp.exe process, it is a high-confidence IOC (Indicator of Compromise).

User-Agent anomalies

Mandiant reported unnatural User-Agent strings that appeared to concatenate two browser identifiers.
For example, a string like Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/120 ... Chrome/119 ... containing two Chrome version numbers.
This is likely a bug in Godzilla’s HTTP client when assembling the User-Agent, or a template concatenation error.

Normal browser updates also change User-Agent strings, but if Event ID 1316, w3wp.exe child processes, JS tampering, and anomalous User-Agents cluster in the same period, treat it as post-compromise activity rather than isolated scanning.

Post-key-rotation cleanup

machineKey regeneration

The first step is generating a unique machineKey with sufficient strength for each KnowledgeDeliver instance.
Recommended sizes: validationKey at 64 bytes (128 hex chars) or longer, decryptionKey at 24 bytes (48 hex chars for 3DES) or 32 bytes (64 hex chars for AES-256).
PowerShell can generate cryptographically secure random values:

# validationKey (64 bytes = 128 hex chars, for HMACSHA256)
$rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::new()
$bytes = [byte[]]::new(64)
$rng.GetBytes($bytes)
$vk = [System.BitConverter]::ToString($bytes).Replace('-','')

# decryptionKey (32 bytes = 64 hex chars, for AES-256)
$bytes = [byte[]]::new(32)
$rng.GetBytes($bytes)
$dk = [System.BitConverter]::ToString($bytes).Replace('-','')

Example web.config entry:

<machineKey
  validationKey="<generated 128 hex chars>"
  decryptionKey="<generated 64 hex chars>"
  validation="HMACSHA256"
  decryption="AES"
/>

Adding a WAF or access restrictions while leaving the old shared key in place does not invalidate existing ViewState payloads crafted with that key.
When switching to AutoGenerate (ASP.NET’s automatic key generation mode), note that a load-balanced multi-server IIS configuration requires all servers to share the same key — explicit key specification is easier to manage in this case.
Using AutoGenerate,IsolateApps generates per-server keys, which breaks ViewState validation when session affinity fails.

Confirming compromise artifacts

Changing the key closes the entry point but does not remove anything placed during the compromise.
Three layers need verification:

Server layer
Microsoft recommended upgrading to ASP.NET 4.8 and enabling AMSI (Antimalware Scan Interface) in their February 2025 article on ASP.NET machineKey abuse.
With AMSI enabled, assemblies loaded via Assembly.Load(byte[]) are scanned in memory by registered AMSI providers such as Windows Defender.
Fileless web shells like BLUEBEAM are scanned at the moment of loading.
However, AMSI bypass techniques (AmsiScanBuffer patching, CLR version spoofing, etc.) are well known to attackers, so AMSI alone is not sufficient.

The Attack Surface Reduction (ASR) rule “Block webshell creation for Servers” (GUID: a8f5898e-1dc8-49a9-9878-85004b8a61e6) is relevant.
This rule blocks .aspx/.ashx/.php file creation from w3wp.exe, but since BLUEBEAM does not write files, it does not directly defend against this specific attack.
It does serve as a safety net if the attacker attempts to plant a disk-based web shell as a backup.

Web application layer
Verify the integrity of .js, .aspx, and .config files under the web root.
If change management has recorded hashes, diff against the pre-compromise state.
Check library JS files for <script src="https://..."> tags pointing to external domains, document.createElement('script'), and unfamiliar fetch/XMLHttpRequest additions.

Beyond file restoration, the period during which tampered JS was served matters: LMS users who accessed the site during that window may need to be notified.

Endpoint layer
If Cobalt Strike Beacon reached endpoints, identify the infection scope:

  • Check DNS server or proxy logs for queries to the C2 domain
  • Check TLS inspection logs for certificate characteristics (Subject, Issuer, JA3 hash). Cobalt Strike’s default certificate has a distinctive Subject (CN=Major Cobalt Strike in default config; custom certificates are often self-signed)
  • Check process trees for the stager EXE name and injected processes (rundll32.exe, dllhost.exe launched with no arguments)
  • Check for periodic connections (~60-second intervals) to the same host in endpoint network logs

Network-side measures

If the LMS is directly reachable from the internet, add access restrictions.
IP-based restrictions can be difficult for LMS serving external students, but verify whether management/authoring pages and student-facing pages can be separated at the network level.

ViewState deserialization attacks use POST requests, so WAF detection rules are an option:

  • Block POSTs where the __VIEWSTATE parameter exceeds a size threshold (normal maximum + margin)
  • Rate-limit POSTs with Content-Type: application/x-www-form-urlencoded containing __VIEWSTATE
  • Signature-match known ysoserial.net gadget chain Base64 patterns

False positives are possible since legitimate ViewState can grow large on complex pages.
Additionally, ASP.NET Core CVE-2026-40372 represents a separate attack surface in the ASP.NET family, so ViewState-specific defenses alone may not be sufficient.

References