Spec-Zone.ru › MySQL 5.7

12.11 Функции XML

Таблица 12.16 Функции XML

Таблица 12.16 Функции XML
Имя Описание
ExtractValue() Извлечение значения из XML-строки с использованием XPath-нотации
UpdateXML() Возврат заменённого фрагмента XML

В этом разделе рассматриваются XML и связанные с ним функции в MySQL.

Примечание

Получить XML-форматированный вывод из MySQL можно с помощью клиентов mysql и mysqldump путем вызова их с параметром --xml. См. Раздел 4.5.1, «mysql — Клиент командной строки MySQL» и Раздел 4.5.4, «mysqldump — Программа резервного копирования базы данных».

Доступны две функции, предоставляющие базовые возможности XPath 1.0 (XML Path Language, версия 1.0). Некоторая базовая информация о синтаксисе и использовании XPath представлена ниже в этом разделе; однако углубленное обсуждение этих тем выходит за рамки данного руководства, и вы должны обратиться к стандарту XML Path Language (XPath) 1.0 для получения окончательной информации. Полезным ресурсом для тех, кто только знакомится с XPath или хочет освежить в памяти основы, является Учебник по XPath на сайте Zvon.org, доступный на нескольких языках.

Примечание

Эти функции находятся в стадии разработки. Мы продолжаем улучшать эти и другие аспекты функций XML и XPath в MySQL 5.7 и последующих версиях. Вы можете обсудить их, задать вопросы об них и получить помощь от других пользователей в форуме пользователей MySQL по XML.

