Back to blog

CVE-2026-45659: how an ordinary site member turns on-prem SharePoint into a remote code execution box

· · 18 min read
CVE-2026-45659: how an ordinary site member turns on-prem SharePoint into a remote code execution box

On May 26, 2026, Microsoft published an out-of-band advisory for a SharePoint Server flaw that it had, by its own admission, quietly patched two weeks earlier and then forgotten to tell anyone about. The verdict is short and uncomfortable: any authenticated user with the lowest meaningful permission on a site, a plain Site Member, can send a crafted payload and execute arbitrary code on the SharePoint server itself. The bug is tracked as CVE-2026-45659 and carries a CVSS 8.8 (High) score with the vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. The striking part is not the score. It is that the fix shipped in the May 12, 2026 security updates, but the CVE was "inadvertently omitted" from the release notes, so for roughly two weeks defenders had the patch without knowing the hole existed.

This is a two-layered breakdown. The first part is for readers who need to decide quickly whether they have anything to do, and exactly what. The second part dissects the bug the way an analyst would reconstruct it: where the untrusted data enters, why the deserializer trusts it, and how that becomes code execution in the SharePoint worker process. The article closes by looking at how the Pragma Core platform addresses exactly the class of problem that made this possible, the kind of insecure deserialization that hides in a framework most teams treat as a black box.


Part I. Executive breakdown

What happened

SharePoint Server is Microsoft's on-premises collaboration and document platform. It runs on top of ASP.NET and IIS, and like most large .NET applications it moves a lot of internal state around as serialized objects: blobs of bytes that the server later rehydrates back into live objects in memory. Deserialization is the act of turning those bytes back into objects. It is meant to be an internal plumbing detail.

CVE-2026-45659 is what happens when that plumbing trusts the wrong input. Somewhere in a SharePoint request path, the server takes data that an authenticated user can influence and feeds it into a deserializer without first proving that the data is safe to reconstruct. .NET deserialization is dangerous because the act of rebuilding certain object types runs code as a side effect. An attacker does not need to "upload a program." They craft a serialized object whose very reconstruction triggers a chain of method calls that ends in command execution. The classic mental model: you handed the building a sealed box and said "please unpack this," and the act of unpacking it builds a working machine that runs the attacker's instructions.

The concrete result is remote code execution in the security context of the SharePoint application. Microsoft's advisory is explicit about the precondition: "an authenticated attacker, who has a minimum of Site Member permissions, could execute code remotely on the SharePoint Server." No administrator role. No user interaction. A Site Member is the everyday contributor account that exists by the thousands in any large SharePoint deployment.

Who is affected

The flaw is on-premises only. SharePoint Online (the Microsoft 365 hosted service) is not in scope, because Microsoft patches and operates that fleet. Self-hosted servers are where the risk lives.

Component Status
SharePoint Server Subscription Edition Vulnerable below build 16.0.19725.20280. Apply KB5002863 (or any May 12, 2026 or later update).
SharePoint Server 2019 Vulnerable below build 16.0.10417.20128. Apply KB5002870.
SharePoint Enterprise Server 2016 Vulnerable below build 16.0.5552.1002. Apply KB5002868.
SharePoint Online (Microsoft 365) Not affected. Serviced by Microsoft.

There is a subtlety that decides whether you have work to do. If you installed the May 12, 2026 security updates already, you are protected and no further action is required, because the fix shipped then even though the CVE was not named. If you skipped or deferred that month's updates because nothing in the release notes looked urgent, you are exposed and did not know it. That is the trap of a CVE that was omitted from the release it fixed: a defender doing reasonable risk-based patching could have rationally postponed the exact update that closed this hole. Microsoft rates the bug "less likely to be exploited" and there is no public proof of concept at the time of writing, but "less likely" is a forecast, not a guarantee, and SharePoint's recent history argues for treating deserialization bugs as material.

Why this matters beyond SharePoint

