【入門編】 XML外部エンティティ (XXE) 攻撃の仕組みとパーサー設定 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

こんにちは!日々の開発やインフラの管理、本当にお疲れ様です。

セキュリティの勉強を始めると、「共通鍵暗号」や「公開鍵暗号」、そして「XXE」といった、何やら難しそうな専門用語がたくさん出てきて圧倒されてしまいますよね。「自分にはまだ早いかも……」と不安になるかもしれませんが、安心してください!一歩ずつ、身近な例に置き換えながら優しく紐解いていけば、誰でも確実に理解できるようになります。

今回は、セキュリティの世界で新人さんが最初に直面しやすい大きな壁の一つである「XML外部エンティティ(XXE)攻撃」について、その仕組みと、今日から使える具体的な対策を一緒に見ていきましょう。

—

1. 身近な例えで理解する「XML」と「XXE攻撃」

まずは、そもそも「XML」や「XXE」って何をするものなのか、私たちの日常にある「郵便物とポスト」に例えて考えてみましょう。

XMLは「便利なオーダーメイドの封筒」

システムとシステムがデータをやり取りするとき、ただの文字の羅列を送るよりも、「これは名前です」「こっちが住所です」とタグ(目印)がついている方が、受け取る側も整理しやすくて便利ですよね。この、タグを使ってデータを綺麗に整理して伝えるための仕組みが「XML」です。

便利な機能の裏に潜む「魔の小包」

XMLには、「外部エンティティ」という、ちょっと変わった機能があります。これは、XMLの書類の中に「外にある別のファイルを読み込んで、ここに内容を差し込んでおいてね」と指示できる機能です。

例えば、会社のサーバーの中にある「今月の売上表(大切な秘密のファイル)」を指し示す呪文をXMLの中にこっそり書いておくと、XMLを読み込む機械(パーサーと呼びます)が親切心からそのファイルを開き、中身を読み取ってしまいます。

これを悪用するのが XXE(XML External Entity)攻撃 です。
攻撃者は、一見無害に見えるXMLのなかに「サーバーの中にあるパスワードファイルや秘密のデータを読み取って、こっそり私に送って!」という意地悪な指示を仕込みます。防犯がガバガバなポスト(設定が不十分なXMLパーサー)にその手紙を投函してしまうと、ポストの管理人が言われるがままに大切な秘密の書類を取り出し、攻撃者に渡しちゃう……というわけです。恐ろしいですよね。

—

2. 実際の攻撃はどのように行われるのか?

では、もう少し具体的に、システムがどのように狙われるのかを見てみましょう。

例えば、ユーザーからの入力を受け取って画面に表示する、次のような処理があったとします。システムは、受け取ったXMLをそのまま「XMLパーサー」という翻訳・解釈マシーンに渡して処理させます。

このとき、もしXMLパーサーが「外のファイルを読み込んでもいいよ」という甘い設定のままだと、攻撃者は次のような細工をしたXMLを送ります。

<?xml version="1.0" encoding="UTF-8"?>
<!-- 外部のファイル(例:Linuxのパスワードファイル /etc/passwd)を指し示す「xxe」という名前の変数(エンティティ)を定義します -->
<!DOCTYPE test [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<!-- 本文の中で、先ほど定義した変数「xxe」を呼び出します -->
<root>
  <name>&xxe;</name>
</root>

このXMLを受け取ったシステムは、親切心から file:///etc/passwd を読み込み、その機密データをレスポンスとして攻撃者に返してしまうのです。これがXXE攻撃の恐るべきメカニズムです。

—

3. 一歩ずつ対策を学んでいきましょう!

「なんだか怖くなってきた……うちのシステムは大丈夫かな?」と思いましたか?
大丈夫です!原因が「外のファイルを勝手に読みに行ってしまうこと」にあるのなら、「外のファイルを絶対に読みに行かないように設定(お留守番モードに設定)」してしまえばいいのです。

ここからは、開発現場でよく使われるプログラミング言語(PHPやJavaなど)を例に、具体的な対策コードを見ていきましょう。

PHP(DOMDocument)での対策例

PHPでXMLを安全に読み込むためには、外部エンティティの読み込みを禁止するフラグを明示的にオフにする必要があります。

<?php
// 安全なXML読み込みのサンプルコード

$xmlString = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<root><name>テストユーザー</name></root>
XML;

// DOMDocumentのインスタンスを作成します
$dom = new DOMDocument();

// 【超重要】外部エンティティの読み込みを無効化(ブロック)します!
// この1行を書くだけで、XXE攻撃を完全にシャットアウトできます。
libxml_disable_entity_loader(true);

// 安全にXMLをロードします
$dom->loadXML($xmlString, LIBXML_NOENT | LIBXML_DTDLOAD);

$results = $dom->getElementsByTagName('name');
foreach ($results as $node) {
    echo "読み込んだ名前: " . htmlspecialchars($node->nodeValue, ENT_QUOTES, 'UTF-8') . "\n";
}
?>

Java(DocumentBuilderFactory)での対策例

Javaの場合も同様に、ファクトリーの設定を変更して外部DTDや外部エンティティの読み込みを明示的に禁止します。

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import java.io.StringReader;

public class SecureXmlParser {
    public static void main(String[] args) {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

            // 【超重要】XXE攻撃を防ぐためのセキュリティ設定
            // 1. 外部DTD(文書型定義)の読み込みを禁止する
            dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            
            // 2. 上記が使えない古いパーサーの場合は、個別に外部ジェネリックエンティティを無効化する
            dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            
            // 3. 外部パラメータエンティティも無効化する
            dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            
            // 4. 外部から読み込む際のDTDスフェッチを無効化する
            dbf.setAttribute("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

            DocumentBuilder db = dbf.newDocumentBuilder();
            
            String xmlData = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><name>テスト</name></root>";
            Document doc = db.parse(new InputSource(new StringReader(xmlData)));
            
            System.out.println("XMLのパースに成功しました(安全に処理されています)");

        } catch (Exception e) {
            System.err.println("セキュリティエラーまたは不正なXMLです: " + e.getMessage());
        }
    }
}

—

4. まとめと現場での心構え

いかがでしたでしょうか?
XXE攻撃は仕組み自体は少し複雑に見えますが、要するに「信頼できない外部からのデータを処理するときに、余計な機能(ファイルの読み込みなど)を働かせないようにする」という基本を守るだけで、しっかりと防ぐことができます。

実務においては、自分たちが新しく書いたコードだけでなく、プロジェクトで利用している古いライブラリや、外部からインポートしたXML処理用の共通部品などが、デフォルトで安全な設定になっているかを確認することが大切です。

セキュリティ対策は、一度覚えてしまえば次からの開発における強力な武器になります。焦らず、一つひとつの仕組みを丁寧な目で確認しながら、より安全で頑丈なシステムを作っていきましょう!応援しています!

コメント

タイトルとURLをコピーしました