XPath-выражения, используемые с этими функциями, поддерживают переменные пользователя и локальные переменные хранимых программ. Переменные пользователя слабо проверяются; переменные, локальные для хранимых программ, проверяются строго (см. также ошибку #26518):

  • Переменные пользователя (слабая проверка). Переменные с синтаксисом $@variable_name (т.е., переменные пользователя) не проверяются. Сервер не выдает предупреждений или ошибок, если переменная имеет неправильный тип или ранее не была присвоена значение. Это также означает, что пользователь несет полную ответственность за любые опечатки, поскольку не выдается никаких предупреждений, если (например), $@myvairable используется там, где нужно $@myvariable.

    Пример:

    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-выражений, содержащих переменные, как указано в спецификации XPath 1.0.

Примечание

Переменная пользователя, используемая для хранения XPath-выражения, обрабатывается как пустая строка. По этой причине нельзя хранить XPath-выражение в виде переменной пользователя. (Ошибка #32911)

  • ExtractValue(xml_frag, xpath_expr)

    ExtractValue() принимает два строковых аргумента, фрагмент XML-разметки xml_frag и XPath-выражение xpath_expr (также известное как локатор); возвращает текст (CDATA) первого текстового узла, являющегося потомком элемента или элементов, соответствующих XPath-выражению.

    Использование этой функции эквивалентно выполнению соответствия с использованием xpath_expr после добавления /text(). Другими словами, ExtractValue('<a><b>Sakila</b></a>', '/a/b') и ExtractValue('<a><b>Sakila</b></a>', '/a/b/text()') дают одинаковый результат.

    Если найдено несколько соответствий, содержимое первого текстового потомка каждого соответствующего элемента возвращается (в порядке соответствия) как одна строка, разделенная пробелами.

    Если для выражения не найдено соответствующего текстового узла (включая неявный /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 |
    +------+------+------+------+---------+
    

    Эта функция использует текущее сортирование SQL для сравнения со contains(), выполняя ту же сортировку агрегирования, что и другие строковые функции (такие как CONCAT()), учитывая согласование согласованности их аргументов; см. Раздел 10.8.4, «Согласованность сортировки в выражениях» для объяснения правил, определяющих это поведение.

    (Ранее всегда использовалось двоичное — то есть, чувствительное к регистру — сравнение.)

    NULL возвращается, если xml_frag содержит элементы, которые не правильно вложены или закрыты, и генерируется предупреждение, как показано в этом примере:

    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-фрагмент. Все три аргумента должны быть строками.

    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 или хочет освежить в памяти основы, является Учебник по XPath на сайте Zvon.org, доступный на нескольких языках.

Следуют описания и примеры некоторых основных XPath-выражений:

  • /tag

    Совпадает с <tag/> только тогда, когда <tag/> является корневым элементом.

    Пример: /a соответствует <a><b/></a>, поскольку он соответствует самому внешнему (корневому) тегу. Он не соответствует внутреннему элементу a в <b><a/></b>, поскольку в этом случае он является дочерним элементом другого элемента.

  • /tag1/tag2

    Совпадает с <tag2/> только в том случае, если он является потомком <tag1/>, и <tag1/> является корневым элементом.

    Пример: /a/b соответствует элементу b в фрагменте XML <a><b/></a>, поскольку он является потомком корневого элемента a. Он не соответствует <b><a/></b>, поскольку в этом случае b является корневым элементом (и, следовательно, потомком ни одного другого элемента). Выражение XPath также не соответствует <a><c><b/></c></a>; здесь b является потомком a, но не является потомком a.

    Данная конструкция расширяется до трех и более элементов. Например, выражение XPath /a/b/c соответствует элементу c во фрагменте <a><b><c/></b></a>.

  • //tag

    Совпадает с любым экземпляром <tag>.

    Пример: //a соответствует элементу a в любом из следующих: <a><b><c/></b></a>; <c><a><b/></a></b>; <c><b><a/></b></c>.

    // можно комбинировать с /. Например, //a/b соответствует элементу b в любом из фрагментов <a><b/></a> или <c><a><b/></a></c>.

    Примечание

    //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 соответствует элементу b в любом из фрагментов XML <a><b/></a> или <c><b/></c>. Однако выражение не соответствует фрагменту <b><a/></b>, потому что b должно быть потомком какого-либо другого элемента. Подстановка может использоваться в любой позиции: выражение /*/b/* соответствует любому потомку элемента b, который сам по себе не является корневым элементом.

  • Вы можете сопоставить любой из нескольких локаторов с помощью оператора | (UNION). Например, выражение //b|//c соответствует всем элементам b и c в целевом XML.

  • Также возможно сопоставление элемента на основе значения одного или нескольких его атрибутов. Это делается с помощью синтаксиса tag[@attribute="value"]. Например, выражение //b[@id="idB"] соответствует второму элементу b во фрагменте <a><b id="idA"/><c/><b id="idB"/></a>. Для сопоставления с любым элементом, имеющим attribute="value", используйте выражение XPath //*[attribute="value"].

    Для фильтрации нескольких значений атрибутов просто используйте несколько условий сравнения атрибутов друг за другом. Например, выражение //b[@c="x"][@d="y"] соответствует элементу <b c="x" d="y"/>, встречающемуся где угодно в данном фрагменте XML.

    Для поиска элементов, для которых один и тот же атрибут соответствует любому из нескольких значений, вы можете использовать несколько локализованных выражений, объединённых оператором |. Например, для соответствия всем элементам b, у которых атрибут c имеет любое из значений 23 или 17, используйте выражение //b[@c="23"]|//b[@c="17"]. Вы также можете использовать логический оператор or для этой цели: //b[@c="23" or @c="17"].

    Примечание

    Разница между or и | заключается в том, что or объединяет условия, а | объединяет наборы результатов.

Ограничения XPath. Синтаксис XPath, поддерживаемый этими функциями, в настоящее время имеет следующие ограничения:

  • Сравнение наборов узлов (например, '/a/b[@c=@d]') не поддерживается.

  • Поддерживаются все стандартные операторы сравнения XPath (Баг #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::name и axis::*) поддерживаются, как показано в этих примерах:

    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)
    
  • «Вверх-вниз» навигация не поддерживается в тех случаях, когда путь может привести «выше» корневого элемента. То есть, вы не можете использовать выражения, которые соответствуют потомкам предков данного элемента, где один или несколько предков текущего элемента также являются предками корневого элемента (см. Баг #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

Выражения XPath, передаваемые в качестве аргументов в ExtractValue() и UpdateXML(), могут содержать двоеточие (:) в селекторах элементов, что позволяет использовать их с разметкой, использующей обозначение 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)
Важно

Заменяемый XML, используемый в качестве третьего аргумента UpdateXML(), не проверяется на то, состоит ли он только из должным образом вложенных и закрытых элементов.

Инъекции XPath. инъекция кода возникает, когда вредоносный код вводится в систему, чтобы получить несанкционированный доступ к привилегиям и данным. Она основана на использовании предположений разработчиков о типе и содержимом данных, вводимых пользователем. XPath не является исключением в этом отношении.

Распространённый сценарий, в котором это может произойти, — это случай приложения, которое обрабатывает авторизацию, сопоставляя комбинацию имени пользователя и пароля с теми, что найдены в файле XML, используя выражение XPath, подобное этому:

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

Это эквивалент XPath выражения SQL-запросу, такому как этот:

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

PHP-приложение, использующее XPath, может обрабатывать процесс входа в систему следующим образом:

<?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, оно по сути эквивалентно выражению, которое соответствует атрибуту id каждого элемента user в документе XML:

//user/attribute::id

Один из способов обойти эту атаку — просто привести переменные, подлежащие интерполяции, в определении $xpath, заставляя значения, переданные веб-формой, преобразовываться в строки:

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

Это та же стратегия, которую часто рекомендуют для предотвращения атак типа SQL-инъекции. В общем, практические рекомендации по предотвращению атак типа XPath-инъекции аналогичны рекомендациям по предотвращению атак типа SQL-инъекции:

  • Никогда не принимайте непроверенные данные от пользователей в вашем приложении.

  • Проверяйте тип всех данных, отправленных пользователем; отбрасывайте или преобразовывайте данные, имеющие неверный тип.

  • Проверяйте числовые данные на значения, выходящие за пределы диапазона; усекайте, округляйте или отбрасывайте значения, выходящие за пределы диапазона. Проверяйте строки на наличие недопустимых символов и либо удаляйте их, либо отбрасывайте входные данные, содержащие такие символы.

  • Не выводите явных сообщений об ошибках, которые могут предоставить неавторизованному пользователю подсказки, которые могут быть использованы для взлома системы; вместо этого записывайте их в файл или таблицу базы данных.

Так же, как атаки типа SQL-инъекции могут использоваться для получения информации о схемах баз данных, так и атаки типа XPath-инъекции могут использоваться для обхода XML-файлов, чтобы раскрыть их структуру, как обсуждается в статье Амита Клейна «Blind XPath Injection» (Blind XPath Injection (PDF-файл, 46 КБ)).

Также важно проверить выходные данные, отправляемые обратно клиенту. Рассмотрим, что может произойти, когда мы используем функцию 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, пользователю как одну строку вывода. Для дополнительной защиты необходимо также проверить выходные данные перед их возвратом пользователю. Вот простой пример:

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)

В общем случае, рекомендации по безопасному возврату данных пользователям аналогичны рекомендациям по приему пользовательских данных. Их можно обобщить следующим образом:

  • Всегда проверяйте тип выходных данных и допустимые значения.

  • Никогда не разрешайте неавторизованным пользователям просматривать сообщения об ошибках, которые могут предоставить информацию о приложении, которую можно использовать для его эксплуатации.

© 2025 Oracle
Licensed under the GPLv2 License.
https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/xml-functions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API