XXEと.NET Framework
XXEとは
XXE(XML External Entity)とは、XMLの外部参照機能を利用して、サーバ内部のファイル内容を取得(漏えい)したり、内部ネットワーク上のファイルにアクセスしたりする不正行為
XXEは、XMLを扱うアプリケーションで発現する可能性があるので、XML文書を取り扱う際には注意する必要がある。
.NET FrameworkとXML
.NET FrameworkでXMLを扱うには、Sytem.Xml名前空間を利用が考えられる。
よって、以降は、System.Xml.XmlDocumentクラスを使って、再現させたり、対策を考えてみたりしよう。
結論
手っ取り早くXXEを予防したければ、System.Xml.XmlDocument#XmlResolver プロパティを「NULL」にする。
.NET FrameworkでXXEを発現させる
下記のソースコードのプログラムは、第一引数にXMLファイル、第二引数に外部参照の動作モードを指定するようになっているので、既定の動作モードで、XMLファイル(C:\z\xmlxxe\in1.xml)を読んでみる。
こんな感じ。
.NET Framework の場合は、既定ではXXE攻撃を受けてしまうということになる。
外部参照のオブジェクトをNULLにする
次はSystem.Xml.XmlDocument#XmlResolver プロパティを「NULL」にしてみる
つまり、手っ取り早くXXEを予防したければ、System.Xml.XmlDocument#XmlResolver プロパティを「NULL」にする。
外部参照のオブジェクトを自作する
次は「外部参照機能を限定的に使いたいけど、XXEを防ぎたい」という場合は、リゾルバを自作すればいい。
つまり、System.Xml.XmlDocument#XmlResolver プロパティは「System.Xml.XmlResolver」型なので、それを継承したクラスを自作して、それをSystem.Xml.XmlDocument#XmlResolver プロパティに割り当てれば、外部参照を自由に制御する事ができる。
上書き必須のメソッドは、「object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)」メソッドだけ。
これの肝になる部分は、第一引数に外部参照のURLを示すUri型が来るので、それに応じた「System.IO.Stream」型を返せばいいだけ。
例えば、認証が必要だったり、特定のURIだけ許可したり、というような個別カスタマイズが可能。
ソースコードの「exXmlResolver.cs」の「exXmlResolverクラス」は、全てのSystem.Uriに対して、空のSystem.IO.MemoryStremを返すだけとした。
これは、実質的にSystem.Xml.XmlDocument#XmlResolver プロパティを「NULL」にした時と同じような挙動になると思う。
こんな感じになる。
外部参照のオブジェクトを自作するための子クラス
.NET Frameworkの場合、System.Xml.XmlResolverを直接継承しなくても、以下のクラスがあるので、自分の仕様に適合した既存クラスを使ってもよいだろう。
- .NET Framework System.Xml.XmlSecureResolver
- .NET Framework System.Xml.XmlUrlResolver
- .NET Framework System.Xml.XmlXapResolver
- .NET Framework System.Xml.XmlPreloadedResolver
ソースコード(Program.cs)
using System;
using System.IO;
using System.Xml;
namespace XMLandXXEtest{
class Program{
static void Main(string[] args){
Boolean Isresolve = true;
exXmlResolver myExXmlResolver = null;
if (1 < args.Length) {
if (args[1].ToLower() == "null"){
Isresolve = false;
}else {
myExXmlResolver = new exXmlResolver();
}
}
if (0 == args.Length) {
Console.WriteLine("usage: XMLandXXEtest.exe <<xmlFile>> [NotResolve]");
}else{
StreamReader sr = new StreamReader(new FileStream(args[0], FileMode.Open, FileAccess.Read));
String str = sr.ReadToEnd();
sr.Close();
XmlDocument tempXmlDocument = new XmlDocument();
if (Isresolve == false){
tempXmlDocument.XmlResolver = null;
}else if (myExXmlResolver != null){
tempXmlDocument.XmlResolver = myExXmlResolver;
}
tempXmlDocument.LoadXml(str);
Console.WriteLine("after XML Resolve is (OuterXml)");
Console.WriteLine(tempXmlDocument.OuterXml);
Console.WriteLine("----------------------------------");
Console.WriteLine("after XML Resolve is (InnerXml)");
Console.WriteLine(tempXmlDocument.InnerXml);
Console.WriteLine("----------------------------------");
Console.WriteLine("after XML Resolve is (InnerText)");
Console.WriteLine(tempXmlDocument.InnerText);
Console.WriteLine("----------Detail------------------");
foreach (XmlNode x in tempXmlDocument.ChildNodes){
WroteXMLnode(x, "");
}
}
}
static void WroteXMLnode(XmlNode node, String spaceStr) {
Console.WriteLine(spaceStr + "name=" + node.Name + ", type=" + node.NodeType.ToString());
Console.WriteLine(spaceStr + "valT=" + node.InnerText);
Console.WriteLine(spaceStr + "valX=" + node.InnerXml);
if (node.HasChildNodes == true){
foreach (XmlNode x in node.ChildNodes){
WroteXMLnode(x, spaceStr + " ");
}
}
}
}
}
ソースコード(exXmlResolver.cs)
空の MemoryStream を返すだけのリゾルバ
using System;
using System.IO;
using System.Xml;
namespace XMLandXXEtest{
public class exXmlResolver : XmlResolver{
/// <summary>
/// コンストラクタ
/// </summary>
public exXmlResolver() { }
/// <summary>
/// 唯一、実装する必要のあるメソッド
/// </summary>
/// <param name="absoluteUri"></param>
/// <param name="role"></param>
/// <param name="ofObjectToReturn"></param>
/// <returns></returns>
public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn){
if (absoluteUri == null){
Console.WriteLine("Uri: null");
}else {
Console.WriteLine("Uri: " + absoluteUri.ToString());
}
if (role == null) {
Console.WriteLine("Role: null");
}else if (role == String.Empty){
Console.WriteLine("Role: ");
}else{
Console.WriteLine("Role: " + role);
}
if (ofObjectToReturn == null){
Console.WriteLine("Type: null");
}else{
Console.WriteLine("Type: " + ofObjectToReturn.ToString());
}
MemoryStream mem = new MemoryStream();
return mem;
}
/// <summary>
///
/// </summary>
public override System.Net.ICredentials Credentials{
set { }
}
}
}