XML injection

XML injection in C#

Description

XML injection occurs when untrusted data is inserted where it is interpreted as markup rather than text. XmlWriter.WriteRaw and WriteRawAsync neither escape the input nor check whether it is valid XML. Assigning to an XmlNode.InnerXml property parses the string and replaces child nodes. Attackers can therefore add, replace or rearrange nodes and attributes in generated XML.

This differs from XXE, which accesses files or networks through external entities, and XPath injection, which changes query syntax.

Potential impact

  • The structure and meaning of generated XML can change.
  • Downstream parsers, signature/validation logic or business logic may trust attacker-created nodes or attributes.
  • Later transformations into other formats or commands may introduce further security problems.

Remediation

Keep structure and element/attribute names under developer control. Write untrusted values through format-aware APIs.

  • Use XmlWriter.WriteString, the value argument of WriteElementString, or the value argument of WriteAttributeString for text.
  • Set DOM text with InnerText instead of InnerXml.
  • If raw output cannot be removed, escape the data for its context at the final insertion point, only where it is used as text. SecurityElement.Escape is a limited alternative for text inside fixed XML structure; prefer format-aware APIs.
  • If complete XML fragments must be selectable, use a small allow-list of source-code literals and control its contents and modification permissions.

XmlConvert.EncodeName and EncodeLocalName produce XML names. They are not text encoders and do not make arbitrary XML fragments safe. Restrict dynamic element or attribute names to an allow-list based on the application's schema and business rules; name encoding alone is not authorization.

Examples

Before

csharp
using System.Xml;

void WriteProfile(XmlWriter writer, XmlElement target, string input)
{
    writer.WriteRaw("<displayName>" + input + "</displayName>");
    target.InnerXml = input;
}

In WriteRaw, an input containing </displayName> can close the fixed element and insert other nodes. InnerXml parses input as child-node markup and throws XmlException for malformed XML.

After

csharp
using System.Xml;

void WriteProfile(XmlWriter writer, XmlElement target, string input)
{
    writer.WriteStartElement("profile");
    writer.WriteAttributeString("label", input);
    writer.WriteElementString("displayName", input);
    writer.WriteEndElement();
    target.InnerText = input;
}

Names are fixed, and each API encodes input as text in the appropriate position.

When raw output cannot be removed

Escape at the final insertion point only when the value is clearly used as text inside fixed structure.

csharp
using System.Security;
using System.Xml;

void WriteProfile(XmlWriter writer, string input)
{
    writer.WriteRaw(
        "<displayName>" + SecurityElement.Escape(input) + "</displayName>"
    );
}

When selecting a complete fragment, accept only exact matches to an unchanged local literal allow-list.

csharp
using System.Collections.Generic;
using System.Xml;

void WriteSection(XmlWriter writer, string fragment)
{
    var allowedFragments = new HashSet<string>
    {
        "<summary/>",
        "<details/>"
    };

    if (!allowedFragments.Contains(fragment))
    {
        return;
    }

    writer.WriteRaw(fragment);
}

Apply schema and business validation for XML names, XXE protection and XPath injection prevention separately.

Related standards

References