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のリテラル定義(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のセキュリティ入門書に記載するコンテンツのレビューも兼ねてブログを書いています。コメント、感想は大歓迎です。
参考リンク:
- http://www.w3.org/TR/xml/
- http://www.w3.org/TR/xpath/
- http://www.w3.org/TR/xpath20/
- https://www.owasp.org/index.php/XPATH_Injection
- https://www.owasp.org/index.php/Blind_XPath_Injection