FlawOpen

flawopen.com/XML 外部エンティティ(XXE)/Python XML パーサーの安全性

Python の xml.etree.ElementTree は XXE に安全ですか?

CWE-611:XML 外部エンティティ参照リファレンスページ
要点

ファイル読み取りやネットワークを使う典型的な XXE に対し、Python 組み込みの Expat ベースパーサーは既定でローカルファイルにアクセスせず、通信も行いません。ただし XML 全般が安全とは限りません。Expat 2.7.2 未満はエンティティ展開や長いトークンによる DoS の影響を受ける可能性があります。lxml の既定値もバージョンで異なるため、固定して明示設定するか、信頼できない XML には defusedxml を使います。

危険 — バージョンと設定で露出範囲が変わる

XXE(CWE-611):
攻撃者が制御する XML の外部エンティティを解決し、ローカルファイルの読み取りやサーバー発のリクエストが起きる可能性があります。
エンティティ展開 DoS:
入れ子や繰り返し参照の展開で CPU やメモリを過剰に消費します。
Expat:
Python 組み込み XML モジュールが使う C パーサー。実行時の版は pyexpat.EXPAT_VERSION で確認します。
DTD:
文書型宣言でエンティティを定義でき、設定によって外部リソースを要求します。
手順 1

信頼できない XML がパーサーに届く

アップロード、SVG、SAML メッセージ、連携データが信頼境界を越えて XML API に渡されます。

手順 2

実装と設定が動作を決める

標準ライブラリは Expat、lxml は libxml2 を使います。版、外部解決、展開、設定値で到達可能な動作が変わります。

手順 3

解決や展開が影響を起こす

外部解決が有効なら、エンティティがローカルまたはリモートの内容を要求できます。脆弱な展開や長いトークンも資源を消費します。

手順 4

プロセス権限が影響を制限する

ファイル権限とネットワーク出口が XXE の影響を制限します。入力上限、更新済みパーサー、安全設定、隔離も有効です。

Python の現行ガイドでは、Expat は既定でローカルファイルやネットワークにアクセスしません。一方、2.7.2 未満は billion-laughs、quadratic blowup、長いトークン、過剰なメモリ消費の影響を受ける可能性があります。Python は同梱またはシステムの Expat を使うため、pyexpat.EXPAT_VERSION を確認してください。これは版依存の資源枯渇リスクであり、現行 ElementTree が外部エンティティを解決する意味ではありません。

強化 — DTD と外部リソースを拒否
# Standard-library external access is off by default,
# but old Expat builds have resource-exhaustion risks.
import xml.etree.ElementTree as ET
from xml.parsers import expat

print(expat.EXPAT_VERSION)  # inspect the runtime parser
root = ET.fromstring(untrusted_xml)

# lxml before 5.0 defaulted resolve_entities=True.
# Explicit unsafe settings illustrate the risky boundary.
from lxml import etree
parser = etree.XMLParser(resolve_entities=True, load_dtd=True)
root = etree.fromstring(untrusted_xml, parser)
区別すべき3つの XML リスク
from defusedxml.ElementTree import fromstring

# Reject DTD declarations when the format does not require them.
root = fromstring(untrusted_xml, forbid_dtd=True)

# If lxml is required, set its security options explicitly.
from lxml import etree
parser = etree.XMLParser(
    resolve_entities=False,
    no_network=True,
    load_dtd=False,
    huge_tree=False,
)
root = etree.fromstring(untrusted_xml, parser)
# Do not run XInclude on untrusted trees without a restricted loader.

実際に使うパーサーに安全な設定を選ぶ

defusedxml.ElementTree は既定でエンティティ宣言と外部参照を拒否します。DTD が不要な形式では forbid_dtd=True も指定します。lxml では resolve_entities=False、load_dtd=False、no_network=True、huge_tree=False を明示します。現行版は内部エンティティのみ解決し、lxml 5.0 より前は既定で解決していました。制限付き loader なしに信頼できないツリーで XInclude を実行しないでください。展開・解析前に入力サイズを制限します。

パーサーの既定値を版ごとに確認する理由

「Python XML」は単一の設定ではありません。標準ライブラリは Expat を使い、ビルドにより同梱版またはシステム版になります。lxml は libxml2 を包み、5.0 でエンティティ解決の既定値を変更しました。wrapper や明示設定も挙動を変えます。Python、Expat、lxml、libxml2 の版を記録し、各入力点で安全設定を明示してください。

よくある質問

ElementTree は既定で外部エンティティからファイルを読みますか?

いいえ。Python の説明では Expat は既定でローカルファイルやネットワークにアクセスしません。版を確認し、XXE の読み取りと古いパーサーの資源枯渇を区別してください。

現行 lxml は既定で危険ですか?

版をまたいで一括りにしないでください。現行 XMLParser は内部エンティティのみ解決し、DTD を読み込まず、ネットワークも使いません。lxml 5.0 で resolve_entities は True から internal に変わりました。設定を明示し wrapper も確認します。

defusedxml は既定で全 DTD を拒否しますか?

いいえ。エンティティと外部参照は拒否しますが forbid_dtd は False です。DTD 不要なら True にしてください。

安全なパーサーなら XML の資源リスクはすべてなくなりますか?

いいえ。圧縮・展開後サイズを制限し、パーサーを更新し、huge_tree を避け、無制限 loader で信頼できない XInclude を処理しないでください。

一次資料

関連項目 XML 外部実体(XXE)インジェクション(CWE-611):仕組みと防ぎ方 — flawopen.com .NET XmlReader は XXE に安全ですか? Java XXE:DocumentBuilderFactory は既定で安全?