XMLインジェクション

C#のXMLインジェクション

説明

信頼できないデータがテキストではなくマークアップとして解釈される位置に入ると、XMLインジェクションが発生します。XmlWriter.WriteRawとWriteRawAsyncは、入力のエスケープもXMLとしての検証も行いません。XmlNode.InnerXmlへの代入では文字列を解析し、既存の子ノードを置き換えます。そのため、生成するXMLのノードや属性を追加・置換・再配置される可能性があります。

外部エンティティでファイルやネットワークにアクセスするXXEや、クエリの構文を変えるXPathインジェクションとは別の問題です。

想定される影響

  • 生成するXMLの構造や意味が変わる可能性があります。
  • 後続のパーサー、署名・検証処理、業務処理が攻撃者のノードや属性を信頼する可能性があります。
  • 別の形式やコマンドへ変換する後続処理で、さらに問題が起こる可能性があります。

対処方法

XMLの構造と要素・属性の名前を開発者が管理し、信頼できない値は形式を認識するAPIで書き込んでください。

  • テキストにはXmlWriter.WriteString、WriteElementStringの値引数、WriteAttributeStringの値引数を使ってください。
  • DOMのテキストはInnerXmlではなくInnerTextに設定してください。
  • 生の出力を廃止できない場合は、データがテキストとしてだけ使われる最終挿入位置で、文脈に合わせてエスケープしてください。SecurityElement.Escapeは固定のXML構造内のテキストに限った代替手段です。形式を認識するAPIを優先してください。
  • 完全なXML断片を選ばせる必要があれば、コードのリテラルで定義した小さな許可リストを使い、その内容と変更権限を管理してください。

XmlConvert.EncodeNameとEncodeLocalNameはXMLの名前を作るAPIです。テキストのエンコーダーでも、任意のXML断片を安全にするAPIでもありません。動的な要素名や属性名は、名前のエンコードだけで許可せず、スキーマと業務規則に基づく許可リストで制限してください。

例

変更前

csharp
using System.Xml;

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

WriteRawでは、input内の</displayName>などで固定の要素を閉じ、別のノードを挿入できます。InnerXmlは子ノードのマークアップとして入力を解析し、整形式でないXMLにはXmlExceptionを発生させます。

変更後

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;
}

名前は固定されており、各APIがinputを該当位置のテキストとしてエンコードします。

生の出力を廃止できない場合

固定の構造内でテキストとしてだけ使うことが明確な場合に限り、最終挿入位置でエスケープしてください。

csharp
using System.Security;
using System.Xml;

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

完全なXML断片を選ぶ場合は、変更されないローカルなリテラル一覧と完全に一致する値だけを許可してください。

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);
}

XML名のスキーマ・業務規則の検証、XXE対策、XPathインジェクション対策は別途適用してください。

関連基準

参考資料