How .NET Deserialization Turns Attacker Data Into Code Execution
🛡️ Security Intermediate 5 min read

How .NET Deserialization Turns Attacker Data Into Code Execution

Deserialization is safe until the format can name types and call methods. A walk through how attacker-controlled bytes become a gadget chain and then running code, and how to design so they cannot.

Published: September 26, 2026 • Updated: September 26, 2026
deserializationremote code executiongadget chaindotnetapplication security

Serialization is how a program turns a live object, with its fields and structure, into a flat stream of bytes it can store or send. DeserializationDeserialization🛡️The process of converting stored or transmitted data back into an object. Insecure deserialization can allow attackers to execute code by manipulating serialized data. is the reverse: taking those bytes and rebuilding the object in memory. It is one of the most ordinary operations in modern software, and it is also one of the most dangerous when the bytes come from someone you do not trust. The SharePoint remote code execution flaw CVE-2026-65660, exploited in September 2026 after Microsoft quietly reclassified it from a spoofing bug, ends in exactly this kind of deserialization. Understanding why that final step is code execution rather than just data corruption is the point of this article.

The Core Problem: Data That Describes Behavior

A safe deserializer reads bytes and populates simple values: a number here, a string there. The trouble begins when the serialized format can describe not just data but which types to create and which of their methods to call. Formats and libraries in the .NET, Java, Python, and PHP ecosystems all have this property to some degree. When the byte stream can say "construct an object of type X and set property Y," an attacker who controls the stream can ask for types that were never meant to appear in that context.

The attacker is not injecting new code in the sense of shipping a compiled binary. They are assembling a chain of objects out of classes that already exist in the application and its libraries. Each class in the chain does something innocuous by itself. Strung together, their constructors, property setters, and callbacks perform an action the developer never intended, such as launching a process or loading an assembly. This assembled sequence is called a gadget chainGadget Chain🛡️A sequence of objects assembled from classes already present in an application and its libraries, arranged so that reconstructing them during deserialization performs an unintended action such as running a command or loading code. Finding one is the core of deserialization exploitation., and finding one is the craft of deserialization exploitation.

From Trusted Bytes to a Running Payload

Consider how CVE-2026-65660 reaches this point. SharePoint has an allowlist, called SafeControlsSafeControls🛡️A SharePoint allowlist that specifies which server-side controls a page is permitted to load, meant to stop arbitrary .NET types from being instantiated through web-part markup. Bypassing it, as CVE-2026-65660 does, lets an attacker load a control the platform never validated., that is supposed to decide which server-side controls a page may load. The vulnerabilityVulnerability🛡️A weakness in software, hardware, or processes that can be exploited by attackers to gain unauthorized access or cause harm. lets an attacker smuggle a control past that check by exploiting sloppy quote handling when markup is reconstructed, so the type that gets loaded is not the type that was validated. Once an attacker-chosen .NET type can be instantiated, the exploitExploit🛡️Code or technique that takes advantage of a vulnerability to cause unintended behavior, such as gaining unauthorized access. uses deserialization primitives such as XamlServices.Parse and LosFormatter to walk a gadget chain into code execution. The allowlist bypass is the door; deserialization is what walks through it.

This pattern recurs across platforms. The specific classes differ, but the shape is constant: untrusted input reaches a deserializer that is willing to instantiate arbitrary types, and a gadget chain converts that willingness into execution. It is closely related to other input-to-execution bugs. If you have read how server-side template injection becomes remote code execution, you have seen the same arc through a different mechanism: attacker input crosses from data into the engine's control flow.

Why the Payload Often Never Touches Disk

A traditional web attack drops a file, a webshell script, into a directory the web server will execute. Deserialization payloads frequently skip that step entirely. Because the gadget chain runs inside the application's own process, it can build and load code directly in memory. The observed CVE-2026-65660 payloads did exactly this: a first stage disabled a .NET type-check safeguard, and a second stage carried an encrypted loader that decrypted an assembly and called Assembly.Load on the bytes, all without writing the final payload to disk.

For defenders, this changes where the evidence lives. A file-integrity scan or an antivirus signature that watches the filesystem may see nothing. The artifacts are in process memory, in the timing and shape of the requests, and in the logs. Detecting this class of attack is a distinct skill, which is why we treat it separately in how to hunt for in-memory webshells that leave no file on disk.

What a Correct Defense Looks Like

The durable fix is to stop deserializing untrusted data with a mechanism that can instantiate arbitrary types. Concretely, that means several layers:

  • **Prefer data-only formats.** Parsing JSON or a similar format into known, fixed types gives the attacker no way to name arbitrary classes. The parser builds the shapes you defined and nothing else.
  • **Constrain the deserializer.** Where a rich serializer is unavoidable, restrict the set of types it will construct through an allowlist, and keep type-check safeguards enabled. The CVE-2026-65660 payload's first act was to disable one such safeguard, which tells you how much those safeguards matter.
  • **Validate before you deserialize, on the right data.** SharePoint's bug was precisely that the check ran against different bytes than the ones that were parsed. A validation step is only worth something if it inspects the exact input the dangerous operation will consume.
  • **Reduce blast radiusBlast Radius🛡️The full set of systems, data, and access an attacker can reach after compromising a given asset. Ranking assets by blast radius rather than by how exposed they are pushes high-reach systems like a firewall management console to the top of the priority list..** The SharePoint worker process ran the payload with its own privileges. Running services with least privilegeLeast Privilege🛡️A design principle that grants each user, role, or process only the permissions it needs to do its job and no more. Applied to session roles in a remote-access tool, least privilege means a role that never moves files should not carry file-transfer permission. and isolating them from the rest of the network limits what a successful chain can reach.

The Takeaway

Deserialization is not exotic. It runs in almost every application that accepts structured input, and most of the time it is perfectly safe. It becomes remote code execution when the format is expressive enough to name types and call methods, the input is attacker-controlled, and a gadget chain exists to weaponize that expressiveness. CVE-2026-65660 is a clean example: an allowlist bypass supplied the untrusted type, and deserialization did the rest. Whenever you accept serialized objects from outside a trust boundary, assume an attacker can and will try to make the deserializer build something you did not plan for, and design so that it simply cannot.