def test_fuzzable_request(self): dl = DiskList() uri = URL('http://w3af.org/?id=2') qsr1 = HTTPQSRequest(uri, method='GET', headers=Headers([('Referer', 'http://w3af.org/') ])) uri = URL('http://w3af.org/?id=3') qsr2 = HTTPQSRequest(uri, method='OPTIONS', headers=Headers([('Referer', 'http://w3af.org/') ])) uri = URL('http://w3af.org/?id=7') qsr3 = HTTPQSRequest(uri, method='FOO', headers=Headers([('Referer', 'http://w3af.org/') ])) dl.append(qsr1) dl.append(qsr2) self.assertEqual(dl[0], qsr1) self.assertEqual(dl[1], qsr2) self.assertFalse(qsr3 in dl) self.assertTrue(qsr2 in dl)
def test_remove_table_then_add(self): disk_list = DiskList() disk_list.append(1) disk_list.cleanup() self.assertRaises(AssertionError, disk_list.append, 1)
def test_to_unicode(self): dl = DiskList() dl.append(1) dl.append(2) dl.append(3) self.assertEqual(unicode(dl), u'<DiskList [1, 2, 3]>')
def test_specific_serializer_with_http_response(self): # # This test runs in 26.42 seconds on my workstation # body = '<html><a href="http://moth/abc.jsp">test</a></html>' headers = Headers([('Content-Type', 'text/html')]) url = URL('http://w3af.com') response = HTTPResponse(200, body, headers, url, url, _id=1) def dump(http_response): return msgpack.dumps(http_response.to_dict(), use_bin_type=True) def load(serialized_object): data = msgpack.loads(serialized_object, raw=False) return HTTPResponse.from_dict(data) count = 30000 dl = DiskList(dump=dump, load=load) for i in xrange(0, count): # This tests the serialization dl.append(response) # This tests the deserialization _ = dl[i]
class RESTAPIOutput(OutputPlugin): """ Store all log messages on a DiskList :author: Andres Riancho ([email protected]) """ def __init__(self): super(RESTAPIOutput, self).__init__() self.log = DiskList(table_prefix='RestApiScanLog') self.log_id = -1 def get_log_id(self): self.log_id += 1 return self.log_id def debug(self, msg_string, new_line=True): """ This method is called from the output object. The output object was called from a plugin or from the framework. This method should take an action for debug messages. """ m = Message(DEBUG, self._clean_string(msg_string), self.get_log_id()) self.log.append(m) def information(self, msg_string, new_line=True): """ This method is called from the output object. The output object was called from a plugin or from the framework. This method should take an action for informational messages. """ m = Message(INFORMATION, self._clean_string(msg_string), self.get_log_id()) self.log.append(m) def error(self, msg_string, new_line=True): """ This method is called from the output object. The output object was called from a plugin or from the framework. This method should take an action for error messages. """ m = Message(ERROR, self._clean_string(msg_string), self.get_log_id()) self.log.append(m) def vulnerability(self, msg_string, new_line=True, severity=MEDIUM): """ This method is called from the output object. The output object was called from a plugin or from the framework. This method should take an action when a vulnerability is found. """ m = Message(VULNERABILITY, self._clean_string(msg_string), self.get_log_id()) m.set_severity(severity) self.log.append(m) def console(self, msg_string, new_line=True): """ This method is used by the w3af console to print messages to the outside """ m = Message(CONSOLE, self._clean_string(msg_string), self.get_log_id()) self.log.append(m)
class content_sniffing(GrepPlugin): """ Check if all responses have X-Content-Type-Options header set :author: Andres Riancho ([email protected]) """ def __init__(self): super(content_sniffing, self).__init__() self._vuln_count = 0 self._vulns = DiskList(table_prefix='content_sniffing') self._ids = DiskList(table_prefix='content_sniffing') def grep(self, request, response): """ Check if all responses have X-Content-Type-Options header set :param request: The HTTP request object. :param response: The HTTP response object :return: None, all results are saved in the kb. """ if self._vuln_count > MAX_REPORTS: return ct_options_value, _ = response.get_headers().iget( CT_OPTIONS_HEADER, None) if ct_options_value is not None: if ct_options_value.strip().lower() == NOSNIFF: return self._vuln_count += 1 if response.get_url() not in self._vulns: self._vulns.append(response.get_url()) self._ids.append(response.id) def end(self): if not self._vuln_count: return response_ids = [_id for _id in self._ids] desc = 'Some URLs returned an HTTP response without the' \ ' recommended HTTP header X-Content-Type-Options.' \ 'The list of vulnerable URLs is:\n\n - %s' desc %= ' - '.join([str(url) + '\n' for url in self._vulns]) v = Vuln('Missing X-Content-Type-Options header', desc, severity.LOW, response_ids, self.get_name()) self.kb_append_uniq_group(self, 'content_sniffing', v, group_klass=CTSniffingInfoSet) def get_long_desc(self): """ :return: A DETAILED description of the plugin functions and features. """ return """
def test_slice_all(self): disk_list = DiskList() disk_list.append("1") disk_list.append("2") dl_copy = disk_list[:] self.assertIn("1", dl_copy) self.assertIn("2", dl_copy)
def test_slice_all(self): disk_list = DiskList() disk_list.append('1') disk_list.append('2') dl_copy = disk_list[:] self.assertIn('1', dl_copy) self.assertIn('2', dl_copy)
def test_slice_greater_than_length(self): disk_list = DiskList() disk_list.append('1') disk_list.append('2') dl_copy = disk_list[:50] self.assertIn('1', dl_copy) self.assertIn('2', dl_copy) self.assertEqual(2, len(dl_copy))
def test_sorted(self): dl = DiskList() dl.append("abc") dl.append("def") dl.append("aaa") sorted_dl = sorted(dl) self.assertEqual(["aaa", "abc", "def"], sorted_dl)
def test_slice_first_N(self): disk_list = DiskList() disk_list.append("1") disk_list.append("2") disk_list.append("3") dl_copy = disk_list[:1] self.assertIn("1", dl_copy) self.assertNotIn("2", dl_copy) self.assertNotIn("3", dl_copy)
def test_urlobject(self): dl = DiskList() dl.append(URL('http://w3af.org/?id=2')) dl.append(URL('http://w3af.org/?id=3')) self.assertEqual(dl[0], URL('http://w3af.org/?id=2')) self.assertEqual(dl[1], URL('http://w3af.org/?id=3')) self.assertFalse(URL('http://w3af.org/?id=4') in dl) self.assertTrue(URL('http://w3af.org/?id=2') in dl)
def test_slice_first_N(self): disk_list = DiskList() disk_list.append('1') disk_list.append('2') disk_list.append('3') dl_copy = disk_list[:1] self.assertIn('1', dl_copy) self.assertNotIn('2', dl_copy) self.assertNotIn('3', dl_copy)
def test_sorted(self): dl = DiskList() dl.append('abc') dl.append('def') dl.append('aaa') sorted_dl = sorted(dl) self.assertEqual(['aaa', 'abc', 'def'], sorted_dl)
def test_reverse_iteration(self): dl = DiskList() dl.append(1) dl.append(2) dl.append(3) reverse_iter_res = [] for i in reversed(dl): reverse_iter_res.append(i) self.assertEqual(reverse_iter_res, [3, 2, 1])
def test_getitem_negative(self): dl = DiskList() dl.append('a') dl.append('b') dl.append('c') self.assertEqual(dl[-1], 'c') self.assertEqual(dl[-2], 'b') self.assertEqual(dl[-3], 'a') self.assertRaises(IndexError, dl.__getitem__, -4)
def test_clear(self): dl = DiskList() dl.append('a') dl.append('b') self.assertEqual(len(dl), 2) dl.clear() self.assertEqual(len(dl), 0)
def test_extend(self): dl = DiskList() dl.append('a') dl.extend([1, 2, 3]) self.assertEqual(len(dl), 4) self.assertEqual(dl[0], 'a') self.assertEqual(dl[1], 1) self.assertEqual(dl[2], 2) self.assertEqual(dl[3], 3)
def test_getitem_negative(self): dl = DiskList() dl.append("a") dl.append("b") dl.append("c") self.assertEqual(dl[-1], "c") self.assertEqual(dl[-2], "b") self.assertEqual(dl[-3], "a") self.assertRaises(IndexError, dl.__getitem__, -4)
def test_ordered_iter(self): dl = DiskList() dl.append("abc") dl.append("def") dl.append("aaa") sorted_dl = [] for i in dl.ordered_iter(): sorted_dl.append(i) self.assertEqual(["aaa", "abc", "def"], sorted_dl)
def test_ordered_iter(self): dl = DiskList() dl.append('abc') dl.append('def') dl.append('aaa') sorted_dl = [] for i in dl.ordered_iter(): sorted_dl.append(i) self.assertEqual(['aaa', 'abc', 'def'], sorted_dl)
def test_no_specific_serializer_with_string(self): # # This test runs in ~5.1 seconds on my workstation # count = 30000 dl = DiskList() for i in xrange(0, count): i_str = str(i) # This tests the serialization dl.append(i_str) # This tests the deserialization _ = dl[i]
def test_len(self): dl = DiskList() for i in xrange(0, 100): _ = dl.append(i) self.assertEqual(len(dl) == 100, True)
def test_len(self): dl = DiskList() for i in xrange(0, 100): _ = dl.append(i) self.assertEqual(len(dl), 100)
def test_no_specific_serializer_with_http_response(self): # # This test runs in 28.14 seconds on my workstation # body = '<html><a href="http://moth/abc.jsp">test</a></html>' headers = Headers([('Content-Type', 'text/html')]) url = URL('http://w3af.com') response = HTTPResponse(200, body, headers, url, url, _id=1) count = 30000 dl = DiskList() for i in xrange(0, count): # This tests the serialization dl.append(response) # This tests the deserialization _ = dl[i]
def test_specific_serializer_with_string(self): # # This test runs in ~5.0 seconds on my workstation # # It seems that cPickle takes almost no time to serialize # a simple string. # count = 30000 dl = DiskList(load=lambda x: x, dump=lambda x: x) for i in xrange(0, count): i_str = str(i) # This tests the serialization dl.append(i_str) # This tests the deserialization _ = dl[i]
def test_fuzzable_request(self): dl = DiskList() uri = URL("http://w3af.org/?id=2") qsr1 = FuzzableRequest(uri, method="GET", headers=Headers([("Referer", "http://w3af.org/")])) uri = URL("http://w3af.org/?id=3") qsr2 = FuzzableRequest(uri, method="OPTIONS", headers=Headers([("Referer", "http://w3af.org/")])) uri = URL("http://w3af.org/?id=7") qsr3 = FuzzableRequest(uri, method="FOO", headers=Headers([("Referer", "http://w3af.org/")])) dl.append(qsr1) dl.append(qsr2) self.assertEqual(dl[0], qsr1) self.assertEqual(dl[1], qsr2) self.assertFalse(qsr3 in dl) self.assertTrue(qsr2 in dl)
def test_int(self): dl = DiskList() for i in xrange(0, 1000): _ = dl.append(i) for i in xrange(0, 1000 / 2): r = random.randint(0, 1000 - 1) self.assertEqual(r in dl, True) for i in xrange(0, 1000 / 2): r = random.randint(1000, 1000 * 2) self.assertEqual(r in dl, False)
def test_string(self): dl = DiskList() for i in xrange(0, 1000): rnd = ''.join(random.choice(string.letters) for i in xrange(40)) _ = dl.append(rnd) self.assertEqual(rnd in dl, True) for i in string.letters: self.assertNotIn(i, dl) self.assertIn(rnd, dl)
def test_fuzzable_request(self): dl = DiskList() uri = URL('http://w3af.org/?id=2') qsr1 = FuzzableRequest(uri, method='GET', headers=Headers( [('Referer', 'http://w3af.org/')])) uri = URL('http://w3af.org/?id=3') qsr2 = FuzzableRequest(uri, method='OPTIONS', headers=Headers( [('Referer', 'http://w3af.org/')])) uri = URL('http://w3af.org/?id=7') qsr3 = FuzzableRequest(uri, method='FOO', headers=Headers( [('Referer', 'http://w3af.org/')])) dl.append(qsr1) dl.append(qsr2) self.assertEqual(dl[0], qsr1) self.assertEqual(dl[1], qsr2) self.assertFalse(qsr3 in dl) self.assertTrue(qsr2 in dl)
def test_unicode(self): dl = DiskList() dl.append(u"à") dl.append(u"המלצת השבוע") dl.append([u"à"]) self.assertEqual(dl[0], u"à") self.assertEqual(dl[1], u"המלצת השבוע") self.assertEqual(dl[2], [u"à"])
def test_unicode(self): dl = DiskList() dl.append(u'à') dl.append(u'המלצת השבוע') dl.append([u'à', ]) self.assertEqual(dl[0], u'à') self.assertEqual(dl[1], u'המלצת השבוע') self.assertEqual(dl[2], [u'à', ])
def test_getitem(self): dl = DiskList() dl.append('a') dl.append(1) dl.append([3, 2, 1]) self.assertEqual(dl[0], 'a') self.assertEqual(dl[1], 1) self.assertEqual(dl[2], [3, 2, 1]) self.assertRaises(IndexError, dl.__getitem__, 3)
def test_getitem(self): dl = DiskList() dl.append('a') dl.append(1) dl.append([3, 2, 1]) self.assertEqual(dl[0] == 'a', True) self.assertEqual(dl[1] == 1, True) self.assertEqual(dl[2] == [3, 2, 1], True) self.assertRaises(IndexError, dl.__getitem__, 3)
def test_unicode(self): dl = DiskList() dl.append(u'à') dl.append(u'המלצת השבוע') dl.append([ u'à', ]) self.assertEqual(dl[0], u'à') self.assertEqual(dl[1], u'המלצת השבוע') self.assertEqual(dl[2], [ u'à', ])
def test_pickle(self): dl = DiskList() dl.append('a') dl.append(1) dl.append([3, 2, 1]) values = [] for i in dl: values.append(i) self.assertEqual(values[0], 'a') self.assertEqual(values[1], 1) self.assertEqual(values[2], [3, 2, 1])
def test_pickle(self): dl = DiskList() dl.append('a') dl.append(1) dl.append([3, 2, 1]) values = [] for i in dl: values.append(i) self.assertEqual(values[0] == 'a', True) self.assertEqual(values[1] == 1, True) self.assertEqual(values[2] == [3, 2, 1], True)
class _LineScroller(gtk.TextView, MessageConsumer): """The text view of the Messages window. :author: Facundo Batista <facundobatista =at= taniquetil.com.ar> """ def __init__(self, scroll_bar, active_filter, possible): """ :param scroll_bar: Gtk Vertical Scrollbar object :param active_filter: the filter active at startup. :param possible: all filter keys """ gtk.TextView.__init__(self) MessageConsumer.__init__(self) self.set_editable(False) self.set_cursor_visible(False) self.set_wrap_mode(gtk.WRAP_WORD) self.textbuffer = self.get_buffer() self.show() self.possible = set(possible) self.active_filter = active_filter self.text_position = 0 self.all_messages = DiskList() # scroll bar self.freeze_scrollbar = False scroll_bar.connect("value-changed", self.scroll_changed) # colors self.textbuffer.create_tag("red-fg", foreground="red") self.textbuffer.create_tag("blue-fg", foreground="blue") self.textbuffer.create_tag("brown-fg", foreground="brown") self.bg_colors = { "vulnerability": "red-fg", "information": "blue-fg", "error": "brown-fg", } def filter(self, filtinfo): """Applies a different filter to the textview. :param filtinfo: the new filter """ self.active_filter = filtinfo textbuff = self.textbuffer textbuff.set_text("") for (mtype, text) in self.all_messages: if mtype in filtinfo: colortag = self.bg_colors[mtype] iterl = textbuff.get_end_iter() textbuff.insert_with_tags_by_name(iterl, text, colortag) self.scroll_to_end() def handle_message(self, msg): """Adds a message to the textview. :param msg: The message to add to the textview @returns: None """ yield super(_LineScroller, self).handle_message(msg) textbuff = self.textbuffer text = "[%s] %s\n" % (msg.get_time(), msg.get_msg()) mtype = msg.get_type() # only store it if it's of one of the possible filtered if mtype in self.possible: # store it self.all_messages.append((mtype, text)) antpos = self.text_position self.text_position += len(text) if mtype in self.active_filter: iterl = textbuff.get_end_iter() colortag = self.bg_colors[mtype] textbuff.insert_with_tags_by_name(iterl, text, colortag) self.scroll_to_end() def scroll_to_end(self): if not self.freeze_scrollbar: self.scroll_to_mark(self.textbuffer.get_insert(), 0) def scroll_changed(self, vscrollbar): """Handle scrollbar's "value-changed" signal. Figure out if the scroll should be frozen. If the adjustment's value is not in the last page's range => means it was moved up => the scroll bar should be stopped. """ adj = vscrollbar.get_adjustment() self.freeze_scrollbar = \ False if adj.value >= (adj.upper - adj.page_size) else True
class generic(AuditPlugin): """ Find all kind of bugs without using a fixed database of errors. :author: Andres Riancho ([email protected]) """ ERROR_STRINGS = ['d\'kc"z\'gj\'\"**5*(((;-*`)', ''] def __init__(self): AuditPlugin.__init__(self) # Internal variables self._potential_vulns = DiskList(table_prefix='generic') # User configured variables self._diff_ratio = 0.30 def audit(self, freq, orig_response): """ Find all kind of bugs without using a fixed database of errors. :param freq: A FuzzableRequest """ # First, get the original response and create the mutants mutants = create_mutants(freq, [ '', ], orig_resp=orig_response) for m in mutants: # First I check that the current modified parameter in the mutant # doesn't have an already reported vulnerability. I don't want to # report vulnerabilities more than once. if (m.get_url(), m.get_token_name()) in self._potential_vulns: continue # Now, we request the limit (something that doesn't exist) # If http://localhost/a.php?b=1 # * Then I should request b=12938795 (random number) # # If http://localhost/a.php?b=abc # * Then I should request b=hnv98yks (random alnum) limit_response = self._get_limit_response(m) # Now I request something that could generate an error # If http://localhost/a.php?b=1 # * Then I should request b=d'kcz'gj'"**5*(((*) # # If http://localhost/a.php?b=abc # * Then I should request b=d'kcz'gj'"**5*(((*) # # I also try to trigger errors by sending empty strings # If http://localhost/a.php?b=1 ; then I should request b= # If http://localhost/a.php?b=abc ; then I should request b= for error_string in self.ERROR_STRINGS: m.set_token_value(error_string) error_response = self._uri_opener.send_mutant(m) # Now I compare responses self._analyze_responses(orig_response, limit_response, error_response, m) def _analyze_responses(self, orig_resp, limit_response, error_response, mutant): """ Analyze responses; if error_response doesn't look like orig_resp nor limit_response, then we have a vuln. :return: None """ original_to_error = relative_distance(orig_resp.get_body(), error_response.get_body()) limit_to_error = relative_distance(limit_response.get_body(), error_response.get_body()) original_to_limit = relative_distance(limit_response.get_body(), orig_resp.get_body()) ratio = self._diff_ratio + (1 - original_to_limit) if original_to_error < ratio and limit_to_error < ratio: # Maybe the limit I requested wasn't really a non-existent one # (and the error page really found the limit), # let's request a new limit (one that hopefully doesn't exist) # in order to remove some false positives limit_response2 = self._get_limit_response(mutant) id_list = [orig_resp.id, limit_response.id, error_response.id] if relative_distance( limit_response2.get_body(), limit_response.get_body()) > 1 - self._diff_ratio: # The two limits are "equal"; It's safe to suppose that we have # found the limit here and that the error string really produced # an error self._potential_vulns.append( (mutant.get_url(), mutant.get_token_name(), mutant, id_list)) def _get_limit_response(self, mutant): """ We request the limit (something that doesn't exist) - If http://localhost/a.php?b=1 then I should request b=12938795 (random number) - If http://localhost/a.php?b=abc then I should request b=hnv98yks (random alnum) :return: The limit response object """ mutant_copy = mutant.copy() is_digit = mutant.get_token_original_value().isdigit() value = rand_number(length=8) if is_digit else rand_alnum(length=8) mutant_copy.set_token_value(value) limit_response = self._uri_opener.send_mutant(mutant_copy) return limit_response def end(self): """ This method is called when the plugin wont be used anymore. """ all_findings = kb.kb.get_all_findings() for url, variable, mutant, id_list in self._potential_vulns: for info in all_findings: if info.get_token_name() == variable and info.get_url() == url: break else: desc = 'An unhandled error, which could potentially translate' \ ' to a vulnerability, was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('Unhandled error in web application', desc, severity.LOW, id_list, self.get_name(), mutant) self.kb_append_uniq(self, 'generic', v) self._potential_vulns.cleanup() def get_options(self): """ :return: A list of option objects for this plugin. """ ol = OptionList() d = 'If two strings have a diff ratio less than diff_ratio, then they'\ ' are really different.' o = opt_factory('diff_ratio', self._diff_ratio, d, 'float') ol.add(o) return ol def set_options(self, options_list): """ This method sets all the options that are configured using the user interface generated by the framework using the result of get_options(). :param options_list: A dictionary with the options for the plugin. :return: No value is returned. """ self._diff_ratio = options_list['diff_ratio'].get_value() def get_long_desc(self): """ :return: A DETAILED description of the plugin functions and features. """ return """
class html_file(OutputPlugin): """ Generate HTML report with identified vulnerabilities and log messages. :author: Andres Riancho (([email protected])) """ def __init__(self): OutputPlugin.__init__(self) # Internal variables self._initialized = False self._additional_info = DiskList(table_prefix='html_file') self._enabled_plugins = {} self.template_root = os.path.join(ROOT_PATH, 'plugins', 'output', 'html_file', 'templates') # User configured parameters self._verbose = False self._output_file_name = './report.html' self._template = os.path.join(self.template_root, 'complete.html') def debug(self, message, new_line=True): """ This method is called from the output object. The output object was called from a plugin or from the framework. This method should take an action for debug messages. """ if self._verbose: to_print = self._clean_string(message) self._append_additional_info(to_print, 'debug') def do_nothing(self, *args, **kwargs): pass information = vulnerability = do_nothing def error(self, message, new_line=True): """ This method is called from the output object. The output object was called from a plugin or from the framework. This method should take an action for error messages. """ to_print = self._clean_string(message) self._append_additional_info(to_print, 'error') def console(self, message, new_line=True): """ This method is used by the w3af console to print messages to the outside. """ to_print = self._clean_string(message) self._append_additional_info(to_print, 'console') def _append_additional_info(self, message, msg_type): """ Add a message to the debug table. :param message: The message to add to the table. It's in HTML. :param msg_type: The type of message """ now = time.localtime(time.time()) the_time = time.strftime("%c", now) self._additional_info.append((the_time, msg_type, message)) def set_options(self, option_list): """ Sets the Options given on the OptionList to self. The options are the result of a user entering some data on a window that was constructed using the XML Options that was retrieved from the plugin using get_options() This method MUST be implemented on every plugin. :return: No value is returned. """ self._output_file_name = option_list['output_file'].get_value() self._verbose = option_list['verbose'].get_value() def get_options(self): """ :return: A list of option objects for this plugin. """ ol = OptionList() d = 'The path to the HTML template used to render the report.' o = opt_factory('template', self._template, d, INPUT_FILE) ol.add(o) d = 'File name where this plugin will write to' o = opt_factory('output_file', self._output_file_name, d, OUTPUT_FILE) ol.add(o) d = 'True if debug information will be appended to the report.' o = opt_factory('verbose', self._verbose, d, 'boolean') ol.add(o) return ol def log_enabled_plugins(self, plugins_dict, options_dict): """ This method is called from the output manager object. This method should take an action for the enabled plugins and their configuration. Usually, write the info to a file or print it somewhere. :param plugins_dict: A dict with all the plugin types and the enabled plugins for that type of plugin. :param options_dict: A dict with the options for every plugin. """ self._enabled_plugins = {} # TODO: Improve so it contains the plugin configuration too for plugin_type, enabled in plugins_dict.iteritems(): self._enabled_plugins[plugin_type] = enabled def end(self): """ This method is called when the scan has finished, we perform these main tasks: * Get the target URLs * Get the enabled plugins * Get the vulnerabilities and infos from the KB * Get the debug data * Send all the data to jinja2 for rendering the template """ target_urls = [t.url_string for t in cf.cf.get('targets')] target_domain = cf.cf.get('target_domains')[0] enabled_plugins = self._enabled_plugins findings = kb.kb.get_all_findings() debug_log = ((t, l, smart_unicode(m)) for (t, l, m) in self._additional_info) known_urls = kb.kb.get_all_known_urls() context = { 'target_urls': target_urls, 'target_domain': target_domain, 'enabled_plugins': enabled_plugins, 'findings': findings, 'debug_log': debug_log, 'known_urls': known_urls } # The file was verified to exist when setting the plugin configuration template_fh = file(os.path.expanduser(self._template), 'r') output_fh = file(os.path.expanduser(self._output_file_name), 'w') self._render_html_file(template_fh, context, output_fh) def _render_html_file(self, template_fh, context, output_fh): """ Renders the HTML file using the configured template. Separated as a method to be able to easily test. :param context: A dict containing target urls, enabled plugins, etc. :return: True on successful rendering """ severity_icon = functools.partial(get_severity_icon, self.template_root) env_config = { 'undefined': StrictUndefined, 'trim_blocks': True, 'autoescape': True, 'lstrip_blocks': True } try: jinja2_env = Environment(**env_config) except TypeError: # Kali uses a different jinja2 version, which doesn't have the same # Environment kwargs, so we first try with the version we expect # to have available, and then if it doesn't work apply this # workaround for Kali # # https://github.com/andresriancho/w3af/issues/9552 env_config.pop('lstrip_blocks') jinja2_env = Environment(**env_config) jinja2_env.filters['render_markdown'] = render_markdown jinja2_env.filters['request'] = request_dump jinja2_env.filters['response'] = response_dump jinja2_env.filters['severity_icon'] = severity_icon jinja2_env.filters['severity_text'] = get_severity_text jinja2_env.globals['get_current_date'] = get_current_date jinja2_env.loader = FileSystemLoader(self.template_root) template = jinja2_env.from_string(template_fh.read()) try: rendered_output = template.render(context) except Exception, e: msg = u'Failed to render html report template. Exception: "%s"' om.out.error(msg % e) return False finally:
class cache_control(GrepPlugin): """ Grep every page for Pragma and Cache-Control headers. :author: Andres Riancho ([email protected]) """ SAFE_CONFIG = {'pragma': 'no-cache', 'cache-control': 'no-store'} def __init__(self): GrepPlugin.__init__(self) self._total_count = 0 self._vuln_count = 0 self._vulns = DiskList() self._ids = DiskList() def grep(self, request, response): if response.is_image() or response.is_swf(): return elif response.get_url().get_protocol() == 'http': return elif response.get_code() > 300\ and response.get_code() < 310: return elif response.body == '': return else: self._total_count += 1 cache_control_settings = self._get_cache_control(response) self._analyze_cache_control(cache_control_settings, response) def _get_cache_control(self, response): """ :param response: The http response we want to extract the information from. :return: A list with the headers and meta tag information used to configure the browser cache control. """ res = [] cache_control_headers = self.SAFE_CONFIG.keys() headers = response.get_headers() for _type in cache_control_headers: header_value, _ = headers.iget(_type, None) if header_value is not None: res.append(CacheSettings(_type, header_value.lower())) try: doc_parser = parser_cache.dpc.get_document_parser_for(response) except BaseFrameworkException: pass else: for meta_tag in doc_parser.get_meta_tags(): header_name = meta_tag.get('http-equiv', None) header_value = meta_tag.get('content', None) if header_name is not None and header_value is not None: header_name = header_name.lower() header_value = header_value.lower() if header_name in cache_control_headers: res.append(CacheSettings(header_name, header_value)) return res def _analyze_cache_control(self, cache_control_settings, response): """ Analyze the cache control settings set in headers and meta tags, store the information to report the vulnerabilities. """ received_headers = set() for cache_setting in cache_control_settings: expected_header = self.SAFE_CONFIG[cache_setting.type] received_header = cache_setting.value.lower() received_headers.add(cache_setting.type) if expected_header not in received_header: # The header has an incorrect value self.is_vuln(response) return if len(received_headers) != len(self.SAFE_CONFIG): # No cache control header found self.is_vuln(response) def is_vuln(self, response): self._vuln_count += 1 if response.get_url() not in self._vulns: self._vulns.append(response.get_url()) self._ids.append(response.id) def end(self): # If all URLs implement protection, don't report anything. if not self._vuln_count: return # If none of the URLs implement protection, simply report # ONE vulnerability that says that. if self._total_count == self._vuln_count: desc = 'The whole target web application has no protection (Pragma'\ ' and Cache-Control headers) against sensitive content'\ ' caching.' # If most of the URLs implement the protection but some # don't, report ONE vulnerability saying: "Most are protected, but x, y # are not. if self._total_count > self._vuln_count: desc = 'Some URLs have no protection (Pragma and Cache-Control'\ ' headers) against sensitive content caching. Among them:\n' desc += ' '.join([str(url) + '\n' for url in self._vulns]) response_ids = [_id for _id in self._ids] v = Vuln('Missing cache control for HTTPS content', desc, severity.LOW, response_ids, self.get_name()) self.kb_append_uniq(self, 'cache_control', v, 'URL') self._vulns.cleanup() self._ids.cleanup() def get_long_desc(self): return """\