XXEとJava

# XXEとJava

## XXEとは

XXE(XML External Entity)とは、XMLの外部参照機能を利用して、サーバ内部のファイル内容を取得(漏えい)したり、内部ネットワーク上のファイルにアクセスしたりする不正行為

XXEは、XMLを扱うアプリケーションで発現する可能性があるので、XML文書を取り扱う際には注意する必要がある。

## JavaとXML

JavaでXMLを扱うには、[javax.xml.parsers.DocumentBuilder](https://docs.oracle.com/javase/jp/6/api/javax/xml/parsers/DocumentBuilder.html)を利用が考えられる。

よって、以降は、[javax.xml.parsers.DocumentBuilder](https://docs.oracle.com/javase/jp/6/api/javax/xml/parsers/DocumentBuilder.html)を使って、再現させたり、対策を考えてみたりしよう。

## 結論

XXE基本編のリンクには、JavaVMのプロパティで制御する方法もあるので、手っ取り早くはそちらかもしれない。
その一方で、Javaプログラマであれば、リゾルバを自作して制御することも難しくはないはず。

## JavaでXXEを発現させる

下記のソースコードのプログラムは、第一引数にXMLファイル、第二引数に外部参照の動作モードを指定するようになっているので、既定の動作モードで、XMLファイル([C:\z\xmlxxe\in1.xml](https://qiita.com/tomoki0sanaki/items/015b6e8b807ed256a6e4#xml%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB--in1xml))を読んでみる。

こんな感じ。

![xxe-java-defo.png](https://qiita-image-store.s3.amazonaws.com/0/182822/68b83222-07c7-2d26-b929-d149f57b5f76.png)

Java の場合は、既定ではXXE攻撃を受けてしまうということになる。

## 外部参照のオブジェクトをNULLにする

次は[javax.xml.parsers.DocumentBuilder#setEntityResolver()メソッド](https://docs.oracle.com/javase/jp/6/api/javax/xml/parsers/DocumentBuilder.html#setEntityResolver(org.xml.sax.EntityResolver))で「NULL」を与えてみる。

こんな感じ。

![xxe-java-null.png](https://qiita-image-store.s3.amazonaws.com/0/182822/95e4abd0-c212-17a6-523c-630f14fa0c53.png)

.NET Framework とは異なり、**Nullは既定のリゾルバオブジェクトということになるので、Java の場合は、NULLでもXXE攻撃を受けてしまうということになる。**

## 外部参照のオブジェクトを自作する

次は「**外部参照機能を限定的に使いたいけど、XXEを防ぎたい**」。ていうか「**リゾルバの修正で外部参照機能を防ぎたい**」という場合は、リゾルバを自作すればいい。

つまり、[javax.xml.parsers.DocumentBuilder#setEntityResolver()メソッド](https://docs.oracle.com/javase/jp/6/api/javax/xml/parsers/DocumentBuilder.html#setEntityResolver(org.xml.sax.EntityResolver))の第一引数は「[org.xml.sax.EntityResolver](https://docs.oracle.com/javase/jp/6/api/org/xml/sax/EntityResolver.html)」インターフェイス型なので、それを継承したクラスを自作して、それを[javax.xml.parsers.DocumentBuilder#setEntityResolver()メソッド](https://docs.oracle.com/javase/jp/6/api/javax/xml/parsers/DocumentBuilder.html#setEntityResolver(org.xml.sax.EntityResolver))の第一引数に割り当てれば、外部参照を自由に制御する事ができる。

上書き必須のメソッドは、「[org.xml.sax.InputSource.InputSource resolveEntity(String publicId,String systemId)](https://docs.oracle.com/javase/jp/6/api/org/xml/sax/EntityResolver.html#resolveEntity(java.lang.String,%20java.lang.String))」メソッドだけ。

これの肝になる部分は、第二引数に外部参照のURLを示すString型が来るので、それに応じた「[org.xml.sax.InputSource.InputSource](https://docs.oracle.com/javase/jp/6/api/org/xml/sax/InputSource.html)」型を返せばいいだけ。

そして、この「[org.xml.sax.InputSource.InputSource](https://docs.oracle.com/javase/jp/6/api/org/xml/sax/InputSource.html)」型からの適切な「[java.io.InputStream](https://docs.oracle.com/javase/jp/6/api/java/io/InputStream.html)」クラス、または「[java.io.Reader](https://docs.oracle.com/javase/jp/6/api/java/io/Reader.html)」クラスを返すようにしてあげればよい。

例えば、認証が必要だったり、特定のURIだけ許可したり、というような個別カスタマイズが可能。

ソースコードの「exEntityResolver.java」の「exEntityResolverクラス」は、全てのURIを示すsystemIdに対して、最終的に空の[java.io.ByteArrayInputStream](https://docs.oracle.com/javase/jp/6/api/java/io/ByteArrayInputStream.html)を返すだけの「exInputSource」クラスを返すことにした。

こんな感じになる。

![xxe-java-cust.png](https://qiita-image-store.s3.amazonaws.com/0/182822/6aefb861-4bdb-ee98-73ab-603050c3029c.png)


# ソースコード(XXEtest.java)

```java:XXEtest.java
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import java.io.File;
import org.xml.sax.EntityResolver;

public class XXEtest{
 public static void main(String args[]){
  System.out.println("java.exe test <<inFile>> <<Resolver>>");
  if(0 < args.length){
   Boolean IsResolve = false;
   exEntityResolver myExEntityResolver = null;
   if(1 < args.length){
    if(args[1].equals("null") == true){
     IsResolve = true;
    }else{
     myExEntityResolver = new exEntityResolver();
    }
   }
   try{
    DocumentBuilderFactory tempDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder tempDocumentBuilder = tempDocumentBuilderFactory.newDocumentBuilder();
    if(IsResolve == true){
     tempDocumentBuilder.setEntityResolver(null);
    }else if(myExEntityResolver != null){
     tempDocumentBuilder.setEntityResolver(myExEntityResolver);
    }
    Document tempDocument = tempDocumentBuilder.parse(new File(args[0]));
    System.out.println("getTextContent  : " + tempDocument.getTextContent());
    System.out.print("getXmlStandalone: ");
    tempDocument.setXmlStandalone(true);
    System.out.println(tempDocument.getXmlStandalone());
    System.out.println("========Detail=================");
    PrintNode(tempDocument.getChildNodes(), "");
   }catch(Exception e){
    e.printStackTrace();
   }
  }
 }
 static void PrintNode(NodeList nodeList, String spacer){
  Node node = null;
  for(int i=0; i< nodeList.getLength(); i++){
   node = nodeList.item(i);
   String typeStr = "unknown";
   switch(node.getNodeType()){
    case Node.ELEMENT_NODE:
     typeStr = "ELEMENT_NODE";
     break;
    case Node.ATTRIBUTE_NODE:
     typeStr = "ATTRIBUTE_NODE";
     break;
    case Node.TEXT_NODE:
     typeStr = "TEXT_NODE";
     break;
    case Node.CDATA_SECTION_NODE:
     typeStr = "CDATA_SECTION_NODE";
     break;
    case Node.ENTITY_REFERENCE_NODE:
     typeStr = "ENTITY_REFERENCE_NODE";
     break;
    case Node.ENTITY_NODE:
     typeStr = "ENTITY_NODE";
     break;
    case Node.PROCESSING_INSTRUCTION_NODE:
     typeStr = "PROCESSING_INSTRUCTION_NODE";
     break;
    case Node.COMMENT_NODE:
     typeStr = "COMMENT_NODE";
     break;
    case Node.DOCUMENT_NODE:
     typeStr = "DOCUMENT_NODE";
     break;
    case Node.DOCUMENT_TYPE_NODE:
     typeStr = "DOCUMENT_TYPE_NODE";
     break;
    case Node.NOTATION_NODE:
     typeStr = "NOTATION_NODE";
     break;
   }
   System.out.println(spacer + "name =" + node.getNodeName() + ", type=" + typeStr);
   System.out.println(spacer + "value=" + node.getNodeValue());
   System.out.println(spacer + "getTextContent=" + node.getTextContent());
   if(node.hasChildNodes() == true){
    PrintNode(node.getChildNodes(), spacer + " ");
   }
  }
 }
}
```

# ソースコード(exEntityResolver.java)

空の ByteArrayInputStream を返すだけの InputSource クラスを返すだけのリゾルバ

```java:exEntityResolver.java
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.IOException;

public class exEntityResolver implements EntityResolver{
 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException{
  System.out.println("publicId: " + publicId);
  System.out.println("systemId: " + systemId);
  return (InputSource)new exInputSource();
 }
}
```

# ソースコード(exInputSource.java)

空の ByteArrayInputStream を返すだけの InputSource クラス。

```java:exInputSource.java
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.Reader;
import java.io.InputStreamReader;

public class exInputSource extends InputSource{
 ByteArrayInputStream myStream;
 public exInputSource(){
  byte[] hako = new byte[0];
  this.myStream = new ByteArrayInputStream(hako);
 }
 public InputStream getByteStream(){
  return (InputStream)this.myStream;
 }
 public Reader getCharacterStream(){
  return (Reader)new InputStreamReader(this.myStream);
 }
}
```

## 戻る

[XXE基本編へ戻る](https://qiita.com/tomoki0sanaki/items/015b6e8b807ed256a6e4)