sp_xml_preparedocument存在安全风险,因解析含恶意DTD/实体的XML可能引发拒绝服务攻击,故严禁直接处理不受信任源的XML文档。

XML数据进SQL前必须剥离或拒绝DTD声明
XML本身不执行SQL,但当应用把XML内容(比如user_info字段)直接拼进SQL语句、或交由支持XPath的函数(如UPDATEXML、EXTRACTVALUE)处理时,攻击者就能用内嵌DTD触发实体膨胀,或用恶意XPath构造报错注入。关键不是“XML是否合法”,而是“它会不会被当作可执行上下文解析”。
常见错误现象:java.lang.OutOfMemoryError: Java heap space出现在解析用户上传的XML后;数据库日志里出现UPDATEXML(1, CONCAT('~', DATABASE()), 1)这类明显非业务逻辑的查询。
- 禁用外部实体:在SAX/DOM/STAX解析器初始化时显式关闭
http://apache.org/xml/features/disallow-doctype-decl(JAXP)或FEATURE_SECURE_PROCESSING - 拒绝含
/code>或<code>的XML:在接收层用正则<code>/快速拦截,比等解析器报错更早切断攻击链 - 若业务真需DTD(极少见),必须限定为白名单本地路径,且禁用
%参数实体和递归引用
别让XML字段值直接进SQL字符串拼接
很多系统把XML当“普通文本”存进TEXT或CLOB字段,却在另一处用CONCAT把它拼进动态SQL——这等于给攻击者开了XPath注入的后门。哪怕你校验了XML格式,只要最终进了UPDATEXML()这类函数的第二个参数,就可能泄露DATABASE()或USER()。
使用场景:接口接收客户资料XML,后台提取其中<phone></phone>节点值,用于WHERE条件查用户。
- 正确做法:先用标准XML解析器(如Java的
DocumentBuilder)提取phone文本,再作为预编译参数传入PreparedStatement - 错误做法:
"SELECT * FROM users WHERE phone = UPDATEXML(?, '/root/phone/text()', '')"—— 这里?看似参数化,但函数内部仍会解析XPath,风险未解除 - MySQL 5.7+可考虑用
XMLQUERY(8.0+)替代UPDATEXML,前者默认不展开实体,且支持更严格的命名空间控制
XSD验证不能替代输入过滤
XSD能约束结构和类型,但防不住<username>admin' OR '1'='1</username>这种合法XML内容里的SQL元字符。它只管“像不像”,不管“害不害”。很多团队花大力气写XSD,结果上线后仍被报错注入打穿。
性能影响:完整XSD验证(尤其带xs:pattern正则)比简单DOM解析慢3–5倍,高并发下易成瓶颈。
- 必须配合白名单过滤:对提取出的每个文本节点(如
username、email),额外做长度限制+字符集检查(例如邮箱只允许[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}) - 避免在XSD里用
xs:pattern匹配复杂业务规则——正则引擎本身可能被恶意长字符串拖垮(ReDoS) - 生产环境禁用
javax.xml.validation.Validator#validate的setResourceResolver,防止攻击者通过xi:include加载远程XSD
预编译要覆盖到XPath表达式本身
开发者常误以为“用了PreparedStatement就安全”,却忽略XPath字符串本身也是用户可控输入。比如UPDATEXML(data, ?, 'newval')中第二个参数若来自前端,照样能注入CONCAT('~', DATABASE())。
容易踩的坑:把XPath硬编码成'/user/name'就觉得没问题,但实际代码里可能是"/" + userInput + "/name"拼出来的。
- XPath应视为不可信输入,要么完全静态(如固定路径
/order/items),要么经严格白名单校验(仅允许[a-zA-Z0-9/_]且长度 - MySQL中优先用
XMLTABLE(8.0.16+)替代UPDATEXML,它要求XPath为字面量,无法参数化,反而从语法上杜绝了动态XPath注入 - PostgreSQL的
xmltable()同样不接受参数化XPath,且默认禁用外部实体,比MySQL更省心
真正难缠的是那些既需要灵活XPath又得处理不可信XML的场景——这时候别硬扛,换JSON,或者把XML解析逻辑移到应用层,数据库只存解析后的结构化字段。

















