Add XML vulnerability checking

This adds an XML plug-in based on the documentation an defusedxml.

Change-Id: Id775cd0f3d45fd2e9dac1c5bca5c36e0b5618066
This commit is contained in:
Rob Fletcher 2015-04-24 09:58:26 -07:00
parent 6e2e387e4f
commit 18285c1bb9
12 changed files with 673 additions and 0 deletions

279
bandit/plugins/xml.py Normal file
View File

@ -0,0 +1,279 @@
# -*- coding:utf-8 -*-
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Most of this file is based off of Christian Heimes' work on defusedxml:
# https://pypi.python.org/pypi/defusedxml/#defusedxml-sax
import bandit
from bandit.core.test_properties import *
###############################################################################
# check function calls
###############################################################################
@checks('Call')
def etree_celement_function_calls(context):
if type(context.call_function_name_qual) == str:
qlist = context.call_function_name_qual.split('.')
qual = '.'.join(qlist[:-1])
func = qlist[-1]
blacklist = ['parse', 'iterparse', 'fromstring', 'XMLParser']
if 'xml.etree.cElementTree' == qual and func in blacklist:
s = ("Using xml.etree.cElementTree.%s to parse untrusted XML data"
" is known to be vulnerable to XML attacks. Replace "
" xml.etree.cElementTree.%s with defusedxml.cElementTree.%s"
" function.")
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text=s % (func, func, func))
@checks('Call')
def etree_element_function_calls(context):
if type(context.call_function_name_qual) == str:
qlist = context.call_function_name_qual.split('.')
qual = '.'.join(qlist[:-1])
func = qlist[-1]
blacklist = ['parse', 'iterparse', 'fromstring', 'XMLParser']
if 'xml.etree.ElementTree' == qual and func in blacklist:
s = ("Using xml.etree.ElementTree.%s to parse untrusted XML data"
" is known to be vulnerable to XML attacks. Replace"
" xml.etree.ElementTree.%s with defusedxml.ElementTree.%s"
" function.")
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text=s % (func, func, func))
@checks('Call')
def expatreader_function_calls(context):
if type(context.call_function_name_qual) == str:
qlist = context.call_function_name_qual.split('.')
qual = '.'.join(qlist[:-1])
func = qlist[-1]
blacklist = ['create_parser']
if 'xml.sax.expatreader' == qual and func in blacklist:
s = ("Using xml.sax.expatreader.%s to parse untrusted XML data is"
" known to be vulnerable to XML attacks. Replace"
" xml.sax.expatreader.%s with defusedxml.expatreader.%s"
" function.")
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text=s % (func, func, func))
@checks('Call')
def expatbuilder_function_calls(context):
if type(context.call_function_name_qual) == str:
qlist = context.call_function_name_qual.split('.')
qual = '.'.join(qlist[:-1])
func = qlist[-1]
blacklist = ['parse', 'parseString']
if 'xml.dom.expatbuilder' == qual and func in blacklist:
s = ("Using xml.dom.expatbuilder.%s to parse untrusted XML data"
" is known to be vulnerable to XML attacks. Replace"
" xml.dom.expatbuilder.%s with defusedxml.expatbuilder.%s"
" function.")
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text=s % (func, func, func))
@checks('Call')
def sax_function_calls(context):
if type(context.call_function_name_qual) == str:
qlist = context.call_function_name_qual.split('.')
qual = '.'.join(qlist[:-1])
func = qlist[-1]
blacklist = ['parse', 'parseString', 'make_parser']
if 'xml.sax' == qual and func in blacklist:
s = ("Using xml.sax.%s to parse untrusted XML data is known to"
" be vulnerable to XML attacks. Replace xml.sax.%s with"
" defusedxml.sax.%s function.")
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text=s % (func, func, func))
@checks('Call')
def minidom_function_calls(context):
if type(context.call_function_name_qual) == str:
qlist = context.call_function_name_qual.split('.')
qual = '.'.join(qlist[:-1])
func = qlist[-1]
blacklist = ['parse', 'parseString']
if 'xml.dom.minidom' == qual and func in blacklist:
s = ("Using xml.dom.minidom.%s to parse untrusted XML data is"
" known to be vulnerable to XML attacks. Replace"
" xml.dom.minidom.%s with defusedxml.minidom.%s"
" function.")
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text=s % (func, func, func))
@checks('Call')
def pulldom_function_calls(context):
if type(context.call_function_name_qual) == str:
qlist = context.call_function_name_qual.split('.')
qual = '.'.join(qlist[:-1])
func = qlist[-1]
blacklist = ['parse', 'parseString']
if 'xml.dom.pulldom' == qual and func in blacklist:
s = ("Using xml.dom.pulldom.%s to parse untrusted XML data is"
" known to be vulnerable to XML attacks. Replace"
" xml.dom.pulldom.%s with defusedxml.pulldom.%s function.")
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text=s % (func, func, func))
@checks('Call')
def lxml_function_calls(context):
if type(context.call_function_name_qual) == str:
qlist = context.call_function_name_qual.split('.')
qual = '.'.join(qlist[:-1])
func = qlist[-1]
blacklist = ['parse', 'fromstring', 'RestrictedElement',
'GlobalParserTLS', 'getDefaultParser', 'check_docinfo']
if 'lxml.etree' == qual and func in blacklist:
s = ("Using lxml.etree.%s to parse untrusted XML data is"
" known to be vulnerable to XML attacks. Replace"
" lxml.etree.%s with defused.lxml.%s function.")
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text=s % (func, func, func))
###############################################################################
# check imports
###############################################################################
@checks('Import', 'ImportFrom')
def etree_celement_import(context):
if context.is_module_being_imported('xml.etree.cElementTree'):
s = ("Using xml.etree.cElementTree to parse untrusted XML data is"
" known to be vulnerable to XML attacks. Replace"
" xml.etree.cElementTree with defusedxml.cElementTree package.")
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
text=s)
@checks('Import', 'ImportFrom')
def etree_element_import(context):
if context.is_module_being_imported('xml.etree.ElementTree'):
s = ("Using xml.etree.ElementTree to parse untrusted XML data is"
" known to be vulnerable to XML attacks. Replace"
" xml.etree.ElementTree with defusedxml.ElementTree package.")
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
text=s)
@checks('Import', 'ImportFrom')
def expatreader_import(context):
if context.is_module_being_imported('xml.sax.expatreader'):
s = ("Using xml.sax.expatreader to parse untrusted XML data is known"
" to be vulnerable to XML attacks. Replace xml.sax.expatreader"
" with defusedxml.expatreader package.")
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
text=s)
@checks('Import', 'ImportFrom')
def sax_import(context):
if context.is_module_being_imported('xml.sax'):
s = ("Using xml.sax to parse untrusted XML data is known to be"
" vulnerable to XML attacks. Replace xml.sax with defusedxml.sax"
" package.")
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
text=s)
@checks('Import', 'ImportFrom')
def expatbuilder_import(context):
if context.is_module_being_imported('xml.dom.expatbuilder'):
s = ("Using xml.dom.expatbuilder to parse untrusted XML data is known"
" to be vulnerable to XML attacks. Replace xml.dom.expatbuilder"
" with defusedxml.expatbuilder package.")
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
text=s)
@checks('Import', 'ImportFrom')
def minidom_import(context):
if context.is_module_being_imported('xml.dom.minidom'):
s = ("Using xml.dom.minidom to parse untrusted XML data is known to be"
" vulnerable to XML attacks. Replace xml.dom.minidom with"
" defusedxml.minidom package.")
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
text=s)
@checks('Import', 'ImportFrom')
def pulldom_import(context):
if context.is_module_being_imported('xml.dom.pulldom'):
s = ("Using xml.dom.pulldom to parse untrusted XML data is known to be"
" vulnerable to XML attacks. Replace xml.dom.pulldom with"
" defusedxml.pulldom package.")
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
text=s)
# this one is 'HIGH' instead of 'LOW' because we know the entire package has
# to be monkeypatched via defusedxml.xmlrpc.monkey_patch()
@checks('Import', 'ImportFrom')
def xmlrpclib_import(context):
if context.is_module_being_imported('xmlrpclib'):
s = ("Using xmlrpclib to parse untrusted XML data is known to be"
" vulnerable to XML attacks. Use defused.xmlrpc.monkey_patch()"
" function to monkey-patch xmlrpclib and mitigate XML"
" vulnerabilities.")
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.HIGH,
text=s)
@checks('Import', 'ImportFrom')
def lxml_import(context):
if(context.is_module_being_imported('lxml.etree') or
context.is_module_being_imported('lxml')):
s = ("Using lxml.etree to parse untrusted XML data is known to be"
" vulnerable to XML attacks. Replace lxml.etree with"
" defused.lxml package.")
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
text=s)

