def test_len(self): dl = DiskList() for i in xrange(0, 100): _ = dl.append(i) self.assertEqual(len(dl) == 100, True)
def __init__(self): GrepPlugin.__init__(self) self._total_count = 0 self._vuln_count = 0 self._vulns = DiskList() self._ids = DiskList()
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 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_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 __init__(self): AuditPlugin.__init__(self) # Internal variables self._potential_vulns = DiskList() # User configured variables self._diff_ratio = 0.30
def __init__(self): ''' Class init ''' GrepPlugin.__init__(self) self._total_count = 0 self._vulns = DiskList() self._urls = DiskList()
def __init__(self): AuditPlugin.__init__(self) # Internal variables self._expected_res_mutant = DiskDict() self._freq_list = DiskList() re_str = '<!--#exec cmd="echo -n (.*?);echo -n (.*?)" -->' self._extract_results_re = re.compile(re_str)
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_remove_table(self): disk_list = DiskList() table_name = disk_list.table_name db = get_default_temp_db_instance() self.assertTrue(db.table_exists(table_name)) disk_list.cleanup() self.assertFalse(db.table_exists(table_name))
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_islice(self): disk_list = DiskList() disk_list.extend('ABCDEFG') EXPECTED = 'CDEFG' result = '' for c in itertools.islice(disk_list, 2, None, None): result += c self.assertEqual(EXPECTED, result)
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_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_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_clear(self): dl = DiskList() dl.append('a') dl.append('b') self.assertEqual(len(dl), 2) dl.clear() self.assertEqual(len(dl), 0)
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 __init__(self): GrepPlugin.__init__(self) # Internal variables self._already_added = DiskList() # Compile all regular expressions and store information to avoid # multiple queries to the same function self._common_directories = get_common_directories() self._compiled_regexes = {} self._compile_regex()
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 __init__(self): AuditPlugin.__init__(self) self._xss_mutants = DiskList() # User configured parameters self._check_persistent_xss = True
def test_thread_safe(self): dl = DiskList() def worker(range_inst): for i in range_inst: dl.append(i) threads = [] _min = 0 for _max in xrange(0, 1100, 100): th = threading.Thread(target=worker, args=(xrange(_min, _max), )) threads.append(th) _min = _max for th in threads: th.start() for th in threads: th.join() for i in xrange(0, 1000): self.assertTrue(i in dl, i) dl_as_list = list(dl) self.assertEqual(len(dl_as_list), len(set(dl_as_list))) dl_as_list.sort() self.assertEqual(dl_as_list, range(1000))
def test_many_instances(self): all_instances = [] amount = 200 for _ in xrange(amount): disk_list = DiskList() all_instances.append(disk_list) self.assertEqual(len(all_instances), amount)
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', True) self.assertEqual(values[1] == 1, True) self.assertEqual(values[2] == [3, 2, 1], True)
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_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_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 __init__(self, w3af): gtk.DrawingArea.__init__(self) MessageConsumer.__init__(self) self.w3af = w3af self.pangolayout = self.create_pango_layout("") # store all messages to be able to redraw self.all_messages = DiskList() self._need_redraw = 0 # control variables self.alreadyStopped = False self.timeGrouping = 2 self.timeBase = int(time.time() * 1000) self.realLeftMargin = MIZQ self.gc = None self._redraw_gen = None # Go live! self.connect("expose-event", self.area_expose_cb) gobject.timeout_add(500, self.draw_handler) self.show()
def __init__(self): OutputPlugin.__init__(self) # Internal variables self._initialized = False self._style_output_file = os.path.join('plugins', 'output', 'html_file', 'style.css') # These attributes hold the file pointers self._file = None self._aditional_info = DiskList() # User configured parameters self._verbose = False self._output_file_name = '~/report.html'
class LogGraph(gtk.DrawingArea, MessageConsumer): '''Defines a log visualization widget that shows an XY plot :author: Facundo Batista <facundobatista =at= taniquetil.com.ar> ''' def __init__(self, w3af): gtk.DrawingArea.__init__(self) MessageConsumer.__init__(self) self.w3af = w3af self.pangolayout = self.create_pango_layout("") # store all messages to be able to redraw self.all_messages = DiskList() self._need_redraw = 0 # control variables self.alreadyStopped = False self.timeGrouping = 2 self.timeBase = int(time.time() * 1000) self.realLeftMargin = MIZQ self.gc = None self._redraw_gen = None # Go live! self.connect("expose-event", self.area_expose_cb) gobject.timeout_add(500, self.draw_handler) self.show() def draw_handler(self): ''' Draws the graph. ''' # gtk.MAPPED: the widget can be displayed on the screen. # flags: http://pygtk.org/docs/pygtk/class-gtkobject.html#method-gtkobject--flags if self.flags() & gtk.MAPPED: if self._redraw_gen is None: self._redraw_gen = self._redraw_all() reset = self._redraw_gen.next() if reset: self._redraw_gen = None return True def handle_message(self, msg): '''Adds a message to the all_messages DiskList which is then used as a source for drawing the graph. @returns: True to keep calling it, and False when all it's done. ''' yield super(LogGraph, self).handle_message(msg) mmseg = int(msg.get_real_time() * 1000) mtype = msg.get_type() if mtype == "vulnerability": sever = msg.get_severity() else: sever = None self.all_messages.append((mmseg, mtype, sever)) def _redraw_all(self): ''' Redraws all the graph. ''' '''Redraws all the graph.''' if self.gc is None: # sorry, not exposed yet... yield True # do we have enough data to start? if len(self.all_messages) < 2: yield True self.window.clear() (w, h) = self.window.get_size() # some size helpers pan = self.all_messages[-1][0] - self.all_messages[0][0] tspan = pan / self.timeGrouping usableWidth = w - MDER - self.realLeftMargin if tspan > usableWidth: self.timeGrouping *= int(tspan / usableWidth) + 1 tspan = pan / self.timeGrouping elif tspan < usableWidth // 2 and self.timeGrouping>1: self.timeGrouping //= 2 tspan = pan / self.timeGrouping # real left margin txts = ["", "Vulns", "Info", "", "Debug"] maxw = 0 for txt in txts: self.pangolayout.set_text(txt) (tw, th) = self.pangolayout.get_pixel_size() if tw > maxw: maxw = tw # 5 for the tick, 3 separating lm = self.realLeftMargin = int(maxw) + MIZQ + 8 # the axis self.gc.set_rgb_fg_color(colors.whitesmoke) self.window.draw_rectangle(self.gc, True, lm, MSUP, w-MDER-lm, h-MINF-MSUP) self.gc.set_rgb_fg_color(colors.black) self.window.draw_line(self.gc, lm, MSUP, lm, h-MINF+10) self.window.draw_line(self.gc, lm, h-MINF, w-MDER, h-MINF) # small horizontal ticks for x,timepoint in self._calculateXTicks(w-lm-MDER): posx = x + lm self.window.draw_line(self.gc, posx, h-MINF+5, posx, h-MINF) self.pangolayout.set_text(timepoint) (tw, th) = self.pangolayout.get_pixel_size() self.window.draw_layout(self.gc, posx-tw//2, h-MINF+10, self.pangolayout) self.pangolayout.set_text("[s]") (tw, th) = self.pangolayout.get_pixel_size() self.window.draw_layout(self.gc, w-MDER+5, h-MINF-th // 2, self.pangolayout) # small vertical ticks and texts sep = (h-MSUP-MINF) / 4 self.posHorizItems = {} self.maxItemHeight = {} posyant = MSUP for i,txt in enumerate(txts): if not txt: continue posy = int(MSUP + i*sep) self.posHorizItems[txt] = posy self.maxItemHeight[txt] = posy - posyant - 1 posyant = posy self.window.draw_line(self.gc, lm-5, posy, lm, posy) self.pangolayout.set_text(txt) (tw,th) = self.pangolayout.get_pixel_size() self.window.draw_layout(self.gc, lm-tw-8, posy-th//2, self.pangolayout) # draw the info countingPixel = 0 pixelQuant = 0 mesind = 0 while True: for (mmseg, mtype, sever) in itertools.islice(self.all_messages, mesind, None, None): mesind += 1 pixel = (mmseg - self.timeBase) // self.timeGrouping posx = self.realLeftMargin + pixel # if out of bound, restart draw if posx > (w-MDER): yield True if mtype == "debug": if pixel == countingPixel: pixelQuant += 1 else: countingPixel = pixel self._drawItem_debug(posx, pixelQuant) pixelQuant = 1 elif mtype == "information": self._drawItem_info(posx) elif mtype == "vulnerability": self._drawItem_vuln(posx, sever) yield False def _drawItem_debug(self, posx, quant): posy = self.posHorizItems["Debug"] - 1 quant = min(quant, self.maxItemHeight["Debug"]) self.gc.set_rgb_fg_color(colors.grey) self.window.draw_line(self.gc, posx, posy, posx, posy - quant) self.gc.set_rgb_fg_color(colors.black) def _drawItem_info(self, posx): posy = self.posHorizItems["Info"] self.gc.set_rgb_fg_color(colors.blue) self.window.draw_rectangle(self.gc, True, posx - 1, posy - 1, 2, 2) self.gc.set_rgb_fg_color(colors.black) def _drawItem_vuln(self, posx, sever): posy = self.posHorizItems["Vulns"] self.gc.set_rgb_fg_color(colors.red) if sever == severity.LOW: sever = 4 elif sever == severity.MEDIUM: sever = 10 else: sever = 20 self.window.draw_rectangle( self.gc, True, posx - 1, posy - sever, 2, sever) self.gc.set_rgb_fg_color(colors.black) def area_expose_cb(self, area, event): style = self.get_style() self.gc = style.fg_gc[gtk.STATE_NORMAL] self._redraw_gen = self._redraw_all() return True def _calculateXTicks(self, width): '''Returns the ticks X position and time.''' paso = width / 10 for i in range(10): punto = int(paso * i) label = "%.2f" % (punto * self.timeGrouping / 1000) yield punto, label
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() # 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_var()) 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_mod_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) #om.out.debug('original_to_error: ' + str(original_to_error) ) #om.out.debug('limit_to_error: ' + str(limit_to_error) ) #om.out.debug('original_to_limit: ' + str(original_to_limit) ) #om.out.debug('ratio: ' + str(ratio) ) if original_to_error < ratio and limit_to_error < ratio: # Maybe the limit I requested wasn't really a non-existant 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_var(), mutant, id_list)) def _get_limit_response(self, m): ''' 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 ''' # Copy the dc, needed to make a good vuln report dc = copy.deepcopy(m.get_dc()) if m.get_original_value().isdigit(): m.set_mod_value(rand_number(length=8)) else: m.set_mod_value(rand_alnum(length=8)) limit_response = self._uri_opener.send_mutant(m) # restore the dc m.set_dc(dc) return limit_response def end(self): ''' This method is called when the plugin wont be used anymore. ''' all_vulns_and_infos = kb.kb.get_all_vulns() all_vulns_and_infos.extend(kb.kb.get_all_infos()) for url, variable, mutant, id_list in self._potential_vulns: for info in all_vulns_and_infos: if info.get_var() == variable and info.get_url() == url: break else: desc = 'An unidentified vulnerability was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('Unidentified vulnerability', desc, severity.MEDIUM, 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 OptionList: 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 xss(AuditPlugin): ''' Identify cross site scripting vulnerabilities. :author: Andres Riancho ( [email protected] ) :author: Taras ( [email protected] ) ''' PAYLOADS = [ 'RANDOMIZE</->', 'RANDOMIZE/*', 'RANDOMIZE"RANDOMIZE', "RANDOMIZE'RANDOMIZE", "RANDOMIZE`", "RANDOMIZE =" ] def __init__(self): AuditPlugin.__init__(self) self._xss_mutants = DiskList() # User configured parameters self._check_persistent_xss = True def audit(self, freq, orig_response): ''' Tests an URL for XSS vulnerabilities. :param freq: A FuzzableRequest ''' fake_mutants = create_mutants(freq, ['',]) # Run this in the worker pool in order to get different # parameters tested at the same time. self.worker_pool.map(self._check_xss_in_parameter, fake_mutants) def _check_xss_in_parameter(self, mutant): ''' Tries to identify (persistent) XSS in one parameter. ''' if not self._identify_trivial_xss(mutant): self._search_xss(mutant) def _report_vuln(self, mutant, response, mod_value): ''' Create a Vuln object and store it in the KB. :return: None ''' csp_protects = site_protected_against_xss_by_csp(response) vuln_severity = severity.LOW if csp_protects else severity.MEDIUM desc = 'A Cross Site Scripting vulnerability was found at: %s' desc = desc % mutant.found_at() if csp_protects: desc += 'The risk associated with this vulnerability was lowered'\ ' because the site correctly implements CSP. The'\ ' vulnerability is still a risk for the application since'\ ' only the latest versions of some browsers implement CSP'\ ' checking.' v = Vuln.from_mutant('Cross site scripting vulnerability', desc, vuln_severity, response.id, self.get_name(), mutant) v.add_to_highlight(mod_value) self.kb_append_uniq(self, 'xss', v) def _identify_trivial_xss(self, mutant): ''' Identify trivial cases of XSS where all chars are echoed back and no filter and/or encoding is in place. :return: True in the case where a trivial XSS was identified. ''' payload = replace_randomize(''.join(self.PAYLOADS)) trivial_mutant = mutant.copy() trivial_mutant.set_mod_value(payload) response = self._uri_opener.send_mutant(trivial_mutant) # Add data for the persistent xss checking if self._check_persistent_xss: self._xss_mutants.append((trivial_mutant, response.id)) if payload in response.get_body(): self._report_vuln(mutant, response, payload) return True return False def _search_xss(self, mutant): ''' Analyze the mutant for reflected XSS. @parameter mutant: A mutant that was used to test if the parameter was echoed back or not ''' xss_strings = [replace_randomize(i) for i in self.PAYLOADS] mutant_list = create_mutants( mutant.get_fuzzable_req(), xss_strings, fuzzable_param_list=[mutant.get_var()] ) self._send_mutants_in_threads(self._uri_opener.send_mutant, mutant_list, self._analyze_echo_result) def _analyze_echo_result(self, mutant, response): ''' Do we have a reflected XSS? :return: None, record all the results in the kb. ''' # Add data for the persistent xss checking if self._check_persistent_xss: self._xss_mutants.append((mutant, response.id)) with self._plugin_lock: if self._has_bug(mutant): return mod_value = mutant.get_mod_value() for contexts in get_context(response.get_body(), mod_value): for context in contexts: if context.is_executable() or context.can_break(mod_value): self._report_vuln(mutant, response, mod_value) return def end(self): ''' This method is called when the plugin wont be used anymore. ''' if self._check_persistent_xss: self._identify_persistent_xss() self._xss_mutants.cleanup() def _identify_persistent_xss(self): ''' This method is called to check for persistent xss. Many times a xss isn't on the page we get after the GET/POST of the xss string. This method searches for the xss string on all the pages that are known to the framework. :return: None, Vuln (if any) are saved to the kb. ''' # Get all known fuzzable requests from the core fuzzable_requests = kb.kb.get_all_known_fuzzable_requests() self._send_mutants_in_threads(self._uri_opener.send_mutant, fuzzable_requests, self._analyze_persistent_result, grep=False, cache=False) def _analyze_persistent_result(self, fuzzable_request, response): ''' After performing an HTTP request to "fuzzable_request" and getting "response" analyze if the response contains any of the information sent by any of the mutants. :return: None, Vuln (if any) are saved to the kb. ''' response_body = response.get_body() for mutant, mutant_response_id in self._xss_mutants: mod_value = mutant.get_mod_value() for contexts in get_context(response_body, mod_value): for context in contexts: if context.is_executable() or context.can_break(mod_value): self._report_persistent_vuln(mutant, response, mutant_response_id, mod_value, fuzzable_request) break def _report_persistent_vuln(self, mutant, response, mutant_response_id, mod_value, fuzzable_request): ''' Report a persistent XSS vulnerability to the core. :return: None, a vulnerability is saved in the KB. ''' response_ids = [response.id, mutant_response_id] name = 'Persistent Cross-Site Scripting vulnerability' desc = 'A persistent Cross Site Scripting vulnerability'\ ' was found by sending "%s" to the "%s" parameter'\ ' at %s, which is echoed when browsing to %s.' desc = desc % (mod_value, mutant.get_var(), mutant.get_url(), response.get_url()) csp_protects = site_protected_against_xss_by_csp(response) vuln_severity = severity.MEDIUM if csp_protects else severity.HIGH if csp_protects: desc += 'The risk associated with this vulnerability was lowered'\ ' because the site correctly implements CSP. The'\ ' vulnerability is still a risk for the application since'\ ' only the latest versions of some browsers implement CSP'\ ' checking.' v = Vuln.from_mutant(name, desc, vuln_severity, response_ids, self.get_name(), mutant) v['persistent'] = True v['write_payload'] = mutant v['read_payload'] = fuzzable_request v.add_to_highlight(mutant.get_mod_value()) om.out.vulnerability(v.get_desc()) self.kb_append_uniq(self, 'xss', v) def get_options(self): ''' :return: A list of option objects for this plugin. ''' ol = OptionList() d1 = 'Identify persistent cross site scripting vulnerabilities' h1 = 'If set to True, w3af will navigate all pages of the target one'\ ' more time, searching for persistent cross site scripting'\ ' vulnerabilities.' o1 = opt_factory('persistent_xss', self._check_persistent_xss, d1, 'boolean', help=h1) ol.add(o1) 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(). @parameter options_list: A dictionary with the options for the plugin. :return: No value is returned. ''' self._check_persistent_xss = options_list['persistent_xss'].get_value() def get_long_desc(self): ''' :return: A DETAILED description of the plugin functions and features. ''' return '''
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() # 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_var()) 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_mod_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) #om.out.debug('original_to_error: ' + str(original_to_error) ) #om.out.debug('limit_to_error: ' + str(limit_to_error) ) #om.out.debug('original_to_limit: ' + str(original_to_limit) ) #om.out.debug('ratio: ' + str(ratio) ) if original_to_error < ratio and limit_to_error < ratio: # Maybe the limit I requested wasn't really a non-existant 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_var(), mutant, id_list)) def _get_limit_response(self, m): ''' 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 ''' # Copy the dc, needed to make a good vuln report dc = copy.deepcopy(m.get_dc()) if m.get_original_value().isdigit(): m.set_mod_value(rand_number(length=8)) else: m.set_mod_value(rand_alnum(length=8)) limit_response = self._uri_opener.send_mutant(m) # restore the dc m.set_dc(dc) return limit_response def end(self): ''' This method is called when the plugin wont be used anymore. ''' all_vulns_and_infos = kb.kb.get_all_vulns() all_vulns_and_infos.extend(kb.kb.get_all_infos()) for url, variable, mutant, id_list in self._potential_vulns: for info in all_vulns_and_infos: if info.get_var() == variable and info.get_url() == url: break else: desc = 'An unidentified vulnerability was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('Unidentified vulnerability', desc, severity.MEDIUM, 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 OptionList: 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 path_disclosure(GrepPlugin): ''' Grep every page for traces of path disclosure vulnerabilities. :author: Andres Riancho ([email protected]) ''' def __init__(self): GrepPlugin.__init__(self) # Internal variables self._already_added = DiskList() # Compile all regular expressions and store information to avoid # multiple queries to the same function self._common_directories = get_common_directories() self._compiled_regexes = {} self._compile_regex() def _compile_regex(self): ''' :return: None, the result is saved in self._path_disc_regex_list ''' # # I tried to enhance the performance of this plugin by putting # all the regular expressions in one (1|2|3|4...|N) # That gave no visible result. # for path_disclosure_string in self._common_directories: regex_string = '(%s.*?)[^A-Za-z0-9\._\-\\/\+~]' regex_string = regex_string % path_disclosure_string regex = re.compile(regex_string, re.IGNORECASE) self._compiled_regexes[path_disclosure_string] = regex def _potential_disclosures(self, html_string): ''' Taking into account that regular expressions are slow, we first apply this function to check if the HTML string has potential path disclosures. With this performance enhancement we reduce the plugin run time to 1/8 of the time in cases where no potential disclosures are found, and around 1/3 when potential disclosures *are* found. :return: A list of the potential path disclosures ''' potential_disclosures = [] for path_disclosure_string in self._common_directories: if path_disclosure_string in html_string: potential_disclosures.append(path_disclosure_string) return potential_disclosures def grep(self, request, response): ''' Identify the path disclosure vulnerabilities. :param request: The HTTP request object. :param response: The HTTP response object :return: None, the result is saved in the kb. ''' if not response.is_text_or_html(): return if self.find_path_disclosure(request, response): self._update_KB_path_list() def find_path_disclosure(self, request, response): ''' Actually find the path disclosure vulnerabilities ''' html_string = response.get_body() for potential_disclosure in self._potential_disclosures(html_string): path_disc_regex = self._compiled_regexes[potential_disclosure] match_list = path_disc_regex.findall(html_string) # Decode the URL, this will transform things like # http://host.tld/?id=%2Fhome # into, # http://host.tld/?id=/home realurl = response.get_url().url_decode() # Sort by the longest match, this is needed for filtering out # some false positives please read the note below. match_list.sort(self._longest) for match in match_list: # This if is to avoid false positives if not request.sent(match) and not \ self._attr_value(match, html_string): # Check for dups if (realurl, match) in self._already_added: continue # There is a rare bug also, which is triggered in cases like this one: # # >>> import re # >>> re.findall('/var/www/.*','/var/www/foobar/htdocs/article.php') # ['/var/www/foobar/htdocs/article.php'] # >>> re.findall('/htdocs/.*','/var/www/foobar/htdocs/article.php') # ['/htdocs/article.php'] # >>> # # What I need to do here, is to keep the longest match. for realurl_added, match_added in self._already_added: if match_added.endswith(match): break else: # Note to self: I get here when "break" is NOT executed. # It's a new one, report! self._already_added.append((realurl, match)) desc = 'The URL: "%s" has a path disclosure'\ ' vulnerability which discloses "%s".' desc = desc % (response.get_url(), match) v = Vuln('Path disclosure vulnerability', desc, severity.LOW, response.id, self.get_name()) v.set_url(realurl) v['path'] = match v.add_to_highlight(match) self.kb_append(self, 'path_disclosure', v) return True return False def _longest(self, a, b): ''' :param a: A string. :param a: Another string. :return: The longest string. ''' return cmp(len(a), len(b)) def _attr_value(self, path_disclosure_string, response_body): ''' This method was created to remove some false positives. :return: True if path_disclosure_string is the value of an attribute inside a tag. Examples: path_disclosure_string = '/home/image.png' response_body = '....<img src="/home/image.png">...' return: True path_disclosure_string = '/home/image.png' response_body = '...<b>Error while processing /home/image.png</b>...' return: False ''' regex = '<.+?(["|\']%s["|\']).*?>' % re.escape(path_disclosure_string) regex_res = re.findall(regex, response_body) in_attr = path_disclosure_string in regex_res return in_attr def _update_KB_path_list(self): ''' If a path disclosure was found, I can create a list of full paths to all URLs ever visited. This method updates that list. ''' path_disc_vulns = kb.kb.get('path_disclosure', 'path_disclosure') url_list = kb.kb.get_all_known_urls() # Now I find the longest match between one of the URLs that w3af has # discovered, and one of the path disclosure strings that this plugin # has found. I use the longest match because with small match_list I # have more probability of making a mistake. longest_match = '' longest_path_disc_vuln = None for path_disc_vuln in path_disc_vulns: for url in url_list: path_and_file = url.get_path() if path_disc_vuln['path'].endswith(path_and_file): if len(longest_match) < len(path_and_file): longest_match = path_and_file longest_path_disc_vuln = path_disc_vuln # Now I recalculate the place where all the resources are in disk, all # this is done taking the longest_match as a reference, so... if we # don't have a longest_match, then nothing is actually done if not longest_match: return # Get the webroot webroot = longest_path_disc_vuln['path'].replace(longest_match, '') # # This if fixes a strange case reported by Olle # if webroot[0] == '/': # IndexError: string index out of range # That seems to be because the webroot == '' # if not webroot: return # Check what path separator we should use (linux / windows) path_sep = '/' if webroot.startswith('/') else '\\' # Create the remote locations remote_locations = [] for url in url_list: remote_path = url.get_path().replace('/', path_sep) remote_locations.append(webroot + remote_path) remote_locations = list(set(remote_locations)) kb.kb.raw_write(self, 'list_files', remote_locations) kb.kb.raw_write(self, 'webroot', webroot) def end(self): self._already_added.cleanup() def get_long_desc(self): ''' :return: A DETAILED description of the plugin functions and features. ''' return '''
def test_not(self): dl = DiskList() self.assertFalse(dl)