Add XML vulnerability checking
This adds an XML plug-in based on the documentation an defusedxml. Change-Id: Id775cd0f3d45fd2e9dac1c5bca5c36e0b5618066
This commit is contained in:
279
bandit/plugins/xml.py
Normal file
279
bandit/plugins/xml.py
Normal 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
219
docs/xml.md
Normal 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
|
||||||
18
examples/xml_etree_celementtree.py
Normal file
18
examples/xml_etree_celementtree.py
Normal 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()
|
||||||
18
examples/xml_etree_elementtree.py
Normal file
18
examples/xml_etree_elementtree.py
Normal 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()
|
||||||
10
examples/xml_expatbuilder.py
Normal file
10
examples/xml_expatbuilder.py
Normal 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)
|
||||||
5
examples/xml_expatreader.py
Normal file
5
examples/xml_expatreader.py
Normal 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
9
examples/xml_lxml.py
Normal 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
14
examples/xml_minidom.py
Normal 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
14
examples/xml_pulldom.py
Normal 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
37
examples/xml_sax.py
Normal 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
10
examples/xml_xmlrpc.py
Normal 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()
|
||||||
@@ -68,6 +68,8 @@ class FunctionalTests(unittest.TestCase):
|
|||||||
:param example_script: Filename of an example script to test
|
:param example_script: Filename of an example script to test
|
||||||
:param expect: dict with expected counts of issue types
|
: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)
|
self.run_example(example_script)
|
||||||
expected = 0
|
expected = 0
|
||||||
result = 0
|
result = 0
|
||||||
@@ -302,3 +304,41 @@ class FunctionalTests(unittest.TestCase):
|
|||||||
'''Test Mako templates for XSS.'''
|
'''Test Mako templates for XSS.'''
|
||||||
expect = {'SEVERITY': {'MEDIUM': 3}, 'CONFIDENCE': {'HIGH': 3}}
|
expect = {'SEVERITY': {'MEDIUM': 3}, 'CONFIDENCE': {'HIGH': 3}}
|
||||||
self.check_example('mako_templating.py', expect)
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user