The pattern here is insecure deserialization of untrusted data, catalogued as CWE-502, and it is one of the most reliably weaponized bug classes in the .NET and Java worlds. The reason is structural: serialization frameworks were designed for trusted, in-process or server-to-server data, then got wired up to handle attacker-reachable input as applications grew. Once untrusted bytes reach a polymorphic deserializer, the existence of "gadget chains" (sequences of legitimate library methods that, when reconstructed in the right order, perform dangerous actions) turns a data-parsing operation into arbitrary code execution.

SharePoint is a high-value instance of the pattern, but the same shape appears anywhere a system accepts serialized objects, view state, cached session data, message-queue payloads, or signed tokens that are honored on the basis of a key the attacker might recover. If you run .NET services with BinaryFormatter, LosFormatter, ObjectStateFormatter, or unconstrained DataContractSerializer/Json.NET type handling, you own a piece of this problem.

Recommended actions

  1. Upgrade now. Apply the May 12, 2026 (or later) cumulative update for your edition: KB5002863 for Subscription Edition, KB5002870 for Server 2019, KB5002868 for Server 2016. Confirm the build number afterward, do not trust the KB count alone.
  2. Verify, do not assume. Pull the actual Microsoft.SharePoint build from each web and application server and compare it to the fixed builds in the table. Farms patch unevenly, and one un-patched server in a farm is the whole farm's exposure.
  3. Treat May 2026 as mandatory in your records. If your change log shows that month's update as "deferred" or "skipped," reclassify it as a security update and schedule it immediately.
  4. Turn on AMSI integration for SharePoint. Microsoft's AMSI integration lets Defender (or another AMSI provider) inspect request bodies before SharePoint processes them, which has blunted prior SharePoint deserialization exploitation. It is an interim control, not a substitute for the patch.
  5. Rotate ASP.NET machine keys after patching. SharePoint deserialization and view-state abuse historically lets attackers recover the ValidationKey/DecryptionKey used to sign __VIEWSTATE. Rotating keys post-patch invalidates any forged state an attacker may already hold.
  6. Audit Site Member sprawl. The precondition is a low-privilege authenticated account. Review who holds Site Member and contributor rights, disable dormant accounts, and require MFA, because in this bug an ordinary contributor credential is an RCE credential.

Part II. Technical breakdown

Background: serialization, ViewState, and the .NET trust assumption

To see why CVE-2026-45659 is dangerous you need the mental model that ASP.NET and SharePoint were built on. ASP.NET Web Forms, which SharePoint's classic surface is built atop, maintains control state across HTTP round-trips by serializing a tree of objects into a field called __VIEWSTATE and sending it to the browser, which sends it back on the next request. To stop tampering, the server signs (and optionally encrypts) that blob using keys stored in the machineKey configuration: a ValidationKey and a DecryptionKey. The server's invariant is simple: "if the MAC verifies, this serialized object came from me, so it is safe to deserialize."

That invariant has two failure modes, and both have haunted SharePoint. The first is when the keys leak, because then an attacker can forge a __VIEWSTATE that verifies and gets deserialized as trusted. The second, and the one that matters for this CVE, is when a code path deserializes untrusted input that was never supposed to be MAC-protected in the first place, or deserializes with a formatter that resolves arbitrary types. In .NET, deserializing into arbitrary types is the entire problem. Serializers like BinaryFormatter and LosFormatter faithfully reconstruct whatever type the byte stream names, calling constructors, property setters, and callbacks (OnDeserialized, IDeserializationCallback) as they go. Security researchers have spent years cataloguing "gadget chains," real types in the .NET framework and common libraries whose reconstruction side effects can be strung together to run a process. Tools like ysoserial.net automate building these payloads.

So the design invariant the bug violates is this: a deserializer must only ever rehydrate data whose origin and type are constrained. CWE-502 is precisely the class where that constraint is missing.

The vulnerability: deserialization of attacker-influenced data on an authenticated path

Microsoft's description is deliberately terse: "Deserialization of untrusted data in Microsoft Office SharePoint allows an authorized attacker to execute code over a network." Microsoft has not published the vulnerable method, and there is no public proof of concept, so the responsible thing is to describe the shape of the flaw rather than invent a specific line. The shape is well established from the broader SharePoint deserialization lineage, and the CWE-502 classification plus the PR:L precondition pin it down.

