From 2360ba200d020bae6a8f2a725ae9402b7f3c8baf Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 5 May 2014 15:41:00 +0200 Subject: [PATCH 01/58] Added UTF7_check --- test/test_utf7.py | 6 ++++++ test/test_utf7_check.py | 20 ++++++++++++++++++++ webvulnscan/attacks/__init__.py | 3 ++- webvulnscan/attacks/utf7.py | 1 + webvulnscan/attacks/utf7_check.py | 18 ++++++++++++++++++ 5 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 test/test_utf7.py create mode 100644 test/test_utf7_check.py create mode 100644 webvulnscan/attacks/utf7.py create mode 100644 webvulnscan/attacks/utf7_check.py diff --git a/test/test_utf7.py b/test/test_utf7.py new file mode 100644 index 0000000..12b16cd --- /dev/null +++ b/test/test_utf7.py @@ -0,0 +1,6 @@ +import unittest +import tutil +import webvulnscan.attacks.utf7 + +class UTF7Test(unittest.TestCase): + pass \ No newline at end of file diff --git a/test/test_utf7_check.py b/test/test_utf7_check.py new file mode 100644 index 0000000..9d90d18 --- /dev/null +++ b/test/test_utf7_check.py @@ -0,0 +1,20 @@ +import unittest +import tutil +import webvulnscan.attacks.utf7_check + + +class UTF7_checkTest(unittest.TestCase): + attack = webvulnscan.attacks.utf7_check + + @tutil.webtest(False) + def test_utf7_check_static_site(): + return{ + '/': u'''''', + } + + @tutil.webtest(True) + def test_utf7_check_dangerous(): + return{'/': ( + 200, b'''''', + {'Content-Type': 'text/html; charset=UTF-7'}), + } diff --git a/webvulnscan/attacks/__init__.py b/webvulnscan/attacks/__init__.py index 4af52c1..2bd8d8b 100644 --- a/webvulnscan/attacks/__init__.py +++ b/webvulnscan/attacks/__init__.py @@ -6,7 +6,8 @@ from .clickjack import clickjack from .cookiescan import cookiescan from .exotic_characters import exotic_characters +from .utf7_check import utf7_check def all_attacks(): - return [xss, csrf, crlf, breach, clickjack, cookiescan, exotic_characters] + return [xss, csrf, crlf, breach, clickjack, cookiescan, exotic_characters,utf7_check] diff --git a/webvulnscan/attacks/utf7.py b/webvulnscan/attacks/utf7.py new file mode 100644 index 0000000..1cfe99a --- /dev/null +++ b/webvulnscan/attacks/utf7.py @@ -0,0 +1 @@ +from ..utils import attack \ No newline at end of file diff --git a/webvulnscan/attacks/utf7_check.py b/webvulnscan/attacks/utf7_check.py new file mode 100644 index 0000000..f25f290 --- /dev/null +++ b/webvulnscan/attacks/utf7_check.py @@ -0,0 +1,18 @@ +from ..utils import attack + + +@attack() +def utf7_check(client, log, page): + content_type = page.headers['Content-Type'] + if ';' in content_type: + # Use Content-Type to get the charset + charset = content_type.split(';')[1].split('=')[1] + # If charset is not UTF-7 there is nothing to do + if charset != 'utf-7' and charset != 'UTF-7': + return + else: + # If charset is UTF-7 give warning + log('vuln', page.url, 'UTF-7', + 'Website uses UTF-7') + else: + return From 2faf06d2274420f8c8f40cb2000403c9b7877099 Mon Sep 17 00:00:00 2001 From: garet12 Date: Thu, 5 Jun 2014 17:11:56 +0200 Subject: [PATCH 02/58] First version of simple XML parser (does not work yet) --- test/test_billion_laughs.py | 62 +++++++++++++++++++++++++++++++++++++ test/testdoc | 28 +++++++++++++++++ test/testdoc~ | 28 +++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 test/test_billion_laughs.py create mode 100644 test/testdoc create mode 100644 test/testdoc~ diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py new file mode 100644 index 0000000..abb1669 --- /dev/null +++ b/test/test_billion_laughs.py @@ -0,0 +1,62 @@ +import unittest +import tutil +import re +from collections import Counter + + +def XMLParser(xml): + counter = 0 + entity = dict() + if isinstance(xml, file): + xml = xml.read() + # Get the entity names and store them as keys + entity_lines = re.findall(r"\<\!ENTITY(.*)\>", xml) + print entity_lines + # Get the entity values and count different elements of the value + for elements in entity_lines: + if "&" in elements.split(" ")[2] and ";" in elements.split(" ")[2]: + keyvalues = re.findall(r"\&(\w+)\;", elements.split(" ")[2]) + else: + keyvalues = re.findall(r"\"(\w+)\"", elements.split(" ")[2]) + entity.update({elements.split(" ")[1]: [keyvalues]}) + value_count = Counter(keyvalues) + entity[elements.split(" ")[1]].append(value_count.items()) + print entity + # print entity_lines + # Go through every line of the XML File + for lines in xml.split("\n"): + print lines + # Count every character + counter += len(lines) + # print entity + for keys in entity.keys(): + # Check whether any of the stored keys can be found in one line + linekeys = re.findall(r"\&(\w+)\;", lines) + # If a stored key could be found in one of the lines and it is not + # an entity declaration, it is probably an entity reference + if keys in linekeys and "!ENTITY" not in lines: + # Count the entity once again + counter += len(entity[keys][0]) + for i in range(1, len(entity[keys])): + if entity[keys][i][0][0] in entity.keys(): + current_key = entity[keys][i][0][0] + while current_key in entity.keys(): + counter+=entity[keys][i][0][1]**entity[current_key][i][0][1] + current_key=entity[current_key][i][0][0] + # TODO Endlosschleife bei lol fixen + print current_key + print entity[keys][i][0][1] + print counter + + +class BillionLaughsTest(unittest.TestCase): + pass + +if __name__ == '__main__': + # XMLParser("test test2 test3 lol23 232 3l123 ]>") + f = open("testdoc", "r") + XMLParser(f) + f.close() + # unittest.main() + # Fuer mich relevant sind nur die ENTITYs und Kommentare, den Rest kann ich + # einfach counten diff --git a/test/testdoc b/test/testdoc new file mode 100644 index 0000000..43fd286 --- /dev/null +++ b/test/testdoc @@ -0,0 +1,28 @@ + + + + + + + + + + + + +]> + + + + + %s + &lol6; + %s + + + + + diff --git a/test/testdoc~ b/test/testdoc~ new file mode 100644 index 0000000..7d5321e --- /dev/null +++ b/test/testdoc~ @@ -0,0 +1,28 @@ + + + + + + + + + + + + +]> + + + + + %s + &lol1; + %s + + + + + From 69630c0fa51746a706c521408d38da955d795921 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sun, 8 Jun 2014 16:46:06 +0200 Subject: [PATCH 03/58] Second version of xml parser --- test/test_billion_laughs.py | 132 ++++++++++++++++++++++++------------ test/testdoc | 2 +- 2 files changed, 90 insertions(+), 44 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index abb1669..a678028 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -4,49 +4,91 @@ from collections import Counter -def XMLParser(xml): - counter = 0 - entity = dict() - if isinstance(xml, file): - xml = xml.read() - # Get the entity names and store them as keys - entity_lines = re.findall(r"\<\!ENTITY(.*)\>", xml) - print entity_lines - # Get the entity values and count different elements of the value - for elements in entity_lines: - if "&" in elements.split(" ")[2] and ";" in elements.split(" ")[2]: - keyvalues = re.findall(r"\&(\w+)\;", elements.split(" ")[2]) +def get_objects(xml_data): + pos = 0 + for idx, c in enumerate(xml_data): + if c == '<': + # TODO: re.find + if re.match(r'!ENTITY', xml_data[idx + 1:]): + # xml_data[idx + 1:].startswith('!ENTITY'): + ename = re.search( + r'(\w+)', xml_data[xml_data.find(' ', idx + 1):]).group() + evalue = re.search(r'"(.*)"', xml_data[idx + 1:]).group() + yield ('entity', (ename, evalue)) + else: + yield ('element', None) + elif c == '&': + endpos = xml_data.find(';', idx + 1) + yield ('entity_reference', xml_data[idx + 1:endpos]) else: - keyvalues = re.findall(r"\"(\w+)\"", elements.split(" ")[2]) - entity.update({elements.split(" ")[1]: [keyvalues]}) - value_count = Counter(keyvalues) - entity[elements.split(" ")[1]].append(value_count.items()) - print entity - # print entity_lines - # Go through every line of the XML File - for lines in xml.split("\n"): - print lines - # Count every character - counter += len(lines) - # print entity - for keys in entity.keys(): - # Check whether any of the stored keys can be found in one line - linekeys = re.findall(r"\&(\w+)\;", lines) - # If a stored key could be found in one of the lines and it is not - # an entity declaration, it is probably an entity reference - if keys in linekeys and "!ENTITY" not in lines: - # Count the entity once again - counter += len(entity[keys][0]) - for i in range(1, len(entity[keys])): - if entity[keys][i][0][0] in entity.keys(): - current_key = entity[keys][i][0][0] - while current_key in entity.keys(): - counter+=entity[keys][i][0][1]**entity[current_key][i][0][1] - current_key=entity[current_key][i][0][0] - # TODO Endlosschleife bei lol fixen - print current_key - print entity[keys][i][0][1] - print counter + yield ('text', c) + + +def get_xml_length(xml_data, entities={}): + res = 0 + for otype, ovalue in get_objects(xml_data): + print res + if otype == 'element': + res += 5 + elif otype == 'entity': + ename, evalue = ovalue + print entities + entities[ename] = get_xml_length(evalue, entities) + elif otype == 'entity_reference': + res += entities[ovalue] + else: # text + res += len(ovalue) + return res + + +# def XMLParser(xml): +# counter = 0 +# entity = dict() + +# if isinstance(xml, file): +# xml = xml.read() + +# Get the entity names and store them as keys +# entity_lines = re.findall(r"\<\!ENTITY(.*)\>", xml) +# print entity_lines +# Get the entity values and count different elements of the value +# for elements in entity_lines: +# if "&" in elements.split(" ")[2] and ";" in elements.split(" ")[2]: +# keyvalues = re.findall(r"\&(\w+)\;", elements.split(" ")[2]) +# else: +# keyvalues = re.findall(r"\"(\w+)\"", elements.split(" ")[2]) +# entity.update({elements.split(" ")[1]: [keyvalues]}) +# value_count = Counter(keyvalues) +# entity[elements.split(" ")[1]].append(value_count.items()) +# print entity +# print entity_lines +# Go through every line of the XML File +# for lines in xml.split("\n"): +# print lines +# Count every character +# counter += len(lines) +# print entity +# for keys in entity.keys(): +# Check whether any of the stored keys can be found in one line +# linekeys = re.findall(r"\&(\w+)\;", lines) +# If a stored key could be found in one of the lines and it is not +# an entity declaration, it is probably an entity reference +# if keys in linekeys and "!ENTITY" not in lines: +# Count the entity once again +# counter += len(entity[keys][0]) +# for i in range(1, len(entity[keys])): +# if entity[keys][i][0][0] in entity.keys(): +# current_key = entity[keys][i][0][0] +# while current_key in entity.keys(): +# counter += entity[keys][i][0][ +# 1] ** entity[current_key][i][0][1] +# current_key = entity[current_key][i][0][0] +# TODO Endlosschleife bei lol fixen +# TODO Counter anpassen (wird noch zu viel +# gecounted) +# print current_key +# print entity[keys][i][0][1] +# print counter class BillionLaughsTest(unittest.TestCase): @@ -55,7 +97,11 @@ class BillionLaughsTest(unittest.TestCase): if __name__ == '__main__': # XMLParser("test test2 test3 lol23 232 3l123 ]>") f = open("testdoc", "r") - XMLParser(f) + xml_data = f.read() + # XMLParser(f) + res=get_xml_length(xml_data) + res=res*9.31322575*10**-10 + print str(res)+" GB" f.close() # unittest.main() # Fuer mich relevant sind nur die ENTITYs und Kommentare, den Rest kann ich diff --git a/test/testdoc b/test/testdoc index 43fd286..0c6b223 100644 --- a/test/testdoc +++ b/test/testdoc @@ -19,7 +19,7 @@ %s - &lol6; + %s From 5ec98bf7739c4a4225f508c80f9cc8c7df7a3515 Mon Sep 17 00:00:00 2001 From: Oktay Sarier Date: Sun, 8 Jun 2014 16:47:46 +0200 Subject: [PATCH 04/58] Delete testdoc~ --- test/testdoc~ | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 test/testdoc~ diff --git a/test/testdoc~ b/test/testdoc~ deleted file mode 100644 index 7d5321e..0000000 --- a/test/testdoc~ +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - -]> - - - - - %s - &lol1; - %s - - - - - From 0bb015acf92eca1f2aededfdf0d1a4bd9baa0e5b Mon Sep 17 00:00:00 2001 From: garet12 Date: Sun, 8 Jun 2014 16:54:35 +0200 Subject: [PATCH 05/58] Updated gitignore --- .gitignore | 1 + test/test_utf7.py | 6 ------ webvulnscan/attacks/utf7.py | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 test/test_utf7.py delete mode 100644 webvulnscan/attacks/utf7.py diff --git a/.gitignore b/.gitignore index 205f2f1..83551d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.py[cod] +*~ # C extensions *.so diff --git a/test/test_utf7.py b/test/test_utf7.py deleted file mode 100644 index 12b16cd..0000000 --- a/test/test_utf7.py +++ /dev/null @@ -1,6 +0,0 @@ -import unittest -import tutil -import webvulnscan.attacks.utf7 - -class UTF7Test(unittest.TestCase): - pass \ No newline at end of file diff --git a/webvulnscan/attacks/utf7.py b/webvulnscan/attacks/utf7.py deleted file mode 100644 index 1cfe99a..0000000 --- a/webvulnscan/attacks/utf7.py +++ /dev/null @@ -1 +0,0 @@ -from ..utils import attack \ No newline at end of file From b58670d8bb91a838c248b617d37900cad0c643d6 Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 9 Jun 2014 12:36:33 +0200 Subject: [PATCH 06/58] Better regex for entities --- test/test_billion_laughs.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index a678028..4dcdd4c 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -5,15 +5,13 @@ def get_objects(xml_data): - pos = 0 for idx, c in enumerate(xml_data): if c == '<': - # TODO: re.find if re.match(r'!ENTITY', xml_data[idx + 1:]): # xml_data[idx + 1:].startswith('!ENTITY'): ename = re.search( - r'(\w+)', xml_data[xml_data.find(' ', idx + 1):]).group() - evalue = re.search(r'"(.*)"', xml_data[idx + 1:]).group() + r'\s(\w+)', xml_data[idx + 1:]).group(1) + evalue = re.search(r'"(.*)"', xml_data[idx + 1:]).group(1) yield ('entity', (ename, evalue)) else: yield ('element', None) @@ -27,13 +25,13 @@ def get_objects(xml_data): def get_xml_length(xml_data, entities={}): res = 0 for otype, ovalue in get_objects(xml_data): - print res + # print res if otype == 'element': res += 5 elif otype == 'entity': ename, evalue = ovalue - print entities entities[ename] = get_xml_length(evalue, entities) + # print entities elif otype == 'entity_reference': res += entities[ovalue] else: # text @@ -99,9 +97,9 @@ class BillionLaughsTest(unittest.TestCase): f = open("testdoc", "r") xml_data = f.read() # XMLParser(f) - res=get_xml_length(xml_data) - res=res*9.31322575*10**-10 - print str(res)+" GB" + res = get_xml_length(xml_data) + res = res * 9.31322575 * 10 ** -10 + print str(res) + " GB" f.close() # unittest.main() # Fuer mich relevant sind nur die ENTITYs und Kommentare, den Rest kann ich From 8f818eb4672061f1027aaced915fa6f048e37ec6 Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 9 Jun 2014 14:21:41 +0200 Subject: [PATCH 07/58] More accurate counting --- test/test_billion_laughs.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 4dcdd4c..e3bfbe0 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -17,21 +17,25 @@ def get_objects(xml_data): yield ('element', None) elif c == '&': endpos = xml_data.find(';', idx + 1) + print xml_data[idx+1:endpos] yield ('entity_reference', xml_data[idx + 1:endpos]) else: - yield ('text', c) + # Die einzelnen Buchstaben der Referenzen werden immer wieder gezaehlt, wodurch zu viel gezaehlt wird + # Hier muss noch eine bessere Überprüfung hin + if '&' in xml_data: + continue + else: + yield ('text', c) def get_xml_length(xml_data, entities={}): res = 0 for otype, ovalue in get_objects(xml_data): - # print res if otype == 'element': res += 5 elif otype == 'entity': ename, evalue = ovalue entities[ename] = get_xml_length(evalue, entities) - # print entities elif otype == 'entity_reference': res += entities[ovalue] else: # text From 51ce038b8de3b2835166f6dc2429010c908283c9 Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 9 Jun 2014 14:23:37 +0200 Subject: [PATCH 08/58] Small fix --- test/test_billion_laughs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index e3bfbe0..72f0b1c 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -17,11 +17,10 @@ def get_objects(xml_data): yield ('element', None) elif c == '&': endpos = xml_data.find(';', idx + 1) - print xml_data[idx+1:endpos] yield ('entity_reference', xml_data[idx + 1:endpos]) else: # Die einzelnen Buchstaben der Referenzen werden immer wieder gezaehlt, wodurch zu viel gezaehlt wird - # Hier muss noch eine bessere Überprüfung hin + # Hier muss noch eine bessere Ueberpruefung hin if '&' in xml_data: continue else: From e2030528301880f67d35ad8c19dd0de4108e8598 Mon Sep 17 00:00:00 2001 From: garet12 Date: Tue, 10 Jun 2014 14:33:20 +0200 Subject: [PATCH 09/58] Better check for character skip --- test/test_billion_laughs.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 72f0b1c..5668c67 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -5,26 +5,28 @@ def get_objects(xml_data): + skip = False for idx, c in enumerate(xml_data): - if c == '<': - if re.match(r'!ENTITY', xml_data[idx + 1:]): - # xml_data[idx + 1:].startswith('!ENTITY'): - ename = re.search( - r'\s(\w+)', xml_data[idx + 1:]).group(1) - evalue = re.search(r'"(.*)"', xml_data[idx + 1:]).group(1) - yield ('entity', (ename, evalue)) - else: - yield ('element', None) - elif c == '&': - endpos = xml_data.find(';', idx + 1) - yield ('entity_reference', xml_data[idx + 1:endpos]) - else: - # Die einzelnen Buchstaben der Referenzen werden immer wieder gezaehlt, wodurch zu viel gezaehlt wird - # Hier muss noch eine bessere Ueberpruefung hin - if '&' in xml_data: - continue + if skip == False: + if c == '<': + if re.match(r'!ENTITY', xml_data[idx + 1:]): + # xml_data[idx + 1:].startswith('!ENTITY'): + ename = re.search( + r'\s(\w+)', xml_data[idx + 1:]).group(1) + evalue = re.search(r'"(.*)"', xml_data[idx + 1:]).group(1) + yield ('entity', (ename, evalue)) + else: + yield ('element', None) + elif c == '&': + endpos = xml_data.find(';', idx + 1) + skip = True + yield ('entity_reference', xml_data[idx + 1:endpos]) else: yield ('text', c) + else: # skip = True + if c == ';': + skip = False + continue def get_xml_length(xml_data, entities={}): From 8683103d72b507069145b6e15c883173e0799e5b Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 14 Jun 2014 15:56:16 +0200 Subject: [PATCH 10/58] Working counter for billion laughs --- test/test_billion_laughs.py | 104 +++++++++--------------------------- test/testdoc | 2 +- 2 files changed, 25 insertions(+), 81 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 5668c67..c9024e4 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -5,36 +5,33 @@ def get_objects(xml_data): - skip = False - for idx, c in enumerate(xml_data): - if skip == False: - if c == '<': - if re.match(r'!ENTITY', xml_data[idx + 1:]): - # xml_data[idx + 1:].startswith('!ENTITY'): - ename = re.search( - r'\s(\w+)', xml_data[idx + 1:]).group(1) - evalue = re.search(r'"(.*)"', xml_data[idx + 1:]).group(1) - yield ('entity', (ename, evalue)) - else: - yield ('element', None) - elif c == '&': - endpos = xml_data.find(';', idx + 1) - skip = True - yield ('entity_reference', xml_data[idx + 1:endpos]) - else: - yield ('text', c) - else: # skip = True - if c == ';': - skip = False - continue + idx = 0 + while idx < len(xml_data): + if xml_data[idx] == '<' and re.match(r'!ENTITY', xml_data[idx + 1:]): + ename = re.search( + r'\s(\w+)', xml_data[idx + 1:]).group(1) + evalue = re.search(r'"(.*)"', xml_data[idx + 1:]).group(1) + endpos = xml_data.find('>', idx + 1) + yield ('entity', (ename, evalue)) + + idx += (endpos - idx) + + elif xml_data[idx] == '&': + endpos = xml_data.find(';', idx + 1) + yield ('entity_reference', xml_data[idx + 1:endpos]) + + idx += (endpos - idx) + + else: + yield ('text', xml_data[idx]) + + idx += 1 def get_xml_length(xml_data, entities={}): res = 0 for otype, ovalue in get_objects(xml_data): - if otype == 'element': - res += 5 - elif otype == 'entity': + if otype == 'entity': ename, evalue = ovalue entities[ename] = get_xml_length(evalue, entities) elif otype == 'entity_reference': @@ -44,68 +41,15 @@ def get_xml_length(xml_data, entities={}): return res -# def XMLParser(xml): -# counter = 0 -# entity = dict() - -# if isinstance(xml, file): -# xml = xml.read() - -# Get the entity names and store them as keys -# entity_lines = re.findall(r"\<\!ENTITY(.*)\>", xml) -# print entity_lines -# Get the entity values and count different elements of the value -# for elements in entity_lines: -# if "&" in elements.split(" ")[2] and ";" in elements.split(" ")[2]: -# keyvalues = re.findall(r"\&(\w+)\;", elements.split(" ")[2]) -# else: -# keyvalues = re.findall(r"\"(\w+)\"", elements.split(" ")[2]) -# entity.update({elements.split(" ")[1]: [keyvalues]}) -# value_count = Counter(keyvalues) -# entity[elements.split(" ")[1]].append(value_count.items()) -# print entity -# print entity_lines -# Go through every line of the XML File -# for lines in xml.split("\n"): -# print lines -# Count every character -# counter += len(lines) -# print entity -# for keys in entity.keys(): -# Check whether any of the stored keys can be found in one line -# linekeys = re.findall(r"\&(\w+)\;", lines) -# If a stored key could be found in one of the lines and it is not -# an entity declaration, it is probably an entity reference -# if keys in linekeys and "!ENTITY" not in lines: -# Count the entity once again -# counter += len(entity[keys][0]) -# for i in range(1, len(entity[keys])): -# if entity[keys][i][0][0] in entity.keys(): -# current_key = entity[keys][i][0][0] -# while current_key in entity.keys(): -# counter += entity[keys][i][0][ -# 1] ** entity[current_key][i][0][1] -# current_key = entity[current_key][i][0][0] -# TODO Endlosschleife bei lol fixen -# TODO Counter anpassen (wird noch zu viel -# gecounted) -# print current_key -# print entity[keys][i][0][1] -# print counter - - class BillionLaughsTest(unittest.TestCase): pass if __name__ == '__main__': - # XMLParser("test test2 test3 lol23 232 3l123 ]>") f = open("testdoc", "r") xml_data = f.read() - # XMLParser(f) res = get_xml_length(xml_data) - res = res * 9.31322575 * 10 ** -10 + print str(res) + " Byte" + res = res / float(2 ** 30) print str(res) + " GB" f.close() # unittest.main() - # Fuer mich relevant sind nur die ENTITYs und Kommentare, den Rest kann ich - # einfach counten diff --git a/test/testdoc b/test/testdoc index 0c6b223..7d5321e 100644 --- a/test/testdoc +++ b/test/testdoc @@ -19,7 +19,7 @@ %s - + &lol1; %s From 7809350c44ab04d593f54474017903e0631e14d8 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 14 Jun 2014 15:56:57 +0200 Subject: [PATCH 11/58] Changed testdoc --- test/testdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testdoc b/test/testdoc index 7d5321e..32ea403 100644 --- a/test/testdoc +++ b/test/testdoc @@ -19,7 +19,7 @@ %s - &lol1; + &lol9; %s From c09a3374d7e1fbfe2d4837679f86e96a6587345a Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 14 Jun 2014 16:01:39 +0200 Subject: [PATCH 12/58] Small change --- test/test_billion_laughs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index c9024e4..4d6c9ef 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -7,7 +7,8 @@ def get_objects(xml_data): idx = 0 while idx < len(xml_data): - if xml_data[idx] == '<' and re.match(r'!ENTITY', xml_data[idx + 1:]): + c = xml_data[idx] + if c == '<' and re.match(r'!ENTITY', xml_data[idx + 1:]): ename = re.search( r'\s(\w+)', xml_data[idx + 1:]).group(1) evalue = re.search(r'"(.*)"', xml_data[idx + 1:]).group(1) @@ -16,14 +17,14 @@ def get_objects(xml_data): idx += (endpos - idx) - elif xml_data[idx] == '&': + elif c == '&': endpos = xml_data.find(';', idx + 1) yield ('entity_reference', xml_data[idx + 1:endpos]) idx += (endpos - idx) else: - yield ('text', xml_data[idx]) + yield ('text', c) idx += 1 From 4df33be8091b9bc53516b58114293727c3ea6ecb Mon Sep 17 00:00:00 2001 From: garet12 Date: Sun, 15 Jun 2014 15:16:37 +0200 Subject: [PATCH 13/58] Added test cases (not complete yet) --- test/test_billion_laughs.py | 106 +++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 4d6c9ef..3c54730 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -1,7 +1,7 @@ import unittest import tutil import re -from collections import Counter +import time def get_objects(xml_data): @@ -43,7 +43,107 @@ def get_xml_length(xml_data, entities={}): class BillionLaughsTest(unittest.TestCase): - pass + + def test_empty(self): + xml_doc = '''''' + res = get_xml_length(xml_doc) + self.assertEqual(res, 0) + + def test_lol1(self): + xml_doc = ''' + + +&lol1; +''' + res = get_xml_length(xml_doc) + self.assertTrue(res >= 30) + + def test_no_reference(self): + xml_doc = ''' + + + +''' + res = get_xml_length(xml_doc) + self.assertTrue(res < 30) + + def test_lol1_multiple(self): + xml_doc = ''' + + + +]> +&lol1;&lol1;&lol1; +''' + res = get_xml_length(xml_doc) + self.assertTrue(res >= 90) + + def test_quadratic_blowup(self): + xml_doc = ''' + + +]> +&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A; +''' + res = get_xml_length(xml_doc) + self.assertTrue(res >= 2000) + + def test_missing_bracket1(self): + xml_doc = ''' + + +&A; +''' + res = get_xml_length(xml_doc) + self.assertTrue(False) + + def test_missing_bracket2(self): + xml_doc = ''' + + +]> +&A; +''' + res = get_xml_length(xml_doc) + self.assertTrue(False) + + def test_missing_semicolon(self): + xml_doc = ''' + + +]> +&A +''' + res = get_xml_length(xml_doc) + self.assertTrue(False) + + def test_missing_slash(self): + xml_doc = ''' + + +]> +&A; +''' + res = get_xml_length(xml_doc) + self.assertTrue(False) + + def test_missing_quotes(self): + xml_doc = ''' + + +]> +&A; +''' + res = get_xml_length(xml_doc) + self.assertTrue(False) if __name__ == '__main__': f = open("testdoc", "r") @@ -53,4 +153,4 @@ class BillionLaughsTest(unittest.TestCase): res = res / float(2 ** 30) print str(res) + " GB" f.close() - # unittest.main() + unittest.main() From c9d926ab4031c4ab98d0485abe4ef34d5d8b868c Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Mon, 16 Jun 2014 13:42:19 +0200 Subject: [PATCH 14/58] [billionlaughs] Fix parsing --- test/test_billion_laughs.py | 125 ++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 42 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 3c54730..92651c2 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -4,24 +4,28 @@ import time -def get_objects(xml_data): +def get_objects(xml_data, allow_entity_def=True): idx = 0 while idx < len(xml_data): c = xml_data[idx] - if c == '<' and re.match(r'!ENTITY', xml_data[idx + 1:]): - ename = re.search( - r'\s(\w+)', xml_data[idx + 1:]).group(1) - evalue = re.search(r'"(.*)"', xml_data[idx + 1:]).group(1) - endpos = xml_data.find('>', idx + 1) - yield ('entity', (ename, evalue)) - - idx += (endpos - idx) - - elif c == '&': + if c == '<' and allow_entity_def: + m = re.match(r'''(?x) + !ENTITY\s+ + (?P[a-zA-Z0-9_-]+)\s+ + "(?P[^"]*)"\s* + > + ''', xml_data[idx + 1:]) + if m: + value = get_objects(m.group('value'), allow_entity_def=False) + yield ('entity', (m.group('name'), value)) + idx += len(m.group(0)) + 1 + continue + + if c == '&': endpos = xml_data.find(';', idx + 1) yield ('entity_reference', xml_data[idx + 1:endpos]) - idx += (endpos - idx) + idx = endpos else: yield ('text', c) @@ -29,12 +33,19 @@ def get_objects(xml_data): idx += 1 -def get_xml_length(xml_data, entities={}): + + +def get_xml_length(xml_data): + object_stream = get_objects(xml_data) + return _calc_xml_length(object_stream, {}) + + +def _calc_xml_length(object_stream, entities): res = 0 - for otype, ovalue in get_objects(xml_data): + for otype, ovalue in object_stream: if otype == 'entity': ename, evalue = ovalue - entities[ename] = get_xml_length(evalue, entities) + entities[ename] = _calc_xml_length(evalue, entities) elif otype == 'entity_reference': res += entities[ovalue] else: # text @@ -42,33 +53,63 @@ def get_xml_length(xml_data, entities={}): return res +def _make_list(gen): + gen = list(gen) + return [ + (key, (val[0], _make_list(val[1]))) + if key == 'entity' + else (key, val) + for key, val in gen] + + + class BillionLaughsTest(unittest.TestCase): + def test_entity_parsing(self): + self.assertEqual( + _make_list(get_objects( + '''''')), + [('entity', ('x', [('text', 'y')]))] + ) + + self.assertEqual( + _make_list(get_objects( + '''&y;x''')), + [('entity_reference', 'y'), ('text', 'x')] + ) + + self.assertEqual( + _make_list(get_objects( + '''&ya;x''')), + [('entity_reference', 'ya'), ('text', 'x')] + ) + + def test_empty(self): - xml_doc = '''''' - res = get_xml_length(xml_doc) + xml_str = '''''' + res = get_xml_length(xml_str) self.assertEqual(res, 0) def test_lol1(self): - xml_doc = ''' + xml_str = ''' &lol1; ''' - res = get_xml_length(xml_doc) + res = get_xml_length(xml_str) self.assertTrue(res >= 30) def test_no_reference(self): - xml_doc = ''' + xml_str = ''' ''' - res = get_xml_length(xml_doc) + res = get_xml_length(xml_str) self.assertTrue(res < 30) def test_lol1_multiple(self): - xml_doc = ''' + xml_str = ''' @@ -76,81 +117,81 @@ def test_lol1_multiple(self): ]> &lol1;&lol1;&lol1; ''' - res = get_xml_length(xml_doc) + res = get_xml_length(xml_str) self.assertTrue(res >= 90) def test_quadratic_blowup(self): - xml_doc = ''' + xml_str = ''' ]> &A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A; ''' - res = get_xml_length(xml_doc) + res = get_xml_length(xml_str) self.assertTrue(res >= 2000) def test_missing_bracket1(self): - xml_doc = ''' + xml_str = ''' &A; ''' - res = get_xml_length(xml_doc) + res = get_xml_length(xml_str) self.assertTrue(False) def test_missing_bracket2(self): - xml_doc = ''' + xml_str = ''' ]> &A; ''' - res = get_xml_length(xml_doc) + res = get_xml_length(xml_str) self.assertTrue(False) def test_missing_semicolon(self): - xml_doc = ''' + xml_str = ''' ]> &A ''' - res = get_xml_length(xml_doc) + res = get_xml_length(xml_str) self.assertTrue(False) def test_missing_slash(self): - xml_doc = ''' + xml_str = ''' ]> &A; ''' - res = get_xml_length(xml_doc) + res = get_xml_length(xml_str) self.assertTrue(False) def test_missing_quotes(self): - xml_doc = ''' + xml_str = ''' ]> &A; ''' - res = get_xml_length(xml_doc) + res = get_xml_length(xml_str) self.assertTrue(False) if __name__ == '__main__': - f = open("testdoc", "r") - xml_data = f.read() - res = get_xml_length(xml_data) - print str(res) + " Byte" - res = res / float(2 ** 30) - print str(res) + " GB" - f.close() + # f = open("testdoc", "r") + # xml_data = f.read() + # res = get_xml_length(xml_data) + # print str(res) + " Byte" + # res = res / float(2 ** 30) + # print str(res) + " GB" + # f.close() unittest.main() From 2e04bc703a9ffc498b17de29e2677a81e7f40aa4 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Mon, 16 Jun 2014 16:17:43 +0200 Subject: [PATCH 15/58] [billionlaughs] Fail for invalid entity references --- test/test_billion_laughs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 92651c2..aa9d672 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -84,12 +84,15 @@ def test_entity_parsing(self): [('entity_reference', 'ya'), ('text', 'x')] ) - def test_empty(self): xml_str = '''''' res = get_xml_length(xml_str) self.assertEqual(res, 0) + def test_invalid_reference(self): + xml_str = '&a;' + self.assertRaises(BaseException, get_xml_length, xml_str) + def test_lol1(self): xml_str = ''' From 52ca867ebe9c15875096374fed6cc72c645ac1fa Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 16 Jun 2014 16:28:16 +0200 Subject: [PATCH 16/58] Tests fixed --- test/test_billion_laughs.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 92651c2..76be1d8 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -1,7 +1,6 @@ import unittest -import tutil import re -import time +import sys def get_objects(xml_data, allow_entity_def=True): @@ -33,8 +32,6 @@ def get_objects(xml_data, allow_entity_def=True): idx += 1 - - def get_xml_length(xml_data): object_stream = get_objects(xml_data) return _calc_xml_length(object_stream, {}) @@ -47,7 +44,7 @@ def _calc_xml_length(object_stream, entities): ename, evalue = ovalue entities[ename] = _calc_xml_length(evalue, entities) elif otype == 'entity_reference': - res += entities[ovalue] + res += entities[ovalue] else: # text res += len(ovalue) return res @@ -62,7 +59,6 @@ def _make_list(gen): for key, val in gen] - class BillionLaughsTest(unittest.TestCase): def test_entity_parsing(self): @@ -84,7 +80,6 @@ def test_entity_parsing(self): [('entity_reference', 'ya'), ('text', 'x')] ) - def test_empty(self): xml_str = '''''' res = get_xml_length(xml_str) @@ -120,6 +115,14 @@ def test_lol1_multiple(self): res = get_xml_length(xml_str) self.assertTrue(res >= 90) + def test_lol1_missing_semicolon(self): + xml_str = ''' + + +&lol1; +''' + self.assertRaises(KeyError, get_xml_length, xml_str) + def test_quadratic_blowup(self): xml_str = ''' @@ -139,8 +142,7 @@ def test_missing_bracket1(self): ]> &A; ''' - res = get_xml_length(xml_str) - self.assertTrue(False) + self.assertRaises(KeyError, get_xml_length, xml_str) def test_missing_bracket2(self): xml_str = ''' @@ -150,8 +152,7 @@ def test_missing_bracket2(self): ]> &A; ''' - res = get_xml_length(xml_str) - self.assertTrue(False) + self.assertRaises(KeyError, get_xml_length, xml_str) def test_missing_semicolon(self): xml_str = ''' @@ -161,8 +162,7 @@ def test_missing_semicolon(self): ]> &A ''' - res = get_xml_length(xml_str) - self.assertTrue(False) + self.assertRaises(KeyError, get_xml_length, xml_str) def test_missing_slash(self): xml_str = ''' @@ -173,7 +173,7 @@ def test_missing_slash(self): &A; ''' res = get_xml_length(xml_str) - self.assertTrue(False) + self.assertTrue(res >= 23) def test_missing_quotes(self): xml_str = ''' @@ -183,8 +183,7 @@ def test_missing_quotes(self): ]> &A; ''' - res = get_xml_length(xml_str) - self.assertTrue(False) + self.assertRaises(KeyError, get_xml_length, xml_str) if __name__ == '__main__': # f = open("testdoc", "r") From 85dd7af9d9650a76401fb5836f11bda687791a85 Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 16 Jun 2014 16:30:41 +0200 Subject: [PATCH 17/58] Tests fixed --- test/test_billion_laughs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 12e2a16..a446c10 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -125,7 +125,7 @@ def test_lol1_missing_semicolon(self): &lol1; ''' - self.assertRaises(KeyError, get_xml_length, xml_str) + self.assertRaises(BaseException, get_xml_length, xml_str) def test_quadratic_blowup(self): xml_str = ''' @@ -146,7 +146,7 @@ def test_missing_bracket1(self): ]> &A; ''' - self.assertRaises(KeyError, get_xml_length, xml_str) + self.assertRaises(BaseException, get_xml_length, xml_str) def test_missing_bracket2(self): xml_str = ''' @@ -156,7 +156,7 @@ def test_missing_bracket2(self): ]> &A; ''' - self.assertRaises(KeyError, get_xml_length, xml_str) + self.assertRaises(BaseException, get_xml_length, xml_str) def test_missing_semicolon(self): xml_str = ''' @@ -166,7 +166,7 @@ def test_missing_semicolon(self): ]> &A ''' - self.assertRaises(KeyError, get_xml_length, xml_str) + self.assertRaises(BaseException, get_xml_length, xml_str) def test_missing_slash(self): xml_str = ''' @@ -187,7 +187,7 @@ def test_missing_quotes(self): ]> &A; ''' - self.assertRaises(KeyError, get_xml_length, xml_str) + self.assertRaises(BaseException, get_xml_length, xml_str) if __name__ == '__main__': # f = open("testdoc", "r") From 3ec59aa0075b949d8182c4d903e5b7513ac4ea1a Mon Sep 17 00:00:00 2001 From: garet12 Date: Fri, 20 Jun 2014 15:57:10 +0200 Subject: [PATCH 18/58] First version of test through html form --- test/test_billion_laughs.py | 109 +++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index a446c10..ad0c22d 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -1,6 +1,10 @@ import unittest import re -import sys +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import socket +from urlparse import urlparse +import cgi +import urllib2 def get_objects(xml_data, allow_entity_def=True): @@ -44,7 +48,7 @@ def _calc_xml_length(object_stream, entities): ename, evalue = ovalue entities[ename] = _calc_xml_length(evalue, entities) elif otype == 'entity_reference': - res += entities[ovalue] + res += entities[ovalue] else: # text res += len(ovalue) return res @@ -59,6 +63,104 @@ def _make_list(gen): for key, val in gen] +class Handler(BaseHTTPRequestHandler): + + def _default_page(self): + self.send_response(200) + self.end_headers() + self.wfile.write(''' + + + + BillionLaughsTest + + +

