2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

XXEとJava

Last updated at Posted at 2019-04-17

XXEとJava

XXEとは

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

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

JavaとXML

JavaでXMLを扱うには、javax.xml.parsers.DocumentBuilderを利用が考えられる。

よって、以降は、javax.xml.parsers.DocumentBuilderを使って、再現させたり、対策を考えてみたりしよう。

結論

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

JavaでXXEを発現させる

下記のソースコードのプログラムは、第一引数にXMLファイル、第二引数に外部参照の動作モードを指定するようになっているので、既定の動作モードで、XMLファイル(C:\z\xmlxxe\in1.xml)を読んでみる。

こんな感じ。

xxe-java-defo.png

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

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

次はjavax.xml.parsers.DocumentBuilder#setEntityResolver()メソッドで「NULL」を与えてみる。

こんな感じ。

xxe-java-null.png

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

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

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

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

上書き必須のメソッドは、「org.xml.sax.InputSource.InputSource resolveEntity(String publicId,String systemId)」メソッドだけ。

これの肝になる部分は、第二引数に外部参照のURLを示すString型が来るので、それに応じた「org.xml.sax.InputSource.InputSource」型を返せばいいだけ。

そして、この「org.xml.sax.InputSource.InputSource」型からの適切な「java.io.InputStream」クラス、または「java.io.Reader」クラスを返すようにしてあげればよい。

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

ソースコードの「exEntityResolver.java」の「exEntityResolverクラス」は、全てのURIを示すsystemIdに対して、最終的に空のjava.io.ByteArrayInputStreamを返すだけの「exInputSource」クラスを返すことにした。

こんな感じになる。

xxe-java-cust.png

ソースコード(XXEtest.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 クラスを返すだけのリゾルバ

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 クラス。

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基本編へ戻る

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?