The dangerous pattern in .NET looks structurally like this. Untrusted input reaches a formatter that resolves types without a binder:

// Illustrative of the CWE-502 anti-pattern (not Microsoft's actual code).
// An authenticated request supplies a serialized blob via a form field,
// query parameter, or a property persisted earlier on a list item.
public object RestoreState(string clientSuppliedState)
{
    byte[] raw = Convert.FromBase64String(clientSuppliedState);
    using (var ms = new MemoryStream(raw))
    {
        // The bug class: a polymorphic formatter with no SerializationBinder,
        // so the byte stream chooses which .NET type gets reconstructed.
        var formatter = new LosFormatter();   // or BinaryFormatter / ObjectStateFormatter
        return formatter.Deserialize(ms);     // gadget chain executes here
    }
}

The crucial detail is what Deserialize does. It does not return a passive bag of data. It builds live objects, and building certain objects runs code. A gadget chain payload is a serialized object graph chosen so that its reconstruction ends in something like Process.Start. The deserializer is doing exactly what it was told: rebuild this object. The flaw is that "this object" was chosen by the attacker.

For CVE-2026-45659, the entry point is reachable by a Site Member. That tells us the vulnerable path is not an admin-only configuration surface but an everyday operation: rendering a control, restoring saved state, importing or processing list/web-part content, or a handler that accepts a serialized property. Whatever the specific endpoint, the security gate that should sit between "authenticated user input" and "deserialize" is either absent or insufficient.

Root cause: the missing type constraint

The root cause is not "SharePoint deserializes." Every large application deserializes constantly. The root cause is deserialization without a constraint on which types may be instantiated, applied to a stream a low-privilege user can influence. Two safeguards would each independently neuter the bug, and their absence is the vulnerability:

// Safeguard 1: a SerializationBinder allow-list (refuse unexpected types).
sealed class StrictBinder : SerializationBinder
{
    static readonly HashSet<string> Allowed = new() { "MyApp.SafeState" };
    public override Type BindToType(string assembly, string typeName)
    {
        if (!Allowed.Contains(typeName))
            throw new SerializationException($"blocked type: {typeName}");
        return Type.GetType($"{typeName}, {assembly}");
    }
}

// Safeguard 2: do not use a polymorphic binary formatter for external data
// at all. Use a schema-bound serializer (DataContract/System.Text.Json)
// that maps to a fixed, known type and cannot resolve arbitrary classes.

When a binder rejects every type except the handful the feature legitimately needs, a gadget-chain payload fails at the moment the deserializer tries to bind a SortedSet, an ObjectDataProvider, or whatever gadget the chain leads with. No binder means the stream gets the final say. The fix Microsoft shipped almost certainly amounts to constraining the type resolution on this path, validating the input before it reaches the formatter, or moving the path off a polymorphic formatter entirely.

Exploitation: from a serialized field to a worker-process shell

The exploitation flow for a CWE-502 RCE of this shape is mechanical once the entry point is known:

  1. Authenticate as a Site Member. Any contributor credential works, valid login, no privilege escalation needed. This is the PR:L in the CVSS vector.
  2. Build a gadget-chain payload. Use a known .NET gadget (for example an ObjectDataProvider-based chain) carrying the command to run. Public tooling generates these as a serialized, then Base64-encoded, blob.
  3. Deliver it on the vulnerable path. Place the blob in whatever field the endpoint feeds to the deserializer, a form value, a persisted property, a control-state field.
  4. Server reconstructs the object and runs the command. Deserialization executes the chain in the context of the SharePoint application pool identity.
POST /<vulnerable-endpoint> HTTP/1.1
Host: sharepoint.internal
Cookie: <valid Site Member session>
Content-Type: application/x-www-form-urlencoded

state=<base64 gadget-chain payload>
# server-side, conceptually
LosFormatter.Deserialize(payload)  ->  INSUFFICIENT type check
gadget chain reconstructs          ->  Process.Start("cmd /c ...")

