class TestWAVSEPIdenticalPOST(WAVSEPTest): base_path = ('/wavsep/active/SQL-Injection/' 'SInjection-Detection-Evaluation-POST-200Identical/') target_url = get_wavsep_http(base_path) def test_found_sqli_wavsep_identical_post(self): expected_path_param = { (u'Case01-InjectionInView-Numeric-Blind-200ValidResponseWithDefaultOnException.jsp', u'transactionId'), (u'Case02-InjectionInView-String-Blind-200ValidResponseWithDefaultOnException.jsp', u'username'), (u'Case03-InjectionInView-Date-Blind-200ValidResponseWithDefaultOnException.jsp', u'transactionDate'), (u'Case04-InjectionInUpdate-Numeric-TimeDelayExploit-200Identical.jsp', u'transactionId'), (u'Case05-InjectionInUpdate-String-TimeDelayExploit-200Identical.jsp', u'description'), (u'Case06-InjectionInUpdate-Date-TimeDelayExploit-200Identical.jsp', u'transactionDate'), (u'Case07-InjectionInUpdate-NumericWithoutQuotes-TimeDelayExploit-200Identical.jsp', u'transactionId'), (u'Case08-InjectionInUpdate-DateWithoutQuotes-TimeDelayExploit-200Identical.jsp', u'transactionDate'), } # None is OK to miss -> 100% coverage ok_to_miss = set() skip_startwith = {'index.jsp'} kb_addresses = {('sqli', 'sqli'), ('blind_sqli', 'blind_sqli')} self._scan_assert(self.config, expected_path_param, ok_to_miss, kb_addresses, skip_startwith)
def test_xss_false_positive_1516(self): target_url = get_wavsep_http('/wavsep/active/Reflected-XSS/' 'RXSS-Detection-Evaluation-GET/' 'Case24-Js2ScriptTag.jsp?userinput=1234') self._scan(target_url, CONFIG) vulns = self.kb.get('blind_sqli', 'blind_sqli') self.assertEquals(0, len(vulns))
class TestWAVSEPWithDifferentiationPOST(WAVSEPTest): base_path = ('/wavsep/active/SQL-Injection/' 'SInjection-Detection-Evaluation-POST-200Valid/') target_url = get_wavsep_http(base_path) def test_found_sqli_wavsep_differentiation_post(self): expected_path_param = { (u'Case01-InjectionInLogin-String-LoginBypass-WithDifferent200Responses.jsp', u'username'), (u'Case01-InjectionInLogin-String-LoginBypass-WithDifferent200Responses.jsp', u'password'), (u'Case02-InjectionInSearch-String-UnionExploit-WithDifferent200Responses.jsp', u'msg'), (u'Case03-InjectionInCalc-String-BooleanExploit-WithDifferent200Responses.jsp', u'username'), (u'Case04-InjectionInUpdate-String-CommandInjection-WithDifferent200Responses.jsp', u'msg'), (u'Case05-InjectionInSearchOrderBy-String-BinaryDeliberateRuntimeError-WithDifferent200Responses.jsp', u'orderby'), (u'Case06-InjectionInView-Numeric-PermissionBypass-WithDifferent200Responses.jsp', u'transactionId'), (u'Case07-InjectionInSearch-Numeric-UnionExploit-WithDifferent200Responses.jsp', u'msgId'), (u'Case08-InjectionInCalc-Numeric-BooleanExploit-WithDifferent200Responses.jsp', u'minBalanace'), (u'Case09-InjectionInUpdate-Numeric-CommandInjection-WithDifferent200Responses.jsp', u'msgid'), (u'Case10-InjectionInSearchOrderBy-Numeric-BinaryDeliberateRuntimeError-WithDifferent200Responses.jsp', u'orderby'), (u'Case11-InjectionInView-Date-PermissionBypass-WithDifferent200Responses.jsp', u'transactionDate'), (u'Case12-InjectionInSearch-Date-UnionExploit-WithDifferent200Responses.jsp', u'transactionDate'), (u'Case13-InjectionInCalc-Date-BooleanExploit-WithDifferent200Responses.jsp', u'transactionDate'), (u'Case14-InjectionInUpdate-Date-CommandInjection-WithDifferent200Responses.jsp', u'transactionDate'), (u'Case15-InjectionInSearch-DateWithoutQuotes-UnionExploit-WithDifferent200Responses.jsp', u'transactionDate'), (u'Case16-InjectionInView-NumericWithoutQuotes-PermissionBypass-WithDifferent200Responses.jsp', u'transactionId'), (u'Case17-InjectionInSearch-NumericWithoutQuotes-UnionExploit-WithDifferent200Responses.jsp', u'msgId'), (u'Case18-InjectionInCalc-NumericWithoutQuotes-BooleanExploit-WithDifferent200Responses.jsp', u'minBalanace'), (u'Case19-InjectionInUpdate-NumericWithoutQuotes-CommandInjection-WithDifferent200Responses.jsp', u'msgid'), } # None is OK to miss -> 100% coverage ok_to_miss = set() skip_startwith = {'index.jsp'} kb_addresses = {('sqli', 'sqli'), ('blind_sqli', 'blind_sqli')} self._scan_assert(self.config, expected_path_param, ok_to_miss, kb_addresses, skip_startwith)
def test_1557_random_number_of_results(self): """ Pseudo-random number of vulnerabilities found in audit phase (xss) https://github.com/andresriancho/w3af/issues/1557 """ script = TEST_SCRIPT_1557 % (OUTPUT_PATH, get_wavsep_http()) file(SCRIPT_PATH, "w").write(script) python_executable = sys.executable VULN_STRING = "A Cross Site Scripting vulnerability was found at" URL_VULN_RE = re.compile('%s: "(.*?)"' % VULN_STRING) all_previous_vulns = [] loops = 2 if is_running_on_ci() else 10 for i in xrange(loops): print("Start run #%s" % i) found_vulns = set() p = subprocess.Popen( [python_executable, "w3af_console", "-n", "-s", SCRIPT_PATH], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=False, universal_newlines=True, ) stdout, stderr = p.communicate() i_vuln_count = stdout.count(VULN_STRING) print("%s vulnerabilities found" % i_vuln_count) self.assertNotEqual(i_vuln_count, 0, stdout) for line in stdout.split("\n"): if VULN_STRING in line: found_vulns.add(URL_VULN_RE.search(line).group(1)) for previous_found in all_previous_vulns: self.assertEqual(found_vulns, previous_found) all_previous_vulns.append(found_vulns)
def test_1557_random_number_of_results(self): """ Pseudo-random number of vulnerabilities found in audit phase (xss) https://github.com/andresriancho/w3af/issues/1557 """ script = TEST_SCRIPT_1557 % (OUTPUT_PATH, get_wavsep_http()) file(SCRIPT_PATH, 'w').write(script) python_executable = sys.executable VULN_STRING = 'A Cross Site Scripting vulnerability was found at' URL_VULN_RE = re.compile('%s: "(.*?)"' % VULN_STRING) all_previous_vulns = [] loops = 2 if is_running_on_ci() else 10 for i in xrange(loops): print('Start run #%s' % i) found_vulns = set() p = subprocess.Popen( [python_executable, 'w3af_console', '-n', '-s', SCRIPT_PATH], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=False, universal_newlines=True) stdout, stderr = p.communicate() i_vuln_count = stdout.count(VULN_STRING) print('%s vulnerabilities found' % i_vuln_count) self.assertNotEqual(i_vuln_count, 0, stdout) for line in stdout.split('\n'): if VULN_STRING in line: found_vulns.add(URL_VULN_RE.search(line).group(1)) for previous_found in all_previous_vulns: self.assertEqual(found_vulns, previous_found) all_previous_vulns.append(found_vulns)
class TestWAVSEPExperimentalPOST(WAVSEPTest): base_path = ('/wavsep/active/SQL-Injection/' 'SInjection-Detection-Evaluation-POST-200Error-Experimental/') target_url = get_wavsep_http(base_path) def test_found_sqli_wavsep_experimental_post(self): expected_path_param = { (u'Case01-InjectionInInsertValues-String-BinaryDeliberateRuntimeError-With200Errors.jsp', u'target'), (u'Case01-InjectionInInsertValues-String-BinaryDeliberateRuntimeError-With200Errors.jsp', u'msg') } # None is OK to miss -> 100% coverage ok_to_miss = set() skip_startwith = {'index.jsp'} kb_addresses = {('sqli', 'sqli'), ('blind_sqli', 'blind_sqli')} self._scan_assert(self.config, expected_path_param, ok_to_miss, kb_addresses, skip_startwith)
class TestXSS(PluginTest): XSS_PATH = get_moth_http('/audit/xss/') XSS_302_URL = 'http://moth/w3af/audit/xss/302/' XSS_URL_SMOKE = get_moth_http('/audit/xss/') WAVSEP_BASE = '/active/RXSS-Detection-Evaluation-GET/' WAVSEP_PATH = get_wavsep_http(WAVSEP_BASE) WAVSEP_2919 = get_wavsep_http('%sCase16-Js2ScriptSupportingProperty.jsp' % WAVSEP_BASE) _run_configs = { 'cfg': { 'target': None, 'plugins': { 'audit': (PluginConfig('xss', ('persistent_xss', True, PluginConfig.BOOL)), ), 'crawl': (PluginConfig('web_spider', ('only_forward', True, PluginConfig.BOOL)), ) }, }, 'smoke': { 'target': XSS_URL_SMOKE + 'simple_xss.py?text=1', 'plugins': { 'audit': (PluginConfig('xss', ('persistent_xss', True, PluginConfig.BOOL)), ), }, } } def normalize_kb_data(self, xss_vulns): """ Take the XSS vulns as input and translate them into a list of tuples which contain: - Vulnerable URL - Vulnerable parameter - All parameters that were sent """ kb_data = [(str(m.get_url()), m.get_token_name(), tuple(sorted(m.get_dc().keys()))) for m in (xv.get_mutant() for xv in xss_vulns)] return kb_data def normalize_expected_data(self, target_url, expected): """ Take a list with the expected vulnerabilities to be found as input and translate them into a list of tuples which contain: - Vulnerable URL - Vulnerable parameter - All parameters that were sent """ expected_data = [(target_url + e[0], e[1], tuple(sorted(e[2]))) for e in expected] return expected_data @attr('smoke') def test_find_one_xss(self): """ Simplest possible test to verify that we identify XSSs. """ cfg = self._run_configs['smoke'] self._scan(cfg['target'], cfg['plugins']) xss_vulns = self.kb.get('xss', 'xss') kb_data = self.normalize_kb_data(xss_vulns) EXPECTED = [ ('simple_xss.py', 'text', ['text']), ] expected_data = self.normalize_expected_data(self.XSS_URL_SMOKE, EXPECTED) self.assertEquals( set(expected_data), set(kb_data), ) def test_2919_javascript_src_frame(self): """ https://github.com/andresriancho/w3af/issues/2919 https://github.com/andresriancho/w3af/issues/1557 """ cfg = self._run_configs['smoke'] self._scan(self.WAVSEP_2919 + '?userinput=1', cfg['plugins']) xss_vulns = self.kb.get('xss', 'xss') kb_data = self.normalize_kb_data(xss_vulns) EXPECTED = [ ('Case16-Js2ScriptSupportingProperty.jsp', 'userinput', ['userinput']), ] expected_data = self.normalize_expected_data(self.WAVSEP_PATH, EXPECTED) self.assertEquals( set(expected_data), set(kb_data), ) def test_no_false_positive_499(self): """ Avoiding false positives in the case where the payload is echoed back inside an attribute and the quotes are removed. :see: https://github.com/andresriancho/w3af/pull/499 """ cfg = self._run_configs['smoke'] self._scan(self.XSS_PATH + '499_check.py?text=1', cfg['plugins']) xss_vulns = self.kb.get('xss', 'xss') self.assertEquals(0, len(xss_vulns), xss_vulns) def scan_file_upload_fuzz_files(self): cfg = self._run_configs['cfg'] target_path = get_php_moth_http('/audit/file_upload/echo_content/') self._scan(target_path, cfg['plugins']) def test_user_configured_find_in_file_upload_content(self): """ Do not send file content mutants unless the user configures it. https://github.com/andresriancho/w3af/issues/3149 """ # Set the value to False (True is the default) cf.save('fuzz_form_files', False) try: self.scan_file_upload_fuzz_files() finally: # Restore the default cf.save('fuzz_form_files', True) xss_vulns = self.kb.get('xss', 'xss') self.assertEqual(len(xss_vulns), 0, xss_vulns) def test_find_in_file_upload_content(self): """ Find XSS in the content of an uploaded file https://github.com/andresriancho/w3af/issues/3149 """ self.scan_file_upload_fuzz_files() target_path = get_php_moth_http('/audit/file_upload/echo_content/') xss_vulns = self.kb.get('xss', 'xss') kb_data = self.normalize_kb_data(xss_vulns) EXPECTED = [ ('txt_uploader.php', 'txt_file', ['txt_file']), ] expected_data = self.normalize_expected_data(target_path, EXPECTED) self.assertEquals( set(expected_data), set(kb_data), ) def test_found_xss(self): cfg = self._run_configs['cfg'] self._scan(self.XSS_PATH, cfg['plugins']) xss_vulns = self.kb.get('xss', 'xss') kb_data = self.normalize_kb_data(xss_vulns) expected = [ # Trivial ('simple_xss.py', 'text', ['text']), # Form with GET method # https://github.com/andresriancho/w3af/issues/3149 ('simple_xss_GET_form.py', 'text', ['text']), # Form with multipart enctype # https://github.com/andresriancho/w3af/issues/3149 ('xss_multipart_form.py', 'text', ['text']), # Simple filters ('script_insensitive_blacklist_xss.py', 'text', ['text']), ('script_blacklist_xss.py', 'text', ['text']), # Simple encodings ('lower_str_xss.py', 'text', ['text']), # Forms with POST ('simple_xss_form.py', 'text', ['text']), ('two_inputs_form.py', 'address', ['address', 'name']), # Persistent XSS ('persistent_xss_form.py', 'text', ['text']), # XSS with CSP ('xss_with_safe_csp.py', 'text', ['text']), ('xss_with_weak_csp.py', 'text', ['text']), ] expected_data = self.normalize_expected_data(self.XSS_PATH, expected) self.assertEquals( set(expected_data), set(kb_data), ) # Now we want to verify that the vulnerability with safe CSP has lower # severity than the one with weak CSP csp_vulns = [v for v in xss_vulns if 'csp.py' in v.get_url()] self.assertEqual(len(csp_vulns), 2) severities = [v.get_severity() for v in csp_vulns] self.assertEqual(set(severities), {severity.MEDIUM, severity.LOW}, csp_vulns) @attr('ci_fails') def test_found_xss_with_redirect(self): cfg = self._run_configs['cfg'] self._scan(self.XSS_302_URL, cfg['plugins']) xss_vulns = self.kb.get('xss', 'xss') kb_data = self.normalize_kb_data(xss_vulns) expected = [('302.php', 'x', ('x', )), ('302.php', 'a', ('a', )), ('printer.php', 'a', ( 'a', 'added', )), ('printer.php', 'added', ( 'a', 'added', )), ('printer.php', 'added', ('added', )), ('printer.php', 'x', ('x', 'added')), ('printer.php', 'added', ('x', 'added'))] expected_data = self.normalize_expected_data(self.XSS_302_URL, expected) self.assertEquals( set(expected_data), set(kb_data), ) def test_found_wavsep_get_xss(self): cfg = self._run_configs['cfg'] self._scan(self.WAVSEP_PATH, cfg['plugins']) xss_vulns = self.kb.get('xss', 'xss') kb_data = self.normalize_kb_data(xss_vulns) expected = [ ('Case01-Tag2HtmlPageScope.jsp', 'userinput', ['userinput']), ('Case02-Tag2TagScope.jsp', 'userinput', ['userinput']), ('Case03-Tag2TagStructure.jsp', 'userinput', ['userinput']), ('Case04-Tag2HtmlComment.jsp', 'userinput', ['userinput']), ('Case05-Tag2Frameset.jsp', 'userinput', ['userinput']), ('Case06-Event2TagScope.jsp', 'userinput', ['userinput']), ('Case07-Event2DoubleQuotePropertyScope.jsp', 'userinput', ['userinput']), ('Case08-Event2SingleQuotePropertyScope.jsp', 'userinput', ['userinput']), ('Case09-SrcProperty2TagStructure.jsp', 'userinput', ['userinput' ]), ('Case10-Js2DoubleQuoteJsEventScope.jsp', 'userinput', ['userinput']), ('Case11-Js2SingleQuoteJsEventScope.jsp', 'userinput', ['userinput']), ('Case12-Js2JsEventScope.jsp', 'userinput', ['userinput']), #('Case13-Vbs2DoubleQuoteVbsEventScope.jsp', 'userinput', ['userinput']), #('Case14-Vbs2SingleQuoteVbsEventScope.jsp', 'userinput', ['userinput']), ('Case15-Vbs2VbsEventScope.jsp', 'userinput', ['userinput']), ('Case16-Js2ScriptSupportingProperty.jsp', 'userinput', ['userinput']), #('Case17-Js2PropertyJsScopeDoubleQuoteDelimiter.jsp', 'userinput', ['userinput']), #('Case18-Js2PropertyJsScopeSingleQuoteDelimiter.jsp', 'userinput', ['userinput']), #('Case19-Js2PropertyJsScope.jsp', 'userinput', ['userinput']), #('Case20-Vbs2PropertyVbsScopeDoubleQuoteDelimiter.jsp', 'userinput', ['userinput']), #('Case21-Vbs2PropertyVbsScope.jsp', 'userinput', ['userinput']), ('Case22-Js2ScriptTagDoubleQuoteDelimiter.jsp', 'userinput', ['userinput']), ('Case23-Js2ScriptTagSingleQuoteDelimiter.jsp', 'userinput', ['userinput']), ('Case24-Js2ScriptTag.jsp', 'userinput', ['userinput']), ('Case25-Vbs2ScriptTagDoubleQuoteDelimiter.jsp', 'userinput', ['userinput']), ('Case26-Vbs2ScriptTag.jsp', 'userinput', ['userinput']), ('Case27-Js2ScriptTagOLCommentScope.jsp', 'userinput', ['userinput']), ('Case28-Js2ScriptTagMLCommentScope.jsp', 'userinput', ['userinput']), ('Case29-Vbs2ScriptTagOLCommentScope.jsp', 'userinput', ['userinput']), ('Case30-Tag2HtmlPageScopeMultipleVulnerabilities.jsp', 'userinput', ['userinput', 'userinput2']), ('Case30-Tag2HtmlPageScopeMultipleVulnerabilities.jsp', 'userinput2', ['userinput', 'userinput2']), ('Case31-Tag2HtmlPageScopeDuringException.jsp', 'userinput', ['userinput']), ('Case32-Tag2HtmlPageScopeValidViewstateRequired.jsp', 'userinput', ['userinput', '__VIEWSTATE']), ] expected_data = self.normalize_expected_data(self.WAVSEP_PATH, expected) self.assertEquals( set(expected_data), set(kb_data), )