XPathインジェクション:XMLの「構造」が牙を剥く瞬間
やあ。現場で戦うエンジニア諸君。今日は、多くの開発者が「XMLなんて古い技術だろ?」と油断している隙を突く、XPathインジェクションについて話そう。
REST APIが主流の現代でも、レガシーなエンタープライズシステムや、設定ファイルをXMLで管理するツールは依然として多い。そして、そうしたシステムで最も恐ろしいのは、「ユーザーからの入力をそのままクエリに埋め込む」という、Web黎明期からの悪しき伝統が生き残っていることだ。
1. XPathインジェクションの「盲点」:論理を書き換える快感
SQLインジェクションが「データベースのテーブルを破壊する」ものだとしたら、XPathインジェクションは「XMLツリーの構造を悪用して、認可の壁をすり抜ける」ものだ。
例えば、ユーザー名とパスワードをXMLファイルで管理しているとする。
ここで、検索クエリが以下のようになっているとしよう。
//user[username/text()='$user_input' and password/text()='$pass_input']
攻撃者が $user_input に ' or '1'='1 を入力したらどうなる?
クエリは //user[username/text()='' or '1'='1' and ...] となり、最初のユーザー(多くの場合管理者)をいとも簡単に特定できてしまう。これが、攻撃者の最初の「足掛かり」だ。
2. なぜ「サニタイズ」だけでは不十分なのか
多くの若手エンジニアは、str_replace や正規表現で ' や " をエスケープしようとする。だが、これは「戦場に竹槍で挑む」のと同義だ。
XMLは構造化データであり、エスケープ漏れ一つでツリーの階層を飛び越えられる。例えば、ancestor:: や following-sibling:: といった軸指定を使えば、認証をすり抜けるだけでなく、システム内の全データを総なめにすることも可能だ。
対策の原則はただ一つ:「クエリに直接、ユーザー入力を混ぜるな」。
3. 実践:パラメータ化された安全なクエリ
現代のライブラリには、SQLのプリペアドステートメントのように、変数を安全にバインドする機能が備わっている。これを正しく使おう。
Python (lxml) を使用したセキュアな実装例
Pythonの lxml を使う場合、変数を直接文字列結合してはいけない。xpath 関数の variables 引数を使うのが鉄則だ。
from lxml import etree
def get_user_data(xml_data, username_input):
root = etree.fromstring(xml_data)
# 【重要】直接クエリに埋め込まず、変数バインドを使う
# $user という変数を定義し、そこに値を安全に渡す
query = “//user[username=$user]”
# 第2引数に辞書形式で変数をマッピングする
results = root.xpath(query, user=username_input)
if results:
return “ユーザー発見”
return “アクセス拒否”
攻撃者が ‘ or ‘1’=’1 を入力しても、これなら安全に処理される
PHP (DOMXPath) の場合
PHPには残念ながら直接的な「パラメータバインド」機能が標準のDOMXPathには存在しない。だからこそ、外部からの入力は徹底的にバリデーション(ホワイトリスト方式)を行う必要がある。
function get_user_xml($dom, $username) {
// 1. バリデーション: 英数字以外は徹底的に排除する
if (!preg_match(‘/^[a-zA-Z0-9]+$/’, $username)) {
throw new Exception(“不正な入力です。”);
}
$xpath = new DOMXPath($dom);
// 2. 構造破壊を防ぐため、シングルクォートを適切に処理する(可能ならクエリ構築自体を避ける)
$query = sprintf(“//user[username=’%s’]”, $xpath->quote($username));
return $xpath->query($query);
}
4. インフラ層で守る「最後の砦」
アプリケーションの修正が追いつかない場合、あるいは多層防御を強固にしたい場合は、WAFでのブロックが有効だ。
Nginx/ModSecurity (OWASP Core Rule Set) を使用しているなら、以下のシグネチャを意識してほしい。
ModSecurityのカスタムルール例(概念)
XPathの攻撃で頻出する文字列を検知して遮断する
SecRule ARGS “@rx (?i)(ancestor|following-sibling|namespace|//|\[.=.\])” \
“id:1000001,phase:2,deny,status:403,msg:’XPath Injection Detected'”
チーフからの助言
脆弱性を見つけるのは楽しいが、それを「塞ぎ続ける」のは地味で泥臭い作業だ。しかし、この地道な作業こそが、夜中に電話で叩き起こされる回数を劇的に減らす。
1. ライブラリの仕様を疑え:自分の使っているライブラリが「パラメータ化」をサポートしているか、公式ドキュメントを読み込め。
2. ホワイトリストを愛せ:入力値が期待通りの型か、文字種か。これだけで9割の攻撃は防げる。
3. XMLの必要性を再考せよ:可能ならJSONや他の構造化データへの移行を検討してくれ。技術的負債を解消することも、我々エンジニアの重要な仕事だ。
セキュリティは「魔法」ではない。論理的な積み重ねだ。今日のコードが、君のシステムを守る盾になることを願っている。何か不明な点があれば、いつでも共有してくれ。
コメント