详解XML节点属性排序
Posted ybdesire
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解XML节点属性排序相关的知识,希望对你有一定的参考价值。
1. 引入
为了对xml文件计算hash值,需要固定xml中item的属性顺序。否则相同xml但属性顺序不同,就会造成hash值不同(MITRE的Defense Evasion逃脱检测)。
就是想让如下两种写法: <a x="11" b="12" a="13"></a>
和 <a x="11" b="12" a="13"></a>
,经过属性排序后,都变为 <a a="13" b="12" x="11"></a>
。注意这里要解决的问题是对xml节点内部的属性排序,不是节点之间的排序。
如何用python实现“对xml节点中的属性进行排序”呢?
2. 问题分析
为了解决这个问题,查了xml解析的各种库,Google搜了关键词:
- sort xml attributes alphabetically
- canonical XML python
- xml attribute sort python
- python sort xml elements alphabetically
- python xml attribute order
- sort android manifest by python
- python write xml with attribute order
- python enforcing specific order for xml attributes
- python enforcing order for xml attributes
- python enforcing xml attributes order
- xml c14n
看了各种资料,写代码做了一些测试,得出如下结论:
-
在XML中,本身是对属性没有顺序(order)要求的,也就是XML标准中认为XML的顺序无意义
-
只有把XML写到.XML文件中,属性的顺序是对最终XML的表现形式(hash值)有影响
-
往大一点说,这个问题属于XML规范化(XML Canonicalization,c14n)问题,W3C对这个c14n有专门的定义(见参考2)
-
然而,并没有什么成熟的python的库来实现c14n。有一些个人实现的c14n就是对tag做了规范化(简单的replace)。有一些库的老版本做过c14n但后来又deprecated了。其他一些搜到的方法也跑不通
-
根据如下链接,发现了一点蛛丝马迹:貌似
xml.etree.ElementTree
写xml到文件中时,会改变xml的attributes的顺序,而且根据回复看这个是将结果变为有序的。详见参考3.
3. 测试
- 属性无序的xml文件
创建一个名为easy1.xml
的文件,内容如下,
<a x="11" b="12" a="13">
<b y="21" c="22" d="23">
<c t="31" f="32" e="33">
</c>
</b>
</a>
- python程序
import xml.etree.ElementTree as ET
tree = ET.parse('easy1.xml')# read xml from file
tree.write('tmp1.xml', encoding="UTF-8")# write xml content to file
程序只做了很简单的事情,读入xml文件,然后直接写到另一个文件中。
- 结果
程序运行后,得到的tmp1.xml
文件中的内容如下
<a a="13" b="12" x="11">
<b c="22" d="23" y="21">
<c e="33" f="32" t="31">
</c>
</b>
</a>
可见最终结果是和原始easy1.xml
文件内容不一样的,这里的各个节点内部的属性都变的有序了。
所以验证了第2部分第5个结论。
4. 为什么能做到有序
- 动态调试源码
单纯通过这样简单的验证,是不敢保证每一次这样操作都是有序的。有可能是ElementTree将xml解析为dict,这样因为dict的无序性,上一步的测试结果可能就是意外。
所以还需要查看下2中的源码实现原理,看看源码中是否做了属性有序的保证。通过(见参考1)中方法,找到如下源码:
- /home/xxx/anaconda3/envs/xxxxx/lib/python3.7/xml/etree/ElementTree.py
打开这个文件,在其函数_serialize_xml
中,如下位置,加入断点(或者print)进行动态调试:
if items or namespaces:
if namespaces:
for v, k in sorted(namespaces.items(),
key=lambda x: x[1]): # sort on prefix
if k:
k = ":" + k
write(" xmlns%s=\\"%s\\"" % (
k,
_escape_attrib(v)
))
print(items)#这里加print调试
for k, v in sorted(items): # lexical order
if isinstance(k, QName):
k = k.text
if isinstance(v, QName):
v = qnames[v.text]
else:
v = _escape_attrib(v)
write(" %s=\\"%s\\"" % (qnames[k], v))
运行3中的python程序,可得输出结果(去掉非list的内容,因为这里只有sorted只对list有效)为:
[('x', '11'), ('b', '12'), ('a', '13')]
[('y', '21'), ('c', '22'), ('d', '23')]
[('t', '31'), ('f', '32'), ('e', '33')]
这里的输出结果,就是源码items的内容,从结果上也能看到这都是xml节点的属性。通过sorted排序后,这些属性就被写到外部文件中了。
所以,从源码调试中,验证了ElementTree
的write操作,是会对xml的节点属性进行排序的,排序规则是(sorted函数自带的lexical order,也就是按字符串排序)。
5. 结论
- 用xml.etree.ElementTree来解析manifest文件,这个库在将xml写到外部文件时,会对属性排序后再写出去
- 很多其他库解析manifest会保持原始的attribute顺序,比如lxml
6. 参考
- 如何动态调试Python的第三方库,https://blog.csdn.net/ybdesire/article/details/54649211
- XML Canonicalization, https://www.w3.org/TR/xml-exc-c14n/
- https://stackoverflow.com/questions/14257978/elementtree-setting-attribute-order
以上是关于详解XML节点属性排序的主要内容,如果未能解决你的问题,请参考以下文章