Feb 10, 2026·6 min read·45 visits
The .NET `DataSet` class trusts inline XML schemas too much. Attackers can tell it to load a `XamlReader` disguised as a database column, leading to immediate RCE. This affects SharePoint, Visual Studio, and any .NET app using `ReadXml` on untrusted input.
A critical Remote Code Execution (RCE) vulnerability in the .NET Framework, SharePoint, and Visual Studio caused by unsafe handling of XML input in the `DataSet` and `DataTable` classes. By exploiting the `ReadXml` method, attackers can define arbitrary types via an inline schema, forcing the application to deserialize malicious gadgets that execute code in the context of the host process.
If you've been in the .NET world for a while, you remember DataSet and DataTable. They are the relics of a bygone era—bulky, memory-hungry objects designed to represent database tables in memory. They were the darlings of the Enterprise application stack in the mid-2000s. But like many things from that era (looking at you, SOAP), they have a fatal attraction to XML.
Here is the problem: DataSet doesn't just hold data; it holds metadata. It wants to know the schema of your data so it can enforce types. To make life easy for developers, Microsoft allowed DataSet to read this schema directly from the XML input via the ReadXml() method.
CVE-2020-1147 is the realization that if you let an attacker define the schema of the data they are sending you, they can make your application instantiate objects you never intended to exist. It's the deserialization vulnerability that keeps on giving, famously plaguing SharePoint and leading to the 'ToolShell' discoveries years later.
The root cause is a classic failure of trust boundaries. When DataSet.ReadXml() parses input, it looks for an inline XML schema (defined by xs:schema). Inside that schema, standard XML types are expected. However, Microsoft added a special attribute called msdata:DataType. This attribute tells the .NET runtime: "Hey, this column isn't just a string; it's actually a complex .NET object of this specific type."
Under the hood, DataSet sees this attribute and thinks, "Okay, the user says this column contains objects of type System.Windows.Markup.XamlReader. I should prepare the XmlSerializer to handle that."
This is where the logic falls apart. The application essentially takes a type name provided by the attacker and hands it over to the serializer. Unlike BinaryFormatter, which is dangerous by default, XmlSerializer is usually safe because it requires known types. But DataSet bypasses this safety by dynamically telling the serializer what types to expect based on the attacker's input. It creates a bridge between safe XML parsing and unsafe object instantiation.
> [!NOTE] > This isn't just about crashing the app. By specifying a gadget class, we can trick the serializer into setting properties on that class, triggering a chain reaction that ends in code execution.
So, we can instantiate a type. Great. But which type? We need a "gadget"—a class that does something dangerous when its properties are set or when it is initialized. Enter the ExpandedWrapper and XamlReader combo.
This specific attack chain is a work of art. We can't just instantiate Process.Start directly because XmlSerializer is picky. We need a wrapper. The gadget chain looks like this:
ExpandedWrapper: This is an internal class in System.Data.Services. It's a generic wrapper that allows us to bundle two items together. It's useful here because it's serializable and helps us satisfy type requirements.XamlReader: This is the nuclear option. The XamlReader class in WPF has a Parse() method. If you can pass a string of XAML to this method, it will compile and execute it. It's basically eval() for .NET UI markup.The vulnerability allows us to define a column in our fake DataTable with the type ExpandedWrapper<XamlReader, ObjectDataProvider>. When the serializer processes the row data, it hydrates this wrapper. Inside the wrapper, we place a payload that the XamlReader parses.
Here is a visualization of the flow:
Let's build the bomb. The payload consists of two parts: the Schema (the setup) and the DiffGram (the trigger).
First, we define the schema. We claim we are sending a DataSet named somedataset. Inside, we define a table Exp_Table with a column named pwn. The critical part is the msdata:DataType attribute, which points to our gadget chain:
<xs:element name="pwn"
msdata:DataType="System.Data.Services.Internal.ExpandedWrapper`2[[System.Windows.Markup.XamlReader, ...], [System.Windows.Data.ObjectDataProvider, ...]], ..."
type="xs:anyType" minOccurs="0"/>Next, we provide the data in the diffgr:diffgram block. This is where the actual serialized object lives. We wrap our malicious XAML inside a CDATA block effectively hidden inside the serialized stream intended for the XamlReader.
The XAML payload typically uses ObjectDataProvider to invoke System.Diagnostics.Process.Start. The final XML looks innocent enough to a firewall—just a bunch of schema definitions and data—but to the .NET runtime, it's a command execution instruction.
When DataSet.ReadXml(payload) is called:
ExpandedWrapper is instantiated.XamlReader wakes up, parses the command string, and pops a shell.Why was this such a big deal? Because DataSet is everywhere. It is the bedrock of legacy enterprise .NET applications.
SharePoint was the most high-profile victim. SharePoint relies heavily on XML and DataSet for internal communication and Web Parts. An attacker could send a crafted POST request to a vulnerable SharePoint endpoint and execute code as the service account (usually Network Service or a specific farm account). This typically means full compromise of the SharePoint farm.
The CVSS score of 7.8 (High) is slightly misleading; in many internal network scenarios or public-facing web apps using ReadXml on user uploads, this is a functional 9.8 (Critical). The EPSS score sits comfortably above 93%, meaning if you have this exposed, bots are likely already trying to exploit it.
Furthermore, this isn't just a "one and done" bug. As seen with the "ToolShell" research in 2025, incomplete patches and new gadget variations keep breathing life into this attack vector. Attackers leverage this to install webshells, dump databases, or pivot into the domain controller.
Microsoft's patch for CVE-2020-1147 introduced a type allowlist mechanism for DataSet and DataTable. Essentially, they stopped trusting msdata:DataType implicitly. If the type isn't on the list or clearly safe, the deserializer throws a tantrum and refuses to proceed.
However, patches are only as good as their application.
Developers must:
ReadXml(), stop. If you can't stop, use the overload that takes XmlReadMode. Avoid XmlReadMode.Auto or XmlReadMode.DiffGram on untrusted input.XmlReader that forbids DTD processing (DtdProcessing.Prohibit) and validate schemas strictly before passing them to DataSet.For Security Teams:
Write detection rules for the string System.Data.Services.Internal.ExpandedWrapper appearing in HTTP bodies. Normal traffic rarely, if ever, sends this specific internal type across the wire. It is a high-fidelity indicator of an attempted exploit.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
.NET Framework Microsoft | 2.0 - 4.8 | July 2020 Security Rollup |
SharePoint Server 2019 Microsoft | < July 2020 Update | July 2020 PU |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-502 (Deserialization of Untrusted Data) |
| CVSS v3.1 | 7.8 (High) |
| Attack Vector | Network |
| EPSS Score | 0.9343 (93.43%) |
| Exploit Status | Active / Weaponized |
| Key Gadget | ExpandedWrapper + XamlReader |
A high-severity access control vulnerability in ToolHive CLI before v0.30.1 and ToolHive Studio before v0.38.0 allows local containerized MCP servers to bypass network isolation. This enables malicious workloads to establish TCP/IP connections to administrative and control plane endpoints exposed on the host loopback interface.
AnyCable is a real-time communication server. Prior to version 1.6.15, its Pusher-compatible REST API suffered from an authentication bypass vulnerability because it failed to verify that the request body matched the signature-validated body_md5 parameter. This allows attackers to perform replay attacks with modified body contents.
A denial-of-service vulnerability exists in AnyIO prior to version 4.14.2. Standard error streams of process-pool workers are connected to an operating system pipe that is never drained by the parent process. This allows a worker to fill the pipe buffer and deadlock indefinitely.
CVE-2026-63349 is a critical privilege-dropping bypass vulnerability in the AnyIO asynchronous framework (versions 4.14.0 and 4.14.1) on POSIX platforms. Due to a variable assignment typo, supplementary groups specified by the developer are not correctly propagated to the execution backend, resulting in subprocesses retaining the parent process's elevated supplementary group permissions.
CVE-2026-63406 is an information disclosure vulnerability in AnyCable-go prior to version 1.6.15. The built-in telemetry client is enabled by default with a hardcoded public authentication token ('secret'). This client digests highly sensitive configuration parameters and command-line arguments, including JWT secrets and RPC secrets, into a stable SHA-256 fingerprint. This fingerprint is sent over public networks, exposing those administrative secrets to offline dictionary and brute-force attacks if intercepted.
CVE-2026-84992 is a Cross-Site Scripting (XSS) vulnerability affecting md-editor-v3 before version 6.5.4. It occurs because the fenced-code block language parser directly interpolates unescaped language metadata into unquoted HTML attributes inside the custom rendering callback. This bypasses the built-in XSSPlugin which runs during the parsing phase, before rendering.