BillionLaughsTest

+
+ OpenID + +
+ + + '''.encode("utf-8")) + + def _serve_request(self): + parsed_path = urlparse(self.path) + + if parsed_path.path == "/": + self._default_page() + elif parsed_path.path == "/form": + self.do_POST() + else: + self.send_error(404, "File not Found!") + + def do_POST(self): + form = cgi.FieldStorage( + fp=self.rfile, + headers=self.headers, + environ={'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': self.headers['Content-Type'], + }) + + self.send_response(200) + self.end_headers() + self.wfile.write('Client: %s\n' % str(self.client_address)) + self.wfile.write('User-agent: %s\n' % str(self.headers['user-agent'])) + self.wfile.write('Path: %s\n' % self.path) + self.wfile.write('Form data:\n') + + for field in form.keys(): + self.wfile.write('\t%s=%s\n' % (field, form[field].value)) + self.get_XML(form[field].value) + return + + def get_XML(self,url): + if "http://" not in url: + url = "http://" + url + headers = {'Accept': 'application/xrds+xml'} + req = urllib2.Request(url, None, headers) + response = urllib2.urlopen(req) + html = response.read() + print html + if 'x-xrds-location' in html: + xrds_loc = re.search(r'content="(?P[^"]*)"\s*>' + , html[html.find(' 65536: + self.requestline = '' + self.request_version = '' + self.command = '' + self.send_error(414) + return + if not self.raw_requestline: + self.close_connection = 1 + return + if not self.parse_request(): + return + + self._serve_request() + self.wfile.flush() + except socket.timeout as e: + self.log_error("Request timed out: %r", e) + self.close_connection = 1 + return + + +def main(): + server_class = HTTPServer + httpd = server_class(("", 8000), Handler) + httpd.serve_forever() + + class BillionLaughsTest(unittest.TestCase): def test_entity_parsing(self): @@ -197,4 +299,5 @@ def test_missing_quotes(self): # res = res / float(2 ** 30) # print str(res) + " GB" # f.close() - unittest.main() + # unittest.main() + main() From 67d796e39e9a82bddfd40f0fd8f570823ff88dcf Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 21 Jun 2014 14:08:38 +0200 Subject: [PATCH 19/58] Added openID testserver --- test/openID_test.py | 99 +++++++++++++++++++++++++++++++++++++ test/test_billion_laughs.py | 16 +++--- 2 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 test/openID_test.py diff --git a/test/openID_test.py b/test/openID_test.py new file mode 100644 index 0000000..3f9c252 --- /dev/null +++ b/test/openID_test.py @@ -0,0 +1,99 @@ +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import socket +from urlparse import urlparse + +PORT = 8080 + +class Handler(BaseHTTPRequestHandler): + + def _default_page(self): + self.send_response(200) + self.end_headers() + self.wfile.write(''' + + + + OpenID Test Server + + + +