-> code exec as SharePoint app-pool identity
-> recover machineKey -> forge __VIEWSTATE -> persistent farm-wide RCE

The impact does not stop at one command. The SharePoint application pool identity can read the farm configuration, and from initial code execution an attacker can recover the machineKey material, which is what made the 2025 SharePoint deserialization incidents so severe. With the validation and decryption keys, an attacker forges __VIEWSTATE payloads that every server in the farm accepts as legitimate, converting a one-shot RCE into durable, authenticated-looking persistence that survives the original entry point being closed. That is why rotating machine keys after patching is on the action list, not optional cleanup.

The CVSS vector confirms the severity shape: network attack vector (AV:N), low complexity (AC:L), low privileges (PR:L), no user interaction (UI:N), and high impact on confidentiality, integrity, and availability (C:H/I:H/A:H). The only thing standing between an attacker and the server is a single low-privilege login.

Affected versions

All fixes shipped within the May 12, 2026 monthly security updates. There are no separate backports to track; the relevant cumulative update for each edition contains the fix.

Timeline

Date Event
2026-05-12 Fix ships silently inside the May 2026 cumulative security updates (KB5002863 / KB5002870 / KB5002868).
2026-05-22 CVE-2026-45659 is published in NVD as CWE-502, CVSS 8.8.
2026-05-26 Microsoft releases the public MSRC advisory for CVE-2026-45659.
2026-05-27 Microsoft updates the advisory to note the CVE was "inadvertently omitted" from the May 2026 Security Updates and that already-patched customers need no action.

A note on the discovery methodology

Microsoft credits a private report and assesses the bug as "less likely to be exploited," with no public proof of concept at disclosure. What is more instructive than the discovery itself is the disclosure failure around it. The fix was correct and shipped on time. The communication broke: the CVE fell out of the release notes, so the patch went out unlabeled. For two weeks the world had the cure without the diagnosis.

That gap is the real lesson for defenders. Risk-based patching depends on accurate advisories, and when the advisory is silent, a perfectly rational deferral decision becomes a window of exposure. Discovering CWE-502 bugs is a matter of tracing untrusted input to polymorphic deserializers, which is exactly the kind of cross-function dataflow that rewards systematic code-level review over signature scanning. The harder organizational problem, knowing whether you actually applied a fix that nobody named, is one of inventory and continuous tracking, not of cleverness.


What we should learn from CVE-2026-45659

  1. Deserialization is code execution, not data parsing. Any time untrusted bytes reach a polymorphic .NET (or Java) deserializer, treat it as an arbitrary-code sink, not an I/O operation. The defense is a type allow-list or a schema-bound serializer, applied at the boundary, every time.
  2. Low privilege is not low risk. The whole bug hinges on PR:L. A "Site Member can do this" precondition sounds reassuring until you count how many Site Member accounts exist in a real tenant. Authorization-gated does not mean safe when the gate is set this low.
  3. An unnamed fix is an unapplied fix. A patch that is not clearly tied to a CVE in the release notes will be deferred by exactly the teams doing disciplined, risk-based patching. Reconcile what you shipped against what was actually fixed, do not rely on advisories being complete.
  4. One server behind is the whole farm exposed. Distributed products like SharePoint patch per node. Verify build numbers across every server, because the attacker only needs the one you missed.
  5. Plan for post-exploitation, not just the entry point. SharePoint deserialization leads to machine-key recovery and forged view state. Patching closes the door; rotating secrets evicts anyone who already walked through it. A vulnerability response that stops at "we patched" leaves persistence in place.

How Pragma Core addresses this class of problem

CVE-2026-45659 is the kind of issue classical scanners struggle with, because the bug is not a single bad line. It is an untrusted value that flows across request handling into a deserializer that was never constrained to safe types, on a path reachable by a low-privilege role. The danger is in the relationship between input source, trust assumption, and sink. Pragma Core is built to reason about exactly those relationships. Here is how it maps onto this specific bug.

SAST tuned for deserialization sinks, not just injection

