flawopen.com/XML 外部エンティティ(XXE)/Python XML パーサーの安全性
Python の xml.etree.ElementTree は XXE に安全ですか?
ファイル読み取りやネットワークを使う典型的な 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:
- 文書型宣言でエンティティを定義でき、設定によって外部リソースを要求します。
信頼できない XML がパーサーに届く
アップロード、SVG、SAML メッセージ、連携データが信頼境界を越えて XML API に渡されます。
実装と設定が動作を決める
標準ライブラリは Expat、lxml は libxml2 を使います。版、外部解決、展開、設定値で到達可能な動作が変わります。
解決や展開が影響を起こす
外部解決が有効なら、エンティティがローカルまたはリモートの内容を要求できます。脆弱な展開や長いトークンも資源を消費します。
プロセス権限が影響を制限する
ファイル権限とネットワーク出口が XXE の影響を制限します。入力上限、更新済みパーサー、安全設定、隔離も有効です。
Python の現行ガイドでは、Expat は既定でローカルファイルやネットワークにアクセスしません。一方、2.7.2 未満は billion-laughs、quadratic blowup、長いトークン、過剰なメモリ消費の影響を受ける可能性があります。Python は同梱またはシステムの Expat を使うため、pyexpat.EXPAT_VERSION を確認してください。これは版依存の資源枯渇リスクであり、現行 ElementTree が外部エンティティを解決する意味ではありません。
# 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)
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 を実行しないでください。展開・解析前に入力サイズを制限します。
- ✓すべての XML 入力点とパーサーを把握します:ElementTree/Expat、lxml/libxml2、SAX/DOM、SVG、SAML、アーカイブ取り込み、後続の XInclude。
- ✓可能なら Expat 2.7.2 以降を含むサポート対象 Python を使い、
pyexpat.EXPAT_VERSIONで実行時の版を確認します。 - ✓信頼できない XML には
defusedxmlを使います。DTD 不要ならforbid_dtd=Trueで拒否します。 - ✓lxml を使う場合は保守対象版を固定し、エンティティ、DTD、ネットワーク、huge-tree を明示的に無効化します。変わり得る既定値に依存しないでください。
- ✓圧縮前後のサイズを制限し、必要に応じ解析を隔離し、外部通信なしの無害な DTD/Entity テストを CI で行います。
パーサーの既定値を版ごとに確認する理由
「Python XML」は単一の設定ではありません。標準ライブラリは Expat を使い、ビルドにより同梱版またはシステム版になります。lxml は libxml2 を包み、5.0 でエンティティ解決の既定値を変更しました。wrapper や明示設定も挙動を変えます。Python、Expat、lxml、libxml2 の版を記録し、各入力点で安全設定を明示してください。
よくある質問
いいえ。Python の説明では Expat は既定でローカルファイルやネットワークにアクセスしません。版を確認し、XXE の読み取りと古いパーサーの資源枯渇を区別してください。
版をまたいで一括りにしないでください。現行 XMLParser は内部エンティティのみ解決し、DTD を読み込まず、ネットワークも使いません。lxml 5.0 で resolve_entities は True から internal に変わりました。設定を明示し wrapper も確認します。
いいえ。エンティティと外部参照は拒否しますが forbid_dtd は False です。DTD 不要なら True にしてください。
いいえ。圧縮・展開後サイズを制限し、パーサーを更新し、huge_tree を避け、無制限 loader で信頼できない XInclude を処理しないでください。