Documentation Home
MySQL 8.0 リファレンスマニュアル
Download this Manual
PDF (US Ltr) - 36.1Mb
PDF (A4) - 36.2Mb


このページは機械翻訳したものです。

12.12 XML 関数

表 12.16 「XML 関数」

名前 説明
ExtractValue() XPath 表記法を使用した XML 文字列からの値の抽出
UpdateXML() 置換後 XML フラグメントを返します

このセクションでは、MySQL での XML および関連する機能について説明します。

注記

--xml オプションを付けて呼び出すと、mysql および mysqldump クライアントで XML 書式の出力を MySQL から取得できます。 セクション4.5.1「mysql — MySQL コマンドラインクライアント」およびセクション4.5.4「mysqldump — データベースバックアッププログラム」を参照してください。

基本的な XPath 1.0 (XML Path Language、バージョン 1.0) の機能を提供する 2 つの関数が使用可能です。 XPath の構文および使用方法に関する基本情報の一部は、このセクションの後半で説明しますが、これらのトピックの詳細はこのマニュアルの範囲外であるため、「XML Path Language (XPath) 1.0 標準」で明確な情報を参照する必要があります。 XPath がはじめてのユーザーや基本の復習を希望するユーザーに役立つリソースは、複数の言語で入手できる「Zvon.org XPath Tutorial」です。

注記

これらの関数はまだ開発中です。 XML および XPath 機能のこれらの側面やその他の側面については、MySQL 8.0 以降で引き続き改善します。 これらについて議論したり、質問したり、MySQL XML ユーザーフォーラムでほかのユーザーからの支援を得たりすることもできます。