Off-the-shelf SAST is tuned for taint flows that end in SQL or shell sinks and routinely misses CWE-502, because the dangerous operation is a Deserialize call that looks innocuous in isolation. Pragma Core's static analysis can be tuned to the pattern that defines this bug: untrusted input reaching LosFormatter, BinaryFormatter, ObjectStateFormatter, or DataContractSerializer without a SerializationBinder or type allow-list. A deserializer fed by request-controlled data with no type constraint is a high-confidence finding, flagged with the exact source-to-sink path, not a generic "review this" note.

Autonomous AI agents for attack-chain investigation

The autonomous agents reason over attack chains rather than stopping at a single flagged call. For a bug like this, the agent asks the question that surfaces it: which deserialization sinks are reachable from a handler that only requires an authenticated, low-privilege role, and is the type that gets reconstructed constrained anywhere along the way? That is precisely the chain, low-privilege entry to unconstrained deserializer to RCE, that a human researcher reconstructed here, posed systematically across the whole codebase.

Interactive call graphs with vulnerability overlay

Pragma Core auto-generates call graphs for any connected repo and overlays findings, so teams see where untrusted input propagates and where it crosses a trust boundary. The CVE-2026-45659 flaw lived in the relationship between the request handler and the deserialization method, not in either one alone. A call graph that traces the path from the authenticated endpoint to the Deserialize call, and shows that no validation node sits between them, makes a missing type constraint visible instead of hypothetical.

Continuous tracking of third-party packages and platform builds

Pragma Core tracks every third-party component and its version across all connected repos and surfaces vulnerable versions with CVSS scores and fixed upgrade paths. For an organization running SharePoint adjacent code or extensions, this is the answer to the question that this CVE made painful: which of our servers are below the fixed build, and were we actually on the May 2026 update? The moment a CVE like this lands in the catalog, the affected version range is matched against what you run, so an "inadvertently omitted" advisory does not become a silent exposure window.

Full SBOM per repository

Pragma Core generates a complete inventory of libraries, versions, and package URLs per repository, exportable as CycloneDX JSON. The slowest part of responding to CVE-2026-45659 was answering "which installations do we have and which build is each one on" fast enough to act. A current SBOM turns that from a fire drill into a query.

Human-guided AppSec investigations

The expert-led research module lets an AppSec operator drive deeper analysis, supported by the autonomous agents and the repository context already in the workspace, focused on what the team feels is fragile. Hunting for the next CWE-502 sink in a large .NET application is exactly that kind of investigation: targeted, source-assisted, and systematic, the same work the researcher who reported this bug did, applied as a routine instead of a lucky find.


Closing thoughts

CVE-2026-45659 is not, fundamentally, a bug about SharePoint. It is a bug about a deserializer that was allowed to reconstruct whatever type an authenticated user named, on a path that only asked for the lowest privilege in the product. The same pattern lives in any application that turns attacker-influenced bytes back into objects without constraining the result, in .NET, in Java, in any framework where serialization was designed for trusted data and later wired up to untrusted input. The patch closes this instance. The pattern remains wherever the next unconstrained deserializer sits.

The difference between treating this article as a curiosity and using it as an audit trigger comes down to AppSec maturity and code visibility: whether you can answer "where do we deserialize untrusted input, and is it type-constrained" for your own systems today, and whether you actually know which of your servers got the fix. Organizations that want to move from "we scan and report" to "we systematically investigate what is fragile" can reach Pragma Core at pragma-core.com for a demo.


Sources

Related posts
CVE-2026-74820: how an unsanitized ORDER BY clause turned ServiceNow's AI Platform into an unauthenticated database backdoor
Sep 18, 2026
CVE-2026-85978: how one path normalization mismatch turned Akana's admin console into unauthenticated remote code execution
Sep 9, 2026
CVE-2026-78174: how an unredacted session token in a diagnostic log turned a low-privileged WatchGuard Dimension admin into super admin
Sep 1, 2026

Start securing your codebase today

Connect your repositories and let AI agents handle continuous scanning, research, and triage.

Have questions? Get in touch →