详解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

看了各种资料,写代码做了一些测试,得出如下结论:

  1. 在XML中,本身是对属性没有顺序(order)要求的,也就是XML标准中认为XML的顺序无意义

  2. 只有把XML写到.XML文件中,属性的顺序是对最终XML的表现形式(hash值)有影响

  3. 往大一点说,这个问题属于XML规范化(XML Canonicalization,c14n)问题,W3C对这个c14n有专门的定义(见参考2)

  4. 然而,并没有什么成熟的python的库来实现c14n。有一些个人实现的c14n就是对tag做了规范化(简单的replace)。有一些库的老版本做过c14n但后来又deprecated了。其他一些搜到的方法也跑不通

  5. 根据如下链接,发现了一点蛛丝马迹:貌似 xml.etree.ElementTree 写xml到文件中时,会改变xml的attributes的顺序,而且根据回复看这个是将结果变为有序的。详见参考3.

3. 测试

  1. 属性无序的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>
  1. 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文件,然后直接写到另一个文件中。

  1. 结果

程序运行后,得到的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. 为什么能做到有序

  1. 动态调试源码

单纯通过这样简单的验证,是不敢保证每一次这样操作都是有序的。有可能是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. 结论

  1. 用xml.etree.ElementTree来解析manifest文件,这个库在将xml写到外部文件时,会对属性排序后再写出去
  2. 很多其他库解析manifest会保持原始的attribute顺序,比如lxml

6. 参考

  1. 如何动态调试Python的第三方库,https://blog.csdn.net/ybdesire/article/details/54649211
  2. XML Canonicalization, https://www.w3.org/TR/xml-exc-c14n/
  3. https://stackoverflow.com/questions/14257978/elementtree-setting-attribute-order

以上是关于详解XML节点属性排序的主要内容,如果未能解决你的问题,请参考以下文章

详解SimpleXML添加_修改_删除_遍历XML节点属性

Xpath 详解

solr schema.xml Field属性详解

详解堆排序算法

算法知识详解堆排序算法

logback logback.xml 常用配置详解