XML 인젝션

C# XML 인젝션

설명

XML 인젝션은 신뢰할 수 없는 데이터가 텍스트가 아니라 XML 마크업으로 해석되는 위치에 삽입될 때 발생합니다. XmlWriter.WriteRaw와 WriteRawAsync는 전달받은 데이터를 이스케이프하거나 XML로서 유효한지 검사하지 않습니다. XmlNode.InnerXml 계열 속성에 값을 대입하면 해당 문자열을 파싱하여 기존 자식 노드를 교체합니다. 따라서 공격자는 생성된 XML의 노드와 속성을 추가·교체·재배치할 수 있습니다.

XML 인젝션은 외부 엔터티로 파일이나 네트워크 자원을 읽게 하는 XXE, XPath 질의를 변경하는 XPath 인젝션과 서로 다른 취약점입니다.

잠재적 영향

  • 생성된 XML 문서의 구조와 의미가 변경될 수 있습니다.
  • 다운스트림 파서, 서명·검증 로직 또는 비즈니스 로직이 공격자가 만든 노드와 속성을 신뢰할 수 있습니다.
  • 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 인젝션 방지는 별도로 적용하세요.

관련 기준

참조