これらの関数で使用される XPath の式では、ユーザー変数およびローカルストアドプログラム変数がサポートされています。 ユーザー変数は簡単にチェックされます。ストアドプログラムへのローカル変数は厳密にチェックされます (Bug#26518 も参照してください)。

  • ユーザー変数 (簡単なチェック).  構文 $@variable_name を使用する変数 (つまり、ユーザー変数) はチェックされません。 変数の型が間違っている場合や、変数に値が事前に割り当てられていない場合でも、サーバーから警告やエラーが発行されません。 これは、$@myvariable が意図された場所で (たとえば)$@myvairable が使用された場合、警告が表示されないため、ユーザーが入力ミスを完全に担当することも意味します。

    例:

    mysql> SET @xml = '<a><b>X</b><b>Y</b></a>';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SET @i =1, @j = 2;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SELECT @i, ExtractValue(@xml, '//b[$@i]');
    +------+--------------------------------+
    | @i   | ExtractValue(@xml, '//b[$@i]') |
    +------+--------------------------------+
    |    1 | X                              |
    +------+--------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT @j, ExtractValue(@xml, '//b[$@j]');
    +------+--------------------------------+
    | @j   | ExtractValue(@xml, '//b[$@j]') |
    +------+--------------------------------+
    |    2 | Y                              |
    +------+--------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT @k, ExtractValue(@xml, '//b[$@k]');
    +------+--------------------------------+
    | @k   | ExtractValue(@xml, '//b[$@k]') |
    +------+--------------------------------+
    | NULL |                                |
    +------+--------------------------------+
    1 row in set (0.00 sec)
  • ストアドプログラム内の変数 (厳密なチェック).  これらの関数をストアドプログラム内部で呼び出すときに、構文 $variable_name を使用する変数を宣言し、これらの関数で使用できます。 このような変数は、定義されているストアドプログラムへのローカル変数であり、型および値について厳密にチェックされます。

    例:

    mysql> DELIMITER |
    
    mysql> CREATE PROCEDURE myproc ()
        -> BEGIN
        ->   DECLARE i INT DEFAULT 1;
        ->   DECLARE xml VARCHAR(25) DEFAULT '<a>X</a><a>Y</a><a>Z</a>';
        ->
        ->   WHILE i < 4 DO
        ->     SELECT xml, i, ExtractValue(xml, '//a[$i]');
        ->     SET i = i+1;
        ->   END WHILE;
        -> END |
    Query OK, 0 rows affected (0.01 sec)
    
    mysql> DELIMITER ;
    
    mysql> CALL myproc();
    +--------------------------+---+------------------------------+
    | xml                      | i | ExtractValue(xml, '//a[$i]') |
    +--------------------------+---+------------------------------+
    | <a>X</a><a>Y</a><a>Z</a> | 1 | X                            |
    +--------------------------+---+------------------------------+
    1 row in set (0.00 sec)
    
    +--------------------------+---+------------------------------+
    | xml                      | i | ExtractValue(xml, '//a[$i]') |
    +--------------------------+---+------------------------------+
    | <a>X</a><a>Y</a><a>Z</a> | 2 | Y                            |
    +--------------------------+---+------------------------------+
    1 row in set (0.01 sec)
    
    +--------------------------+---+------------------------------+
    | xml                      | i | ExtractValue(xml, '//a[$i]') |
    +--------------------------+---+------------------------------+
    | <a>X</a><a>Y</a><a>Z</a> | 3 | Z                            |
    +--------------------------+---+------------------------------+
    1 row in set (0.01 sec)

    パラメータ.  ストアドルーチン内部の XPath 式で使用され、パラメータとして渡される変数も、厳密なチェックの対象です。

ユーザー変数やストアドプログラムへのローカル変数を含む式は、その他の点 (表記法は除く) では、XPath 1.0 仕様で規定されている変数を含む XPath 式のルールに準拠する必要があります。

注記

XPath 式の格納に使用されるユーザー変数は、空の文字列として扱われます。 このため、ユーザー変数として XPath 式を格納することはできません。 (Bug #32911)

  • ExtractValue(xml_frag, xpath_expr)

    ExtractValue() は、XML マークアップ xml_frag のフラグメントと XPath 式 xpath_expr (ロケータとも呼ばれる) の 2 つの文字列引数を取ります。これは、XPath 式に一致する要素の子である最初のテキストノードのテキスト (CDATA) を返します。

    この関数を使用することは、/text() を追加したあとに xpath_expr を使用して一致を実行することと同等です。 言い換えると、ExtractValue('<a><b>Sakila</b></a>', '/a/b')ExtractValue('<a><b>Sakila</b></a>', '/a/b/text()') では同じ結果が生成されます。

    複数の一致が見つかった場合は、一致する各要素の 1 番目の子テキストノードの内容が空白文字で区切られた単一文字列として (一致した順序で) 返されます。

    式に一致するテキストノード (暗黙的な /text() を含む) が見つからない場合は、どのような理由でも、xpath_expr が有効で、xml_frag が適切にネストされ、閉じられた要素で構成されていれば、空の文字列が返されます。 空の要素で一致することと、まったく一致しないこととは区別されません。 これは意図的なものです。

    xml_frag で一致する要素が見つからなかったのか、またはこのような要素は見つかったが、子テキストノードが含まれていなかったのかを判断する必要がある場合は、XPath count() 関数を使用する式の結果をテストしてください。 たとえば、次に示すように、これらのステートメントの両方で空の文字列が返されます。

    mysql> SELECT ExtractValue('<a><b/></a>', '/a/b');
    +-------------------------------------+
    | ExtractValue('<a><b/></a>', '/a/b') |
    +-------------------------------------+
    |                                     |
    +-------------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT ExtractValue('<a><c/></a>', '/a/b');
    +-------------------------------------+
    | ExtractValue('<a><c/></a>', '/a/b') |
    +-------------------------------------+
    |                                     |
    +-------------------------------------+
    1 row in set (0.00 sec)

    ただし、次のコマンドを使用すれば、実際に一致する要素があったかどうかを判断できます。

    mysql> SELECT ExtractValue('<a><b/></a>', 'count(/a/b)');
    +-------------------------------------+
    | ExtractValue('<a><b/></a>', 'count(/a/b)') |
    +-------------------------------------+
    | 1                                   |
    +-------------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT ExtractValue('<a><c/></a>', 'count(/a/b)');
    +-------------------------------------+
    | ExtractValue('<a><c/></a>', 'count(/a/b)') |
    +-------------------------------------+
    | 0                                   |
    +-------------------------------------+
    1 row in set (0.01 sec)
    重要

    ExtractValue() では CDATA のみが返され、一致するタグ内に含まれるタグや、それらの内容は返されません (次の例で、val1 として返された結果を参照してください)。

    mysql> SELECT
        ->   ExtractValue('<a>ccc<b>ddd</b></a>', '/a') AS val1,
        ->   ExtractValue('<a>ccc<b>ddd</b></a>', '/a/b') AS val2,
        ->   ExtractValue('<a>ccc<b>ddd</b></a>', '//b') AS val3,
        ->   ExtractValue('<a>ccc<b>ddd</b></a>', '/b') AS val4,
        ->   ExtractValue('<a>ccc<b>ddd</b><b>eee</b></a>', '//b') AS val5;
    
    +------+------+------+------+---------+
    | val1 | val2 | val3 | val4 | val5    |
    +------+------+------+------+---------+
    | ccc  | ddd  | ddd  |      | ddd eee |
    +------+------+------+------+---------+

    この関数では、contains() との比較を実行し、その他の文字列関数 (CONCAT() など) と同じ照合順序アグリゲーションを実行し、それらの引数の照合順序強制性を考慮に入れる際に、現在の SQL 照合順序が使用されます。この動作を制御するルールの説明については、セクション10.8.4「式での照合の強制性」を参照してください。

    (以前は、大文字と小文字が区別されるバイナリが常に使用されていました。)

    次の例に示すように、xml_frag に、適切にネストされていない要素や閉じられていない要素が含まれ、警告が生成された場合は、NULL が返されます。

    mysql> SELECT ExtractValue('<a>c</a><b', '//a');
    +-----------------------------------+
    | ExtractValue('<a>c</a><b', '//a') |
    +-----------------------------------+
    | NULL                              |
    +-----------------------------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> SHOW WARNINGS\G
    *************************** 1. row ***************************
      Level: Warning
       Code: 1525
    Message: Incorrect XML value: 'parse error at line 1 pos 11:
             END-OF-INPUT unexpected ('>' wanted)'
    1 row in set (0.00 sec)
    
    mysql> SELECT ExtractValue('<a>c</a><b/>', '//a');
    +-------------------------------------+
    | ExtractValue('<a>c</a><b/>', '//a') |
    +-------------------------------------+
    | c                                   |
    +-------------------------------------+
    1 row in set (0.00 sec)
  • UpdateXML(xml_target, xpath_expr, new_xml)

    この関数は、XML マークアップ xml_target の特定のフラグメントの一部を新しい XML フラグメント new_xml に置き換えてから、変更された XML を返します。 置換された xml_target の一部は、ユーザーが指定した XPath 式 xpath_expr に一致します。

    xpath_expr に一致する式が見つからない場合、または複数の一致が見つかった場合、この関数は元の xml_target XML フラグメントを返します。 3 つの引数はすべて文字列にする必要があります。

    mysql> SELECT
        ->   UpdateXML('<a><b>ccc</b><d></d></a>', '/a', '<e>fff</e>') AS val1,
        ->   UpdateXML('<a><b>ccc</b><d></d></a>', '/b', '<e>fff</e>') AS val2,
        ->   UpdateXML('<a><b>ccc</b><d></d></a>', '//b', '<e>fff</e>') AS val3,
        ->   UpdateXML('<a><b>ccc</b><d></d></a>', '/a/d', '<e>fff</e>') AS val4,
        ->   UpdateXML('<a><d></d><b>ccc</b><d></d></a>', '/a/d', '<e>fff</e>') AS val5
        -> \G
    
    *************************** 1. row ***************************
    val1: <e>fff</e>
    val2: <a><b>ccc</b><d></d></a>
    val3: <a><e>fff</e><d></d></a>
    val4: <a><b>ccc</b><e>fff</e></a>
    val5: <a><d></d><b>ccc</b><d></d></a>
注記

XPath の構文および使用方法の詳細は、このマニュアルの範囲外です。 最終的な情報については、「XML Path Language (XPath) 1.0 仕様」を参照してください。 XPath がはじめてのユーザーや基本の復習を希望するユーザーに役立つリソースは、複数の言語で入手できる「Zvon.org XPath Tutorial」です。

一部の基本的な XPath 式の説明および例は、次のとおりです。

  • /tag

    <tag/> がルート要素の場合にかぎり、<tag/> に一致します。

    例: /a はいちばん外側の (ルート) タグに一致するため、<a><b/></a> には一致があります。 この例では、別の要素の子であるため、<b><a/></b> の内側の a 要素には一致しません。

  • /tag1/tag2

    <tag1/> の子であり、<tag1/> がルート要素である場合にかぎり、<tag2/> に一致します。

    例: /a/b はルート要素 a の子であるため、XML フラグメント <a><b/></a>b 要素に一致します。 この場合、b はルート要素 (その他の要素の子) であるため、<b><a/></b> には一致がありません。 XPath 式でも <a><c><b/></c></a> に一致がありません。ここで、ba の子孫ですが、実際には a の子ではありません。

    この構成は、3 つ以上の要素に拡張できます。 たとえば、XPath 式 /a/b/c は、フラグメント <a><b><c/></b></a> 内の c 要素に一致します。

  • //tag

    <tag> の任意のインスタンスに一致します。

    例: //a は、<a><b><c/></b></a><c><a><b/></a></b><c><b><a/></b></c> のいずれかの a 要素に一致します。

    /// と組み合わせることができます。 たとえば、//a/b は、フラグメント <a><b/></a> または <c><a><b/></a></c> のいずれかの b 要素と一致します。

    注記

    //tag/descendant-or-self::*/tag と同等です。 よく見られる誤りは、これと /descendant-or-self::tag とを混同することです。次に示すように、実際には後者の式ではまったく異なる結果が生成される可能性があります。

    mysql> SET @xml = '<a><b><c>w</c><b>x</b><d>y</d>z</b></a>';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SELECT @xml;
    +-----------------------------------------+
    | @xml                                    |
    +-----------------------------------------+
    | <a><b><c>w</c><b>x</b><d>y</d>z</b></a> |
    +-----------------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT ExtractValue(@xml, '//b[1]');
    +------------------------------+
    | ExtractValue(@xml, '//b[1]') |
    +------------------------------+
    | x z                          |
    +------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT ExtractValue(@xml, '//b[2]');
    +------------------------------+
    | ExtractValue(@xml, '//b[2]') |
    +------------------------------+
    |                              |
    +------------------------------+
    1 row in set (0.01 sec)
    
    mysql> SELECT ExtractValue(@xml, '/descendant-or-self::*/b[1]');
    +---------------------------------------------------+
    | ExtractValue(@xml, '/descendant-or-self::*/b[1]') |
    +---------------------------------------------------+
    | x z                                               |
    +---------------------------------------------------+
    1 row in set (0.06 sec)
    
    mysql> SELECT ExtractValue(@xml, '/descendant-or-self::*/b[2]');
    +---------------------------------------------------+
    | ExtractValue(@xml, '/descendant-or-self::*/b[2]') |
    +---------------------------------------------------+
    |                                                   |
    +---------------------------------------------------+
    1 row in set (0.00 sec)
    
    
    mysql> SELECT ExtractValue(@xml, '/descendant-or-self::b[1]');
    +-------------------------------------------------+
    | ExtractValue(@xml, '/descendant-or-self::b[1]') |
    +-------------------------------------------------+
    | z                                               |
    +-------------------------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT ExtractValue(@xml, '/descendant-or-self::b[2]');
    +-------------------------------------------------+
    | ExtractValue(@xml, '/descendant-or-self::b[2]') |
    +-------------------------------------------------+
    | x                                               |
    +-------------------------------------------------+
    1 row in set (0.00 sec)
  • * 演算子は、任意の要素に一致するワイルドカードとして機能します。 たとえば、式 /*/b は、XML フラグメント <a><b/></a> または <c><b/></c> 内の b 要素に一致します。 ただし、b はその他の要素の子である必要があるため、この式ではフラグメント <b><a/></b> 内の一致は生成されません。 ワイルドカードは任意の位置で使用できます: 式/*/b/* は、それ自体がルート要素ではない b 要素の子と一致します。

  • | (UNION) 演算子を使用すれば、複数のロケータのいずれかに一致できます。 たとえば、式 //b|//c は、XML ターゲット内のすべての b および c 要素に一致します。

  • その属性の 1 つ以上の値に基づいて、要素に一致することもできます。 これは、構文 tag[@attribute="value"] を使用して実行されます。 たとえば、式 //b[@id="idB"] は、フラグメント <a><b id="idA"/><c/><b id="idB"/></a> 内の 2 番目の b 要素に一致します。 attribute="value" を含む任意の要素に対して一致を行うには、XPath 式 //*[attribute="value"] を使用します。

    複数の属性値をフィルタ処理するには、単に複数の属性比較句を連続して使用するだけです。 たとえば、式 //b[@c="x"][@d="y"] は、指定した XML フラグメントの任意の場所で発生した要素 <b c="x" d="y"/> に一致します。

    同じ属性が複数の値のいずれかに一致する要素を見つけるには、| 演算子で結合された複数のロケータを使用します。 たとえば、c 属性の値が 23 または 17 であるすべての b 要素に一致するには、式 //b[@c="23"]|//b[@c="17"] を使用します。 この目的のために、//b[@c="23" or @c="17"] のように論理 or 演算子を使用することもできます。

    注記

    or| の相違点として、or は条件を結合するのに対し、| は結果セットを結合します。

XPath の制限.  現在、これらの関数でサポートされている XPath 構文は、次の制限の対象となっています。

  • ノードセット間の比較 ('/a/b[@c=@d]' など) はサポートされていません。

  • 標準の XPath 比較演算子はすべてサポートされています。 (Bug #22823)

  • 相対ロケータ式は、ルートノードのコンテキストで解決されます。 たとえば、次のようなクエリーと結果を考えてみます。

    mysql> SELECT ExtractValue(
        ->   '<a><b c="1">X</b><b c="2">Y</b></a>',
        ->    'a/b'
        -> ) AS result;
    +--------+
    | result |
    +--------+
    | X Y    |
    +--------+
    1 row in set (0.03 sec)

    この場合は、ロケータ a/b/a/b に解決されています。

    相対ロケータは、述語内でもサポートされています。 次の例では、d[../@c="1"]/a/b[@c="1"]/d として解決されています。

    mysql> SELECT ExtractValue(
        ->      '<a>
        ->        <b c="1"><d>X</d></b>
        ->        <b c="2"><d>X</d></b>
        ->      </a>',
        ->      'a/b/d[../@c="1"]')
        -> AS result;
    +--------+
    | result |
    +--------+
    | X      |
    +--------+
    1 row in set (0.00 sec)
  • スカラー値 (変数参照、リテラル、数字、およびスカラー関数の呼び出しを含む) として評価する式が前に付けられたロケータは許可されず、使用するとエラーが発生します。

  • :: 演算子を次のようなノード型と組み合わせることは、サポートされていません。

    • axis::comment()

    • axis::text()

    • axis::processing-instructions()

    • axis::node()

    ただし、次の例に示すように、名前のテスト (axis::nameaxis::* など) はサポートされています。

    mysql> SELECT ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::b');
    +-------------------------------------------------------+
    | ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::b') |
    +-------------------------------------------------------+
    | x                                                     |
    +-------------------------------------------------------+
    1 row in set (0.02 sec)
    
    mysql> SELECT ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::*');
    +-------------------------------------------------------+
    | ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::*') |
    +-------------------------------------------------------+
    | x y                                                   |
    +-------------------------------------------------------+
    1 row in set (0.01 sec)
  • パスがルート要素を上方向に導いている場合は、上下の移動がサポートされていません。 つまり、現在の要素の 1 つ以上の祖先がルート要素の祖先でもある場合は、指定された要素の祖先の子孫で一致する式を使用できません (Bug #16321 を参照してください)。

  • 次の XPath 関数はサポートされていないか、または説明したような既知の問題があります。

    • id()

    • lang()

    • local-name()

    • name()

    • namespace-uri()

    • normalize-space()

    • starts-with()

    • string()

    • substring-after()

    • substring-before()

    • translate()

  • 次の軸はサポートされていません。

    • following-sibling

    • following

    • preceding-sibling

    • preceding

ExtractValue() および UpdateXML() への引数として渡される XPath 式の要素セレクタ内には、コロン文字 (:) が含まれている可能性があります。これにより、XML 名前空間の表記法を使用しているマークアップとの使用が有効になります。 例:

mysql> SET @xml = '<a>111<b:c>222<d>333</d><e:f>444</e:f></b:c></a>';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT ExtractValue(@xml, '//e:f');
+-----------------------------+
| ExtractValue(@xml, '//e:f') |
+-----------------------------+
| 444                         |
+-----------------------------+
1 row in set (0.00 sec)

mysql> SELECT UpdateXML(@xml, '//b:c', '<g:h>555</g:h>');
+--------------------------------------------+
| UpdateXML(@xml, '//b:c', '<g:h>555</g:h>') |
+--------------------------------------------+
| <a>111<g:h>555</g:h></a>                   |
+--------------------------------------------+
1 row in set (0.00 sec)

これは、いくつかの点で Apache Xalan およびその他の一部のパーサーで許可されているものと似ていますが、名前空間の制限や namespace-uri() および local-name() 関数の使用を必要とするよりも大幅に単純です。

エラー処理.  ExtractValue()UpdateXML() のどちらの場合でも、使用される XPath ロケータが有効であり、検索対象の XML が適切にネストされ、閉じられた要素で構成されている必要があります。 ロケータが無効な場合は、次のようなエラーが生成されます。

mysql> SELECT ExtractValue('<a>c</a><b/>', '/&a');
ERROR 1105 (HY000): XPATH syntax error: '&a'

xml_frag が適切にネストされ、閉じられている要素で構成されていない場合は、次の例に示すように、NULL が返され、警告が生成されます。

mysql> SELECT ExtractValue('<a>c</a><b', '//a');
+-----------------------------------+
| ExtractValue('<a>c</a><b', '//a') |
+-----------------------------------+
| NULL                              |
+-----------------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Warning
   Code: 1525
Message: Incorrect XML value: 'parse error at line 1 pos 11:
         END-OF-INPUT unexpected ('>' wanted)'
1 row in set (0.00 sec)

mysql> SELECT ExtractValue('<a>c</a><b/>', '//a');
+-------------------------------------+
| ExtractValue('<a>c</a><b/>', '//a') |
+-------------------------------------+
| c                                   |
+-------------------------------------+
1 row in set (0.00 sec)
重要

UpdateXML() への第 3 引数として使用される置換用の XML は、適切にネストされ、閉じられている要素のみで構成されているかどうかを判断するためにチェックされません

XPath インジェクション.  コードインジェクションは、権限やデータへの不正アクセス権を取得するために、悪意のあるコードがシステムに導入された場合に発生します。 これは、ユーザーが入力したデータの型や内容について開発者が行なった想定の悪用に基づいています。 これに関しては、XPath も例外ではありません。

この問題が発生する可能性のある一般的なシナリオは、次のような XPath 式を使用して、ログイン名とパスワードの組み合わせを XML ファイル内で見つかったものと一致させることで承認を処理するアプリケーションのケースです。

//user[login/text()='neapolitan' and password/text()='1c3cr34m']/attribute::id

この XPath 式は、次のような SQL ステートメントと同等です。

SELECT id FROM users WHERE login='neapolitan' AND password='1c3cr34m';

XPath を使用している PHP アプリケーションでは、次のようにログインプロセスが処理される可能性があります。

<?php

  $file     =   "users.xml";

  $login    =   $POST["login"];
  $password =   $POST["password"];

  $xpath = "//user[login/text()=$login and password/text()=$password]/attribute::id";

  if( file_exists($file) )
  {
    $xml = simplexml_load_file($file);

    if($result = $xml->xpath($xpath))
      echo "You are now logged in as user $result[0].";
    else
      echo "Invalid login name or password.";
  }
  else
    exit("Failed to open $file.");

?>

入力時にはチェックが実行されません。 これは、悪意のあるユーザーがログイン名とパスワードの両方に ' or 1=1 と入力することで、テストを回避できることを意味します。その結果、$xpath が次のように評価されます。

//user[login/text()='' or 1=1 and password/text()='' or 1=1]/attribute::id

角括弧内の式は常に true と評価されるため、事実上、XML ドキュメント内のすべての user 要素の id 属性に一致する次の式と同じです。

//user/attribute::id

この攻撃を回避する方法の 1 つは、$xpath の定義内に挿入される変数名を単に引用符で囲むだけです。これにより、Web フォームから渡された値が強制的に文字列に変換されます。

$xpath = "//user[login/text()='$login' and password/text()='$password']/attribute::id";

これは、SQL インジェクション攻撃を回避する際に推奨されることの多い方法と同じです。 一般に、XPath インジェクション攻撃を回避するために従うべき方法は、SQL インジェクションを回避するための方法と同じです。

  • アプリケーションでは、テストされていないユーザーデータは許可されません。

  • ユーザーが送信したすべてのデータの型をチェックします。不正な型のデータは拒否または変換します。

  • 数値データに範囲外の値が含まれていないかをテストします。範囲外の値は切り捨てるか、丸めるか、拒否します。 文字列に不正な文字が含まれていないかをテストし、不正な文字が含まれる入力は削除するか拒否します。

  • 明示的なエラーメッセージは、システムを危険にさらすために使用できる手がかりを未承認ユーザーに与える可能性があるため、出力しないでください。その代わりに、ファイルやデータベーステーブルにログを記録してください。

SQL インジェクション攻撃を使用すればデータベーススキーマに関する情報を取得できるように、XPath インジェクションを使用すれば、Amit Klein 氏の論文『Blind XPath Injection』(PDF ファイル、46K バイト) で説明されているように、XML ファイルをスキャンして構造を明らかにできます。

クライアントに返送される出力をチェックすることも重要です。 MySQL の ExtractValue() 関数を使用すると何が発生する可能性があるのかを検討します。

mysql> SELECT ExtractValue(
    ->     LOAD_FILE('users.xml'),
    ->     '//user[login/text()="" or 1=1 and password/text()="" or 1=1]/attribute::id'
    -> ) AS id;
+-------------------------------+
| id                            |
+-------------------------------+
| 00327 13579 02403 42354 28570 |
+-------------------------------+
1 row in set (0.01 sec)

ExtractValue() は複数の一致を空白で区切られた単一の文字列として返すため、このインジェクション攻撃によって、users.xml 内に含まれるすべての有効な ID が単一の出力行としてユーザーに提供されます。 追加の保護手段として、ユーザーに返される前に出力のテストも行うべきです。 次に、単純な例を示します。

mysql> SELECT @id = ExtractValue(
    ->     LOAD_FILE('users.xml'),
    ->     '//user[login/text()="" or 1=1 and password/text()="" or 1=1]/attribute::id'
    -> );
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT IF(
    ->     INSTR(@id, ' ') = 0,
    ->     @id,
    ->     'Unable to retrieve user ID')
    -> AS singleID;
+----------------------------+
| singleID                   |
+----------------------------+
| Unable to retrieve user ID |
+----------------------------+
1 row in set (0.00 sec)

一般に、ユーザーにデータをセキュアに返すためのガイドラインは、ユーザー入力を受け入れるためのガイドラインと同じです。 それらは、次のように要約できます。

  • 常に、出力データの型および許可される値をテストします。

  • 未承認ユーザーがエラーメッセージを表示することを許可しないでください。アプリケーションに関する情報が提供される可能性があり、それを悪用されるおそれがあります。