def __detect_wordpress_installation(self, url, wordpress_urls): """ Try to detect a wordpress instalation in the current path. :param url: URL where try to find the WordPress installation. :type url: str :param wordpress_urls: string with wordlist name with WordPress URLs. :type wordpress_urls: str :return: True if wordpress installation found. False otherwise. :rtype: bool """ Logger.log_more_verbose( "Detecting Wordpress instalation in URI: '%s'." % url) total_urls = 0 urls_found = 0 error_page = get_error_page(url).raw_data for u in WordListLoader.get_wordlist(wordpress_urls): total_urls += 1 tmp_url = urljoin(url, u) r = HTTP.get_url(tmp_url, use_cache=False) if r.status == "200": # Try to detect non-default error pages ratio = get_diff_ratio(r.raw_response, error_page) if ratio < 0.35: urls_found += 1 discard_data(r) # If Oks > 85% continue if (urls_found / float(total_urls)) < 0.85: # If all fails, make another last test url_wp_admin = urljoin(url, "wp-admin/") try: p = HTTP.get_url(url_wp_admin, use_cache=False, allow_redirects=False) if p: discard_data(p) except Exception, e: return False if p.status == "302" and "wp-login.php?redirect_to=" in p.headers.get( "Location", ""): return True else: return False
def __detect_wordpress_installation(self, url, wordpress_urls): """ Try to detect a wordpress instalation in the current path. :param url: URL where try to find the WordPress installation. :type url: str :param wordpress_urls: string with wordlist name with WordPress URLs. :type wordpress_urls: str :return: True if wordpress installation found. False otherwise. :rtype: bool """ Logger.log_more_verbose("Detecting Wordpress instalation in URI: '%s'." % url) total_urls = 0 urls_found = 0 error_page = get_error_page(url).raw_data for u in WordListLoader.get_wordlist(wordpress_urls): total_urls += 1 tmp_url = urljoin(url, u) r = HTTP.get_url(tmp_url, use_cache=False) if r.status == "200": # Try to detect non-default error pages ratio = get_diff_ratio(r.raw_response, error_page) if ratio < 0.35: urls_found += 1 discard_data(r) # If Oks > 85% continue if (urls_found / float(total_urls)) < 0.85: # If all fails, make another last test url_wp_admin = urljoin(url, "wp-admin/") try: p = HTTP.get_url(url_wp_admin, use_cache=False, allow_redirects=False) if p: discard_data(p) except Exception, e: return False if p.status == "302" and "wp-login.php?redirect_to=" in p.headers.get("Location", ""): return True else: return False
def find_htm_file(url): new_file = [] for file_name in ['DeveloperMenu.htm']: url_check = url[1:] if url.startswith("/") else url tmp_u = urljoin(url_check, file_name) p = HTTP.get_url(tmp_u, use_cache=False, method="GET") if p.status == "200": file_save = download(tmp_u) new_file = re.findall(r'href=[\'"]?([^\'" >]+)', file_save.raw_data) return new_file
def recv_info(self, info): m_return = [] m_url = info.url m_hostname = info.hostname m_url_robots_txt = urljoin(m_url, 'robots.txt') p = None try: msg = "Looking for robots.txt in: %s" % m_hostname Logger.log_more_verbose(msg) p = download(m_url_robots_txt, self.check_download) except NetworkOutOfScope: Logger.log_more_verbose("URL out of scope: %s" % (m_url_robots_txt)) return except Exception, e: Logger.log_more_verbose("Error while processing %r: %s" % (m_url_robots_txt, str(e))) return
def find_xml_files(url): new_file = [] for file_name in ['execute.xml', 'DeveloperMenu.xml']: url_check = url[1:] if url.startswith("/") else url tmp_u = urljoin(url_check, file_name) p = HTTP.get_url(tmp_u, use_cache=False, method="GET") if p.status == "200": file_save = download(tmp_u) tree = ET.fromstring(file_save.raw_data) try: for links in tree.findall('Object'): Logger.log(links.find('ObjLink').text) new_file.append(links.find('ObjLink').text) except Exception: ##raise # XXX DEBUG pass return new_file
def run(self, info): m_return = [] m_url = info.url m_hostname = info.hostname m_url_robots_txt = urljoin(m_url, 'robots.txt') p = None try: msg = "Looking for robots.txt in: %s" % m_hostname Logger.log_more_verbose(msg) p = download(m_url_robots_txt, self.check_download) except NetworkOutOfScope: Logger.log_more_verbose("URL out of scope: %s" % (m_url_robots_txt)) return except Exception, e: Logger.log_more_verbose("Error while processing %r: %s" % (m_url_robots_txt, str(e))) return
def parse_nikto_results(info, output_filename): """ Convert the output of a Nikto scan to the GoLismero data model. :param info: Data object to link all results to (optional). :type info: BaseURL :param output_filename: Path to the output filename. The format should always be CSV. :type output_filename: :returns: Results from the Nikto scan, and the vulnerability count. :rtype: list(Data), int """ # Parse the scan results. # On error log the exception and continue. results = [] vuln_count = 0 hosts_seen = set() urls_seen = {} try: with open(output_filename, "rU") as f: csv_reader = reader(f) for row in csv_reader: try: # Each row (except for the first) has always # the same 7 columns, but some may be empty. if len(row) < 7: continue host, ip, port, vuln_tag, method, path, text = row[:7] # Report domain names and IP addresses. if ( (info is None or host != info.hostname) and host not in hosts_seen ): hosts_seen.add(host) if host in Config.audit_scope: results.append( Domain(host) ) if ip not in hosts_seen: hosts_seen.add(ip) if ip in Config.audit_scope: results.append( IP(ip) ) # Skip rows not informing of vulnerabilities. if not vuln_tag: continue # Calculate the vulnerable URL. if info is not None: target = urljoin(info.url, path) else: if port == 443: target = urljoin("https://%s/" % host, path) else: target = urljoin("http://%s/" % host, path) # Skip if out of scope. if target not in Config.audit_scope: continue # Report the URLs. if (target, method) not in urls_seen: url = URL(target, method) urls_seen[ (target, method) ] = url results.append(url) else: url = urls_seen[ (target, method) ] # Get the reference URLs. refs = extract_from_text(text) refs.difference_update(urls_seen.itervalues()) # Report the vulnerabilities. if vuln_tag == "OSVDB-0": kwargs = {"level": "informational"} else: kwargs = extract_vuln_ids( "%s: %s" % (vuln_tag, text)) kwargs["description"] = text if text else None kwargs["references"] = refs if "osvdb" in kwargs and "OSVDB-0" in kwargs["osvdb"]: tmp = list(kwargs["osvdb"]) tmp.remove("OSVDB-0") if tmp: kwargs["osvdb"] = tuple(tmp) else: del kwargs["osvdb"] if vuln_tag == "OSVDB-0": vuln = UncategorizedVulnerability(url, **kwargs) else: vuln = VulnerableWebApp(url, **kwargs) results.append(vuln) vuln_count += 1 # On error, log the exception and continue. except Exception, e: Logger.log_error_verbose(str(e)) Logger.log_error_more_verbose(format_exc()) # On error, log the exception. except Exception, e: Logger.log_error_verbose(str(e)) Logger.log_error_more_verbose(format_exc())
def run(self, info): # Get the base URL to the SpiderFoot API. base_url = Config.plugin_args["url"] # Find out if we should delete the scan when we're done. must_delete = Config.audit_config.boolean( Config.plugin_args.get("delete", "y")) # We need to catch SystemExit in order to stop and delete the scan. scan_id = None try: # Create a new scan. resp = post(urljoin(base_url, "startscan"), { "scanname": Config.audit_name, "scantarget": info.hostname, "modulelist": self.get_list("modulelist", "module_"), "typelist": self.get_list("typelist", "type_"), }) if resp.status_code != 200: r = resp.content p = r.find("<div class=\"alert alert-error\">") if p >= 0: p = r.find("<h4>", p) + 4 q = r.find("</h4>", p) m = r[p:q].strip() raise RuntimeError("Could not start scan, reason: " + m) # Wait until the scan is finished. try: interval = float(Config.plugin_args.get("interval", "5.0")) except Exception: interval = 5.0 url_scanlist = urljoin(base_url, "scanlist") last_msg = "" is_created = False scan_id = None while True: resp = get(url_scanlist) if resp.status_code != 200: status = "ERROR-FAILED" break scanlist = resp.json() found = False for scan in scanlist: scan_id, scan_name = scan[:2] status, count = scan[-2:] if scan_name == Config.audit_name: found = True break if found: is_created = True is_finished = status in ("FINISHED", "ABORTED", "ERROR-FAILED") msg = "Status: %s (%s elements%s)" % ( status, count, " so far" if not is_finished else "" ) if msg != last_msg: last_msg = msg Logger.log_verbose(msg) if is_finished: break else: if not is_created: Logger.log_verbose("Status: CREATING") else: Logger.log_verbose("Status: DELETED") Logger.log_error( "Scan deleted from the SpiderFoot UI, aborting!") return sleep(interval) # Tell the user if the scan didn't finish correctly. results = None try: has_partial = is_created and int(count) > 0 except Exception: has_partial = is_created try: # Get the scan results. if has_partial: Logger.log_error("Scan didn't finish correctly!") Logger.log("Attempting to load partial results...") parser = SpiderFootParser() url = parse_url("scaneventresultexport", base_url) url.query_params = {"id": scan_id, "type": "ALL"} resp = get(url.url) if resp.status_code != 200: Logger.log_error( "Could not get scan results, error code: %s" % resp.status_code) else: results = parser.parse(StringIO(resp.content)) if results: if len(results) == 1: Logger.log("Loaded 1 result.") else: Logger.log("Loaded %d results." % len(results)) else: Logger.log("No results loaded.") else: Logger.log_error("Scan didn't finish correctly, aborting!") finally: # Delete the scan. try: if is_created and must_delete: url = parse_url("scandelete", base_url) url.query_params = {"id": scan_id, "confirm": "1"} get(url.url) ##if resp.status_code != 200: ## Logger.log_error_more_verbose( ## "Could not delete scan, error code: %s" ## % resp.status_code) except Exception, e: tb = format_exc() Logger.log_error_verbose(str(e)) Logger.log_error_more_verbose(tb) # Return the results. return results
def recv_info(self, info): m_url = info.url Logger.log_more_verbose("Start to process URL: %r" % m_url) # # Get the remote web server fingerprint # m_webserver_finger = info.get_associated_informations_by_category(WebServerFingerprint.information_type) m_wordlist = set() # There is fingerprinting information? if m_webserver_finger: m_webserver_finger = m_webserver_finger.pop() m_server_canonical_name = m_webserver_finger.name_canonical m_servers_related = m_webserver_finger.related # Set with related web servers # # Load wordlists # m_wordlist_update = m_wordlist.update # Common wordlist try: w = Config.plugin_extra_config["common"] m_wordlist_update([l_w for l_w in w.itervalues()]) except KeyError: Logger.log_error("Can't load wordlists") # Wordlist of server name try: w = Config.plugin_extra_config["%s_predictables" % m_server_canonical_name] m_wordlist_update([l_w for l_w in w.itervalues()]) except KeyError: Logger.log_error("Can't load wordlists") # Wordlist of related with the server found try: for l_servers_related in m_servers_related: w = Config.plugin_extra_config["%s_predictables" % m_server_canonical_name] m_wordlist_update([l_w for l_w in w.itervalues()]) except KeyError: Logger.log_error("Can't load wordlists") else: # Common wordlists try: w = Config.plugin_extra_config["common"] m_wordlist.update([l_w for l_w in w.itervalues()]) except KeyError: Logger.log_error("Can't load wordlists") # Load content of wordlists m_urls = set() m_urls_update = m_urls.update # Fixed Url m_url_fixed = m_url if m_url.endswith("/") else "%s/" % m_url for l_w in m_wordlist: # Use a copy of wordlist to avoid modify the original source l_loaded_wordlist = WordListLoader.get_advanced_wordlist_as_list(l_w) m_urls_update((urljoin(m_url_fixed, (l_wo[1:] if l_wo.startswith("/") else l_wo)) for l_wo in l_loaded_wordlist)) # Generates the error page m_error_response = get_error_page(m_url) # Create the matching analyzer try: m_store_info = MatchingAnalyzer(m_error_response, min_ratio=0.65) except ValueError: # Thereis not information return # Create the partial funs _f = partial(process_url, severity_vectors['predictables'], get_http_method(m_url), m_store_info, self.update_status, len(m_urls)) # Process the URLs for i, l_url in enumerate(m_urls): _f((i, l_url)) # Generate and return the results. return generate_results(m_store_info.unique_texts)
def http_analyzers(main_url, update_status_func, number_of_entries=4): """ Analyze HTTP headers for detect the web server. Return a list with most possible web servers. :param main_url: Base url to test. :type main_url: str :param update_status_func: function used to update the status of the process :type update_status_func: function :param number_of_entries: number of resutls tu return for most probable web servers detected. :type number_of_entries: int :return: Web server family, Web server version, Web server complete description, related web servers (as a dict('SERVER_RELATED' : set(RELATED_NAMES))), others web server with their probabilities as a dict(CONCRETE_WEB_SERVER, PROBABILITY) """ # Load wordlist directly related with a HTTP fields. # { HTTP_HEADER_FIELD : [wordlists] } m_wordlists_HTTP_fields = { "Accept-Ranges" : "accept-range", "Server" : "banner", "Cache-Control" : "cache-control", "Connection" : "connection", "Content-Type" : "content-type", "WWW-Authenticate" : "htaccess-realm", "Pragma" : "pragma", "X-Powered-By" : "x-powered-by" } m_actions = { 'GET' : { 'wordlist' : 'Wordlist_get' , 'weight' : 1 , 'protocol' : 'HTTP/1.1', 'method' : 'GET' , 'payload': '/' }, 'LONG_GET' : { 'wordlist' : 'Wordlist_get_long' , 'weight' : 1 , 'protocol' : 'HTTP/1.1', 'method' : 'GET' , 'payload': '/%s' % ('a' * 200) }, 'NOT_FOUND' : { 'wordlist' : 'Wordlist_get_notfound' , 'weight' : 2 , 'protocol' : 'HTTP/1.1', 'method' : 'GET' , 'payload': '/404_NOFOUND__X02KAS' }, 'HEAD' : { 'wordlist' : 'Wordlist_head' , 'weight' : 3 , 'protocol' : 'HTTP/1.1', 'method' : 'HEAD' , 'payload': '/' }, 'OPTIONS' : { 'wordlist' : 'Wordlist_options' , 'weight' : 2 , 'protocol' : 'HTTP/1.1', 'method' : 'OPTIONS' , 'payload': '/' }, 'DELETE' : { 'wordlist' : 'Wordlist_delete' , 'weight' : 5 , 'protocol' : 'HTTP/1.1', 'method' : 'DELETE' , 'payload': '/' }, 'TEST' : { 'wordlist' : 'Wordlist_attack' , 'weight' : 5 , 'protocol' : 'HTTP/1.1', 'method' : 'TEST' , 'payload': '/' }, 'INVALID' : { 'wordlist' : 'Wordlist_wrong_method' , 'weight' : 5 , 'protocol' : 'HTTP/9.8', 'method' : 'GET' , 'payload': '/' }, 'ATTACK' : { 'wordlist' : 'Wordlist_wrong_version' , 'weight' : 2 , 'protocol' : 'HTTP/1.1', 'method' : 'GET' , 'payload': "/etc/passwd?format=%%%%&xss=\x22><script>alert('xss');</script>&traversal=../../&sql='%20OR%201;"} } # Store results for others HTTP params m_d = ParsedURL(main_url) m_hostname = m_d.hostname m_port = m_d.port m_debug = False # Only for develop # Counter of banners. Used when others methods fails. m_banners_counter = Counter() # Score counter m_counters = HTTPAnalyzer(debug=m_debug) # Var used to update the status m_data_len = len(m_actions) i = 1 # element in process for l_action, v in m_actions.iteritems(): if m_debug: print "###########" l_method = v["method"] l_payload = v["payload"] l_proto = v["protocol"] l_wordlist = v["wordlist"] # Each type of probe hast different weight. # # Weights go from 0 - 5 # l_weight = v["weight"] # Make the URL l_url = urljoin(main_url, l_payload) # Make the raw request #l_raw_request = "%(method)s %(payload)s %(protocol)s\r\nHost: %(host)s:%(port)s\r\nConnection: Close\r\n\r\n" % ( l_raw_request = "%(method)s %(payload)s %(protocol)s\r\nHost: %(host)s\r\n\r\n" % ( { "method" : l_method, "payload" : l_payload, "protocol" : l_proto, "host" : m_hostname, "port" : m_port } ) if m_debug: print "REQUEST" print l_raw_request # Do the connection l_response = None try: m_raw_request = HTTP_Raw_Request(l_raw_request) discard_data(m_raw_request) l_response = HTTP.make_raw_request( host = m_hostname, port = m_port, raw_request = m_raw_request, callback = check_raw_response) if l_response: discard_data(l_response) except NetworkException,e: Logger.log_error_more_verbose("Server-Fingerprint plugin: No response for URL (%s) '%s'. Message: %s" % (l_method, l_url, str(e))) continue if not l_response: Logger.log_error_more_verbose("No response for URL '%s'." % l_url) continue if m_debug: print "RESPONSE" print l_response.raw_headers # Update the status update_status_func((float(i) * 100.0) / float(m_data_len)) Logger.log_more_verbose("Making '%s' test." % (l_wordlist)) i += 1 # Analyze for each wordlist # # Store the server banner try: m_banners_counter[l_response.headers["Server"]] += l_weight except KeyError: pass # # ===================== # HTTP directly related # ===================== # # for l_http_header_name, l_header_wordlist in m_wordlists_HTTP_fields.iteritems(): # Check if HTTP header field is in response if l_http_header_name not in l_response.headers: continue l_curr_header_value = l_response.headers[l_http_header_name] # Generate concrete wordlist name l_wordlist_path = Config.plugin_extra_config[l_wordlist][l_header_wordlist] # Load words for the wordlist l_wordlist_instance = WordListLoader.get_wordlist_as_dict(l_wordlist_path) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_curr_header_value) m_counters.inc(l_matches, l_action, l_weight, l_http_header_name, message="HTTP field: " + l_curr_header_value) # # ======================= # HTTP INdirectly related # ======================= # # # # Status code # =========== # l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["statuscode"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_response.status) m_counters.inc(l_matches, l_action, l_weight, "statuscode", message="Status code: " + l_response.status) # # Status text # =========== # l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["statustext"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_response.reason) m_counters.inc(l_matches, l_action, l_weight, "statustext", message="Status text: " + l_response.reason) # # Header space # ============ # # Count the number of spaces between HTTP field name and their value, for example: # -> Server: Apache 1 # The number of spaces are: 1 # # -> Server:Apache 1 # The number of spaces are: 0 # l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["header-space"]) # Looking for matches try: l_http_value = l_response.headers[0] # get the value of first HTTP field l_spaces_num = str(abs(len(l_http_value) - len(l_http_value.lstrip()))) l_matches = l_wordlist_instance.matches_by_value(l_spaces_num) m_counters.inc(l_matches, l_action, l_weight, "header-space", message="Header space: " + l_spaces_num) except IndexError: print "index error header space" pass # # Header capitalafterdash # ======================= # # Look for non capitalized first letter of field name, for example: # -> Content-type: .... # Instead of: # -> Content-Type: .... # l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["header-capitalafterdash"]) # Looking for matches l_valid_fields = [x for x in l_response.headers.iterkeys() if "-" in x] if l_valid_fields: l_h = l_valid_fields[0] l_value = l_h.split("-")[1] # Get the second value: Content-type => type l_dush = None if l_value[0].isupper(): # Check first letter is lower l_dush = 1 else: l_dush = 0 l_matches = l_wordlist_instance.matches_by_value(l_dush) m_counters.inc(l_matches, l_action, l_weight, "header-capitalizedafterdush", message="Capital after dash: %s" % str(l_dush)) # # Header order # ============ # l_header_order = ','.join(l_response.headers.iterkeys()) l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["header-order"]) l_matches = l_wordlist_instance.matches_by_value(l_header_order) m_counters.inc(l_matches, l_action, l_weight, "header-order", message="Header order: " + l_header_order) # # Protocol name # ============ # # For a response like: # -> HTTP/1.0 200 OK # .... # # Get the 'HTTP' value. # try: l_proto = l_response.protocol # Get the 'HTTP' text from response, if available if l_proto: l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["protocol-name"]) l_matches = l_wordlist_instance.matches_by_value(l_proto) m_counters.inc(l_matches, l_action, l_weight, "proto-name", message="Proto name: " + l_proto) except IndexError: print "index error protocol name" pass # # Protocol version # ================ # # For a response like: # -> HTTP/1.0 200 OK # .... # # Get the '1.0' value. # try: l_version = l_response.version # Get the '1.0' text from response, if available if l_version: l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["protocol-version"]) l_matches = l_wordlist_instance.matches_by_value(l_version) m_counters.inc(l_matches, l_action, l_weight, "proto-version", message="Proto version: " + l_version) except IndexError: print "index error protocol version" pass if "ETag" in l_response.headers: l_etag_header = l_response.headers["ETag"] # # ETag length # ================ # l_etag_len = len(l_etag_header) l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["etag-legth"]) l_matches = l_wordlist_instance.matches_by_value(l_etag_len) m_counters.inc(l_matches, l_action, l_weight, "etag-length", message="ETag length: " + str(l_etag_len)) # # ETag Quotes # ================ # l_etag_striped = l_etag_header.strip() if l_etag_striped.startswith("\"") or l_etag_striped.startswith("'"): l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["etag-quotes"]) l_matches = l_wordlist_instance.matches_by_value(l_etag_striped[0]) m_counters.inc(l_matches, l_action, l_weight, "etag-quotes", message="Etag quotes: " + l_etag_striped[0]) if "Vary" in l_response.headers: l_vary_header = l_response.headers["Vary"] # # Vary delimiter # ================ # # Checks if Vary header delimiter is something like this: # -> Vary: Accept-Encoding,User-Agent # Or this: # -> Vary: Accept-Encoding, User-Agent # l_var_delimiter = ", " if l_vary_header.find(", ") else "," l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["vary-delimiter"]) l_matches = l_wordlist_instance.matches_by_value(l_var_delimiter) m_counters.inc(l_matches, l_action, l_weight, "vary-delimiter", message="Vary delimiter: " + l_var_delimiter) # # Vary capitalizer # ================ # # Checks if Vary header delimiter is something like this: # -> Vary: Accept-Encoding,user-Agent # Or this: # -> Vary: accept-encoding,user-agent # l_vary_capitalizer = str(0 if l_vary_header == l_vary_header.lower() else 1) l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["vary-capitalize"]) l_matches = l_wordlist_instance.matches_by_value(l_vary_capitalizer) m_counters.inc(l_matches, l_action, l_weight, "vary-capitalize", message="Vary capitalizer: " + l_vary_capitalizer) # # Vary order # ================ # # Checks order between vary values: # -> Vary: Accept-Encoding,user-Agent # Or this: # -> Vary: User-Agent,Accept-Encoding # l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["vary-order"]) l_matches = l_wordlist_instance.matches_by_value(l_vary_header) m_counters.inc(l_matches, l_action, l_weight, "vary-order", message="Vary order: " + l_vary_header) # # ===================== # HTTP specific options # ===================== # # if l_action == "HEAD": # # HEAD Options # ============ # l_option = l_response.headers.get("Allow") if l_option: l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["options-public"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_option) m_counters.inc(l_matches, l_action, l_weight, "options-allow", message="HEAD option: " + l_option) if l_action == "OPTIONS" or l_action == "INVALID" or l_action == "DELETE": if "Allow" in l_response.headers: # # Options allow # ============= # l_option = l_response.headers.get("Allow") if l_option: l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["options-public"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_option) m_counters.inc(l_matches, l_action, l_weight, "options-allow", message="OPTIONS allow: " + l_action + " # " + l_option) # # Allow delimiter # =============== # l_option = l_response.headers.get("Allow") if l_option: l_var_delimiter = ", " if l_option.find(", ") else "," l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["options-delimited"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_var_delimiter) m_counters.inc(l_matches, l_action, l_weight, "options-delimiter", message="OPTION allow delimiter " + l_action + " # " + l_option) if "Public" in l_response.headers: # # Public response # =============== # l_option = l_response.headers.get("Public") if l_option: l_wordlist_instance = WordListLoader.get_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["options-public"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_option) m_counters.inc(l_matches, l_action, l_weight, "options-public", message="Public response: " + l_action + " # " + l_option)
def parse_nikto_results(info, output_filename): """ Convert the output of a Nikto scan to the GoLismero data model. :param info: Data object to link all results to (optional). :type info: BaseURL :param output_filename: Path to the output filename. The format should always be CSV. :type output_filename: :returns: Results from the Nikto scan, and the vulnerability count. :rtype: list(Data), int """ # Parse the scan results. # On error log the exception and continue. results = [] vuln_count = 0 hosts_seen = set() urls_seen = {} try: with open(output_filename, "rU") as f: csv_reader = reader(f) for row in csv_reader: try: # Each row (except for the first) has always # the same 7 columns, but some may be empty. if len(row) < 7: continue host, ip, port, vuln_tag, method, path, text = row[:7] # Report domain names and IP addresses. if ((info is None or host != info.hostname) and host not in hosts_seen): hosts_seen.add(host) if host in Config.audit_scope: results.append(Domain(host)) if ip not in hosts_seen: hosts_seen.add(ip) if ip in Config.audit_scope: results.append(IP(ip)) # Skip rows not informing of vulnerabilities. if not vuln_tag: continue # Calculate the vulnerable URL. if info is not None: target = urljoin(info.url, path) else: if port == 443: target = urljoin("https://%s/" % host, path) else: target = urljoin("http://%s/" % host, path) # Skip if out of scope. if target not in Config.audit_scope: continue # Report the URLs. if (target, method) not in urls_seen: url = URL(target, method) urls_seen[(target, method)] = url results.append(url) else: url = urls_seen[(target, method)] # Get the reference URLs. refs = extract_from_text(text) refs.difference_update(urls_seen.itervalues()) # Convert the information to the GoLismero model. if vuln_tag == "OSVDB-0": kwargs = {"level": "informational"} else: kwargs = extract_vuln_ids("%s: %s" % (vuln_tag, text)) kwargs["custom_id"] = ";".join( (host, ip, port, vuln_tag, method, path, text)) kwargs["description"] = text if text else None kwargs["references"] = refs if "osvdb" in kwargs and "OSVDB-0" in kwargs["osvdb"]: tmp = list(kwargs["osvdb"]) tmp.remove("OSVDB-0") if tmp: kwargs["osvdb"] = tuple(tmp) else: del kwargs["osvdb"] # Instance the Vulnerability object. if vuln_tag == "OSVDB-0": vuln = UncategorizedVulnerability(url, **kwargs) else: vuln = VulnerableWebApp(url, **kwargs) # Add the vulnerability to the results. results.append(vuln) vuln_count += 1 # On error, log the exception and continue. except Exception, e: Logger.log_error_verbose(str(e)) Logger.log_error_more_verbose(format_exc()) # On error, log the exception. except Exception, e: Logger.log_error_verbose(str(e)) Logger.log_error_more_verbose(format_exc())
def __get_wordpress_version(self, url): """ This function get the current version of wordpress and the last version available for download. :param url: URL fo target. :type url: str. :return: a tuple with (CURRENT_VERSION, LAST_AVAILABLE_VERSION) :type: tuple(str, str) """ url_version = { # Generic "wp-login.php": r"(;ver=)([0-9\.]+)([\-a-z]*)", # For WordPress 3.8 "wp-admin/css/wp-admin-rtl.css": r"(Version[\s]+)([0-9\.]+)", "wp-admin/css/wp-admin.css": r"(Version[\s]+)([0-9\.]+)" } # # Get current version # # URL to find wordpress version url_current_version = urljoin(url, "readme.html") current_version_content_1 = download(url_current_version) if isinstance(current_version_content_1, HTML): current_version_method1 = re.search( r"(<br/>[\s]*[vV]ersion[\s]*)([0-9\.]*)", current_version_content_1.raw_data) if current_version_method1 is None: current_version_method1 = None else: if len(current_version_method1.groups()) != 2: current_version_method1 = None else: current_version_method1 = current_version_method1.group(2) else: current_version_method1 = None # Try to find the version into HTML meta value # Get content of main page current_version_content_2 = download(url) # Try to find the info current_version_method2 = re.search( r"(<meta name=\"generator\" content=\"WordPress[\s]+)([0-9\.]+)", current_version_content_2.raw_data) if current_version_method2 is None: current_version_method2 = None else: if len(current_version_method2.groups()) != 2: current_version_method2 = None else: current_version_method2 = current_version_method2.group(2) # Match versions of the diffentents methods current_version = "unknown" if current_version_method1 is None and current_version_method2 is None: current_version = "unknown" elif current_version_method1 is None and current_version_method2 is not None: current_version = current_version_method2 elif current_version_method1 is not None and current_version_method2 is None: current_version = current_version_method1 elif current_version_method1 is not None and current_version_method2 is not None: if current_version_method1 != current_version_method2: current_version = current_version_method2 else: current_version = current_version_method1 else: current_version = "unknown" # If Current version not found if current_version == "unknown": for url_pre, regex in url_version.iteritems(): # URL to find wordpress version url_current_version = urljoin(url, url_pre) current_version_content = download(url_current_version) discard_data(current_version_content) # Find the version tmp_version = re.search(regex, current_version_content.raw_data) if tmp_version is not None: current_version = tmp_version.group(2) break # Found -> stop search # # Get last version # # URL to get last version of WordPress available url_last_version = "http://wordpress.org/download/" last_version_content = download(url_last_version, allow_out_of_scope=True) if isinstance(last_version_content, HTML): last_version = re.search("(WordPress )([0-9\.]*)", last_version_content.raw_data) if last_version is None: last_version = "unknown" else: if len(last_version.groups()) != 2: last_version = "unknown" else: last_version = last_version.group(2) else: last_version = "unknown" # Discard unused data discard_data(current_version_content_2) discard_data(current_version_content_1) discard_data(last_version_content) return current_version, last_version
class Robots(TestingPlugin): """ This plugin analyzes robots.txt files looking for private pages. """ #-------------------------------------------------------------------------- def get_accepted_types(self): return [BaseURL] #-------------------------------------------------------------------------- def check_download(self, url, name, content_length, content_type): # Returns True to continue or False to cancel. # # Some error page couldn't return non 200 code. # Some error page couldn't return content_type # return (content_type and content_type.strip().lower().split(";")[0] == "text/plain") #-------------------------------------------------------------------------- def check_response(self, request, url, status_code, content_length, content_type): # Returns True to continue or False to cancel. # # Some error page couldn't return non 200 code. # Some error page couldn't return content_type # return (content_type and content_type.strip().lower().startswith("text/")) #-------------------------------------------------------------------------- def run(self, info): m_return = [] m_url = info.url m_hostname = info.hostname m_url_robots_txt = urljoin(m_url, 'robots.txt') p = None try: msg = "Looking for robots.txt in: %s" % m_hostname Logger.log_more_verbose(msg) p = download(m_url_robots_txt, self.check_download) except NetworkOutOfScope: Logger.log_more_verbose("URL out of scope: %s" % (m_url_robots_txt)) return except Exception, e: Logger.log_more_verbose("Error while processing %r: %s" % (m_url_robots_txt, str(e))) return # Check for errors if not p: Logger.log_more_verbose("No robots.txt found.") return u = URL(m_url_robots_txt, referer=m_url) p.add_resource(u) m_return.append(u) m_return.append(p) # Text with info m_robots_text = p.raw_data # Prepare for unicode try: if m_robots_text.startswith(codecs.BOM_UTF8): m_robots_text = m_robots_text.decode('utf-8').lstrip( unicode(codecs.BOM_UTF8, 'utf-8')) elif m_robots_text.startswith(codecs.BOM_UTF16): m_robots_text = m_robots_text.decode('utf-16') except UnicodeDecodeError: Logger.log_error_verbose( "Error while parsing robots.txt: Unicode format error.") return # Extract URLs m_discovered_suspicious = [] m_discovered_urls = [] m_discovered_urls_append = m_discovered_urls.append tmp_discovered = None m_lines = m_robots_text.splitlines() # Var used to update the status m_lines_count = len(m_lines) m_total = float(m_lines_count) for m_step, m_line in enumerate(m_lines): # Remove comments m_octothorpe = m_line.find('#') if m_octothorpe >= 0: m_line = m_line[:m_octothorpe] # Delete init spaces m_line = m_line.rstrip() # Ignore invalid lines if not m_line or ':' not in m_line: continue # Looking for URLs try: m_key, m_value = m_line.split(':', 1) m_key = m_key.strip().lower() m_value = m_value.strip() # Ignore wildcards if '*' in m_value: continue if m_key in ('disallow', 'allow', 'sitemap') and m_value: tmp_discovered = urljoin(m_url, m_value) m_discovered_urls_append(tmp_discovered) # If is a disallow URL, it must be suspicious if m_key.lower() == "disallow": m_discovered_suspicious.append(tmp_discovered) except Exception, e: continue
l_w = l_w[1:] if l_w.startswith("/") else l_w tmp_u = urljoin(m_url, l_w) except ValueError, e: Logger.log_error("Failed to parse key, from wordlist, '%s'" % tmp_u) continue urls.add(tmp_u) for l_w in wordlist: # Use a copy of wordlist to avoid modify the original source l_loaded_wordlist = WordListLoader.get_wordlist_as_list(l_w) for l_wo in l_loaded_wordlist: try: l_wo = l_wo[1:] if l_wo.startswith("/") else l_wo tmp_u = urljoin(m_url, l_wo) except ValueError, e: Logger.log_error("Failed to parse key, from wordlist, '%s'" % tmp_u) continue urls.add(tmp_u) Logger.log_verbose("Loaded %s URLs to test." % len(urls)) # Generates the error page error_response = get_error_page(m_url) # Create the matching analyzer try: store_info = MatchingAnalyzer(error_response.raw_data, min_ratio=0.0) except ValueError, e:
def http_analyzers(main_url, update_status_func, number_of_entries=4): """ Analyze HTTP headers for detect the web server. Return a list with most possible web servers. :param main_url: Base url to test. :type main_url: str :param update_status_func: function used to update the status of the process :type update_status_func: function :param number_of_entries: number of resutls tu return for most probable web servers detected. :type number_of_entries: int :return: Web server family, Web server version, Web server complete description, related web servers (as a dict('SERVER_RELATED' : set(RELATED_NAMES))), others web server with their probabilities as a dict(CONCRETE_WEB_SERVER, PROBABILITY) """ # Load wordlist directly related with a HTTP fields. # { HTTP_HEADER_FIELD : [wordlists] } m_wordlists_HTTP_fields = { "Accept-Ranges" : "accept-range", "Server" : "banner", "Cache-Control" : "cache-control", "Connection" : "connection", "Content-Type" : "content-type", "WWW-Authenticate" : "htaccess-realm", "Pragma" : "pragma", "X-Powered-By" : "x-powered-by" } m_actions = { 'GET' : { 'wordlist' : 'Wordlist_get' , 'weight' : 1 , 'protocol' : 'HTTP/1.1', 'method' : 'GET' , 'payload': '/' }, 'LONG_GET' : { 'wordlist' : 'Wordlist_get_long' , 'weight' : 1 , 'protocol' : 'HTTP/1.1', 'method' : 'GET' , 'payload': '/%s' % ('a' * 200) }, 'NOT_FOUND' : { 'wordlist' : 'Wordlist_get_notfound' , 'weight' : 2 , 'protocol' : 'HTTP/1.1', 'method' : 'GET' , 'payload': '/404_NOFOUND__X02KAS' }, 'HEAD' : { 'wordlist' : 'Wordlist_head' , 'weight' : 3 , 'protocol' : 'HTTP/1.1', 'method' : 'HEAD' , 'payload': '/' }, 'OPTIONS' : { 'wordlist' : 'Wordlist_options' , 'weight' : 2 , 'protocol' : 'HTTP/1.1', 'method' : 'OPTIONS' , 'payload': '/' }, 'DELETE' : { 'wordlist' : 'Wordlist_delete' , 'weight' : 5 , 'protocol' : 'HTTP/1.1', 'method' : 'DELETE' , 'payload': '/' }, 'TEST' : { 'wordlist' : 'Wordlist_attack' , 'weight' : 5 , 'protocol' : 'HTTP/1.1', 'method' : 'TEST' , 'payload': '/' }, 'INVALID' : { 'wordlist' : 'Wordlist_wrong_method' , 'weight' : 5 , 'protocol' : 'HTTP/9.8', 'method' : 'GET' , 'payload': '/' }, 'ATTACK' : { 'wordlist' : 'Wordlist_wrong_version' , 'weight' : 2 , 'protocol' : 'HTTP/1.1', 'method' : 'GET' , 'payload': "/etc/passwd?format=%%%%&xss=\x22><script>alert('xss');</script>&traversal=../../&sql='%20OR%201;"} } # Store results for others HTTP params m_d = ParsedURL(main_url) m_hostname = m_d.hostname m_port = m_d.port m_debug = False # Only for develop # Counter of banners. Used when others methods fails. m_banners_counter = Counter() # Score counter m_counters = HTTPAnalyzer(debug=m_debug) # Var used to update the status m_data_len = len(m_actions) i = 1 # element in process for l_action, v in m_actions.iteritems(): if m_debug: print "###########" l_method = v["method"] l_payload = v["payload"] l_proto = v["protocol"] l_wordlist = v["wordlist"] # Each type of probe hast different weight. # # Weights go from 0 - 5 # l_weight = v["weight"] # Make the URL l_url = urljoin(main_url, l_payload) # Make the raw request #l_raw_request = "%(method)s %(payload)s %(protocol)s\r\nHost: %(host)s:%(port)s\r\nConnection: Close\r\n\r\n" % ( l_raw_request = "%(method)s %(payload)s %(protocol)s\r\nHost: %(host)s\r\n\r\n" % ( { "method" : l_method, "payload" : l_payload, "protocol" : l_proto, "host" : m_hostname, "port" : m_port } ) if m_debug: print "REQUEST" print l_raw_request # Do the connection l_response = None try: m_raw_request = HTTP_Raw_Request(l_raw_request) discard_data(m_raw_request) l_response = HTTP.make_raw_request( host = m_hostname, port = m_port, raw_request = m_raw_request, callback = check_raw_response) if l_response: discard_data(l_response) except NetworkException,e: Logger.log_error_more_verbose("Server-Fingerprint plugin: No response for URL (%s) '%s'. Message: %s" % (l_method, l_url, str(e))) continue if not l_response: Logger.log_error_more_verbose("No response for URL '%s'." % l_url) continue if m_debug: print "RESPONSE" print l_response.raw_headers # Update the status update_status_func((float(i) * 100.0) / float(m_data_len)) Logger.log_more_verbose("Making '%s' test." % (l_wordlist)) i += 1 # Analyze for each wordlist # # Store the server banner try: m_banners_counter[l_response.headers["Server"]] += l_weight except KeyError: pass # # ===================== # HTTP directly related # ===================== # # for l_http_header_name, l_header_wordlist in m_wordlists_HTTP_fields.iteritems(): # Check if HTTP header field is in response if l_http_header_name not in l_response.headers: continue l_curr_header_value = l_response.headers[l_http_header_name] # Generate concrete wordlist name l_wordlist_path = Config.plugin_extra_config[l_wordlist][l_header_wordlist] # Load words for the wordlist l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(l_wordlist_path) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_curr_header_value) m_counters.inc(l_matches, l_action, l_weight, l_http_header_name, message="HTTP field: " + l_curr_header_value) # # ======================= # HTTP INdirectly related # ======================= # # # # Status code # =========== # l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["statuscode"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_response.status) m_counters.inc(l_matches, l_action, l_weight, "statuscode", message="Status code: " + l_response.status) # # Status text # =========== # l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["statustext"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_response.reason) m_counters.inc(l_matches, l_action, l_weight, "statustext", message="Status text: " + l_response.reason) # # Header space # ============ # # Count the number of spaces between HTTP field name and their value, for example: # -> Server: Apache 1 # The number of spaces are: 1 # # -> Server:Apache 1 # The number of spaces are: 0 # l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["header-space"]) # Looking for matches try: l_http_value = l_response.headers[0] # get the value of first HTTP field l_spaces_num = str(abs(len(l_http_value) - len(l_http_value.lstrip()))) l_matches = l_wordlist_instance.matches_by_value(l_spaces_num) m_counters.inc(l_matches, l_action, l_weight, "header-space", message="Header space: " + l_spaces_num) except IndexError: print "index error header space" pass # # Header capitalafterdash # ======================= # # Look for non capitalized first letter of field name, for example: # -> Content-type: .... # Instead of: # -> Content-Type: .... # l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["header-capitalafterdash"]) # Looking for matches l_valid_fields = [x for x in l_response.headers.iterkeys() if "-" in x] if l_valid_fields: l_h = l_valid_fields[0] l_value = l_h.split("-")[1] # Get the second value: Content-type => type l_dush = None if l_value[0].isupper(): # Check first letter is lower l_dush = 1 else: l_dush = 0 l_matches = l_wordlist_instance.matches_by_value(l_dush) m_counters.inc(l_matches, l_action, l_weight, "header-capitalizedafterdush", message="Capital after dash: %s" % str(l_dush)) # # Header order # ============ # l_header_order = ','.join(l_response.headers.iterkeys()) l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["header-order"]) l_matches = l_wordlist_instance.matches_by_value(l_header_order) m_counters.inc(l_matches, l_action, l_weight, "header-order", message="Header order: " + l_header_order) # # Protocol name # ============ # # For a response like: # -> HTTP/1.0 200 OK # .... # # Get the 'HTTP' value. # try: l_proto = l_response.protocol # Get the 'HTTP' text from response, if available if l_proto: l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["protocol-name"]) l_matches = l_wordlist_instance.matches_by_value(l_proto) m_counters.inc(l_matches, l_action, l_weight, "proto-name", message="Proto name: " + l_proto) except IndexError: print "index error protocol name" pass # # Protocol version # ================ # # For a response like: # -> HTTP/1.0 200 OK # .... # # Get the '1.0' value. # try: l_version = l_response.version # Get the '1.0' text from response, if available if l_version: l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["protocol-version"]) l_matches = l_wordlist_instance.matches_by_value(l_version) m_counters.inc(l_matches, l_action, l_weight, "proto-version", message="Proto version: " + l_version) except IndexError: print "index error protocol version" pass if "ETag" in l_response.headers: l_etag_header = l_response.headers["ETag"] # # ETag length # ================ # l_etag_len = len(l_etag_header) l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["etag-legth"]) l_matches = l_wordlist_instance.matches_by_value(l_etag_len) m_counters.inc(l_matches, l_action, l_weight, "etag-length", message="ETag length: " + str(l_etag_len)) # # ETag Quotes # ================ # l_etag_striped = l_etag_header.strip() if l_etag_striped.startswith("\"") or l_etag_striped.startswith("'"): l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["etag-quotes"]) l_matches = l_wordlist_instance.matches_by_value(l_etag_striped[0]) m_counters.inc(l_matches, l_action, l_weight, "etag-quotes", message="Etag quotes: " + l_etag_striped[0]) if "Vary" in l_response.headers: l_vary_header = l_response.headers["Vary"] # # Vary delimiter # ================ # # Checks if Vary header delimiter is something like this: # -> Vary: Accept-Encoding,User-Agent # Or this: # -> Vary: Accept-Encoding, User-Agent # l_var_delimiter = ", " if l_vary_header.find(", ") else "," l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["vary-delimiter"]) l_matches = l_wordlist_instance.matches_by_value(l_var_delimiter) m_counters.inc(l_matches, l_action, l_weight, "vary-delimiter", message="Vary delimiter: " + l_var_delimiter) # # Vary capitalizer # ================ # # Checks if Vary header delimiter is something like this: # -> Vary: Accept-Encoding,user-Agent # Or this: # -> Vary: accept-encoding,user-agent # l_vary_capitalizer = str(0 if l_vary_header == l_vary_header.lower() else 1) l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["vary-capitalize"]) l_matches = l_wordlist_instance.matches_by_value(l_vary_capitalizer) m_counters.inc(l_matches, l_action, l_weight, "vary-capitalize", message="Vary capitalizer: " + l_vary_capitalizer) # # Vary order # ================ # # Checks order between vary values: # -> Vary: Accept-Encoding,user-Agent # Or this: # -> Vary: User-Agent,Accept-Encoding # l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["vary-order"]) l_matches = l_wordlist_instance.matches_by_value(l_vary_header) m_counters.inc(l_matches, l_action, l_weight, "vary-order", message="Vary order: " + l_vary_header) # # ===================== # HTTP specific options # ===================== # # if l_action == "HEAD": # # HEAD Options # ============ # l_option = l_response.headers.get("Allow") if l_option: l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["options-public"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_option) m_counters.inc(l_matches, l_action, l_weight, "options-allow", message="HEAD option: " + l_option) if l_action == "OPTIONS" or l_action == "INVALID" or l_action == "DELETE": if "Allow" in l_response.headers: # # Options allow # ============= # l_option = l_response.headers.get("Allow") if l_option: l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["options-public"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_option) m_counters.inc(l_matches, l_action, l_weight, "options-allow", message="OPTIONS allow: " + l_action + " # " + l_option) # # Allow delimiter # =============== # l_option = l_response.headers.get("Allow") if l_option: l_var_delimiter = ", " if l_option.find(", ") else "," l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["options-delimited"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_var_delimiter) m_counters.inc(l_matches, l_action, l_weight, "options-delimiter", message="OPTION allow delimiter " + l_action + " # " + l_option) if "Public" in l_response.headers: # # Public response # =============== # l_option = l_response.headers.get("Public") if l_option: l_wordlist_instance = WordListLoader.get_advanced_wordlist_as_dict(Config.plugin_extra_config[l_wordlist]["options-public"]) # Looking for matches l_matches = l_wordlist_instance.matches_by_value(l_option) m_counters.inc(l_matches, l_action, l_weight, "options-public", message="Public response: " + l_action + " # " + l_option)
class PredictablesDisclosureBruteforcer(TestingPlugin): #-------------------------------------------------------------------------- def get_accepted_types(self): return [FolderURL] #-------------------------------------------------------------------------- def run(self, info): m_url = info.url Logger.log_more_verbose("Start to process URL: %r" % m_url) # Server specified by param? webserver_finger = Config.plugin_args.get("server_banner", None) if webserver_finger: server_canonical_name = webserver_finger servers_related = [] # Set with related web servers else: # User fingerprint info webserver_finger = info.get_associated_informations_by_category( WebServerFingerprint.information_type) if webserver_finger: webserver_finger = webserver_finger.pop() server_canonical_name = webserver_finger.canonical_name servers_related = webserver_finger.related # Set with related web servers wordlist = set() # Common wordlists try: w = Config.plugin_extra_config["common"] wordlist.update([l_w for l_w in w.itervalues()]) except KeyError: Logger.log_error("Can't load common wordlists") # There is fingerprinting information? if webserver_finger: # # Load wordlists # wordlist_update = wordlist.update # Wordlist of server name try: w = Config.plugin_extra_config["%s_predictables" % server_canonical_name] wordlist_update([l_w for l_w in w.itervalues()]) except KeyError: Logger.log_error( "Can't load predictables wordlists for server: '%s'." % server_canonical_name) # Wordlist of related with the server found try: for l_servers_related in servers_related: w = Config.plugin_extra_config["%s_predictables" % l_servers_related] wordlist_update([l_w for l_w in w.itervalues()]) except KeyError, e: Logger.log_error( "Can't load wordlists predictables wordlists for related webserver: '%s'" % e) # Load content of wordlists urls = set() m_urls_update = urls.add for l_w in wordlist: # Use a copy of wordlist to avoid modify the original source l_loaded_wordlist = WordListLoader.get_wordlist_as_list(l_w) for l_wo in l_loaded_wordlist: try: l_wo = l_wo[1:] if l_wo.startswith("/") else l_wo tmp_u = urljoin(m_url, l_wo) except ValueError, e: Logger.log_error( "Failed to parse key, from wordlist, '%s'" % tmp_u) continue m_urls_update(tmp_u)
def __get_wordpress_version(self, url): """ This function get the current version of wordpress and the last version available for download. :param url: URL fo target. :type url: str. :return: a tuple with (CURRENT_VERSION, LAST_AVAILABLE_VERSION) :type: tuple(str, str) """ url_version = { # Generic "wp-login.php": r"(;ver=)([0-9\.]+)([\-a-z]*)", # For WordPress 3.8 "wp-admin/css/wp-admin-rtl.css": r"(Version[\s]+)([0-9\.]+)", "wp-admin/css/wp-admin.css": r"(Version[\s]+)([0-9\.]+)" } # # Get current version # # URL to find wordpress version url_current_version = urljoin(url, "readme.html") current_version_content_1 = download(url_current_version) if isinstance(current_version_content_1, HTML): current_version_method1 = re.search(r"(<br/>[\s]*[vV]ersion[\s]*)([0-9\.]*)", current_version_content_1.raw_data) if current_version_method1 is None: current_version_method1 = None else: if len(current_version_method1.groups()) != 2: current_version_method1 = None else: current_version_method1 = current_version_method1.group(2) else: current_version_method1 = None # Try to find the version into HTML meta value # Get content of main page current_version_content_2 = download(url) # Try to find the info current_version_method2 = re.search(r"(<meta name=\"generator\" content=\"WordPress[\s]+)([0-9\.]+)", current_version_content_2.raw_data) if current_version_method2 is None: current_version_method2 = None else: if len(current_version_method2.groups()) != 2: current_version_method2 = None else: current_version_method2 = current_version_method2.group(2) # Match versions of the diffentents methods current_version = "unknown" if current_version_method1 is None and current_version_method2 is None: current_version = "unknown" elif current_version_method1 is None and current_version_method2 is not None: current_version = current_version_method2 elif current_version_method1 is not None and current_version_method2 is None: current_version = current_version_method1 elif current_version_method1 is not None and current_version_method2 is not None: if current_version_method1 != current_version_method2: current_version = current_version_method2 else: current_version = current_version_method1 else: current_version = "unknown" # If Current version not found if current_version == "unknown": for url_pre, regex in url_version.iteritems(): # URL to find wordpress version url_current_version = urljoin(url, url_pre) current_version_content = download(url_current_version) discard_data(current_version_content) # Find the version tmp_version = re.search(regex, current_version_content.raw_data) if tmp_version is not None: current_version = tmp_version.group(2) break # Found -> stop search # # Get last version # # URL to get last version of WordPress available url_last_version = "http://wordpress.org/download/" last_version_content = download(url_last_version, allow_out_of_scope=True) if isinstance(last_version_content, HTML): last_version = re.search("(WordPress )([0-9\.]*)", last_version_content.raw_data) if last_version is None: last_version = "unknown" else: if len(last_version.groups()) != 2: last_version = "unknown" else: last_version = last_version.group(2) else: last_version = "unknown" # Discard unused data discard_data(current_version_content_2) discard_data(current_version_content_1) discard_data(last_version_content) return current_version, last_version