XPathクエリ(1)

10月 31, 2013 PHP Security
(Last Updated On: 2018年8月4日)

XPathはXML文書をクエリして要素を取り出す仕組みです。XML文書を検索して結果を返します。

SimpleXMLにはxpathメソッドが用意されています。
SimpleXMLElement::xpath

<?php 
$result = $simple_xml_obj->query('my/x/path'); 
?>


DOMにはDOMXPathクラスが用意されています。
The DOMXPath class

<?php 
$xpath_obj->query('my/x/path', $relative_node); 
?>

いずれもXPathクエリの仕組みは用意されていますが、現在(PHP5.5)の所はエスケープ関数が用意されていません。しかし、XPathクエリを安全に実行するにはエスケープは欠かせません。

XPathには1.0と2.0の仕様があり、それぞれエスケープ仕様を定めています。

XPath 1.0の仕様
XPath 2.0の仕様

XPath 1.0のリテラル定義(3.7 Lexical Structure)

Literal ::= ‘”‘ [^”]* ‘”‘ | “‘” [^’]* “‘”

リテラルは” (ダブルクォート)または’ (シングルクォート)で囲みます。それぞれ ” または ‘ がリテラルの中に現れてはならない事とになっています。XPath 1.0の仕様書にはエスケープ仕様が記載されていません。

XPath 2.0のリテラル定義(3.1.1 Literals)

[42] Literal ::= NumericLiteral | StringLiteral
[43] NumericLiteral ::= IntegerLiteral | DecimalLiteral | DoubleLiteral
[71] IntegerLiteral ::= Digits
[72] DecimalLiteral ::= (“.” Digits) | (Digits “.” [0-9]*)
[73] DoubleLiteral ::= ((“.” Digits) | (Digits (“.” [0-9]*)?)) [eE] [+-]? Digits
[74] StringLiteral ::= (‘”‘ (EscapeQuot | [^”])* ‘”‘) | (“‘” (EscapeApos | [^’])* “‘”)
[75] EscapeQuot ::= ‘””‘
[76] EscapeApos ::= “””
[81] Digits ::= [0-9]+

XPath2.0ではエスケープ方法が明確に定義されており、 データベースと同様にリテラルのクオート文字でクオート文字をエスケープするようになっています。つまり、

  "  のエスケープは ""
  '  のエスケープは ''

とします。

XPath 1.0にはエスケープ仕様が定義されていません。どのXPathを利用しているか知った上でエスケープまたはパラメータのチェックを行わなければなりません。

PHPのSimpleXMLとDOMモジュールはlibxml2と呼ばれるライブラリを利用しています。現在の所、libxml2はXPath 1.0をサポートしており2.0はサポートしていません。XML文書のノード名・属性名に ” または ’ を利用しようとするとエラーとなりパースできません。またXPathクエリのクエリ文字列のノード名・属性名に ‘ または ” が含まれている場合、エラーが発生し処理されません。

クエリ文字列にクオート文字がある場合のエラー例

Warning: SimpleXMLElement::xpath(): Invalid expression in /home/yohgaki/t.php on line 17

Warning: SimpleXMLElement::xpath(): xmlXPathEval: evaluation failed in /home/yohgaki/t.php on line 17

つまりノード名などには ‘ や ” は利用できません。これはXML仕様書(XML 1.0)で確認できます。ノード名や属性名に利用できる文字は 2.3 Common Syntactic Constructs で定義されており ‘ や ” は含まれない事が分かります。

2.3 Common Syntactic Constructs

[4] NameStartChar ::= “:” | [A-Z] | “_” | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
[4a] NameChar ::= NameStartChar | “-” | “.” | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
[5] Name ::= NameStartChar (NameChar)*

ただし、ノードの値や属性値には ‘ と ” は利用可能です。この為、クエリには ‘ と ” を利用可能です。

クオート文字を含んだ検索例

<?php
$string = " 
<books>
 <book>
  <author>let's</author>
  <title>PHP Security #1</title>
 </book>
 <book>
  <author>let</author>
  <title>PHP Security #2</title>
 </book>
";

$xml = new SimpleXMLElement($string);

$result = $xml->xpath('/books/book[author="let\'s"]');

while(list( , $node) = each($result)) {
     var_dump($node);
}
?>

出力

object(SimpleXMLElement)#2 (2) {
  ["author"]=>
  string(5) "let's"
  ["title"]=>
  string(15) "PHP Security #1"
}

サンプルスクリプト中の

$result = $xml->xpath('/books/book[author="let\'s"]');

の中で

"let\'s"

とダブルクォートで囲んだ文字列の中にシングルクォートが在るため問題なく検索できています。しかし、これが

$result = $xml->xpath('/books/book[author=". $_GET['author_name'] ."]');

などがあった場合には不正なクエリの送信可能になります。

XPathクエリについては、続きを書くことにします。

PHPのセキュリティ入門書に記載するコンテンツのレビューも兼ねてブログを書いています。コメント、感想は大歓迎です。

参考リンク:

投稿者: yohgaki