219
docs/xml.md Normal file
View File

@ -0,0 +1,219 @@
Use safe XML libraries to avoid XML vulnerabilities
=====================
XML vulnerabilities are known and well studied. The [defusedxml](https://pypi.python.org/pypi/defusedxml/) library provides a great synposis of XML vulnerabilities, how they're exploited, and which Python libraries are vulnerable to which attacks.
Most XML vulnerabilities essentially amount to Denial of Service attacks but as [previous blackhat presentations](https://media.blackhat.com/eu-13/briefings/Osipov/bh-eu-13-XML-data-osipov-slides.pdf) have shown, XML vulnerabilities can lead to local file reading, intranet access, and some times remote code execution.
We don't attempt to rehash the details of each vulnerability class and instead recommend those interested read [defuxedxml](https://pypi.python.org/pypi/defusedxml/)'s page, including references.
### Incorrect
Currently, the following Python XML libraries are vulnerable to some form of XML attack:
* [xml.sax](https://docs.python.org/2/library/xml.sax.html)
- vulnerable to: billion laughs, quadratic blowup, external entity expansion, DTD retrieval
* [xml.etree.ElementTree](https://docs.python.org/2/library/xml.etree.elementtree.html)
- vulnerable to: billion laughs, quadratic blowup
* [xml.dom.minidom](https://docs.python.org/2/library/xml.dom.minidom.html)
- vulnerable to: billion laughs, quadratic blowup
* [xml.dom.pulldom](https://docs.python.org/2/library/xml.dom.pulldom.html)
- vulnerable to: billion laughs, quadratic blowup, external entity expansion, DTD retrieval
* [xmlrpclib](https://docs.python.org/2/library/xmlrpclib.html)
- vulnerable to: billion laughs, quadratic blowup, decompression bomb
[Python's XML library page](https://docs.python.org/2/library/xml.html#xml-vulnerabilities) indicates that [defusedxml](https://pypi.python.org/pypi/defusedxml/) is the correct choice for XML libraries.
### Correct
#### xml.sax
Replace all xml.sax parsers with defusedxml parsers:
* ```xml.sax.parser()``` -> ```defusedxml.sax.parser()```
* ```xml.sax.parseString()``` -> ```defusedxml.sax.parseString()```
* ```xml.sax.create_parser()``` -> ```defusedxml.sax.parseString()```
Intead of this:
```python
import xml.sax
class ExampleContentHandler(xml.sax.ContentHandler):
def __init__(self):
xml.sax.ContentHandler.__init__(self)
def startElement(self, name, attrs):
print 'start:', name
def endElement(self, name):
print 'end:', name
def characters(self, content):
print 'chars:', content
def main():
xml.sax.parse(open('input.xml'), ExampleContentHandler())
if __name__ == "__main__":
main()
```
Do this:
```python
import xml.sax
import defusedxml.sax
class ExampleContentHandler(xml.sax.ContentHandler):
def __init__(self):
xml.sax.ContentHandler.__init__(self)
def startElement(self, name, attrs):
print 'start:', name
def endElement(self, name):
print 'end:', name
def characters(self, content):
print 'chars:', content
def main():
defusedxml.sax.parse(open('input.xml'), ExampleContentHandler())
if __name__ == "__main__":
main()
```
#### xml.etree.ElementTree
Replace the following instances of xml.etree.ElementTree functions with the corresponding defusedxml functions:
* ```xml.etree.ElementTree.parse()``` -> ```defusedxml.ElementTree.parse()```
* ```xml.etree.ElementTree.iterparse()``` -> ```defusedxml.ElementTree.iterparse()```
* ```xml.etree.ElementTree.fromstring()``` -> ```defusedxml.ElementTree.fromstring()```
* ```xml.etree.ElementTree.XMLParser``` -> ```defusedxml.ElementTree.XMLParser```
Intead of this:
```python
import xml.etree.ElementTree as ET
tree = ET.parse("input.xml")
root = tree.getroot()
```
Do this:
```python
import defusedxml.ElementTree as ET
tree = ET.parse("input.xml")
root = tree.getroot()
```
#### xml.etree.cElementTree
Replace the following instances of xml.etree.cElementTree functions with the corresponding defusedxml functions:
* ```xml.etree.cElementTree.parse()``` -> ```defusedxml.cElementTree.parse()```
* ```xml.etree.cElementTree.iterparse()``` -> ```defusedxml.cElementTree.iterparse()```
* ```xml.etree.cElementTree.fromstring()``` -> ```defusedxml.cElementTree.fromstring()```
* ```xml.etree.cElementTree.XMLParser``` -> ```defusedxml.cElementTree.XMLParser```
Intead of this:
```python
import xml.etree.cElementTree as ET
tree = ET.parse("input.xml")
root = tree.getroot()
```
Do this:
```python
import defusedxml.cElementTree as ET
tree = ET.parse("input.xml")
root = tree.getroot()
```
#### xml.dom.minidom
Replace the following instances of xml.dom.minidom functions with the corresponding defusedxml functions:
* ```xml.dom.minidom.parse()``` -> ```defusedxml.minidom.parse()```
* ```xml.dom.minidom.parseString()``` -> ```defusedxml.minidom.parseString()```
Intead of this:
```python
from xml.dom.minidom import parseString
parseString('<myxml>Some data<empty/> some more data</myxml>')
```
Do this:
```python
from defusedxml.minidom import parseString
parseString('<myxml>Some data<empty/> some more data</myxml>')
```
#### xml.dom.pulldom
Replace the following instances of xml.dom.pulldom functions with the corresponding defusedxml functions:
* ```xml.dom.pulldom.parse()``` -> ```defusedxml.pulldom.parse()```
* ```xml.dom.pulldom.parseString()``` -> ```defusedxml.pulldom.parseString()```
Intead of this:
```python
from xml.dom.pulldom import parseString
parseString('<myxml>Some data<empty/> some more data</myxml>')
```
Do this:
```python
from defusedxml.pulldom import parseString
parseString('<myxml>Some data<empty/> some more data</myxml>')
```
#### xmlrpclib
Taken directly from the defusedxml page:
"The function monkey_patch() enables the fixes, unmonkey_patch() removes the patch and puts the code in its former state."
Intead of this:
```python
from xmlrpclib import ServerProxy, Error
server = ServerProxy("http://betty.userland.com")
print server
try:
print server.examples.getStateName(41)
except Error as v:
print "ERROR", v
```
Do this:
```python
from xmlrpclib import ServerProxy, Error
import defusedxml.xmlrpc
defusedxml.xmlrpc.monkey_patch()
server = ServerProxy("http://betty.userland.com")
print server
try:
print server.examples.getStateName(41)
except Error as v:
print "ERROR", v
```
#### lxml.etree
Replace the following instances of lxml functions with the corresponding defusedxml functions:
* ```lxml.etree.parse()``` -> ```defusedxml.lxml.parse```
* ```lxml.etree.fromstring()``` -> ```defusedxml.lxml.fromstring()```
* ```lxml.etree.RestrictedElement()``` -> ```defusedxml.lxml.RestrictedElement()```
* ```lxml.etree.getDefaultParser()``` -> ```defusedxml.lxml.getDefaultParser()```
* ```lxml.etree.check_docinfo()``` -> ```defusedxml.lxml.check_docinfo()```
Intead of this:
```python
from lxml import etree
root = etree.parse('input.xml')
```
Do this:
```python
from defusedxml.lxml import parse
root = parse('input.xml')
```
## References
* https://pypi.python.org/pypi/defusedxml/
* https://media.blackhat.com/eu-13/briefings/Osipov/bh-eu-13-XML-data-osipov-slides.pdf
* https://docs.python.org/2/library/xml.sax.html
* https://docs.python.org/2/library/xml.etree.elementtree.html
* https://docs.python.org/2/library/xml.dom.minidom.html
* https://docs.python.org/2/library/xml.dom.pulldom.html
* https://docs.python.org/2/library/xmlrpclib.html
* https://docs.python.org/2/library/xml.html#xml-vulnerabilities

View File

@ -0,0 +1,18 @@
import xml.etree.cElementTree as badET
import defusedxml.cElementTree as goodET
xmlString = "<note>\n<to>Tove</to>\n<from>Jani</from>\n<heading>Reminder</heading>\n<body>Don't forget me this weekend!</body>\n</note>"
# unsafe
tree = badET.fromstring(xmlString)
print tree
badET.parse('filethatdoesntexist.xml')
badET.iterparse('filethatdoesntexist.xml')
a = badET.XMLParser()
# safe
tree = goodET.fromstring(xmlString)
print tree
goodET.parse('filethatdoesntexist.xml')
goodET.iterparse('filethatdoesntexist.xml')
a = goodET.XMLParser()

View File

@ -0,0 +1,18 @@
import xml.etree.ElementTree as badET
import defusedxml.ElementTree as goodET
xmlString = "<note>\n<to>Tove</to>\n<from>Jani</from>\n<heading>Reminder</heading>\n<body>Don't forget me this weekend!</body>\n</note>"
# unsafe
tree = badET.fromstring(xmlString)
print tree
badET.parse('filethatdoesntexist.xml')
badET.iterparse('filethatdoesntexist.xml')
a = badET.XMLParser()
# safe
tree = goodET.fromstring(xmlString)
print tree
goodET.parse('filethatdoesntexist.xml')
goodET.iterparse('filethatdoesntexist.xml')
a = goodET.XMLParser()

View File

@ -0,0 +1,10 @@
import xml.dom.expatbuilder as bad
import defusedxml.expatbuilder as good
bad.parse('filethatdoesntexist.xml')
good.parse('filethatdoesntexist.xml')
xmlString = "<note>\n<to>Tove</to>\n<from>Jani</from>\n<heading>Reminder</heading>\n<body>Don't forget me this weekend!</body>\n</note>"
bad.parseString(xmlString)
good.parseString(xmlString)

View File

@ -0,0 +1,5 @@
import xml.sax.expatreader as bad
import defusedxml.expatreader as good
p = bad.create_parser()
b = good.create_parser()

9
examples/xml_lxml.py Normal file
View File

@ -0,0 +1,9 @@
import lxml.etree
import lxml
from lxml import etree
from defusedxml.lxml import fromstring
from defuxedxml import lxml as potatoe
xmlString = "<note>\n<to>Tove</to>\n<from>Jani</from>\n<heading>Reminder</heading>\n<body>Don't forget me this weekend!</body>\n</note>"
root = lxml.etree.fromstring(xmlString)
root = fromstring(xmlString)

14
examples/xml_minidom.py Normal file
View File

@ -0,0 +1,14 @@
from xml.dom.minidom import parseString as badParseString
from defusedxml.minidom import parseString as goodParseString
a = badParseString("<myxml>Some data some more data</myxml>")
print a
b = goodParseString("<myxml>Some data some more data</myxml>")
print b
from xml.dom.minidom import parse as badParse
from defusedxml.minidom import parse as goodParse
a = badParse("somfilethatdoesntexist.xml")
print a
b = goodParse("somefilethatdoesntexist.xml")
print b

14
examples/xml_pulldom.py Normal file
View File

@ -0,0 +1,14 @@
from xml.dom.pulldom import parseString as badParseString
from defusedxml.pulldom import parseString as goodParseString
a = badParseString("<myxml>Some data some more data</myxml>")
print a
b = goodParseString("<myxml>Some data some more data</myxml>")
print b
from xml.dom.pulldom import parse as badParse
from defusedxml.pulldom import parse as goodParse
a = badParse("somfilethatdoesntexist.xml")
print a
b = goodParse("somefilethatdoesntexist.xml")
print b

37
examples/xml_sax.py Normal file
View File

@ -0,0 +1,37 @@
import xml.sax
from xml import sax
import defusedxml.sax
class ExampleContentHandler(xml.sax.ContentHandler):
def __init__(self):
xml.sax.ContentHandler.__init__(self)
def startElement(self, name, attrs):
print 'start:', name
def endElement(self, name):
print 'end:', name
def characters(self, content):
print 'chars:', content
def main():
xmlString = "<note>\n<to>Tove</to>\n<from>Jani</from>\n<heading>Reminder</heading>\n<body>Don't forget me this weekend!</body>\n</note>"
# bad
xml.sax.parseString(xmlString, ExampleContentHandler())
xml.sax.parse('notaxmlfilethatexists.xml', ExampleContentHandler())
sax.parseString(xmlString, ExampleContentHandler())
sax.parse('notaxmlfilethatexists.xml', ExampleContentHandler)
# good
defusedxml.sax.parseString(xmlString, ExampleContentHandler())
# bad
xml.sax.make_parser()
sax.make_parser()
print 'nothing'
# good
defusedxml.sax.make_parser()
if __name__ == "__main__":
main()

10
examples/xml_xmlrpc.py Normal file
View File

@ -0,0 +1,10 @@
import xmlrpclib
from SimpleXMLRPCServer import SimpleXMLRPCServer
def is_even(n):
return n%2 == 0
server = SimpleXMLRPCServer(("localhost", 8000))
print "Listening on port 8000..."
server.register_function(is_even, "is_even")
server.serve_forever()

View File

@ -68,6 +68,8 @@ class FunctionalTests(unittest.TestCase):
:param example_script: Filename of an example script to test
:param expect: dict with expected counts of issue types
'''
# reset scores for subsequent calls to check_example
self.b_mgr.scores = []
self.run_example(example_script)
expected = 0
result = 0
@ -302,3 +304,41 @@ class FunctionalTests(unittest.TestCase):
'''Test Mako templates for XSS.'''
expect = {'SEVERITY': {'MEDIUM': 3}, 'CONFIDENCE': {'HIGH': 3}}
self.check_example('mako_templating.py', expect)
def test_xml(self):
'''Test xml vulnerabilities.'''
expect = {'SEVERITY': {'LOW': 1, 'HIGH': 4},
'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 4}}
self.check_example('xml_etree_celementtree.py', expect)
expect = {'SEVERITY': {'LOW': 1, 'HIGH': 2},
'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 2}}
self.check_example('xml_expatbuilder.py', expect)
expect = {'SEVERITY': {'LOW': 3, 'HIGH': 1},
'CONFIDENCE': {'HIGH': 3, 'MEDIUM': 1}}
self.check_example('xml_lxml.py', expect)
expect = {'SEVERITY': {'LOW': 2, 'HIGH': 2},
'CONFIDENCE': {'HIGH': 2, 'MEDIUM': 2}}
self.check_example('xml_pulldom.py', expect)
expect = {'SEVERITY': {'HIGH': 1},
'CONFIDENCE': {'HIGH': 1}}
self.check_example('xml_xmlrpc.py', expect)
expect = {'SEVERITY': {'LOW': 1, 'HIGH': 4},
'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 4}}
self.check_example('xml_etree_elementtree.py', expect)
expect = {'SEVERITY': {'LOW': 1, 'HIGH': 1},
'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 1}}
self.check_example('xml_expatreader.py', expect)
expect = {'SEVERITY': {'LOW': 2, 'HIGH': 2},
'CONFIDENCE': {'HIGH': 2, 'MEDIUM': 2}}
self.check_example('xml_minidom.py', expect)
expect = {'SEVERITY': {'LOW': 1, 'HIGH': 6},
'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 6}}
self.check_example('xml_sax.py', expect)