なぜPHPアプリにセキュリティホールが多いのか?

第17回XPathインジェクション(その2)

前回はPostgreSQLを例に、データベースシステムでXPathがどのように利用できるか解説しました。PHP本体にもXML機能が組み込まれています。今回はPHP本体のXML機能を使ってXPathの利用方法を解説します。XPathインジェクションの解説は次回行います。今しばらくお待ちください。

PHPのXPath機能

PHP 5.2のDomモジュールはlibxml2のラッパー関数になっています。libxml2はSAX、DOM APIなどXML処理に必要なAPIを提供し、XPath機能もその一つとして提供しています。

DOMXPath
http://www.php.net/manual/en/class.domxpath.php

次回のXPathインジェクションの解説に利用するので、ユーザ情報を管理するXML文書を用意します。

account.xml
<?xml versoin="1.0" encoding="UTF-8" ?>
<account>
 <user id="1">
  <name>user1</name>
  <password>password1</password>
 </user>
 <user id="2">
  <name>user2</name>
  <password>password2</password>
 </user>
 <user id="3">
  <name>user3</name>
  <password>password3</password>
 </user>
 <user id="4">
  <name>user4</name>
  <password>password4</password>
 </user>
</account>

DOMXPathの利用方法

DOMXPathはオブジェクトとして実装されています。DOMオブジェクトを引数にコンストラクタを呼び出し、XPathクエリによりノードを取得します。ノードはDOMElementオブジェクトとして返され、DOMElementメソッドを利用して内容を参照したり変更できるようになります。

簡単なXPathの実行

xpath1.php
<?php
$doc = new DOMDocument;
$doc->load('account.xml');

$xpath = new DOMXPath($doc);

//XPathでaccount文書のすべてのノードを取得
$nodes = $xpath->query('/account/*');

//$nodesはDOMElement。$nもDOMElement
foreach ($nodes as $n) {
  var_dump($n);
}
?>
実行結果
object(DOMElement)#3 (0) {
}
object(DOMElement)#4 (0) {
}
object(DOMElement)#5 (0) {
}
object(DOMElement)#6 (0) {
}

account.xmlは4つのuserノードを持つので、4つのDOMElementオブジェクトがvar_dumpにより出力されます。内容を出力するにはDOMElementオブジェクトからノードを取り出し、そのノードの内容を出力します。

XML文書をユーザ認証データベースとして利用

account.xmlを利用してWebアプリケーションの認証システムを作ることができます。先ほど利用したxpath1.phpのXPathクエリの部分を変えるだけで認証に利用できるようになります。

XPathもSQLと同様に比較や結合、論理演算などの演算子をサポートしています。これを利用すると比較的SQLに近い形で認証データベースを利用できます。

xpath2.php
<?php
$doc = new DOMDocument;
$doc->load('account.xml');

$xpath = new DOMXPath($doc);

//ユーザ名が"user1"でパスワードが"password1"であるノードを取り出す
$nodelist = $xpath->query('/account/user[name="user1" and password="password1"]');

var_dump($nodelist->length, $nodelist->item(0)->nodeValue);
?>

account.xmlには該当するノードがあるので以下のような出力となります。

出力結果
[yohgaki@dev tmp]$ php xpath.php
int(1)
string(22) "
  user1
  password1
 "

ユーザ名かパスワードが一致しなければ結果はないので次のようになります。

出力例
[yohgaki@dev tmp]$ php xpath.php

Notice: Trying to get property of non-object in /home/yohgaki/tmp/xpath.php on line 10
int(0)
NULL

このようにXML文書を利用したユーザ管理システムは比較的簡単に実装できることが分かります。ここではコマンドラインから利用する認証を考えてみます。

$ php auth.php USERNAME PASSWORD

と入力し、USERNAMEとパスワードが一致すると⁠OK to login⁠を出力し、一致しない場合は⁠Not OK to login⁠を表示するプログラムは以下のようになります。

auth.php
<?php
$doc = new DOMDocument;
$doc->load('account.xml');

$xpath = new DOMXPath($doc);

$nodelist = $xpath->query('/account/user[name="'.$argv[1].'" and password="'.$argv[2].'"]');

if ($nodelist->length) {
        echo "OK to login".PHP_EOL;
} else {
        echo "Not OK to login".PHP_EOL;
}
?>

警告:あえて脆弱性があるコードになっています。絶体にこのようなコードで認証しないでください。

試しに実行してみると、一応期待どおりと思われるような動作になっていることが確認できます。

実行例
[yohgaki@dev tmp]$ php auth.php user1 password1
OK to login
[yohgaki@dev tmp]$ php auth.php user1 password2
Not OK to login

まとめ

筆者の好みとしてはDOMElementやDOMNodeオブジェクトとしてではなく、配列として取得できるオプションが欲しいところです。PHP本体のDOMモジュールを利用すると比較的簡単にXML文書の中から必要なノードだけを抽出できることが分かります。XPathの演算子の利用方法もなんとなくSQLと似ていることも分かったと思います。

XMLでデータ管理を行うと、ほかのシステムとのデータ交換が容易になります。しかし、DOMを利用した場合はドキュメント全体を読み込む必要があるので、大きなドキュメントの読み込みには大量のメモリが必要となるなど、注意しなければならないこともあります。

お待たせしました。次回はいよいよXPathインジェクションと対策の解説です。

おすすめ記事

記事・ニュース一覧