文档首页
MySQL 9.0 参考手册
相关文档 下载本手册
PDF (美国信纸) - 40.0Mb
PDF (A4) - 40.1Mb
手册页 (TGZ) - 258.2Kb
手册页 (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


14.11 XML 函数

表 14.16 XML 函数

名称 描述
ExtractValue() 使用 XPath 表示法从 XML 字符串中提取值
UpdateXML() 返回替换后的 XML 片段

本节介绍了 MySQL 中的 XML 及相关功能。

注意

可以通过调用 mysqlmysqldump 客户端并使用 --xml 选项,从 MySQL 中获取 XML 格式的输出。参见 第 6.5.1 节,“mysql — MySQL 命令行客户端”第 6.5.4 节,“mysqldump — 数据库备份程序”.

提供了两个提供基本 XPath 1.0(XML 路径语言,版本 1.0)功能的函数。本节将提供有关 XPath 语法和用法的基本信息;然而,对这些主题的深入讨论超出了本手册的范围,您应该参考 XML 路径语言 (XPath) 1.0 标准 以获取权威信息。对于 XPath 新手或希望复习基本知识的用户,一个有用的资源是 Zvon.org XPath 教程,该教程提供多种语言版本。

注意

这些函数仍在开发中。我们将在 MySQL 9.0 及更高版本中继续改进这些功能和其他方面的 XML 和 XPath 功能。您可以在 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 1.0 规范中提供的包含变量的 XPath 表达式规则。

注意

用于存储 XPath 表达式的用户变量被视为空字符串。因此,无法将 XPath 表达式存储为用户变量。(错误 #32911)

  • ExtractValue(xml_frag, xpath_expr)

    ExtractValue() 接受两个字符串参数,一个 XML 标记片段 xml_frag 和一个 XPath 表达式 xpath_expr(也称为 定位器);它返回与 XPath 表达式匹配的元素或元素的第一个子文本节点的文本 (CDATA)。

    使用此函数相当于在附加 /text() 后使用 xpath_expr 执行匹配。换句话说,ExtractValue('<a><b>Sakila</b></a>', '/a/b')ExtractValue('<a><b>Sakila</b></a>', '/a/b/text()') 会产生相同的结果。如果 xml_fragxpath_exprNULL,则该函数返回 NULL

    如果找到多个匹配项,则每个匹配元素的第一个子文本节点的内容(按匹配顺序)将作为单个用空格分隔的字符串返回。

    如果未找到表达式(包括隐式 /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())相同的排序规则聚合,并考虑其参数的排序规则强制性;有关控制此行为的规则的说明,请参见 第 12.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 片段 new_xml 替换给定 XML 标记片段 xml_target 的单个部分,然后返回已更改的 XML。要替换的 xml_target 部分与用户提供的 XPath 表达式 xpath_expr 匹配。

    如果未找到与 xpath_expr 匹配的表达式,或者找到多个匹配项,则该函数将返回原始 xml_target XML 片段。所有三个参数都应为字符串。如果 UpdateXML() 的任何参数为 NULL,则该函数将返回 NULL

    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 路径语言 (XPath) 1.0 规范。对于 XPath 新手或希望复习基本知识的人来说,一个有用的资源是 Zvon.org XPath 教程,该教程有多种语言版本。

以下是一些基本 XPath 表达式的描述和示例

  • /tag

    当且仅当 <tag/> 是根元素时,才匹配 <tag/>

    示例:/a<a><b/></a> 中存在匹配项,因为它与最外层(根)标签匹配。它与 <b><a/></b> 中的内部 a 元素不匹配,因为在这种情况下,它是另一个元素的子元素。

  • /tag1/tag2

    当且仅当 <tag2/><tag1/> 的子元素,并且 <tag1/> 是根元素时,才匹配 <tag2/>

    示例:/a/b 与 XML 片段 <a><b/></a> 中的 b 元素匹配,因为它是的根元素 a 的子元素。它在 <b><a/></b> 中没有匹配项,因为在这种情况下,b 是根元素(因此不是任何其他元素的子元素)。XPath 表达式在 <a><c><b/></c></a> 中也没有匹配项;在这里,ba 的后代,但实际上不是 a 的子元素。

    此结构可以扩展到三个或更多元素。例如,XPath 表达式 /a/b/c 与片段 <a><b><c/></b></a> 中的 c 元素匹配。

  • //tag

    匹配任何实例的 <tag>

    示例://a 匹配以下任何一个中的 a 元素:<a><b><c/></b></a><c><a><b/></a></b><c><b><a/></b></c>

    // 可以与 / 结合使用。例如,//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><a/></b> 中产生匹配项,因为 b 必须是某个其他元素的子元素。通配符可以在任何位置使用:表达式 /*/b/* 匹配 b 元素的任何子元素,该子元素本身不是根元素。

  • 可以使用 | (UNION) 运算符匹配多个定位符中的任何一个。例如,表达式 //b|//c 匹配 XML 目标中的所有 bc 元素。

  • 也可以根据一个或多个属性的值匹配元素。这使用语法 tag[@attribute="value"] 完成。例如,表达式 //b[@id="idB"] 与片段 <a><b id="idA"/><c/><b id="idB"/></a> 中的第二个 b 元素匹配。要匹配具有 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::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)
  • 不支持 上下 导航,在这种情况下,路径会导致 超出 根元素。也就是说,您不能使用与给定元素的祖先的后代匹配的表达式,其中当前元素的一个或多个祖先也是根元素的祖先(请参见错误 #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() 的第三个参数的替换 XML 不会被检查以确定它是否仅包含正确嵌套和关闭的元素。

XPath 注入。 代码注入 是指将恶意代码引入系统以获得对特权和数据的未经授权的访问。它基于利用开发人员对用户输入数据的类型和内容所做的假设。XPath 在这方面也不例外。

这种情况发生的一个常见场景是应用程序通过将登录名和密码的组合与 XML 文件中找到的组合进行匹配来处理授权,使用类似于以下的 XPath 表达式

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

这是类似于以下 SQL 语句的 XPath 等效项

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

解决这种特定攻击的一种方法是在 $xpath 的定义中简单地引用要插值的变量名,从而强制将从 Web 表单传递的值转换为字符串

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

这与通常推荐用于防止 SQL 注入攻击的策略相同。总的来说,您应该遵循的防止 XPath 注入攻击的实践与防止 SQL 注入攻击的实践相同

  • 永远不要在您的应用程序中接受未经测试的用户数据。

  • 检查所有用户提交的数据类型;拒绝或转换类型错误的数据。

  • 测试数值数据的超出范围值;截断、舍入或拒绝超出范围的值。测试字符串中是否存在非法字符,并将其删除或拒绝包含它们的输入。

  • 不要输出明确的错误消息,这些消息可能会为未经授权的用户提供线索,这些线索可用于危害系统;而是将这些错误消息记录到文件或数据库表中。

正如 SQL 注入攻击可以用来获取有关数据库模式的信息一样,XPath 注入也可以用来遍历 XML 文件以发现它们的结构,如 Amit Klein 在其论文 Blind XPath Injection(PDF 文件,46KB)中所述。

同样重要的是检查发送回客户端的输出。考虑一下当我们使用 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)

一般来说,安全地将数据返回给用户的指南与接受用户输入的指南相同。这些指南可以概括为:

  • 始终测试传出数据的类型和允许值。

  • 绝不允许未经授权的用户查看可能提供有关应用程序信息的错误消息,这些信息可能会被用于攻击应用程序。