OpenID Test Server

+ + '''.encode("utf-8")%PORT) + + def _serve_request(self): + parsed_path = urlparse(self.path) + + if parsed_path.path == "/": + self._default_page() + elif parsed_path.path == "/serverxml": + self.showServerXML() + else: + self.send_error(404, "File not Found!") + + def showServerXML(self): + self.send_response(200) + self.send_header('Content-type', 'application/xrds+xml') + self.end_headers() + + self.wfile.write("""\ + + + + + + + + + + + + +]> + + + + + + &lol9; + + + + + + +""") + + def handle_one_request(self): + try: + self.raw_requestline = self.rfile.readline(65537) + if len(self.raw_requestline) > 65536: + self.requestline = '' + self.request_version = '' + self.command = '' + self.send_error(414) + return + if not self.raw_requestline: + self.close_connection = 1 + return + if not self.parse_request(): + return + + self._serve_request() + self.wfile.flush() + except socket.timeout as e: + self.log_error("Request timed out: %r", e) + self.close_connection = 1 + return + + +def main(): + server_class = HTTPServer + httpd = server_class(("", PORT), Handler) + httpd.serve_forever() + +if __name__ == '__main__': + main() diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index ad0c22d..8441432 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -5,7 +5,7 @@ from urlparse import urlparse import cgi import urllib2 - +import time def get_objects(xml_data, allow_entity_def=True): idx = 0 @@ -114,22 +114,26 @@ def do_POST(self): self.get_XML(form[field].value) return - def get_XML(self,url): + def get_XML(self, url): if "http://" not in url: url = "http://" + url headers = {'Accept': 'application/xrds+xml'} req = urllib2.Request(url, None, headers) response = urllib2.urlopen(req) html = response.read() + #TODO Einfachen Server schreiben, der XML Datei schickt, anstatt Python-OpenID + #TODO Folgenden Code so schreiben, dass er verschiedene Fehler abdecken kann (Exception schmeissen geht auch) + # print html - if 'x-xrds-location' in html: - xrds_loc = re.search(r'content="(?P[^"]*)"\s*>' - , html[html.find(' Date: Sat, 21 Jun 2014 14:59:59 +0200 Subject: [PATCH 20/58] Better errorhandling --- test/openID_test.py | 4 ++-- test/test_billion_laughs.py | 46 ++++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/test/openID_test.py b/test/openID_test.py index 3f9c252..8d1f8db 100644 --- a/test/openID_test.py +++ b/test/openID_test.py @@ -36,7 +36,7 @@ def showServerXML(self): self.send_header('Content-type', 'application/xrds+xml') self.end_headers() - self.wfile.write("""\ + self.wfile.write('''\ @@ -65,7 +65,7 @@ def showServerXML(self): -""") +''') def handle_one_request(self): try: diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 8441432..458be83 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -7,6 +7,7 @@ import urllib2 import time + def get_objects(xml_data, allow_entity_def=True): idx = 0 while idx < len(xml_data): @@ -82,7 +83,7 @@ def _default_page(self): - '''.encode("utf-8")) + '''.encode("utf-8")) def _serve_request(self): parsed_path = urlparse(self.path) @@ -119,22 +120,39 @@ def get_XML(self, url): url = "http://" + url headers = {'Accept': 'application/xrds+xml'} req = urllib2.Request(url, None, headers) - response = urllib2.urlopen(req) + try: + response = urllib2.urlopen(req) + except urllib2.HTTPError, e: + self.wfile.write( + '%s There was a problem with your request!' % e.code) + return + except urllib2.URLError, e: + self.wfile.write('%s' % e.args) + return + html = response.read() - #TODO Einfachen Server schreiben, der XML Datei schickt, anstatt Python-OpenID - #TODO Folgenden Code so schreiben, dass er verschiedene Fehler abdecken kann (Exception schmeissen geht auch) - # - print html - if 'x-xrds-location' in html or 'X-XRDS-Location' in html: - xrds_loc = re.search( - r'content="(?P[^"]*)"\s*', html[html.find(' Date: Sat, 21 Jun 2014 17:07:00 +0200 Subject: [PATCH 21/58] Small changes --- test/openID_test.py | 3 ++- test/test_billion_laughs.py | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/openID_test.py b/test/openID_test.py index 8d1f8db..a5e6cc2 100644 --- a/test/openID_test.py +++ b/test/openID_test.py @@ -4,6 +4,7 @@ PORT = 8080 + class Handler(BaseHTTPRequestHandler): def _default_page(self): @@ -19,7 +20,7 @@ def _default_page(self):

OpenID Test Server

- '''.encode("utf-8")%PORT) + '''.encode("utf-8") %PORT) def _serve_request(self): parsed_path = urlparse(self.path) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 458be83..b0aee7d 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -120,6 +120,7 @@ def get_XML(self, url): url = "http://" + url headers = {'Accept': 'application/xrds+xml'} req = urllib2.Request(url, None, headers) + try: response = urllib2.urlopen(req) except urllib2.HTTPError, e: @@ -136,16 +137,18 @@ def get_XML(self, url): if xrds_loc: xrds_url = xrds_loc.group('value') + try: xrds_doc = urllib2.urlopen(xrds_url) except urllib2.HTTPError, e: self.wfile.write( - '%s There was a problem with your request!' % e.code) + '%s There was a problem with your request!' % e.code) return except urllib2.URLError, e: self.wfile.write('%s' % e.args) return - xrds_doc=xrds_doc.read() + + xrds_doc = xrds_doc.read() res = get_xml_length(xrds_doc) # time.sleep(res/100000) print res From 8ee1005c3148db6294cfe07eea961b4f376ff788 Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 23 Jun 2014 16:29:21 +0200 Subject: [PATCH 22/58] Beginning of test for billion laughs in webvulnscan --- test/billion_laughs_test.py | 333 ++++++++++++++++++++++++++ test/openID_test.py | 44 +++- test/test_billion_laughs.py | 332 +------------------------ webvulnscan/attacks/__init__.py | 3 +- webvulnscan/attacks/billion_laughs.py | 23 ++ webvulnscan/attacks/xss.py | 2 +- 6 files changed, 406 insertions(+), 331 deletions(-) create mode 100644 test/billion_laughs_test.py create mode 100644 webvulnscan/attacks/billion_laughs.py diff --git a/test/billion_laughs_test.py b/test/billion_laughs_test.py new file mode 100644 index 0000000..7af2d5c --- /dev/null +++ b/test/billion_laughs_test.py @@ -0,0 +1,333 @@ +import unittest +import re +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import socket +from urlparse import urlparse +import cgi +import urllib2 +import time + + +def get_objects(xml_data, allow_entity_def=True): + idx = 0 + while idx < len(xml_data): + c = xml_data[idx] + if c == '<' and allow_entity_def: + m = re.match(r'''(?x) + !ENTITY\s+ + (?P[a-zA-Z0-9_-]+)\s+ + "(?P[^"]*)"\s* + > + ''', xml_data[idx + 1:]) + if m: + value = get_objects(m.group('value'), allow_entity_def=False) + yield ('entity', (m.group('name'), value)) + idx += len(m.group(0)) + 1 + continue + + if c == '&': + endpos = xml_data.find(';', idx + 1) + yield ('entity_reference', xml_data[idx + 1:endpos]) + + idx = endpos + + else: + yield ('text', c) + + idx += 1 + + +def get_xml_length(xml_data): + object_stream = get_objects(xml_data) + return _calc_xml_length(object_stream, {}) + + +def _calc_xml_length(object_stream, entities): + res = 0 + for otype, ovalue in object_stream: + if otype == 'entity': + ename, evalue = ovalue + entities[ename] = _calc_xml_length(evalue, entities) + elif otype == 'entity_reference': + res += entities[ovalue] + else: # text + res += len(ovalue) + return res + + +def _make_list(gen): + gen = list(gen) + return [ + (key, (val[0], _make_list(val[1]))) + if key == 'entity' + else (key, val) + for key, val in gen] + + +class Handler(BaseHTTPRequestHandler): + + def _default_page(self): + self.send_response(200) + self.end_headers() + self.wfile.write(''' + + + + BillionLaughsTest + + +

BillionLaughsTest

+
+ OpenID + +
+ + + '''.encode("utf-8")) + + def _serve_request(self): + parsed_path = urlparse(self.path) + + if parsed_path.path == "/": + self._default_page() + elif parsed_path.path == "/form": + self.do_POST() + else: + self.send_error(404, "File not Found!") + + def do_POST(self): + form = cgi.FieldStorage( + fp=self.rfile, + headers=self.headers, + environ={'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': self.headers['Content-Type'], + }) + + self.send_response(200) + self.end_headers() + self.wfile.write('Client: %s\n' % str(self.client_address)) + self.wfile.write('User-agent: %s\n' % str(self.headers['user-agent'])) + self.wfile.write('Path: %s\n' % self.path) + self.wfile.write('Form data:\n') + + for field in form.keys(): + self.wfile.write('\t%s=%s\n' % (field, form[field].value)) + xrds_doc = self.get_XML(form[field].value) + + if xrds_doc is not None: + res = get_xml_length(xrds_doc) + # time.sleep(res/100000) + print res + self.wfile.write('Result: %s\n' % res) + + else: + self.wfile.write('XML document could not be found') + + return + + def get_XML(self, url): + if "http://" not in url: + url = "http://" + url + headers = {'Accept': 'application/xrds+xml'} + req = urllib2.Request(url, None, headers) + + try: + response = urllib2.urlopen(req) + except urllib2.HTTPError, e: + self.wfile.write( + '%s There was a problem with your request!' % e.code) + return + except urllib2.URLError, e: + self.wfile.write('%s' % e.args) + return + + html = response.read() + xrds_loc = re.search( + r'[^"]*)"\s*', html, re.IGNORECASE) + + if xrds_loc: + xrds_url = xrds_loc.group('value') + + try: + xrds_doc = urllib2.urlopen(xrds_url) + except urllib2.HTTPError, e: + self.wfile.write( + '%s There was a problem with your request!' % e.code) + return + except urllib2.URLError, e: + self.wfile.write('%s' % e.args) + return + + return xrds_doc.read() + + else: + return None + + def handle_one_request(self): + try: + self.raw_requestline = self.rfile.readline(65537) + if len(self.raw_requestline) > 65536: + self.requestline = '' + self.request_version = '' + self.command = '' + self.send_error(414) + return + if not self.raw_requestline: + self.close_connection = 1 + return + if not self.parse_request(): + return + + self._serve_request() + self.wfile.flush() + except socket.timeout as e: + self.log_error("Request timed out: %r", e) + self.close_connection = 1 + return + + +def main(): + httpd = HTTPServer(("", 8000), Handler) + httpd.serve_forever() + + +class BillionLaughsTest(unittest.TestCase): + + def test_entity_parsing(self): + self.assertEqual( + _make_list(get_objects( + '''''')), + [('entity', ('x', [('text', 'y')]))] + ) + + self.assertEqual( + _make_list(get_objects( + '''&y;x''')), + [('entity_reference', 'y'), ('text', 'x')] + ) + + self.assertEqual( + _make_list(get_objects( + '''&ya;x''')), + [('entity_reference', 'ya'), ('text', 'x')] + ) + + def test_empty(self): + xml_str = '''''' + res = get_xml_length(xml_str) + self.assertEqual(res, 0) + + def test_invalid_reference(self): + xml_str = '&a;' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_lol1(self): + xml_str = ''' + + +&lol1; +''' + res = get_xml_length(xml_str) + self.assertTrue(res >= 30) + + def test_no_reference(self): + xml_str = ''' + + + +''' + res = get_xml_length(xml_str) + self.assertTrue(res < 30) + + def test_lol1_multiple(self): + xml_str = ''' + + + +]> +&lol1;&lol1;&lol1; +''' + res = get_xml_length(xml_str) + self.assertTrue(res >= 90) + + def test_lol1_missing_semicolon(self): + xml_str = ''' + + +&lol1; +''' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_quadratic_blowup(self): + xml_str = ''' + + +]> +&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A; +''' + res = get_xml_length(xml_str) + self.assertTrue(res >= 2000) + + def test_missing_bracket1(self): + xml_str = ''' + + +&A; +''' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_missing_bracket2(self): + xml_str = ''' + + +]> +&A; +''' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_missing_semicolon(self): + xml_str = ''' + + +]> +&A +''' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_missing_slash(self): + xml_str = ''' + + +]> +&A; +''' + res = get_xml_length(xml_str) + self.assertTrue(res >= 23) + + def test_missing_quotes(self): + xml_str = ''' + + +]> +&A; +''' + self.assertRaises(BaseException, get_xml_length, xml_str) + +if __name__ == '__main__': + # f = open("testdoc", "r") + # xml_data = f.read() + # res = get_xml_length(xml_data) + # print str(res) + " Byte" + # res = res / float(2 ** 30) + # print str(res) + " GB" + # f.close() + # unittest.main() + main() diff --git a/test/openID_test.py b/test/openID_test.py index a5e6cc2..8c0357d 100644 --- a/test/openID_test.py +++ b/test/openID_test.py @@ -20,7 +20,7 @@ def _default_page(self):

OpenID Test Server

- '''.encode("utf-8") %PORT) + '''.encode("utf-8") % PORT) def _serve_request(self): parsed_path = urlparse(self.path) @@ -28,16 +28,39 @@ def _serve_request(self): if parsed_path.path == "/": self._default_page() elif parsed_path.path == "/serverxml": - self.showServerXML() + self.showServerXML(None) + elif parsed_path.path == "/serverxml_dangerous_billion": + self.showServerXML("dangerous_billion") + elif parsed_path.path == "/serverxml_dangerous_quadratic": + self.showServerXML("dangerous_quadratic") else: self.send_error(404, "File not Found!") - def showServerXML(self): + def showServerXML(self, xmlState=None): self.send_response(200) self.send_header('Content-type', 'application/xrds+xml') self.end_headers() - self.wfile.write('''\ + if xmlState is None: + self.wfile.write('''\ + + + + + + + + + + + + +'''.encode("utf-8")) + + elif xmlState == "dangerous_billion": + self.wfile.write('''\ @@ -66,7 +89,15 @@ def showServerXML(self): -''') + '''.encode("utf-8")) + else: #quadratic blowup + self.wfile.write(''' + + +]> +&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A; +'''.encode("utf-8")) def handle_one_request(self): try: @@ -92,8 +123,7 @@ def handle_one_request(self): def main(): - server_class = HTTPServer - httpd = server_class(("", PORT), Handler) + httpd = HTTPServer(("", PORT), Handler) httpd.serve_forever() if __name__ == '__main__': diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index b0aee7d..97b89b9 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -1,328 +1,16 @@ import unittest -import re -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -import socket -from urlparse import urlparse import cgi -import urllib2 -import time +import sys +import tutil +import webvulnscan.attacks.billion_laughs -def get_objects(xml_data, allow_entity_def=True): - idx = 0 - while idx < len(xml_data): - c = xml_data[idx] - if c == '<' and allow_entity_def: - m = re.match(r'''(?x) - !ENTITY\s+ - (?P[a-zA-Z0-9_-]+)\s+ - "(?P[^"]*)"\s* - > - ''', xml_data[idx + 1:]) - if m: - value = get_objects(m.group('value'), allow_entity_def=False) - yield ('entity', (m.group('name'), value)) - idx += len(m.group(0)) + 1 - continue - if c == '&': - endpos = xml_data.find(';', idx + 1) - yield ('entity_reference', xml_data[idx + 1:endpos]) +class billion_laughs(unittest.TestCase): + attack = webvulnscan.attacks.billion_laughs - idx = endpos - - else: - yield ('text', c) - - idx += 1 - - -def get_xml_length(xml_data): - object_stream = get_objects(xml_data) - return _calc_xml_length(object_stream, {}) - - -def _calc_xml_length(object_stream, entities): - res = 0 - for otype, ovalue in object_stream: - if otype == 'entity': - ename, evalue = ovalue - entities[ename] = _calc_xml_length(evalue, entities) - elif otype == 'entity_reference': - res += entities[ovalue] - else: # text - res += len(ovalue) - return res - - -def _make_list(gen): - gen = list(gen) - return [ - (key, (val[0], _make_list(val[1]))) - if key == 'entity' - else (key, val) - for key, val in gen] - - -class Handler(BaseHTTPRequestHandler): - - def _default_page(self): - self.send_response(200) - self.end_headers() - self.wfile.write(''' - - - - BillionLaughsTest - - -

BillionLaughsTest

-
- OpenID - -
- - - '''.encode("utf-8")) - - def _serve_request(self): - parsed_path = urlparse(self.path) - - if parsed_path.path == "/": - self._default_page() - elif parsed_path.path == "/form": - self.do_POST() - else: - self.send_error(404, "File not Found!") - - def do_POST(self): - form = cgi.FieldStorage( - fp=self.rfile, - headers=self.headers, - environ={'REQUEST_METHOD': 'POST', - 'CONTENT_TYPE': self.headers['Content-Type'], - }) - - self.send_response(200) - self.end_headers() - self.wfile.write('Client: %s\n' % str(self.client_address)) - self.wfile.write('User-agent: %s\n' % str(self.headers['user-agent'])) - self.wfile.write('Path: %s\n' % self.path) - self.wfile.write('Form data:\n') - - for field in form.keys(): - self.wfile.write('\t%s=%s\n' % (field, form[field].value)) - self.get_XML(form[field].value) - return - - def get_XML(self, url): - if "http://" not in url: - url = "http://" + url - headers = {'Accept': 'application/xrds+xml'} - req = urllib2.Request(url, None, headers) - - try: - response = urllib2.urlopen(req) - except urllib2.HTTPError, e: - self.wfile.write( - '%s There was a problem with your request!' % e.code) - return - except urllib2.URLError, e: - self.wfile.write('%s' % e.args) - return - - html = response.read() - xrds_loc = re.search( - r'[^"]*)"\s*', html, re.IGNORECASE) - - if xrds_loc: - xrds_url = xrds_loc.group('value') - - try: - xrds_doc = urllib2.urlopen(xrds_url) - except urllib2.HTTPError, e: - self.wfile.write( - '%s There was a problem with your request!' % e.code) - return - except urllib2.URLError, e: - self.wfile.write('%s' % e.args) - return - - xrds_doc = xrds_doc.read() - res = get_xml_length(xrds_doc) - # time.sleep(res/100000) - print res - self.wfile.write('Result: %s\n' % res) - - else: - self.wfile.write('XML document could not be found') - - def handle_one_request(self): - try: - self.raw_requestline = self.rfile.readline(65537) - if len(self.raw_requestline) > 65536: - self.requestline = '' - self.request_version = '' - self.command = '' - self.send_error(414) - return - if not self.raw_requestline: - self.close_connection = 1 - return - if not self.parse_request(): - return - - self._serve_request() - self.wfile.flush() - except socket.timeout as e: - self.log_error("Request timed out: %r", e) - self.close_connection = 1 - return - - -def main(): - server_class = HTTPServer - httpd = server_class(("", 8000), Handler) - httpd.serve_forever() - - -class BillionLaughsTest(unittest.TestCase): - - def test_entity_parsing(self): - self.assertEqual( - _make_list(get_objects( - '''''')), - [('entity', ('x', [('text', 'y')]))] - ) - - self.assertEqual( - _make_list(get_objects( - '''&y;x''')), - [('entity_reference', 'y'), ('text', 'x')] - ) - - self.assertEqual( - _make_list(get_objects( - '''&ya;x''')), - [('entity_reference', 'ya'), ('text', 'x')] - ) - - def test_empty(self): - xml_str = '''''' - res = get_xml_length(xml_str) - self.assertEqual(res, 0) - - def test_invalid_reference(self): - xml_str = '&a;' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_lol1(self): - xml_str = ''' - - -&lol1; -''' - res = get_xml_length(xml_str) - self.assertTrue(res >= 30) - - def test_no_reference(self): - xml_str = ''' - - - -''' - res = get_xml_length(xml_str) - self.assertTrue(res < 30) - - def test_lol1_multiple(self): - xml_str = ''' - - - -]> -&lol1;&lol1;&lol1; -''' - res = get_xml_length(xml_str) - self.assertTrue(res >= 90) - - def test_lol1_missing_semicolon(self): - xml_str = ''' - - -&lol1; -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_quadratic_blowup(self): - xml_str = ''' - - -]> -&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A; -''' - res = get_xml_length(xml_str) - self.assertTrue(res >= 2000) - - def test_missing_bracket1(self): - xml_str = ''' - - -&A; -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_missing_bracket2(self): - xml_str = ''' - - -]> -&A; -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_missing_semicolon(self): - xml_str = ''' - - -]> -&A -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_missing_slash(self): - xml_str = ''' - - -]> -&A; -''' - res = get_xml_length(xml_str) - self.assertTrue(res >= 23) - - def test_missing_quotes(self): - xml_str = ''' - - -]> -&A; -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - -if __name__ == '__main__': - # f = open("testdoc", "r") - # xml_data = f.read() - # res = get_xml_length(xml_data) - # print str(res) + " Byte" - # res = res / float(2 ** 30) - # print str(res) + " GB" - # f.close() - # unittest.main() - main() + @tutil.webtest(False) + def test_billion_laughs_static_site(): + return { + '/': u'''''', + } diff --git a/webvulnscan/attacks/__init__.py b/webvulnscan/attacks/__init__.py index 2bd8d8b..dfa4b36 100644 --- a/webvulnscan/attacks/__init__.py +++ b/webvulnscan/attacks/__init__.py @@ -7,7 +7,8 @@ from .cookiescan import cookiescan from .exotic_characters import exotic_characters from .utf7_check import utf7_check +from .billion_laughs import billion_laughs def all_attacks(): - return [xss, csrf, crlf, breach, clickjack, cookiescan, exotic_characters,utf7_check] + return [xss, csrf, crlf, breach, clickjack, cookiescan, exotic_characters,utf7_check,billion_laughs] diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py new file mode 100644 index 0000000..3227310 --- /dev/null +++ b/webvulnscan/attacks/billion_laughs.py @@ -0,0 +1,23 @@ +from ..utils import attack,modify_parameter + +OPENID_URL= "httpl://localhost:8080" + +def attack_post(client,log,form): + parameters = dict(form.get_parameters()) + for parameter in parameters: + attack_parameters = modify_parameter(parameters, parameter, + OPENID_URL) + print "test" + result = form.send(client, attack_parameters) + log('warn', form.action, + 'HTTP Errors occurs when confronted with html input', + "in parameter TEST") +def search(page): + for form in page.get_forms(): + yield form + +@attack(search) +def billion_laughs(client, log, forms): + print forms + globals()['attack_post'](client, log, forms) + print "test3" \ No newline at end of file diff --git a/webvulnscan/attacks/xss.py b/webvulnscan/attacks/xss.py index c8c36c6..4e2a563 100644 --- a/webvulnscan/attacks/xss.py +++ b/webvulnscan/attacks/xss.py @@ -1,4 +1,4 @@ -from ..utils import attack, change_parameter +from ..utils import attack, change_parameter,modify_parameter XSS_STRING = u'' From 268d8db57f6e2524df9f88e447c11fddd1f7e3bc Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 23 Jun 2014 16:34:05 +0200 Subject: [PATCH 23/58] Revert small stuff --- webvulnscan/attacks/xss.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webvulnscan/attacks/xss.py b/webvulnscan/attacks/xss.py index 4e2a563..c8c36c6 100644 --- a/webvulnscan/attacks/xss.py +++ b/webvulnscan/attacks/xss.py @@ -1,4 +1,4 @@ -from ..utils import attack, change_parameter,modify_parameter +from ..utils import attack, change_parameter XSS_STRING = u'' From 68d692116a2349c2c798f6fcfce23b23c3c82f02 Mon Sep 17 00:00:00 2001 From: garet12 Date: Tue, 24 Jun 2014 16:51:27 +0200 Subject: [PATCH 24/58] BL Test now runs using the webrunner --- test/billion_laughs_test.py | 8 +-- test/openID_test.py | 19 ++++--- test/test_billion_laughs.py | 71 ++++++++++++++++++++++++++- test/test_xss.py | 3 +- webvulnscan/attacks/billion_laughs.py | 24 +++------ 5 files changed, 95 insertions(+), 30 deletions(-) diff --git a/test/billion_laughs_test.py b/test/billion_laughs_test.py index 7af2d5c..4f65eb1 100644 --- a/test/billion_laughs_test.py +++ b/test/billion_laughs_test.py @@ -136,10 +136,10 @@ def get_XML(self, url): except urllib2.HTTPError, e: self.wfile.write( '%s There was a problem with your request!' % e.code) - return + return None except urllib2.URLError, e: self.wfile.write('%s' % e.args) - return + return None html = response.read() xrds_loc = re.search( @@ -153,10 +153,10 @@ def get_XML(self, url): except urllib2.HTTPError, e: self.wfile.write( '%s There was a problem with your request!' % e.code) - return + return None except urllib2.URLError, e: self.wfile.write('%s' % e.args) - return + return None return xrds_doc.read() diff --git a/test/openID_test.py b/test/openID_test.py index 8c0357d..3120572 100644 --- a/test/openID_test.py +++ b/test/openID_test.py @@ -7,7 +7,7 @@ class Handler(BaseHTTPRequestHandler): - def _default_page(self): + def _default_page(self,pageState="serverxml"): self.send_response(200) self.end_headers() self.wfile.write(''' @@ -15,18 +15,21 @@ def _default_page(self): OpenID Test Server - +

OpenID Test Server

- '''.encode("utf-8") % PORT) + '''.encode("utf-8") % (PORT,pageState)) def _serve_request(self): parsed_path = urlparse(self.path) - if parsed_path.path == "/": - self._default_page() + self._default_page("serverxml") + elif parsed_path.path == "/db": + self._default_page("serverxml_dangerous_billion") + elif parsed_path.path == "/dq": + self._default_page("serverxml_dangerous_quadratic") elif parsed_path.path == "/serverxml": self.showServerXML(None) elif parsed_path.path == "/serverxml_dangerous_billion": @@ -90,9 +93,9 @@ def showServerXML(self, xmlState=None): '''.encode("utf-8")) - else: #quadratic blowup - self.wfile.write(''' - + else: # quadratic blowup + self.wfile.write('''\ + ]> diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 97b89b9..9f908bf 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -1,9 +1,74 @@ import unittest import cgi import sys - +import re +import urllib2 +import billion_laughs_test import tutil import webvulnscan.attacks.billion_laughs +import xml.etree.ElementTree as ET + + +def get_XML(url): + if "http://" not in url: + url = "http://" + url + headers = {'Accept': 'application/xrds+xml'} + req = urllib2.Request(url, None, headers) + + try: + response = urllib2.urlopen(req) + except urllib2.HTTPError, e: + print( + '%s There was a problem with your request!' % e.code) + return None + except urllib2.URLError, e: + print('%s' % e.args) + return None + + html = response.read() + xrds_loc = re.search( + r'[^"]*)"\s*', html, re.IGNORECASE) + + if xrds_loc: + xrds_url = xrds_loc.group('value') + + try: + xrds_doc = urllib2.urlopen(xrds_url) + except urllib2.HTTPError, e: + print( + '%s There was a problem with your request!' % e.code) + return None + except urllib2.URLError, e: + print('%s' % e.args) + return None + + return xrds_doc.read() + + else: + return None + + +def form_client(): + form = u'''
+ + +
''' + + def xss_site(req): + if 'url' in req.parameters: + xml=get_XML(req.parameters['url']) + if xml is not None: + res = billion_laughs_test.get_xml_length(xml) + return u'%s' % res + else: + return u'XML document could not be found!' + else: + return u'No input was given!' + + return { + '/': form, + '/send': xss_site, + } class billion_laughs(unittest.TestCase): @@ -14,3 +79,7 @@ def test_billion_laughs_static_site(): return { '/': u'''''', } + + @tutil.webtest(True) + def test_billion_laughs_form(): + return form_client() diff --git a/test/test_xss.py b/test/test_xss.py index edb5fcc..be0f803 100644 --- a/test/test_xss.py +++ b/test/test_xss.py @@ -17,8 +17,9 @@ def form_client(method, echo_param): ''' % method def xss_site(req): + print req + print echo_param(req) return u'' + echo_param(req) + u'' - return { '/': form, '/send': xss_site, diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 3227310..6bc9e7d 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -1,23 +1,15 @@ from ..utils import attack,modify_parameter -OPENID_URL= "httpl://localhost:8080" +OPENID_URL= "http://localhost:8080" -def attack_post(client,log,form): - parameters = dict(form.get_parameters()) - for parameter in parameters: - attack_parameters = modify_parameter(parameters, parameter, - OPENID_URL) - print "test" - result = form.send(client, attack_parameters) - log('warn', form.action, - 'HTTP Errors occurs when confronted with html input', - "in parameter TEST") def search(page): for form in page.get_forms(): - yield form + yield (form,) @attack(search) -def billion_laughs(client, log, forms): - print forms - globals()['attack_post'](client, log, forms) - print "test3" \ No newline at end of file +def billion_laughs(client, log, form): + parameters=dict(form.get_parameters()) + for parameter in parameters: + attack_parameters = modify_parameter(parameters, parameter, + OPENID_URL) + result = form.send(client, attack_parameters) From 29afda3945cba0d1e94490b084cdd59e730fc3af Mon Sep 17 00:00:00 2001 From: garet12 Date: Tue, 24 Jun 2014 16:54:02 +0200 Subject: [PATCH 25/58] Revert small changes --- test/test_xss.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_xss.py b/test/test_xss.py index be0f803..edb5fcc 100644 --- a/test/test_xss.py +++ b/test/test_xss.py @@ -17,9 +17,8 @@ def form_client(method, echo_param): ''' % method def xss_site(req): - print req - print echo_param(req) return u'' + echo_param(req) + u'' + return { '/': form, '/send': xss_site, From 73a4dbc33df7e2bb739ccd63344cc751da6d5e42 Mon Sep 17 00:00:00 2001 From: garet12 Date: Tue, 24 Jun 2014 16:55:04 +0200 Subject: [PATCH 26/58] PEP 8 --- test/openID_test.py | 4 ++-- test/test_billion_laughs.py | 2 +- webvulnscan/attacks/billion_laughs.py | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/test/openID_test.py b/test/openID_test.py index 3120572..1edf48f 100644 --- a/test/openID_test.py +++ b/test/openID_test.py @@ -7,7 +7,7 @@ class Handler(BaseHTTPRequestHandler): - def _default_page(self,pageState="serverxml"): + def _default_page(self, pageState="serverxml"): self.send_response(200) self.end_headers() self.wfile.write(''' @@ -20,7 +20,7 @@ def _default_page(self,pageState="serverxml"):

OpenID Test Server

- '''.encode("utf-8") % (PORT,pageState)) + '''.encode("utf-8") % (PORT, pageState)) def _serve_request(self): parsed_path = urlparse(self.path) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 9f908bf..495c3f3 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -56,7 +56,7 @@ def form_client(): def xss_site(req): if 'url' in req.parameters: - xml=get_XML(req.parameters['url']) + xml = get_XML(req.parameters['url']) if xml is not None: res = billion_laughs_test.get_xml_length(xml) return u'%s' % res diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 6bc9e7d..adbe90b 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -1,14 +1,16 @@ -from ..utils import attack,modify_parameter +from ..utils import attack, modify_parameter + +OPENID_URL = "http://localhost:8080" -OPENID_URL= "http://localhost:8080" def search(page): for form in page.get_forms(): yield (form,) + @attack(search) def billion_laughs(client, log, form): - parameters=dict(form.get_parameters()) + parameters = dict(form.get_parameters()) for parameter in parameters: attack_parameters = modify_parameter(parameters, parameter, OPENID_URL) From fca3e816cce532436e719bb042c153cf5687574a Mon Sep 17 00:00:00 2001 From: garet12 Date: Thu, 26 Jun 2014 16:12:42 +0200 Subject: [PATCH 27/58] First implementation of test --- test/test_billion_laughs.py | 17 +++++++++++------ webvulnscan/attacks/billion_laughs.py | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 495c3f3..4a4b3fa 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -48,16 +48,17 @@ def get_XML(url): return None -def form_client(): +def form_client(value=""): form = u'''
- + -
''' + ''' % value - def xss_site(req): + def result(req): if 'url' in req.parameters: xml = get_XML(req.parameters['url']) if xml is not None: + bla = ET.fromstring(xml) res = billion_laughs_test.get_xml_length(xml) return u'%s' % res else: @@ -67,7 +68,7 @@ def xss_site(req): return { '/': form, - '/send': xss_site, + '/send': result, } @@ -80,6 +81,10 @@ def test_billion_laughs_static_site(): '/': u'''''', } - @tutil.webtest(True) + @tutil.webtest(False) def test_billion_laughs_form(): return form_client() + + # @tutil.webtest(True) + # def test_billion_laughs_dangerous(): + # return form_client("http://localhost:8080/db") diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index adbe90b..94144a5 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -1,6 +1,9 @@ from ..utils import attack, modify_parameter +import time +import multiprocessing OPENID_URL = "http://localhost:8080" +OPENID_URL_BILLION_LAUGHS = "http://localhost:8080/db" def search(page): @@ -12,6 +15,21 @@ def search(page): def billion_laughs(client, log, form): parameters = dict(form.get_parameters()) for parameter in parameters: + print parameters attack_parameters = modify_parameter(parameters, parameter, OPENID_URL) + startTime = time.time() result = form.send(client, attack_parameters) + endTime = time.time() + elapsedTime = endTime - startTime + attack_parameters = modify_parameter(parameters, parameter, + OPENID_URL_BILLION_LAUGHS) + + p = multiprocessing.Process( + target=form.send, args=(client, attack_parameters)) + p.start() + p.join(elapsedTime + 2) + if p.is_alive(): + log('vuln', result.url, "Billion Laughs", "in URL parameter " + parameter) + p.terminate() + p.join() From 773904cd2d0700bbea5875d30dfeb95aa54786e0 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 28 Jun 2014 17:26:52 +0200 Subject: [PATCH 28/58] Automatic start of OpenID Server --- test/billion_laughs_test.py | 12 ++++++---- test/test_billion_laughs.py | 33 ++++++++++++++++----------- webvulnscan/attacks/billion_laughs.py | 22 +++++++++++++++--- {test => webvulnscan}/openID_test.py | 8 ++++--- 4 files changed, 52 insertions(+), 23 deletions(-) rename {test => webvulnscan}/openID_test.py (87%) diff --git a/test/billion_laughs_test.py b/test/billion_laughs_test.py index 4f65eb1..8f36c69 100644 --- a/test/billion_laughs_test.py +++ b/test/billion_laughs_test.py @@ -6,6 +6,7 @@ import cgi import urllib2 import time +import xml.etree.ElementTree as ET def get_objects(xml_data, allow_entity_def=True): @@ -112,16 +113,19 @@ def do_POST(self): for field in form.keys(): self.wfile.write('\t%s=%s\n' % (field, form[field].value)) + if field =='': + continue xrds_doc = self.get_XML(form[field].value) if xrds_doc is not None: res = get_xml_length(xrds_doc) - # time.sleep(res/100000) + bla = ET.fromstring(xrds_doc) + #time.sleep(res/100000) print res self.wfile.write('Result: %s\n' % res) else: - self.wfile.write('XML document could not be found') + self.wfile.write('XML document could not be found\n') return @@ -135,7 +139,7 @@ def get_XML(self, url): response = urllib2.urlopen(req) except urllib2.HTTPError, e: self.wfile.write( - '%s There was a problem with your request!' % e.code) + '%s There was a problem with your request!\n' % e.code) return None except urllib2.URLError, e: self.wfile.write('%s' % e.args) @@ -152,7 +156,7 @@ def get_XML(self, url): xrds_doc = urllib2.urlopen(xrds_url) except urllib2.HTTPError, e: self.wfile.write( - '%s There was a problem with your request!' % e.code) + '%s There was a problem with your request!\n' % e.code) return None except urllib2.URLError, e: self.wfile.write('%s' % e.args) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 4a4b3fa..927d098 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -19,7 +19,7 @@ def get_XML(url): response = urllib2.urlopen(req) except urllib2.HTTPError, e: print( - '%s There was a problem with your request!' % e.code) + '%s There was a problem with your request!\n' % e.code) return None except urllib2.URLError, e: print('%s' % e.args) @@ -36,7 +36,7 @@ def get_XML(url): xrds_doc = urllib2.urlopen(xrds_url) except urllib2.HTTPError, e: print( - '%s There was a problem with your request!' % e.code) + '%s There was a problem with your request!\n' % e.code) return None except urllib2.URLError, e: print('%s' % e.args) @@ -48,19 +48,22 @@ def get_XML(url): return None -def form_client(value=""): +def form_client(value="",vuln=False): form = u'''
- - + +
''' % value def result(req): if 'url' in req.parameters: xml = get_XML(req.parameters['url']) if xml is not None: - bla = ET.fromstring(xml) - res = billion_laughs_test.get_xml_length(xml) - return u'%s' % res + if vuln == True: + ET.fromstring(xml) + return u'Dangerous' + else: + res = billion_laughs_test.get_xml_length(xml) + return u'%s' % res else: return u'XML document could not be found!' else: @@ -76,15 +79,19 @@ class billion_laughs(unittest.TestCase): attack = webvulnscan.attacks.billion_laughs @tutil.webtest(False) - def test_billion_laughs_static_site(): + def test_static_site(): return { '/': u'''''', } @tutil.webtest(False) - def test_billion_laughs_form(): + def test_form(): return form_client() - # @tutil.webtest(True) - # def test_billion_laughs_dangerous(): - # return form_client("http://localhost:8080/db") + @tutil.webtest(True) + def test_billion_laughs_dangerous(): + return form_client("http://localhost:8080/db",True) + + @tutil.webtest(True) + def test_quadratic_blowup_dangerous(): + return form_client("http://localhost:8080/dq",True) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 94144a5..ba6ac15 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -1,6 +1,7 @@ from ..utils import attack, modify_parameter import time import multiprocessing +from ..openID_test import main OPENID_URL = "http://localhost:8080" OPENID_URL_BILLION_LAUGHS = "http://localhost:8080/db" @@ -14,22 +15,37 @@ def search(page): @attack(search) def billion_laughs(client, log, form): parameters = dict(form.get_parameters()) + server = multiprocessing.Process(target=main) + server.start() for parameter in parameters: print parameters + print parameter + if parameter == '': + continue + if parameters[parameter] == 'abcdefgh': + xml_page = OPENID_URL_BILLION_LAUGHS + else: + xml_page = parameters[parameter] attack_parameters = modify_parameter(parameters, parameter, OPENID_URL) startTime = time.time() + server.join(0.001) result = form.send(client, attack_parameters) endTime = time.time() elapsedTime = endTime - startTime attack_parameters = modify_parameter(parameters, parameter, - OPENID_URL_BILLION_LAUGHS) + xml_page) p = multiprocessing.Process( target=form.send, args=(client, attack_parameters)) p.start() - p.join(elapsedTime + 2) + + p.join(elapsedTime + 1) if p.is_alive(): - log('vuln', result.url, "Billion Laughs", "in URL parameter " + parameter) + log('vuln', result.url, "Billion Laughs", + "in URL parameter " + parameter) p.terminate() p.join() + + server.terminate() + server.join() diff --git a/test/openID_test.py b/webvulnscan/openID_test.py similarity index 87% rename from test/openID_test.py rename to webvulnscan/openID_test.py index 1edf48f..971ff74 100644 --- a/test/openID_test.py +++ b/webvulnscan/openID_test.py @@ -94,13 +94,15 @@ def showServerXML(self, xmlState=None): '''.encode("utf-8")) else: # quadratic blowup + ent_a="AAA"*99999 + ref_a="&A;"*99999 self.wfile.write('''\ + ]> -&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A; -'''.encode("utf-8")) +%s +'''.encode("utf-8")%(ent_a,ref_a)) def handle_one_request(self): try: From 1576e8beec01e174d40c187e3801b74f9a3f6be8 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 28 Jun 2014 17:33:44 +0200 Subject: [PATCH 29/58] Added time.sleep instead of real parser to test application --- test/billion_laughs_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/billion_laughs_test.py b/test/billion_laughs_test.py index 8f36c69..a5d4cad 100644 --- a/test/billion_laughs_test.py +++ b/test/billion_laughs_test.py @@ -119,8 +119,8 @@ def do_POST(self): if xrds_doc is not None: res = get_xml_length(xrds_doc) - bla = ET.fromstring(xrds_doc) - #time.sleep(res/100000) + #ET.fromstring(xrds_doc) + time.sleep(res/1000000) print res self.wfile.write('Result: %s\n' % res) From 6b1f5602b5be35ea7e3b1cca959f4aab43083c26 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sun, 29 Jun 2014 13:04:50 +0200 Subject: [PATCH 30/58] Some compatibility stuff for python 3 added --- test/billion_laughs_test.py | 14 +++++++++++--- test/test_billion_laughs.py | 28 +++++++++++++-------------- webvulnscan/attacks/billion_laughs.py | 5 +++-- webvulnscan/openID_test.py | 11 +++++++++-- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/test/billion_laughs_test.py b/test/billion_laughs_test.py index a5d4cad..b264d5b 100644 --- a/test/billion_laughs_test.py +++ b/test/billion_laughs_test.py @@ -1,8 +1,16 @@ import unittest import re -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +try: + from http.server import BaseHTTPRequestHandler, HTTPServer +except ImportError: + from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer + +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + import socket -from urlparse import urlparse import cgi import urllib2 import time @@ -121,7 +129,7 @@ def do_POST(self): res = get_xml_length(xrds_doc) #ET.fromstring(xrds_doc) time.sleep(res/1000000) - print res + print(res) self.wfile.write('Result: %s\n' % res) else: diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 927d098..3399dcc 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -1,11 +1,10 @@ import unittest -import cgi -import sys import re import urllib2 import billion_laughs_test import tutil import webvulnscan.attacks.billion_laughs +import time import xml.etree.ElementTree as ET @@ -48,7 +47,7 @@ def get_XML(url): return None -def form_client(value="",vuln=False): +def form_client(value="", vuln=False): form = u'''
@@ -58,16 +57,15 @@ def result(req): if 'url' in req.parameters: xml = get_XML(req.parameters['url']) if xml is not None: + res = billion_laughs_test.get_xml_length(xml) if vuln == True: - ET.fromstring(xml) - return u'Dangerous' - else: - res = billion_laughs_test.get_xml_length(xml) - return u'%s' % res + # ET.fromstring(xml) + time.sleep(res / 10000) + return u'%s' % res else: return u'XML document could not be found!' else: - return u'No input was given!' + return u'There is no input!' return { '/': form, @@ -79,19 +77,19 @@ class billion_laughs(unittest.TestCase): attack = webvulnscan.attacks.billion_laughs @tutil.webtest(False) - def test_static_site(): + def test_billion_laughs_static_site(): return { '/': u'''''', } @tutil.webtest(False) - def test_form(): + def test_billion_laughs_form(): return form_client() @tutil.webtest(True) - def test_billion_laughs_dangerous(): - return form_client("http://localhost:8080/db",True) + def test_billion_laughs_dangerous1(): + return form_client("http://localhost:8080/db", True) @tutil.webtest(True) - def test_quadratic_blowup_dangerous(): - return form_client("http://localhost:8080/dq",True) + def test_billion_laughs_dangerous2(): + return form_client("http://localhost:8080/dq", True) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index ba6ac15..ca1dba1 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -5,6 +5,7 @@ OPENID_URL = "http://localhost:8080" OPENID_URL_BILLION_LAUGHS = "http://localhost:8080/db" +OPENID_URL_QUADRATIC_BLOWUP = "http://localhost:8080/dq" def search(page): @@ -18,8 +19,8 @@ def billion_laughs(client, log, form): server = multiprocessing.Process(target=main) server.start() for parameter in parameters: - print parameters - print parameter + print(parameters) + print(parameter) if parameter == '': continue if parameters[parameter] == 'abcdefgh': diff --git a/webvulnscan/openID_test.py b/webvulnscan/openID_test.py index 971ff74..f81ba18 100644 --- a/webvulnscan/openID_test.py +++ b/webvulnscan/openID_test.py @@ -1,6 +1,13 @@ -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import socket -from urlparse import urlparse +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + +try: + from http.server import BaseHTTPRequestHandler, HTTPServer +except ImportError: + from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer PORT = 8080 From c3eed4f7cbbcf122d5d7eeeb96c0baef0c3ab218 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sun, 29 Jun 2014 13:09:08 +0200 Subject: [PATCH 31/58] testdoc removed --- test/testdoc | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 test/testdoc diff --git a/test/testdoc b/test/testdoc deleted file mode 100644 index 32ea403..0000000 --- a/test/testdoc +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - -]> - - - - - %s - &lol9; - %s - - - - - From b23a33033b40b579d67b97f02e6c4624679525f9 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sun, 29 Jun 2014 15:41:44 +0200 Subject: [PATCH 32/58] Port is now easier to change --- test/billion_laughs_test.py | 8 -------- test/test_billion_laughs.py | 13 +++++-------- webvulnscan/attacks/billion_laughs.py | 16 +++++++++------- webvulnscan/openID_test.py | 11 ++++++----- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/test/billion_laughs_test.py b/test/billion_laughs_test.py index b264d5b..1a13393 100644 --- a/test/billion_laughs_test.py +++ b/test/billion_laughs_test.py @@ -129,7 +129,6 @@ def do_POST(self): res = get_xml_length(xrds_doc) #ET.fromstring(xrds_doc) time.sleep(res/1000000) - print(res) self.wfile.write('Result: %s\n' % res) else: @@ -334,12 +333,5 @@ def test_missing_quotes(self): self.assertRaises(BaseException, get_xml_length, xml_str) if __name__ == '__main__': - # f = open("testdoc", "r") - # xml_data = f.read() - # res = get_xml_length(xml_data) - # print str(res) + " Byte" - # res = res / float(2 ** 30) - # print str(res) + " GB" - # f.close() # unittest.main() main() diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 3399dcc..79d24f7 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -47,11 +47,11 @@ def get_XML(url): return None -def form_client(value="", vuln=False): +def form_client(vuln=False): form = u''' - + - ''' % value + ''' def result(req): if 'url' in req.parameters: @@ -87,9 +87,6 @@ def test_billion_laughs_form(): return form_client() @tutil.webtest(True) - def test_billion_laughs_dangerous1(): - return form_client("http://localhost:8080/db", True) + def test_billion_laughs_dangerous(): + return form_client(True) - @tutil.webtest(True) - def test_billion_laughs_dangerous2(): - return form_client("http://localhost:8080/dq", True) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index ca1dba1..69e02c5 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -3,10 +3,6 @@ import multiprocessing from ..openID_test import main -OPENID_URL = "http://localhost:8080" -OPENID_URL_BILLION_LAUGHS = "http://localhost:8080/db" -OPENID_URL_QUADRATIC_BLOWUP = "http://localhost:8080/dq" - def search(page): for form in page.get_forms(): @@ -15,8 +11,14 @@ def search(page): @attack(search) def billion_laughs(client, log, form): + + port = 50162 + openid_url = "http://localhost:%i" % port + openid_billion_laughs = "http://localhost:%i/db" % port + openid_quadratic_blowup = "http://localhost:%i/dq" % port + parameters = dict(form.get_parameters()) - server = multiprocessing.Process(target=main) + server = multiprocessing.Process(target=main, args=(port,)) server.start() for parameter in parameters: print(parameters) @@ -24,11 +26,11 @@ def billion_laughs(client, log, form): if parameter == '': continue if parameters[parameter] == 'abcdefgh': - xml_page = OPENID_URL_BILLION_LAUGHS + xml_page = openid_billion_laughs else: xml_page = parameters[parameter] attack_parameters = modify_parameter(parameters, parameter, - OPENID_URL) + openid_url) startTime = time.time() server.join(0.001) result = form.send(client, attack_parameters) diff --git a/webvulnscan/openID_test.py b/webvulnscan/openID_test.py index f81ba18..c9d71d5 100644 --- a/webvulnscan/openID_test.py +++ b/webvulnscan/openID_test.py @@ -9,8 +9,7 @@ except ImportError: from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -PORT = 8080 - +PORT = 50161 class Handler(BaseHTTPRequestHandler): @@ -134,9 +133,11 @@ def handle_one_request(self): return -def main(): - httpd = HTTPServer(("", PORT), Handler) +def main(port): + global PORT + PORT=port + httpd = HTTPServer(("", port), Handler) httpd.serve_forever() if __name__ == '__main__': - main() + main(PORT) From fe3ed61a6fae5e53d2206427517418f34316b245 Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 30 Jun 2014 15:24:50 +0200 Subject: [PATCH 33/58] Another version of BL test --- ...ghs_test.py => billion_laughs_test_app.py} | 4 +- test/test_billion_laughs.py | 10 +- webvulnscan/attacks/billion_laughs.py | 125 ++++++++++++------ .../{openID_test.py => openID_test_server.py} | 38 ++++-- 4 files changed, 118 insertions(+), 59 deletions(-) rename test/{billion_laughs_test.py => billion_laughs_test_app.py} (98%) rename webvulnscan/{openID_test.py => openID_test_server.py} (84%) diff --git a/test/billion_laughs_test.py b/test/billion_laughs_test_app.py similarity index 98% rename from test/billion_laughs_test.py rename to test/billion_laughs_test_app.py index 1a13393..535d546 100644 --- a/test/billion_laughs_test.py +++ b/test/billion_laughs_test_app.py @@ -86,8 +86,8 @@ def _default_page(self):

BillionLaughsTest

-
- OpenID + + OpenID
diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 79d24f7..8bb1b14 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -1,7 +1,7 @@ import unittest import re import urllib2 -import billion_laughs_test +import billion_laughs_test_app import tutil import webvulnscan.attacks.billion_laughs import time @@ -49,15 +49,15 @@ def get_XML(url): def form_client(vuln=False): form = u'''
- +
''' def result(req): - if 'url' in req.parameters: - xml = get_XML(req.parameters['url']) + if 'openid' in req.parameters: + xml = get_XML(req.parameters['openid']) if xml is not None: - res = billion_laughs_test.get_xml_length(xml) + res = billion_laughs_test_app.get_xml_length(xml) if vuln == True: # ET.fromstring(xml) time.sleep(res / 10000) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 69e02c5..5bb5347 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -1,7 +1,6 @@ -from ..utils import attack, modify_parameter -import time -import multiprocessing -from ..openID_test import main +from ..utils import attack +from ..openID_test_server import OpenIDServer +import socket def search(page): @@ -11,44 +10,82 @@ def search(page): @attack(search) def billion_laughs(client, log, form): + def attack(attack_url): + parameters = dict(form.get_parameters()) + for key in parameters: + if 'openid' in key: + parameters[key] = attack_url - port = 50162 - openid_url = "http://localhost:%i" % port - openid_billion_laughs = "http://localhost:%i/db" % port - openid_quadratic_blowup = "http://localhost:%i/dq" % port - - parameters = dict(form.get_parameters()) - server = multiprocessing.Process(target=main, args=(port,)) - server.start() - for parameter in parameters: - print(parameters) - print(parameter) - if parameter == '': - continue - if parameters[parameter] == 'abcdefgh': - xml_page = openid_billion_laughs - else: - xml_page = parameters[parameter] - attack_parameters = modify_parameter(parameters, parameter, - openid_url) - startTime = time.time() - server.join(0.001) - result = form.send(client, attack_parameters) - endTime = time.time() - elapsedTime = endTime - startTime - attack_parameters = modify_parameter(parameters, parameter, - xml_page) - - p = multiprocessing.Process( - target=form.send, args=(client, attack_parameters)) - p.start() - - p.join(elapsedTime + 1) - if p.is_alive(): - log('vuln', result.url, "Billion Laughs", - "in URL parameter " + parameter) - p.terminate() - p.join() - - server.terminate() - server.join() + try: + result = form.send(client, parameters) + except socket.timeout: + return True + return False + + with OpenIDServer.create_server() as openid_server: + if attack(openid_server.benign_url): + log('warn', openid_server.benign_url, "Billion Laughs", + "Test did not work") + return + + for evil_url in openid_server.evil_urls: + if attack(evil_url): + log('vuln', evil_url, "Billion Laughs") + + + + +# from ..utils import attack, modify_parameter +# import time +# import multiprocessing +# from ..openID_test import main + + +# def search(page): +# for form in page.get_forms(): +# yield (form,) + + +# @attack(search) +# def billion_laughs(client, log, form): + +# port = 50162 +# openid_url = "http://localhost:%i" % port +# openid_billion_laughs = "http://localhost:%i/db" % port +# openid_quadratic_blowup = "http://localhost:%i/dq" % port + +# parameters = dict(form.get_parameters()) +# server = multiprocessing.Process(target=main, args=(port,)) +# server.start() +# for parameter in parameters: +# print(parameters) +# print(parameter) +# if parameter == '': +# continue +# if parameters[parameter] == 'abcdefgh': +# xml_page = openid_billion_laughs +# else: +# xml_page = parameters[parameter] +# attack_parameters = modify_parameter(parameters, parameter, +# openid_url) +# server.join(0.001) +# startTime = time.time() +# result = form.send(client, attack_parameters) +# endTime = time.time() +# elapsedTime = endTime - startTime +# attack_parameters = modify_parameter(parameters, parameter, +# xml_page) + +# p = multiprocessing.Process( +# target=form.send, args=(client, attack_parameters)) +# p.start() + +# p.join(elapsedTime + 1) +# if p.is_alive(): +# log('vuln', result.url, "Billion Laughs", +# "in URL parameter " + parameter) +# p.terminate() +# p.join() + +# server.terminate() +# server.join() diff --git a/webvulnscan/openID_test.py b/webvulnscan/openID_test_server.py similarity index 84% rename from webvulnscan/openID_test.py rename to webvulnscan/openID_test_server.py index c9d71d5..90c74b2 100644 --- a/webvulnscan/openID_test.py +++ b/webvulnscan/openID_test_server.py @@ -1,4 +1,5 @@ import socket +import multiprocessing try: from urllib.parse import urlparse except ImportError: @@ -11,6 +12,7 @@ PORT = 50161 + class Handler(BaseHTTPRequestHandler): def _default_page(self, pageState="serverxml"): @@ -100,15 +102,15 @@ def showServerXML(self, xmlState=None): '''.encode("utf-8")) else: # quadratic blowup - ent_a="AAA"*99999 - ref_a="&A;"*99999 + ent_a = "AAA" * 99999 + ref_a = "&A;" * 99999 self.wfile.write('''\ ]> %s -'''.encode("utf-8")%(ent_a,ref_a)) +'''.encode("utf-8") % (ent_a, ref_a)) def handle_one_request(self): try: @@ -133,11 +135,31 @@ def handle_one_request(self): return -def main(port): - global PORT - PORT=port - httpd = HTTPServer(("", port), Handler) +def main(): + httpd = HTTPServer(("", PORT), Handler) httpd.serve_forever() + +class OpenIDServer(): + + class create_server(): + benign_url = "http://localhost:%i" % PORT + evil_urls = ("http://localhost:%i/db" % + PORT, "http://localhost:%i/dq" % PORT) + + def __init__(self): + # self.config=config + self.server = None + + def __enter__(self): + self.server = multiprocessing.Process(target=main) + self.server.start() + self.server.join(0.0001) + return self + + def __exit__(self, type, value, traceback): + self.server.terminate() + + if __name__ == '__main__': - main(PORT) + main() From 68c9dc22a4895018ee9419d6be3ba5957f865a72 Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 30 Jun 2014 16:37:08 +0200 Subject: [PATCH 34/58] Timeout now possible, working version of test --- test/billion_laughs_test_app.py | 6 ++---- test/test_billion_laughs.py | 23 +++++++++-------------- webvulnscan/attacks/billion_laughs.py | 2 +- webvulnscan/client.py | 9 +++++++-- webvulnscan/form.py | 4 ++-- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/test/billion_laughs_test_app.py b/test/billion_laughs_test_app.py index 535d546..70c373b 100644 --- a/test/billion_laughs_test_app.py +++ b/test/billion_laughs_test_app.py @@ -14,7 +14,6 @@ import cgi import urllib2 import time -import xml.etree.ElementTree as ET def get_objects(xml_data, allow_entity_def=True): @@ -121,14 +120,13 @@ def do_POST(self): for field in form.keys(): self.wfile.write('\t%s=%s\n' % (field, form[field].value)) - if field =='': + if field == '': continue xrds_doc = self.get_XML(form[field].value) if xrds_doc is not None: res = get_xml_length(xrds_doc) - #ET.fromstring(xrds_doc) - time.sleep(res/1000000) + time.sleep(res / 1000000) self.wfile.write('Result: %s\n' % res) else: diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 8bb1b14..7d26bde 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -5,7 +5,6 @@ import tutil import webvulnscan.attacks.billion_laughs import time -import xml.etree.ElementTree as ET def get_XML(url): @@ -47,25 +46,22 @@ def get_XML(url): return None -def form_client(vuln=False): +def form_client(vulnerable=False): form = u'''
''' def result(req): - if 'openid' in req.parameters: - xml = get_XML(req.parameters['openid']) - if xml is not None: - res = billion_laughs_test_app.get_xml_length(xml) - if vuln == True: - # ET.fromstring(xml) - time.sleep(res / 10000) - return u'%s' % res - else: - return u'XML document could not be found!' - else: + if 'openid' not in req.parameters: return u'There is no input!' + xml = get_XML(req.parameters['openid']) + if xml is None: + return u'XML document could not be found!' + res = billion_laughs_test_app.get_xml_length(xml) + if vulnerable: + time.sleep(res / 10000) + return u'%s' % res return { '/': form, @@ -89,4 +85,3 @@ def test_billion_laughs_form(): @tutil.webtest(True) def test_billion_laughs_dangerous(): return form_client(True) - diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 5bb5347..9605961 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -17,7 +17,7 @@ def attack(attack_url): parameters[key] = attack_url try: - result = form.send(client, parameters) + form.send(client, parameters, timeout=1) except socket.timeout: return True return False diff --git a/webvulnscan/client.py b/webvulnscan/client.py index bea4984..b52aa02 100644 --- a/webvulnscan/client.py +++ b/webvulnscan/client.py @@ -8,6 +8,7 @@ import gzip import zlib import webvulnscan.log +import socket from .page import Page from .request import Request @@ -82,11 +83,15 @@ def download(self, url_or_request, parameters=None, headers=None): return self._download(request) - def download_page(self, url_or_request, parameters=None, req_headers=None): + def download_page(self, url_or_request, parameters=None, req_headers=None,**kwargs): """ Downloads the content of a site, returns it as page. Throws NotAPage if the content is not a webpage. """ - + timeout=None + for key in kwargs: + if key=="timeout": + timeout=kwargs[key] + socket.setdefaulttimeout(timeout) request, status_code, html_bytes, headers = self.download( url_or_request, parameters, req_headers) diff --git a/webvulnscan/form.py b/webvulnscan/form.py index 9ce81e9..36b32fb 100644 --- a/webvulnscan/form.py +++ b/webvulnscan/form.py @@ -34,9 +34,9 @@ def get_textarea_elements(self): for textarea in self.document.findall('.//textarea'): yield textarea - def send(self, client, parameters): + def send(self, client, parameters,**kwargs): if self.method == "get": url = add_get_params(self.action, parameters) return client.download_page(url) else: - return client.download_page(self.action, parameters) + return client.download_page(self.action, parameters,**kwargs) From da177be51c47c7fa5ef50abd27fc11f905b75c45 Mon Sep 17 00:00:00 2001 From: garet12 Date: Tue, 1 Jul 2014 15:14:36 +0200 Subject: [PATCH 35/58] XML parser has now own module --- test/billion_laughs_test_app.py | 191 +------------------------ test/test_billion_laughs.py | 37 ++--- test/xml_parser.py | 192 ++++++++++++++++++++++++++ webvulnscan/attacks/billion_laughs.py | 1 + webvulnscan/openID_test_server.py | 2 +- 5 files changed, 217 insertions(+), 206 deletions(-) create mode 100644 test/xml_parser.py diff --git a/test/billion_laughs_test_app.py b/test/billion_laughs_test_app.py index 70c373b..0ea7526 100644 --- a/test/billion_laughs_test_app.py +++ b/test/billion_laughs_test_app.py @@ -1,4 +1,4 @@ -import unittest +import xml_parser import re try: from http.server import BaseHTTPRequestHandler, HTTPServer @@ -16,62 +16,6 @@ import time -def get_objects(xml_data, allow_entity_def=True): - idx = 0 - while idx < len(xml_data): - c = xml_data[idx] - if c == '<' and allow_entity_def: - m = re.match(r'''(?x) - !ENTITY\s+ - (?P[a-zA-Z0-9_-]+)\s+ - "(?P[^"]*)"\s* - > - ''', xml_data[idx + 1:]) - if m: - value = get_objects(m.group('value'), allow_entity_def=False) - yield ('entity', (m.group('name'), value)) - idx += len(m.group(0)) + 1 - continue - - if c == '&': - endpos = xml_data.find(';', idx + 1) - yield ('entity_reference', xml_data[idx + 1:endpos]) - - idx = endpos - - else: - yield ('text', c) - - idx += 1 - - -def get_xml_length(xml_data): - object_stream = get_objects(xml_data) - return _calc_xml_length(object_stream, {}) - - -def _calc_xml_length(object_stream, entities): - res = 0 - for otype, ovalue in object_stream: - if otype == 'entity': - ename, evalue = ovalue - entities[ename] = _calc_xml_length(evalue, entities) - elif otype == 'entity_reference': - res += entities[ovalue] - else: # text - res += len(ovalue) - return res - - -def _make_list(gen): - gen = list(gen) - return [ - (key, (val[0], _make_list(val[1]))) - if key == 'entity' - else (key, val) - for key, val in gen] - - class Handler(BaseHTTPRequestHandler): def _default_page(self): @@ -125,7 +69,7 @@ def do_POST(self): xrds_doc = self.get_XML(form[field].value) if xrds_doc is not None: - res = get_xml_length(xrds_doc) + res = xml_parser.get_xml_length(xrds_doc) time.sleep(res / 1000000) self.wfile.write('Result: %s\n' % res) @@ -200,136 +144,5 @@ def main(): httpd.serve_forever() -class BillionLaughsTest(unittest.TestCase): - - def test_entity_parsing(self): - self.assertEqual( - _make_list(get_objects( - '''''')), - [('entity', ('x', [('text', 'y')]))] - ) - - self.assertEqual( - _make_list(get_objects( - '''&y;x''')), - [('entity_reference', 'y'), ('text', 'x')] - ) - - self.assertEqual( - _make_list(get_objects( - '''&ya;x''')), - [('entity_reference', 'ya'), ('text', 'x')] - ) - - def test_empty(self): - xml_str = '''''' - res = get_xml_length(xml_str) - self.assertEqual(res, 0) - - def test_invalid_reference(self): - xml_str = '&a;' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_lol1(self): - xml_str = ''' - - -&lol1; -''' - res = get_xml_length(xml_str) - self.assertTrue(res >= 30) - - def test_no_reference(self): - xml_str = ''' - - - -''' - res = get_xml_length(xml_str) - self.assertTrue(res < 30) - - def test_lol1_multiple(self): - xml_str = ''' - - - -]> -&lol1;&lol1;&lol1; -''' - res = get_xml_length(xml_str) - self.assertTrue(res >= 90) - - def test_lol1_missing_semicolon(self): - xml_str = ''' - - -&lol1; -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_quadratic_blowup(self): - xml_str = ''' - - -]> -&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A; -''' - res = get_xml_length(xml_str) - self.assertTrue(res >= 2000) - - def test_missing_bracket1(self): - xml_str = ''' - - -&A; -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_missing_bracket2(self): - xml_str = ''' - - -]> -&A; -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_missing_semicolon(self): - xml_str = ''' - - -]> -&A -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - - def test_missing_slash(self): - xml_str = ''' - - -]> -&A; -''' - res = get_xml_length(xml_str) - self.assertTrue(res >= 23) - - def test_missing_quotes(self): - xml_str = ''' - - -]> -&A; -''' - self.assertRaises(BaseException, get_xml_length, xml_str) - if __name__ == '__main__': - # unittest.main() main() diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 7d26bde..722cac4 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -1,10 +1,11 @@ import unittest import re import urllib2 -import billion_laughs_test_app +import xml_parser import tutil import webvulnscan.attacks.billion_laughs import time +import socket def get_XML(url): @@ -27,23 +28,24 @@ def get_XML(url): xrds_loc = re.search( r'[^"]*)"\s*', html, re.IGNORECASE) - if xrds_loc: - xrds_url = xrds_loc.group('value') - - try: - xrds_doc = urllib2.urlopen(xrds_url) - except urllib2.HTTPError, e: - print( - '%s There was a problem with your request!\n' % e.code) - return None - except urllib2.URLError, e: - print('%s' % e.args) - return None + if not xrds_loc: + return None - return xrds_doc.read() + xrds_url = xrds_loc.group('value') - else: + try: + xrds_doc = urllib2.urlopen(xrds_url) + except urllib2.HTTPError, e: + print( + '%s There was a problem with your request!\n' % e.code) return None + except urllib2.URLError, e: + print('%s' % e.args) + return None + + return xrds_doc.read() + + def form_client(vulnerable=False): @@ -55,11 +57,14 @@ def form_client(vulnerable=False): def result(req): if 'openid' not in req.parameters: return u'There is no input!' + xml = get_XML(req.parameters['openid']) if xml is None: return u'XML document could not be found!' - res = billion_laughs_test_app.get_xml_length(xml) + res = xml_parser.get_xml_length(xml) if vulnerable: + # ueber req timeout zeug uebergeben + raise(socket.timeout) time.sleep(res / 10000) return u'%s' % res diff --git a/test/xml_parser.py b/test/xml_parser.py new file mode 100644 index 0000000..37b69ca --- /dev/null +++ b/test/xml_parser.py @@ -0,0 +1,192 @@ +import unittest +import re + + +def get_objects(xml_data, allow_entity_def=True): + idx = 0 + while idx < len(xml_data): + c = xml_data[idx] + if c == '<' and allow_entity_def: + m = re.match(r'''(?x) + !ENTITY\s+ + (?P[a-zA-Z0-9_-]+)\s+ + "(?P[^"]*)"\s* + > + ''', xml_data[idx + 1:]) + if m: + value = get_objects(m.group('value'), allow_entity_def=False) + yield ('entity', (m.group('name'), value)) + idx += len(m.group(0)) + 1 + continue + + if c == '&': + endpos = xml_data.find(';', idx + 1) + yield ('entity_reference', xml_data[idx + 1:endpos]) + + idx = endpos + + else: + yield ('text', c) + + idx += 1 + + +def get_xml_length(xml_data): + object_stream = get_objects(xml_data) + return _calc_xml_length(object_stream, {}) + + +def _calc_xml_length(object_stream, entities): + res = 0 + for otype, ovalue in object_stream: + if otype == 'entity': + ename, evalue = ovalue + entities[ename] = _calc_xml_length(evalue, entities) + elif otype == 'entity_reference': + res += entities[ovalue] + else: # text + res += len(ovalue) + return res + + +def _make_list(gen): + gen = list(gen) + return [ + (key, (val[0], _make_list(val[1]))) + if key == 'entity' + else (key, val) + for key, val in gen] + +if __name__ == '__main__': + unittest.main() + + +class XMLParser_test(unittest.TestCase): + + def test_entity_parsing(self): + self.assertEqual( + _make_list(get_objects( + '''''')), + [('entity', ('x', [('text', 'y')]))] + ) + + self.assertEqual( + _make_list(get_objects( + '''&y;x''')), + [('entity_reference', 'y'), ('text', 'x')] + ) + + self.assertEqual( + _make_list(get_objects( + '''&ya;x''')), + [('entity_reference', 'ya'), ('text', 'x')] + ) + + def test_empty(self): + xml_str = '''''' + res = get_xml_length(xml_str) + self.assertEqual(res, 0) + + def test_invalid_reference(self): + xml_str = '&a;' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_lol1(self): + xml_str = ''' + + +&lol1; +''' + res = get_xml_length(xml_str) + self.assertTrue(res >= 30) + + def test_no_reference(self): + xml_str = ''' + + + +''' + res = get_xml_length(xml_str) + self.assertTrue(res < 30) + + def test_lol1_multiple(self): + xml_str = ''' + + + +]> +&lol1;&lol1;&lol1; +''' + res = get_xml_length(xml_str) + self.assertTrue(res >= 90) + + def test_lol1_missing_semicolon(self): + xml_str = ''' + + +&lol1; +''' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_quadratic_blowup(self): + xml_str = ''' + + +]> +&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A;&A; +''' + res = get_xml_length(xml_str) + self.assertTrue(res >= 2000) + + def test_missing_bracket1(self): + xml_str = ''' + + +&A; +''' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_missing_bracket2(self): + xml_str = ''' + + +]> +&A; +''' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_missing_semicolon(self): + xml_str = ''' + + +]> +&A +''' + self.assertRaises(BaseException, get_xml_length, xml_str) + + def test_missing_slash(self): + xml_str = ''' + + +]> +&A; +''' + res = get_xml_length(xml_str) + self.assertTrue(res >= 23) + + def test_missing_quotes(self): + xml_str = ''' + + +]> +&A; +''' + self.assertRaises(BaseException, get_xml_length, xml_str) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 9605961..33de02c 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -22,6 +22,7 @@ def attack(attack_url): return True return False + # Config uebergabe fuer OpenIDServer einrichten with OpenIDServer.create_server() as openid_server: if attack(openid_server.benign_url): log('warn', openid_server.benign_url, "Billion Laughs", diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 90c74b2..5b00e29 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -154,7 +154,7 @@ def __init__(self): def __enter__(self): self.server = multiprocessing.Process(target=main) self.server.start() - self.server.join(0.0001) + self.server.join(0.001) return self def __exit__(self, type, value, traceback): From 23696523bd69d5a462e05df1e1c0940876594f37 Mon Sep 17 00:00:00 2001 From: garet12 Date: Tue, 1 Jul 2014 16:32:58 +0200 Subject: [PATCH 36/58] Some tidying --- test/test_billion_laughs.py | 82 +++++++++++++++++++------------------ webvulnscan/client.py | 11 +++-- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 722cac4..1153555 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -9,43 +9,41 @@ def get_XML(url): - if "http://" not in url: - url = "http://" + url - headers = {'Accept': 'application/xrds+xml'} - req = urllib2.Request(url, None, headers) - - try: - response = urllib2.urlopen(req) - except urllib2.HTTPError, e: - print( - '%s There was a problem with your request!\n' % e.code) - return None - except urllib2.URLError, e: - print('%s' % e.args) - return None - - html = response.read() - xrds_loc = re.search( - r'[^"]*)"\s*', html, re.IGNORECASE) - - if not xrds_loc: - return None - - xrds_url = xrds_loc.group('value') - - try: - xrds_doc = urllib2.urlopen(xrds_url) - except urllib2.HTTPError, e: - print( - '%s There was a problem with your request!\n' % e.code) - return None - except urllib2.URLError, e: - print('%s' % e.args) - return None - - return xrds_doc.read() - - + if "http://" not in url: + url = "http://" + url + headers = {'Accept': 'application/xrds+xml'} + req = urllib2.Request(url, None, headers) + + try: + response = urllib2.urlopen(req) + except urllib2.HTTPError, e: + print( + '%s There was a problem with your request!\n' % e.code) + return None + except urllib2.URLError, e: + print('%s' % e.args) + return None + + html = response.read() + xrds_loc = re.search( + r'[^"]*)"\s*', html, re.IGNORECASE) + + if not xrds_loc: + return None + + xrds_url = xrds_loc.group('value') + + try: + xrds_doc = urllib2.urlopen(xrds_url) + except urllib2.HTTPError, e: + print( + '%s There was a problem with your request!\n' % e.code) + return None + except urllib2.URLError, e: + print('%s' % e.args) + return None + + return xrds_doc.read() def form_client(vulnerable=False): @@ -54,18 +52,22 @@ def form_client(vulnerable=False): ''' - def result(req): - if 'openid' not in req.parameters: + def result(request): + if 'openid' not in request.parameters: return u'There is no input!' - xml = get_XML(req.parameters['openid']) + xml = get_XML(request.parameters['openid']) + if xml is None: return u'XML document could not be found!' + res = xml_parser.get_xml_length(xml) + if vulnerable: # ueber req timeout zeug uebergeben raise(socket.timeout) time.sleep(res / 10000) + return u'%s' % res return { diff --git a/webvulnscan/client.py b/webvulnscan/client.py index b52aa02..7cd6b39 100644 --- a/webvulnscan/client.py +++ b/webvulnscan/client.py @@ -14,11 +14,13 @@ class NotAPage(Exception): + """ The content at the URL in question is not a webpage, but something static (image, text, etc.) """ class Client(object): + """ Client provides a easy interface for accessing web content. """ def __init__(self, log=webvulnscan.log): @@ -83,15 +85,16 @@ def download(self, url_or_request, parameters=None, headers=None): return self._download(request) - def download_page(self, url_or_request, parameters=None, req_headers=None,**kwargs): + def download_page(self, url_or_request, parameters=None, req_headers=None, **kwargs): """ Downloads the content of a site, returns it as page. Throws NotAPage if the content is not a webpage. """ - timeout=None + timeout = None for key in kwargs: - if key=="timeout": - timeout=kwargs[key] + if key == "timeout": + timeout = kwargs[key] socket.setdefaulttimeout(timeout) + request, status_code, html_bytes, headers = self.download( url_or_request, parameters, req_headers) From e94a8addd1717d6d7e252008b3c4759ddb9f2b64 Mon Sep 17 00:00:00 2001 From: garet12 Date: Tue, 1 Jul 2014 16:52:44 +0200 Subject: [PATCH 37/58] Timeout to request added and used for test cases --- test/test_billion_laughs.py | 5 +---- webvulnscan/client.py | 8 ++++---- webvulnscan/request.py | 8 +++++++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 1153555..9217e11 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -4,7 +4,6 @@ import xml_parser import tutil import webvulnscan.attacks.billion_laughs -import time import socket @@ -63,10 +62,8 @@ def result(request): res = xml_parser.get_xml_length(xml) - if vulnerable: - # ueber req timeout zeug uebergeben + if vulnerable and request.timeout%s' % res diff --git a/webvulnscan/client.py b/webvulnscan/client.py index 7cd6b39..0a64103 100644 --- a/webvulnscan/client.py +++ b/webvulnscan/client.py @@ -63,7 +63,7 @@ def _download(self, request): return (request, status_code, response_data, headers) - def download(self, url_or_request, parameters=None, headers=None): + def download(self, url_or_request, parameters=None, headers=None,**kwargs): """ Downloads a URL, returns (request, status_code, response_data, headers) """ @@ -73,7 +73,7 @@ def download(self, url_or_request, parameters=None, headers=None): assert headers is None request = url_or_request.copy() else: - request = Request(url_or_request, parameters, headers) + request = Request(url_or_request, parameters, headers,**kwargs) for header, value in self.additional_headers.items(): request.add_header(header, value) @@ -94,9 +94,9 @@ def download_page(self, url_or_request, parameters=None, req_headers=None, **kwa if key == "timeout": timeout = kwargs[key] socket.setdefaulttimeout(timeout) - + request, status_code, html_bytes, headers = self.download( - url_or_request, parameters, req_headers) + url_or_request, parameters, req_headers,**kwargs) content_type, charset = parse_content_type( headers.get('Content-Type'), diff --git a/webvulnscan/request.py b/webvulnscan/request.py index 61a99ae..ff84f11 100644 --- a/webvulnscan/request.py +++ b/webvulnscan/request.py @@ -6,7 +6,13 @@ class Request(compat.Request): - def __init__(self, url, parameters=None, headers=None): + + def __init__(self, url, parameters=None, headers=None, **kwargs): + self.timeout = None + for key in kwargs: + if key == "timeout": + self.timeout = kwargs[key] + self.parameters = parameters if parameters is None: data = None From 6a135bf62898fe937958e072db42df2c7239f2c1 Mon Sep 17 00:00:00 2001 From: garet12 Date: Wed, 2 Jul 2014 10:48:00 +0200 Subject: [PATCH 38/58] Timeout stuff fixed --- test/test_billion_laughs.py | 11 ++++++++--- webvulnscan/client.py | 6 +++--- webvulnscan/request.py | 10 ++++------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 9217e11..763181a 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -5,6 +5,7 @@ import tutil import webvulnscan.attacks.billion_laughs import socket +import time def get_XML(url): @@ -62,9 +63,13 @@ def result(request): res = xml_parser.get_xml_length(xml) - if vulnerable and request.timeout%s' % res return { diff --git a/webvulnscan/client.py b/webvulnscan/client.py index 0a64103..1846f27 100644 --- a/webvulnscan/client.py +++ b/webvulnscan/client.py @@ -63,7 +63,7 @@ def _download(self, request): return (request, status_code, response_data, headers) - def download(self, url_or_request, parameters=None, headers=None,**kwargs): + def download(self, url_or_request, parameters=None, headers=None, timeout=None): """ Downloads a URL, returns (request, status_code, response_data, headers) """ @@ -73,7 +73,7 @@ def download(self, url_or_request, parameters=None, headers=None,**kwargs): assert headers is None request = url_or_request.copy() else: - request = Request(url_or_request, parameters, headers,**kwargs) + request = Request(url_or_request, parameters, headers, timeout) for header, value in self.additional_headers.items(): request.add_header(header, value) @@ -96,7 +96,7 @@ def download_page(self, url_or_request, parameters=None, req_headers=None, **kwa socket.setdefaulttimeout(timeout) request, status_code, html_bytes, headers = self.download( - url_or_request, parameters, req_headers,**kwargs) + url_or_request, parameters, req_headers, timeout) content_type, charset = parse_content_type( headers.get('Content-Type'), diff --git a/webvulnscan/request.py b/webvulnscan/request.py index ff84f11..4b6b115 100644 --- a/webvulnscan/request.py +++ b/webvulnscan/request.py @@ -7,12 +7,10 @@ class Request(compat.Request): - def __init__(self, url, parameters=None, headers=None, **kwargs): - self.timeout = None - for key in kwargs: - if key == "timeout": - self.timeout = kwargs[key] - + def __init__(self, url, parameters=None, headers=None, timeout=None): + + self.timeout = timeout + self.parameters = parameters if parameters is None: data = None From b78d48991662e416a89ac527f557550e3d2be1fd Mon Sep 17 00:00:00 2001 From: garet12 Date: Wed, 2 Jul 2014 16:53:27 +0200 Subject: [PATCH 39/58] Config support for Billion Laughs Test --- test/test_billion_laughs.py | 10 +++++----- webvulnscan/__init__.py | 5 ++++- webvulnscan/attacks/billion_laughs.py | 7 +++++-- webvulnscan/form.py | 7 ++++--- webvulnscan/openID_test_server.py | 20 +++++++++++++------- webvulnscan/options.py | 17 +++++++++++++++++ webvulnscan/request.py | 2 +- webvulnscan/utils.py | 4 ++-- 8 files changed, 51 insertions(+), 21 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 763181a..33c07e3 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -63,13 +63,13 @@ def result(request): res = xml_parser.get_xml_length(xml) - timeout=getattr(request,'timeout',None) - if vulnerable: + timeout = getattr(request, 'timeout', None) + if vulnerable: if not timeout: - time.sleep(res/100000) - elif timeout%s' % res return { diff --git a/webvulnscan/__init__.py b/webvulnscan/__init__.py index c04e58a..a82dbf4 100644 --- a/webvulnscan/__init__.py +++ b/webvulnscan/__init__.py @@ -87,7 +87,10 @@ def run(options, targets): log('info', page.url, 'crawler', 'Scanning ...') for attack in attacks: - attack(client, log, page) + if attack.__name__ =="billion_laughs": + attack(client,log,page,ip=options.bl_config[0],port=options.bl_config[1]) + else: + attack(client, log, page) finally: if not options.verbose: diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 33de02c..41151d3 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -9,7 +9,7 @@ def search(page): @attack(search) -def billion_laughs(client, log, form): +def billion_laughs(client, log, form, **kwargs): def attack(attack_url): parameters = dict(form.get_parameters()) for key in parameters: @@ -23,7 +23,10 @@ def attack(attack_url): return False # Config uebergabe fuer OpenIDServer einrichten - with OpenIDServer.create_server() as openid_server: + config=[] + for key in kwargs: + config.append(kwargs[key]) + with OpenIDServer.create_server(config) as openid_server: if attack(openid_server.benign_url): log('warn', openid_server.benign_url, "Billion Laughs", "Test did not work") diff --git a/webvulnscan/form.py b/webvulnscan/form.py index 36b32fb..26d77e9 100644 --- a/webvulnscan/form.py +++ b/webvulnscan/form.py @@ -6,6 +6,7 @@ class Form(object): + def __init__(self, url, document): self.document = document self.action = urljoin(url, document.attrib.get('action')) @@ -34,9 +35,9 @@ def get_textarea_elements(self): for textarea in self.document.findall('.//textarea'): yield textarea - def send(self, client, parameters,**kwargs): + def send(self, client, parameters, **kwargs): if self.method == "get": url = add_get_params(self.action, parameters) - return client.download_page(url) + return client.download_page(url, **kwargs) else: - return client.download_page(self.action, parameters,**kwargs) + return client.download_page(self.action, parameters, **kwargs) diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 5b00e29..6e37304 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -10,6 +10,7 @@ except ImportError: from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +IP = "localhost" PORT = 50161 @@ -23,12 +24,12 @@ def _default_page(self, pageState="serverxml"): OpenID Test Server - +

OpenID Test Server

- '''.encode("utf-8") % (PORT, pageState)) + '''.encode("utf-8") % (IP, PORT, pageState)) def _serve_request(self): parsed_path = urlparse(self.path) @@ -143,13 +144,18 @@ def main(): class OpenIDServer(): class create_server(): - benign_url = "http://localhost:%i" % PORT - evil_urls = ("http://localhost:%i/db" % - PORT, "http://localhost:%i/dq" % PORT) - def __init__(self): - # self.config=config + def __init__(self, config): + # Random Port bei lokalem Test -> Wie bekomme ich den Port dann raus ? + self.config = config + if self.config: + global IP, PORT + IP = self.config[0] + PORT = int(self.config[1]) self.server = None + self.benign_url = "http://%s:%i" % (IP, PORT) + self.evil_urls = ("http://%s:%i/db" % + (IP, PORT), "http://%s:%i/dq" % (IP, PORT)) def __enter__(self): self.server = multiprocessing.Process(target=main) diff --git a/webvulnscan/options.py b/webvulnscan/options.py index 55c2d2a..b43cd83 100644 --- a/webvulnscan/options.py +++ b/webvulnscan/options.py @@ -84,6 +84,23 @@ def parse_options(): 'for standard output).') parser.add_option_group(configuration_options) + billion_laughs_configuration_options = OptionGroup( + parser, "Billion Laughs configuration", + "You are able to specify" + "some options for the" + "Billion Laughs test.") + + billion_laughs_configuration_options.add_option( + '--net', dest='bl_config', + action='store', default=None, + nargs=2, + help='This option will run' + 'the Billion Laughs test' + 'on a network application.' + 'You need to pass your IP and an unused port.' + 'If this option is not used' + 'you can just test local applications.') + # Options for scanning for specific vulnerabilities. attack_options = OptionGroup(parser, "Attacks", "If you specify own or several of the " diff --git a/webvulnscan/request.py b/webvulnscan/request.py index 4b6b115..bc7c0ca 100644 --- a/webvulnscan/request.py +++ b/webvulnscan/request.py @@ -8,7 +8,7 @@ class Request(compat.Request): def __init__(self, url, parameters=None, headers=None, timeout=None): - + self.timeout = timeout self.parameters = parameters diff --git a/webvulnscan/utils.py b/webvulnscan/utils.py index dba5dd4..946cd7a 100644 --- a/webvulnscan/utils.py +++ b/webvulnscan/utils.py @@ -120,9 +120,9 @@ def attack(searchfunc=None): if searchfunc is None: searchfunc = lambda page: [(page,)] - def run(cls, client, log, page): + def run(cls, client, log, page, **kwargs): for s in cls.search(page): - cls.attack(client, log, *s) + cls.attack(client, log, *s, **kwargs) def decorator(attackfunc): return type(attackfunc.__name__, (object,), { From 50c8b8ebce43e85c40d375fc37f55167ca5afe97 Mon Sep 17 00:00:00 2001 From: garet12 Date: Thu, 3 Jul 2014 15:05:15 +0200 Subject: [PATCH 40/58] OpenID Server can now use an IP and Port from a config. Also looks better than before. --- webvulnscan/__init__.py | 5 ++-- webvulnscan/attacks/billion_laughs.py | 2 +- webvulnscan/openID_test_server.py | 36 +++++++++++++++++---------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/webvulnscan/__init__.py b/webvulnscan/__init__.py index a82dbf4..d9f9faf 100644 --- a/webvulnscan/__init__.py +++ b/webvulnscan/__init__.py @@ -87,8 +87,9 @@ def run(options, targets): log('info', page.url, 'crawler', 'Scanning ...') for attack in attacks: - if attack.__name__ =="billion_laughs": - attack(client,log,page,ip=options.bl_config[0],port=options.bl_config[1]) + if attack.__name__ == "billion_laughs": + attack(client, log, page, ip=options.bl_config[ + 0], port=options.bl_config[1]) else: attack(client, log, page) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 41151d3..f197a55 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -23,7 +23,7 @@ def attack(attack_url): return False # Config uebergabe fuer OpenIDServer einrichten - config=[] + config = [] for key in kwargs: config.append(kwargs[key]) with OpenIDServer.create_server(config) as openid_server: diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 6e37304..d6f6689 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -10,8 +10,8 @@ except ImportError: from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -IP = "localhost" -PORT = 50161 +IP = None +PORT = None class Handler(BaseHTTPRequestHandler): @@ -136,8 +136,13 @@ def handle_one_request(self): return -def main(): - httpd = HTTPServer(("", PORT), Handler) +def main(queue, ip, port): + httpd = HTTPServer(("", port), Handler) + global IP, PORT + IP = ip + PORT = httpd.server_port + if queue: + queue.put(PORT) httpd.serve_forever() @@ -146,20 +151,25 @@ class OpenIDServer(): class create_server(): def __init__(self, config): - # Random Port bei lokalem Test -> Wie bekomme ich den Port dann raus ? self.config = config + self.ip = "localhost" + self.port = 0 if self.config: - global IP, PORT - IP = self.config[0] - PORT = int(self.config[1]) + self.ip = self.config[0] + self.port = int(self.config[1]) self.server = None - self.benign_url = "http://%s:%i" % (IP, PORT) - self.evil_urls = ("http://%s:%i/db" % - (IP, PORT), "http://%s:%i/dq" % (IP, PORT)) + self.benign_url = None + self.evil_urls = None def __enter__(self): - self.server = multiprocessing.Process(target=main) + queue = multiprocessing.Queue() + self.server = multiprocessing.Process( + target=main, args=(queue, self.ip, self.port)) self.server.start() + self.port = queue.get() + self.benign_url = "http://%s:%i" % (self.ip, self.port) + self.evil_urls = ("http://%s:%i/db" % + (self.ip, self.port), "http://%s:%i/dq" % (self.ip, self.port)) self.server.join(0.001) return self @@ -168,4 +178,4 @@ def __exit__(self, type, value, traceback): if __name__ == '__main__': - main() + main(None, "localhost", 12345) From d496cb378b916c5fc0a732d1b37092194a1c62b3 Mon Sep 17 00:00:00 2001 From: garet12 Date: Fri, 4 Jul 2014 16:59:28 +0200 Subject: [PATCH 41/58] Config is now given to client, test for open id server availability --- webvulnscan/__init__.py | 10 +-- webvulnscan/attacks/billion_laughs.py | 74 +++------------------- webvulnscan/client.py | 3 +- webvulnscan/openID_test_server.py | 88 ++++++++++++++++----------- webvulnscan/utils.py | 4 +- 5 files changed, 68 insertions(+), 111 deletions(-) diff --git a/webvulnscan/__init__.py b/webvulnscan/__init__.py index d9f9faf..700832b 100644 --- a/webvulnscan/__init__.py +++ b/webvulnscan/__init__.py @@ -37,7 +37,7 @@ def run(options, targets): log = Log(verbosity=u'vuln') else: log = Log() - client = Client(log=log) + client = Client(log=log,config=options.bl_config) if options.import_cookies: client.cookie_jar = MozillaCookieJar(options.import_cookies) @@ -86,12 +86,8 @@ def run(options, targets): for page in all_pages: log('info', page.url, 'crawler', 'Scanning ...') - for attack in attacks: - if attack.__name__ == "billion_laughs": - attack(client, log, page, ip=options.bl_config[ - 0], port=options.bl_config[1]) - else: - attack(client, log, page) + for attack in attacks: + attack(client, log, page) finally: if not options.verbose: diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index f197a55..d5473f4 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -1,5 +1,5 @@ from ..utils import attack -from ..openID_test_server import OpenIDServer +from ..openID_test_server import Create_server import socket @@ -9,7 +9,7 @@ def search(page): @attack(search) -def billion_laughs(client, log, form, **kwargs): +def billion_laughs(client, log, form): def attack(attack_url): parameters = dict(form.get_parameters()) for key in parameters: @@ -22,74 +22,16 @@ def attack(attack_url): return True return False - # Config uebergabe fuer OpenIDServer einrichten - config = [] - for key in kwargs: - config.append(kwargs[key]) - with OpenIDServer.create_server(config) as openid_server: + with Create_server(client.config) as openid_server: + if not openid_server: + log('warn',"Billion Laughs", "OpenID Server is not reachable!") + return + if attack(openid_server.benign_url): log('warn', openid_server.benign_url, "Billion Laughs", - "Test did not work") + "Test did not work!") return for evil_url in openid_server.evil_urls: if attack(evil_url): log('vuln', evil_url, "Billion Laughs") - - - - -# from ..utils import attack, modify_parameter -# import time -# import multiprocessing -# from ..openID_test import main - - -# def search(page): -# for form in page.get_forms(): -# yield (form,) - - -# @attack(search) -# def billion_laughs(client, log, form): - -# port = 50162 -# openid_url = "http://localhost:%i" % port -# openid_billion_laughs = "http://localhost:%i/db" % port -# openid_quadratic_blowup = "http://localhost:%i/dq" % port - -# parameters = dict(form.get_parameters()) -# server = multiprocessing.Process(target=main, args=(port,)) -# server.start() -# for parameter in parameters: -# print(parameters) -# print(parameter) -# if parameter == '': -# continue -# if parameters[parameter] == 'abcdefgh': -# xml_page = openid_billion_laughs -# else: -# xml_page = parameters[parameter] -# attack_parameters = modify_parameter(parameters, parameter, -# openid_url) -# server.join(0.001) -# startTime = time.time() -# result = form.send(client, attack_parameters) -# endTime = time.time() -# elapsedTime = endTime - startTime -# attack_parameters = modify_parameter(parameters, parameter, -# xml_page) - -# p = multiprocessing.Process( -# target=form.send, args=(client, attack_parameters)) -# p.start() - -# p.join(elapsedTime + 1) -# if p.is_alive(): -# log('vuln', result.url, "Billion Laughs", -# "in URL parameter " + parameter) -# p.terminate() -# p.join() - -# server.terminate() -# server.join() diff --git a/webvulnscan/client.py b/webvulnscan/client.py index 1846f27..7f37b96 100644 --- a/webvulnscan/client.py +++ b/webvulnscan/client.py @@ -23,7 +23,8 @@ class Client(object): """ Client provides a easy interface for accessing web content. """ - def __init__(self, log=webvulnscan.log): + def __init__(self, log=webvulnscan.log, config=[]): + self.config = config self.cookie_jar = CookieJar() self.opener = self.setup_opener() self.additional_headers = {} diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index d6f6689..95eb771 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -1,5 +1,7 @@ import socket +import urllib2 import multiprocessing +import time try: from urllib.parse import urlparse except ImportError: @@ -10,9 +12,6 @@ except ImportError: from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -IP = None -PORT = None - class Handler(BaseHTTPRequestHandler): @@ -29,7 +28,7 @@ def _default_page(self, pageState="serverxml"):

OpenID Test Server

- '''.encode("utf-8") % (IP, PORT, pageState)) + '''.encode("utf-8") % (self.server.domain_name, self.server.server_port, pageState)) def _serve_request(self): parsed_path = urlparse(self.path) @@ -113,6 +112,7 @@ def showServerXML(self, xmlState=None): %s '''.encode("utf-8") % (ent_a, ref_a)) + # Das muss weg def handle_one_request(self): try: self.raw_requestline = self.rfile.readline(65537) @@ -138,43 +138,61 @@ def handle_one_request(self): def main(queue, ip, port): httpd = HTTPServer(("", port), Handler) - global IP, PORT - IP = ip - PORT = httpd.server_port + httpd.domain_name = ip + port = httpd.server_port if queue: - queue.put(PORT) + queue.put(port) httpd.serve_forever() -class OpenIDServer(): - - class create_server(): - - def __init__(self, config): - self.config = config - self.ip = "localhost" - self.port = 0 - if self.config: - self.ip = self.config[0] - self.port = int(self.config[1]) - self.server = None - self.benign_url = None - self.evil_urls = None - - def __enter__(self): - queue = multiprocessing.Queue() - self.server = multiprocessing.Process( - target=main, args=(queue, self.ip, self.port)) - self.server.start() - self.port = queue.get() - self.benign_url = "http://%s:%i" % (self.ip, self.port) - self.evil_urls = ("http://%s:%i/db" % - (self.ip, self.port), "http://%s:%i/dq" % (self.ip, self.port)) - self.server.join(0.001) +class Create_server(object): + + def __init__(self, config): + self.config = config + self.ip = "localhost" + self.port = 0 + if self.config: + self.ip = self.config[0] + self.port = int(self.config[1]) + self.server = None + self.benign_url = None + self.evil_urls = None + + def __enter__(self): + queue = multiprocessing.Queue() + self.server = multiprocessing.Process( + target=main, args=(queue, self.ip, self.port)) + self.server.start() + self.port = queue.get() + + self.benign_url = "http://%s:%i" % (self.ip, self.port) + self.evil_urls = ("http://%s:%i/db" % + (self.ip, self.port), "http://%s:%i/dq" % (self.ip, self.port)) + + def test_request(benign_url): + wait_time = 0.01 + i = 0 + while i < 3: + try: + urllib2.urlopen(benign_url) + return True + except urllib2.HTTPError: + i += 1 + time.sleep(wait_time) + wait_time = wait_time * 10 + except urllib2.URLError: + i += 1 + time.sleep(wait_time) + wait_time = wait_time * 10 + return False + + if test_request(self.benign_url): return self + else: + return None - def __exit__(self, type, value, traceback): - self.server.terminate() + def __exit__(self, type, value, traceback): + self.server.terminate() if __name__ == '__main__': diff --git a/webvulnscan/utils.py b/webvulnscan/utils.py index 946cd7a..dba5dd4 100644 --- a/webvulnscan/utils.py +++ b/webvulnscan/utils.py @@ -120,9 +120,9 @@ def attack(searchfunc=None): if searchfunc is None: searchfunc = lambda page: [(page,)] - def run(cls, client, log, page, **kwargs): + def run(cls, client, log, page): for s in cls.search(page): - cls.attack(client, log, *s, **kwargs) + cls.attack(client, log, *s) def decorator(attackfunc): return type(attackfunc.__name__, (object,), { From 27c923975b5352854d7e0adb072334117e833b0d Mon Sep 17 00:00:00 2001 From: garet12 Date: Fri, 4 Jul 2014 17:10:56 +0200 Subject: [PATCH 42/58] Removed 'handle_one_request' and added 'do_GET' --- webvulnscan/openID_test_server.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 95eb771..7523fa0 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -1,4 +1,3 @@ -import socket import urllib2 import multiprocessing import time @@ -30,7 +29,7 @@ def _default_page(self, pageState="serverxml"): '''.encode("utf-8") % (self.server.domain_name, self.server.server_port, pageState)) - def _serve_request(self): + def do_GET(self): parsed_path = urlparse(self.path) if parsed_path.path == "/": self._default_page("serverxml") @@ -112,29 +111,6 @@ def showServerXML(self, xmlState=None): %s '''.encode("utf-8") % (ent_a, ref_a)) - # Das muss weg - def handle_one_request(self): - try: - self.raw_requestline = self.rfile.readline(65537) - if len(self.raw_requestline) > 65536: - self.requestline = '' - self.request_version = '' - self.command = '' - self.send_error(414) - return - if not self.raw_requestline: - self.close_connection = 1 - return - if not self.parse_request(): - return - - self._serve_request() - self.wfile.flush() - except socket.timeout as e: - self.log_error("Request timed out: %r", e) - self.close_connection = 1 - return - def main(queue, ip, port): httpd = HTTPServer(("", port), Handler) From c799621ce73134ebeb678474e8a1c24f111943df Mon Sep 17 00:00:00 2001 From: garet12 Date: Fri, 4 Jul 2014 17:14:23 +0200 Subject: [PATCH 43/58] Small change --- webvulnscan/openID_test_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 7523fa0..22a423b 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -155,11 +155,11 @@ def test_request(benign_url): except urllib2.HTTPError: i += 1 time.sleep(wait_time) - wait_time = wait_time * 10 + wait_time *= 10 except urllib2.URLError: i += 1 time.sleep(wait_time) - wait_time = wait_time * 10 + wait_time *= 10 return False if test_request(self.benign_url): From 44ec2389e6ad06f54d1b0420a3b03f528f15f906 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 5 Jul 2014 15:10:49 +0200 Subject: [PATCH 44/58] Python 3 compatibility modifications (not done yet) and some tidying up --- test/test_billion_laughs.py | 30 ++++++++++---------- webvulnscan/attacks/billion_laughs.py | 3 +- webvulnscan/openID_test_server.py | 41 ++++++++++++--------------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/test/test_billion_laughs.py b/test/test_billion_laughs.py index 33c07e3..730743e 100644 --- a/test/test_billion_laughs.py +++ b/test/test_billion_laughs.py @@ -1,6 +1,6 @@ +from __future__ import unicode_literals import unittest import re -import urllib2 import xml_parser import tutil import webvulnscan.attacks.billion_laughs @@ -8,25 +8,28 @@ import time +try: + from urllib.request import Request, URLError, HTTPError, urlopen +except ImportError: + from urllib2 import Request, URLError, HTTPError, urlopen + + def get_XML(url): if "http://" not in url: url = "http://" + url headers = {'Accept': 'application/xrds+xml'} - req = urllib2.Request(url, None, headers) + req = Request(url, None, headers) try: - response = urllib2.urlopen(req) - except urllib2.HTTPError, e: + response = urlopen(req) + except (HTTPError, URLError): print( - '%s There was a problem with your request!\n' % e.code) - return None - except urllib2.URLError, e: - print('%s' % e.args) + 'There was a problem with your request!\n') return None html = response.read() xrds_loc = re.search( - r'[^"]*)"\s*', html, re.IGNORECASE) + r'[^"]*)"\s*', html.decode(), re.IGNORECASE) if not xrds_loc: return None @@ -34,13 +37,10 @@ def get_XML(url): xrds_url = xrds_loc.group('value') try: - xrds_doc = urllib2.urlopen(xrds_url) - except urllib2.HTTPError, e: + xrds_doc = urlopen(xrds_url) + except (HTTPError, URLError): print( - '%s There was a problem with your request!\n' % e.code) - return None - except urllib2.URLError, e: - print('%s' % e.args) + 'There was a problem with your request!\n') return None return xrds_doc.read() diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index d5473f4..184c6fd 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -24,7 +24,8 @@ def attack(attack_url): with Create_server(client.config) as openid_server: if not openid_server: - log('warn',"Billion Laughs", "OpenID Server is not reachable!") + log('warn', "Billion Laughs", "OpenID Server is not reachable!") + raise socket.timeout return if attack(openid_server.benign_url): diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 22a423b..ba3872f 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -1,6 +1,12 @@ -import urllib2 +from __future__ import unicode_literals import multiprocessing import time + +try: + from urllib.request import URLError, HTTPError, urlopen +except ImportError: + from urllib2 import URLError, HTTPError, urlopen + try: from urllib.parse import urlparse except ImportError: @@ -27,7 +33,7 @@ def _default_page(self, pageState="serverxml"):

OpenID Test Server

- '''.encode("utf-8") % (self.server.domain_name, self.server.server_port, pageState)) + '''.encode("utf-8") % (self.server.domain_name, self.server.server_port, pageState,)) def do_GET(self): parsed_path = urlparse(self.path) @@ -145,27 +151,16 @@ def __enter__(self): self.evil_urls = ("http://%s:%i/db" % (self.ip, self.port), "http://%s:%i/dq" % (self.ip, self.port)) - def test_request(benign_url): - wait_time = 0.01 - i = 0 - while i < 3: - try: - urllib2.urlopen(benign_url) - return True - except urllib2.HTTPError: - i += 1 - time.sleep(wait_time) - wait_time *= 10 - except urllib2.URLError: - i += 1 - time.sleep(wait_time) - wait_time *= 10 - return False - - if test_request(self.benign_url): - return self - else: - return None + wait_time = 0.01 + for i in range(3): + try: + urlopen(self.benign_url) + return None + except (HTTPError, URLError): + time.sleep(wait_time) + wait_time *= 10 ** i + i += 1 + return None def __exit__(self, type, value, traceback): self.server.terminate() From 2bf7df9eb1eedaf2f8af149f7dc80aacc4da0a80 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 5 Jul 2014 15:14:04 +0200 Subject: [PATCH 45/58] Fixed a small problem --- webvulnscan/openID_test_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index ba3872f..2da0f3d 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -155,7 +155,7 @@ def __enter__(self): for i in range(3): try: urlopen(self.benign_url) - return None + return self except (HTTPError, URLError): time.sleep(wait_time) wait_time *= 10 ** i From 76f72a91dea1ae1d9416967cf603a7fd8e051613 Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 5 Jul 2014 16:01:01 +0200 Subject: [PATCH 46/58] Fixed small stuff of test request to OpenID Server --- webvulnscan/attacks/billion_laughs.py | 3 +-- webvulnscan/openID_test_server.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 184c6fd..5288174 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -25,8 +25,7 @@ def attack(attack_url): with Create_server(client.config) as openid_server: if not openid_server: log('warn', "Billion Laughs", "OpenID Server is not reachable!") - raise socket.timeout - return + raise Exception("OpenID Server could not be reached !") if attack(openid_server.benign_url): log('warn', openid_server.benign_url, "Billion Laughs", diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 2da0f3d..2dacf46 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -151,14 +151,14 @@ def __enter__(self): self.evil_urls = ("http://%s:%i/db" % (self.ip, self.port), "http://%s:%i/dq" % (self.ip, self.port)) - wait_time = 0.01 + wait_time = 0.001 for i in range(3): try: urlopen(self.benign_url) return self except (HTTPError, URLError): time.sleep(wait_time) - wait_time *= 10 ** i + wait_time *= 10 ** (i + 1) i += 1 return None From 8ff1bd4ada415f9c6e01dcf6ffe6fa0379d94eb7 Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 7 Jul 2014 11:09:24 +0200 Subject: [PATCH 47/58] Exception is raised if OpenID server can't be found --- test/xml_parser.py | 2 +- webvulnscan/attacks/billion_laughs.py | 3 --- webvulnscan/attacks/utf7_check.py | 26 +++++++++++++++----------- webvulnscan/openID_test_server.py | 4 +++- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/test/xml_parser.py b/test/xml_parser.py index 37b69ca..09dc2ca 100644 --- a/test/xml_parser.py +++ b/test/xml_parser.py @@ -12,7 +12,7 @@ def get_objects(xml_data, allow_entity_def=True): (?P[a-zA-Z0-9_-]+)\s+ "(?P[^"]*)"\s* > - ''', xml_data[idx + 1:]) + ''', xml_data[idx + 1:].decode()) if m: value = get_objects(m.group('value'), allow_entity_def=False) yield ('entity', (m.group('name'), value)) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 5288174..91f5ec5 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -23,9 +23,6 @@ def attack(attack_url): return False with Create_server(client.config) as openid_server: - if not openid_server: - log('warn', "Billion Laughs", "OpenID Server is not reachable!") - raise Exception("OpenID Server could not be reached !") if attack(openid_server.benign_url): log('warn', openid_server.benign_url, "Billion Laughs", diff --git a/webvulnscan/attacks/utf7_check.py b/webvulnscan/attacks/utf7_check.py index f25f290..2c1c51d 100644 --- a/webvulnscan/attacks/utf7_check.py +++ b/webvulnscan/attacks/utf7_check.py @@ -3,16 +3,20 @@ @attack() def utf7_check(client, log, page): + if 'Content-Type' not in page.headers: + return + content_type = page.headers['Content-Type'] - if ';' in content_type: - # Use Content-Type to get the charset - charset = content_type.split(';')[1].split('=')[1] - # If charset is not UTF-7 there is nothing to do - if charset != 'utf-7' and charset != 'UTF-7': - return - else: - # If charset is UTF-7 give warning - log('vuln', page.url, 'UTF-7', - 'Website uses UTF-7') - else: + + if ';' not in content_type: return + + # Use Content-Type to get the charset + charset = content_type.split(';')[1].split('=')[1] + # If charset is not UTF-7 there is nothing to do + if charset != 'utf-7' and charset != 'UTF-7': + return + else: + # If charset is UTF-7 give warning + log('vuln', page.url, 'UTF-7', + 'Website uses UTF-7') diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 2dacf46..f0ece10 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -144,6 +144,7 @@ def __enter__(self): queue = multiprocessing.Queue() self.server = multiprocessing.Process( target=main, args=(queue, self.ip, self.port)) + self.server.daemon = True self.server.start() self.port = queue.get() @@ -160,7 +161,8 @@ def __enter__(self): time.sleep(wait_time) wait_time *= 10 ** (i + 1) i += 1 - return None + self.server.terminate() + raise Exception("OpenID Server could not be reached !") def __exit__(self, type, value, traceback): self.server.terminate() From e4a02c827260ba630471ffd97a161ddb8ce3f79d Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 7 Jul 2014 18:03:51 +0200 Subject: [PATCH 48/58] Small changes --- webvulnscan/attacks/billion_laughs.py | 2 +- webvulnscan/client.py | 4 ++-- webvulnscan/openID_test_server.py | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 91f5ec5..85de6c8 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -23,7 +23,7 @@ def attack(attack_url): return False with Create_server(client.config) as openid_server: - + if attack(openid_server.benign_url): log('warn', openid_server.benign_url, "Billion Laughs", "Test did not work!") diff --git a/webvulnscan/client.py b/webvulnscan/client.py index 7f37b96..3b490c2 100644 --- a/webvulnscan/client.py +++ b/webvulnscan/client.py @@ -90,11 +90,11 @@ def download_page(self, url_or_request, parameters=None, req_headers=None, **kwa """ Downloads the content of a site, returns it as page. Throws NotAPage if the content is not a webpage. """ - timeout = None + timeout=None for key in kwargs: if key == "timeout": timeout = kwargs[key] - socket.setdefaulttimeout(timeout) + socket.setdefaulttimeout(timeout) request, status_code, html_bytes, headers = self.download( url_or_request, parameters, req_headers, timeout) diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index f0ece10..70dc47b 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -146,11 +146,12 @@ def __enter__(self): target=main, args=(queue, self.ip, self.port)) self.server.daemon = True self.server.start() - self.port = queue.get() + if queue: + self.port = queue.get() - self.benign_url = "http://%s:%i" % (self.ip, self.port) - self.evil_urls = ("http://%s:%i/db" % - (self.ip, self.port), "http://%s:%i/dq" % (self.ip, self.port)) + self.benign_url = "http://%s:%i".encode('UTF-8') % (self.ip, self.port) + self.evil_urls = ("http://%s:%i/db".encode('UTF-8') % + (self.ip, self.port), "http://%s:%i/dq".encode('UTF-8') % (self.ip, self.port)) wait_time = 0.001 for i in range(3): From 92de331e89cd4adde9734a2b74ad68ca1a764833 Mon Sep 17 00:00:00 2001 From: garet12 Date: Mon, 7 Jul 2014 18:40:17 +0200 Subject: [PATCH 49/58] Fixed posting empty parameters (submit button) --- webvulnscan/attacks/billion_laughs.py | 6 ++++-- webvulnscan/client.py | 2 +- webvulnscan/openID_test_server.py | 3 +-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 85de6c8..41ac937 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -15,15 +15,17 @@ def attack(attack_url): for key in parameters: if 'openid' in key: parameters[key] = attack_url - + try: + if "" in parameters: + del parameters[""] form.send(client, parameters, timeout=1) except socket.timeout: return True return False with Create_server(client.config) as openid_server: - + if attack(openid_server.benign_url): log('warn', openid_server.benign_url, "Billion Laughs", "Test did not work!") diff --git a/webvulnscan/client.py b/webvulnscan/client.py index 3b490c2..7717438 100644 --- a/webvulnscan/client.py +++ b/webvulnscan/client.py @@ -90,7 +90,7 @@ def download_page(self, url_or_request, parameters=None, req_headers=None, **kwa """ Downloads the content of a site, returns it as page. Throws NotAPage if the content is not a webpage. """ - timeout=None + timeout = None for key in kwargs: if key == "timeout": timeout = kwargs[key] diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 70dc47b..628ca17 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -156,12 +156,11 @@ def __enter__(self): wait_time = 0.001 for i in range(3): try: - urlopen(self.benign_url) + urlopen(self.benign_url+"?test") return self except (HTTPError, URLError): time.sleep(wait_time) wait_time *= 10 ** (i + 1) - i += 1 self.server.terminate() raise Exception("OpenID Server could not be reached !") From 96944e0842653d4565192d53787ea1fd75ec077d Mon Sep 17 00:00:00 2001 From: garet12 Date: Tue, 8 Jul 2014 16:29:56 +0200 Subject: [PATCH 50/58] Fixed small stuff --- webvulnscan/attacks/billion_laughs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 41ac937..c546234 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -15,10 +15,10 @@ def attack(attack_url): for key in parameters: if 'openid' in key: parameters[key] = attack_url - + try: - if "" in parameters: - del parameters[""] + if '' in parameters: + del parameters[''] form.send(client, parameters, timeout=1) except socket.timeout: return True From 66b2983ff18402b4cdf6fbbc8d05376d20149db9 Mon Sep 17 00:00:00 2001 From: garet12 Date: Wed, 9 Jul 2014 16:06:43 +0200 Subject: [PATCH 51/58] Removed BL test app because it is not needed anymore --- test/billion_laughs_test_app.py | 148 -------------------------------- 1 file changed, 148 deletions(-) delete mode 100644 test/billion_laughs_test_app.py diff --git a/test/billion_laughs_test_app.py b/test/billion_laughs_test_app.py deleted file mode 100644 index 0ea7526..0000000 --- a/test/billion_laughs_test_app.py +++ /dev/null @@ -1,148 +0,0 @@ -import xml_parser -import re -try: - from http.server import BaseHTTPRequestHandler, HTTPServer -except ImportError: - from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer - -try: - from urllib.parse import urlparse -except ImportError: - from urlparse import urlparse - -import socket -import cgi -import urllib2 -import time - - -class Handler(BaseHTTPRequestHandler): - - def _default_page(self): - self.send_response(200) - self.end_headers() - self.wfile.write(''' - - - - BillionLaughsTest - - -

BillionLaughsTest

-
- OpenID - -
- - - '''.encode("utf-8")) - - def _serve_request(self): - parsed_path = urlparse(self.path) - - if parsed_path.path == "/": - self._default_page() - elif parsed_path.path == "/form": - self.do_POST() - else: - self.send_error(404, "File not Found!") - - def do_POST(self): - form = cgi.FieldStorage( - fp=self.rfile, - headers=self.headers, - environ={'REQUEST_METHOD': 'POST', - 'CONTENT_TYPE': self.headers['Content-Type'], - }) - - self.send_response(200) - self.end_headers() - self.wfile.write('Client: %s\n' % str(self.client_address)) - self.wfile.write('User-agent: %s\n' % str(self.headers['user-agent'])) - self.wfile.write('Path: %s\n' % self.path) - self.wfile.write('Form data:\n') - - for field in form.keys(): - self.wfile.write('\t%s=%s\n' % (field, form[field].value)) - if field == '': - continue - xrds_doc = self.get_XML(form[field].value) - - if xrds_doc is not None: - res = xml_parser.get_xml_length(xrds_doc) - time.sleep(res / 1000000) - self.wfile.write('Result: %s\n' % res) - - else: - self.wfile.write('XML document could not be found\n') - - return - - def get_XML(self, url): - if "http://" not in url: - url = "http://" + url - headers = {'Accept': 'application/xrds+xml'} - req = urllib2.Request(url, None, headers) - - try: - response = urllib2.urlopen(req) - except urllib2.HTTPError, e: - self.wfile.write( - '%s There was a problem with your request!\n' % e.code) - return None - except urllib2.URLError, e: - self.wfile.write('%s' % e.args) - return None - - html = response.read() - xrds_loc = re.search( - r'[^"]*)"\s*', html, re.IGNORECASE) - - if xrds_loc: - xrds_url = xrds_loc.group('value') - - try: - xrds_doc = urllib2.urlopen(xrds_url) - except urllib2.HTTPError, e: - self.wfile.write( - '%s There was a problem with your request!\n' % e.code) - return None - except urllib2.URLError, e: - self.wfile.write('%s' % e.args) - return None - - return xrds_doc.read() - - else: - return None - - def handle_one_request(self): - try: - self.raw_requestline = self.rfile.readline(65537) - if len(self.raw_requestline) > 65536: - self.requestline = '' - self.request_version = '' - self.command = '' - self.send_error(414) - return - if not self.raw_requestline: - self.close_connection = 1 - return - if not self.parse_request(): - return - - self._serve_request() - self.wfile.flush() - except socket.timeout as e: - self.log_error("Request timed out: %r", e) - self.close_connection = 1 - return - - -def main(): - httpd = HTTPServer(("", 8000), Handler) - httpd.serve_forever() - - -if __name__ == '__main__': - main() From 391b723ac8706b7ae9a9620af2658ab772a4bb91 Mon Sep 17 00:00:00 2001 From: garet12 Date: Thu, 24 Jul 2014 14:48:46 +0200 Subject: [PATCH 52/58] Changed Quadratic Blowup XML to a valid XRDS --- webvulnscan/attacks/billion_laughs.py | 4 ++-- webvulnscan/openID_test_server.py | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index c546234..ad64d79 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -15,10 +15,10 @@ def attack(attack_url): for key in parameters: if 'openid' in key: parameters[key] = attack_url - + try: if '' in parameters: - del parameters[''] + del parameters[''] form.send(client, parameters, timeout=1) except socket.timeout: return True diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 628ca17..9959430 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -114,8 +114,20 @@ def showServerXML(self, xmlState=None): ]> -%s -'''.encode("utf-8") % (ent_a, ref_a)) + + + + + %s + %s + + + + + +'''.encode("utf-8") % (ent_a, ref_a, ref_a)) def main(queue, ip, port): @@ -156,7 +168,7 @@ def __enter__(self): wait_time = 0.001 for i in range(3): try: - urlopen(self.benign_url+"?test") + urlopen(self.benign_url + "?test") return self except (HTTPError, URLError): time.sleep(wait_time) From 005a3151e68d2d78a79c9c641b75bae92f4d2b87 Mon Sep 17 00:00:00 2001 From: garet12 Date: Thu, 31 Jul 2014 00:51:16 +0200 Subject: [PATCH 53/58] Content-Type Header added --- webvulnscan/openID_test_server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 9959430..2122a0d 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -22,6 +22,7 @@ class Handler(BaseHTTPRequestHandler): def _default_page(self, pageState="serverxml"): self.send_response(200) + self.send_header('Content-type','text/html') self.end_headers() self.wfile.write(''' From 21703f6ecbe2e25962f95c4d16e4feb636fdbbf8 Mon Sep 17 00:00:00 2001 From: garet12 Date: Thu, 31 Jul 2014 12:59:35 +0200 Subject: [PATCH 54/58] password field in parameters gets an empty string --- webvulnscan/attacks/billion_laughs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index ad64d79..3cdecee 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -15,7 +15,9 @@ def attack(attack_url): for key in parameters: if 'openid' in key: parameters[key] = attack_url - + if 'password'in key: + parameters[key] = '' + try: if '' in parameters: del parameters[''] From 9d476bd896063dcfffc485b571ad46967a146cdf Mon Sep 17 00:00:00 2001 From: garet12 Date: Fri, 1 Aug 2014 15:54:09 +0200 Subject: [PATCH 55/58] no_guessing parameter for form parameters implemented --- webvulnscan/attacks/billion_laughs.py | 13 +++++++------ webvulnscan/form.py | 8 ++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/webvulnscan/attacks/billion_laughs.py b/webvulnscan/attacks/billion_laughs.py index 3cdecee..a8f7c22 100644 --- a/webvulnscan/attacks/billion_laughs.py +++ b/webvulnscan/attacks/billion_laughs.py @@ -11,16 +11,17 @@ def search(page): @attack(search) def billion_laughs(client, log, form): def attack(attack_url): - parameters = dict(form.get_parameters()) + parameters = dict(form.get_parameters(True)) for key in parameters: - if 'openid' in key: + if 'openid' in key.lower(): parameters[key] = attack_url - if 'password'in key: - parameters[key] = '' try: + # Necessary because submit buttons usually do not have names which + # can cause bugs. So the submit buttons are deleted from the + # parameter list. if '' in parameters: - del parameters[''] + del parameters[''] form.send(client, parameters, timeout=1) except socket.timeout: return True @@ -35,4 +36,4 @@ def attack(attack_url): for evil_url in openid_server.evil_urls: if attack(evil_url): - log('vuln', evil_url, "Billion Laughs") + log('vuln', evil_url, "Billion Laughs/Quadratic Blowup") diff --git a/webvulnscan/form.py b/webvulnscan/form.py index 26d77e9..85f53d6 100644 --- a/webvulnscan/form.py +++ b/webvulnscan/form.py @@ -23,9 +23,13 @@ def get_inputs(self): for textarea in self.get_textarea_elements(): yield TextArea(textarea) - def get_parameters(self): + def get_parameters(self, no_guessing=False): for item in self.get_inputs(): - yield (item.get_name, item.guess_value()) + if item.get_type == 'hidden' or not no_guessing: + value = item.guessvalue() + else: + value = '' + yield (item.get_name, value) def get_input_elements(self): for form_input in self.document.findall('.//input'): From a1cd4555d2835b1564093a8a646b93de9c314c3d Mon Sep 17 00:00:00 2001 From: garet12 Date: Sat, 2 Aug 2014 15:31:10 +0200 Subject: [PATCH 56/58] fixed bug --- webvulnscan/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webvulnscan/form.py b/webvulnscan/form.py index 85f53d6..827e0b3 100644 --- a/webvulnscan/form.py +++ b/webvulnscan/form.py @@ -26,7 +26,7 @@ def get_inputs(self): def get_parameters(self, no_guessing=False): for item in self.get_inputs(): if item.get_type == 'hidden' or not no_guessing: - value = item.guessvalue() + value = item.guess_value() else: value = '' yield (item.get_name, value) From eb33733b2a791e2d9961e88e785a65afd544400c Mon Sep 17 00:00:00 2001 From: garet12 Date: Wed, 20 Aug 2014 18:46:35 +0200 Subject: [PATCH 57/58] PEP8 --- webvulnscan/__init__.py | 4 ++-- webvulnscan/attacks/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webvulnscan/__init__.py b/webvulnscan/__init__.py index 700832b..d04cfb3 100644 --- a/webvulnscan/__init__.py +++ b/webvulnscan/__init__.py @@ -37,7 +37,7 @@ def run(options, targets): log = Log(verbosity=u'vuln') else: log = Log() - client = Client(log=log,config=options.bl_config) + client = Client(log=log, config=options.bl_config) if options.import_cookies: client.cookie_jar = MozillaCookieJar(options.import_cookies) @@ -86,7 +86,7 @@ def run(options, targets): for page in all_pages: log('info', page.url, 'crawler', 'Scanning ...') - for attack in attacks: + for attack in attacks: attack(client, log, page) finally: diff --git a/webvulnscan/attacks/__init__.py b/webvulnscan/attacks/__init__.py index dfa4b36..01add74 100644 --- a/webvulnscan/attacks/__init__.py +++ b/webvulnscan/attacks/__init__.py @@ -11,4 +11,4 @@ def all_attacks(): - return [xss, csrf, crlf, breach, clickjack, cookiescan, exotic_characters,utf7_check,billion_laughs] + return [xss, csrf, crlf, breach, clickjack, cookiescan, exotic_characters, utf7_check, billion_laughs] From 4b8f27e1c13a4da8723ab46a7a1c874a2677920f Mon Sep 17 00:00:00 2001 From: garet12 Date: Wed, 20 Aug 2014 18:55:29 +0200 Subject: [PATCH 58/58] PEP-8 --- webvulnscan/openID_test_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webvulnscan/openID_test_server.py b/webvulnscan/openID_test_server.py index 2122a0d..4b7ccb6 100644 --- a/webvulnscan/openID_test_server.py +++ b/webvulnscan/openID_test_server.py @@ -22,7 +22,7 @@ class Handler(BaseHTTPRequestHandler): def _default_page(self, pageState="serverxml"): self.send_response(200) - self.send_header('Content-type','text/html') + self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write('''