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