def _check_if_exists(self, web_shell_url): """ Check if the file exists. :param web_shell_url: The URL to check """ try: response = self._uri_opener.GET(web_shell_url, cache=True) except BaseFrameworkException: om.out.debug('Failed to GET webshell:' + web_shell_url) else: if self._is_possible_backdoor(response): desc = 'A web backdoor was found at: "%s"; this could ' \ 'indicate that the server has been compromised.' desc = desc % response.get_url() v = Vuln('Potential web backdoor', desc, severity.HIGH, response.id, self.get_name()) v.set_url(response.get_url()) kb.kb.append(self, 'backdoors', v) om.out.vulnerability(v.get_desc(), severity=v.get_severity()) fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr)
def send_and_check(self, url): response = self.http_get_and_parse(url) for regex in self.ORACLE_RE: mo = regex.search(response.get_body(), re.DOTALL) if mo: desc = '"%s" version "%s" was detected at "%s".' desc %= (mo.group(1).title(), mo.group(2).title(), response.get_url()) i = Info('Oracle Application Server', desc, response.id, self.get_name()) i.set_url(response.get_url()) kb.kb.append(self, 'oracle_discovery', i) om.out.information(i.get_desc()) fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) break else: msg = ('oracle_discovery found the URL: "%s" but failed to' ' parse it as an Oracle page. The first 50 bytes of' ' the response body is: "%s".') body_start = response.get_body()[:50] om.out.debug(msg % (response.get_url(), body_start))
def _check_if_exists(self, web_shell_url): """ Check if the file exists. :param web_shell_url: The URL to check """ try: response = self._uri_opener.GET(web_shell_url, cache=True) except BaseFrameworkException: om.out.debug('Failed to GET webshell:' + web_shell_url) else: signature = self._match_signature(response) if signature is None: return desc = (u'An HTTP response matching the web backdoor signature' u' "%s" was found at: "%s"; this could indicate that the' u' server has been compromised.') desc %= (signature, response.get_url()) # It's probability is higher if we found a long signature _severity = severity.HIGH if len(signature) > 8 else severity.MEDIUM v = Vuln(u'Potential web backdoor', desc, _severity, response.id, self.get_name()) v.set_url(response.get_url()) kb.kb.append(self, 'backdoors', v) om.out.vulnerability(v.get_desc(), severity=v.get_severity()) fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr)
def crawl(self, fuzzable_request): """ Get the sitemap.xml file and parse it. :param fuzzable_request: A fuzzable_request instance that contains (among other things) the URL to test. """ base_url = fuzzable_request.get_url().base_url() sitemap_url = base_url.url_join('sitemap.xml') response = self._uri_opener.GET(sitemap_url, cache=True) if '</urlset>' not in response: return if is_404(response): return # Send response to core fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) om.out.debug('Parsing xml file with xml.dom.minidom.') try: dom = xml.dom.minidom.parseString(response.get_body()) except Exception, e: msg = 'Exception while parsing sitemap.xml from %s: "%s"' args = (response.get_url(), e) om.out.debug(msg % args) return
def crawl(self, fuzzable_request): """ Get the sitemap.xml file and parse it. :param fuzzable_request: A fuzzable_request instance that contains (among other things) the URL to test. """ base_url = fuzzable_request.get_url().base_url() sitemap_url = base_url.url_join('sitemap.xml') response = self._uri_opener.GET(sitemap_url, cache=True) if '</urlset>' in response and not is_404(response): # Send response to core fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) om.out.debug('Parsing xml file with xml.dom.minidom.') try: dom = xml.dom.minidom.parseString(response.get_body()) except: raise BaseFrameworkException('Error while parsing sitemap.xml') else: raw_url_list = dom.getElementsByTagName("loc") parsed_url_list = [] for url in raw_url_list: try: url = url.childNodes[0].data url = URL(url) except ValueError, ve: msg = 'Sitemap file had an invalid URL: "%s"' om.out.debug(msg % ve) except: om.out.debug('Sitemap file had an invalid format')
def _do_request(self, url, mutant): """ Perform a simple GET to see if the result is an error or not, and then run the actual fuzzing. """ response = self._uri_opener.GET(mutant, cache=True, headers=self._headers) if not (is_404(response) or response.get_code() in (403, 401) or self._return_without_eval(mutant)): # Create the fuzzable request and send it to the core fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) # # Save it to the kb (if new)! # if response.get_url() not in self._seen and response.get_url( ).get_file_name(): desc = 'A potentially interesting file was found at: "%s".' desc = desc % response.get_url() i = Info('Potentially interesting file', desc, response.id, self.get_name()) i.set_url(response.get_url()) kb.kb.append(self, 'files', i) om.out.information(i.get_desc()) # Report only once self._seen.add(response.get_url())
def _do_request(self, url, mutant): """ Perform a simple GET to see if the result is an error or not, and then run the actual fuzzing. """ response = self._uri_opener.GET( mutant, cache=True, headers=self._headers) if not (is_404(response) or response.get_code() in (403, 401) or self._return_without_eval(mutant)): # Create the fuzzable request and send it to the core fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) # # Save it to the kb (if new)! # if response.get_url() not in self._seen and response.get_url().get_file_name(): desc = 'A potentially interesting file was found at: "%s".' desc = desc % response.get_url() i = Info('Potentially interesting file', desc, response.id, self.get_name()) i.set_url(response.get_url()) kb.kb.append(self, 'files', i) om.out.information(i.get_desc()) # Report only once self._seen.add(response.get_url())
def _check_if_exists(self, web_shell_url): """ Check if the file exists. :param web_shell_url: The URL to check """ try: response = self._uri_opener.GET(web_shell_url, cache=True) except BaseFrameworkException: om.out.debug('Failed to GET webshell:' + web_shell_url) else: signature = self._match_signature(response) if signature is None: return desc = (u'An HTTP response matching the web backdoor signature' u' "%s" was found at: "%s"; this could indicate that the' u' server has been compromised.') desc %= (signature, response.get_url()) # It's probability is higher if we found a long signature _severity = severity.HIGH if len( signature) > 8 else severity.MEDIUM v = Vuln(u'Potential web backdoor', desc, _severity, response.id, self.get_name()) v.set_url(response.get_url()) kb.kb.append(self, 'backdoors', v) om.out.vulnerability(v.get_desc(), severity=v.get_severity()) fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr)
def _send_and_check(self, nikto_test): """ This method sends the request to the server. :return: True if the requested URI responded as expected. """ # # Small performance improvement. If all we want to know is if the # file exists or not, lets use HEAD instead of GET. In 99% of the # cases this will work as expected and we'll have a significant # performance improvement. # if nikto_test.is_vulnerable.checks_only_response_code(): try: http_response = self._uri_opener.HEAD(nikto_test.uri) except Exception: return else: if not nikto_test.is_vulnerable.check(http_response): return False function_ptr = getattr(self._uri_opener, nikto_test.method) try: http_response = function_ptr(nikto_test.uri) except BaseFrameworkException as e: msg = ('An exception was raised while requesting "%s", the error' ' message is: "%s".') om.out.error(msg % (nikto_test.uri, e)) return False if nikto_test.is_vulnerable.check(http_response) and \ not is_404(http_response): vdesc = ('pykto plugin found a vulnerability at URL: "%s".' ' Vulnerability description: "%s".') vdesc = vdesc % (http_response.get_url(), nikto_test.message) v = Vuln('Insecure URL', vdesc, severity.LOW, http_response.id, self.get_name()) v.set_uri(http_response.get_uri()) v.set_method(nikto_test.method) kb.kb.append(self, 'vuln', v) om.out.vulnerability(v.get_desc(), severity=v.get_severity()) fr = FuzzableRequest.from_http_response(http_response) self.output_queue.put(fr)
def _analyze_gears_manifest(self, url, response, file_name): if '"entries":' not in response: return # Save it to the kb! desc = ('A gears manifest file was found at: "%s".' ' Each file should be manually reviewed for sensitive' ' information that may get cached on the client.') desc %= url i = Info('Gears manifest resource', desc, response.id, self.get_name()) i.set_url(url) kb.kb.append(self, url, i) om.out.information(i.get_desc()) fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr)
def _check_and_analyze(self, domain_path, php_info_filename): """ Check if a php_info_filename exists in the domain_path. :return: None, everything is put() into the self.output_queue. """ php_info_url = domain_path.url_join(php_info_filename) response = self._uri_opener.GET(php_info_url, cache=True, grep=False) if is_404(response): return # Check if it is a phpinfo file php_version = self.PHP_VERSION_RE.search(response.get_body(), re.I) sysinfo = self.SYSTEM_RE.search(response.get_body(), re.I) if not php_version: return if not sysinfo: return # Create the fuzzable request and send it to the core fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) desc = ('The phpinfo() file was found at: %s. The version' ' of PHP is: "%s" and the system information is:' ' "%s".') desc %= (response.get_url(), php_version.group(2), sysinfo.group(1)) v = Vuln('phpinfo() file found', desc, severity.MEDIUM, response.id, self.get_name()) v.set_url(response.get_url()) kb.kb.append(self, 'phpinfo', v) om.out.vulnerability(v.get_desc(), severity=v.get_severity()) if not self._has_audited: self._has_audited = True self.audit_phpinfo(response)
def _send_request(self, functor, url, mutant): response = functor(mutant, cache=True) if is_404(response): return # Create the fuzzable request and send it to the core fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) # # Save it to the kb! # desc = 'A potentially interesting URL was found at: "%s".' desc %= response.get_url() i = Info('Potentially interesting URL', desc, response.id, self.get_name()) i.set_url(response.get_url()) kb.kb.append_uniq(self, 'url', i, filter_by='URL') om.out.information(i.get_desc())
class pykto(CrawlPlugin): """ A nikto port to python. :author: Andres Riancho ([email protected]) """ def __init__(self): CrawlPlugin.__init__(self) # internal variables self._exec = True self._already_analyzed = ScalableBloomFilter() # User configured parameters self._db_file = os.path.join(ROOT_PATH, 'plugins', 'crawl', 'pykto', 'scan_database.db') self._extra_db_file = os.path.join(ROOT_PATH, 'plugins', 'crawl', 'pykto', 'w3af_scan_database.db') self._cgi_dirs = ['/cgi-bin/'] self._admin_dirs = ['/admin/', '/adm/'] self._users = ['adm', 'bin', 'daemon', 'ftp', 'guest', 'listen', 'lp', 'mysql', 'noaccess', 'nobody', 'nobody4', 'nuucp', 'operator', 'root', 'smmsp', 'smtp', 'sshd', 'sys', 'test', 'unknown'] self._nuke = ['/', '/postnuke/', '/postnuke/html/', '/modules/', '/phpBB/', '/forum/'] self._mutate_tests = False def crawl(self, fuzzable_request): """ Runs pykto to the site. :param fuzzable_request: A fuzzable_request instance that contains (among other things) the URL to test. """ if not self._exec and not self._mutate_tests: # dont run anymore raise RunOnce() else: # Run the basic scan (only once) url = fuzzable_request.get_url().base_url() if url not in self._already_analyzed: self._already_analyzed.add(url) self._run(url) self._exec = False # And now mutate if the user configured it... if self._mutate_tests: # Tests need to be mutated url = fuzzable_request.get_url().get_domain_path() if url not in self._already_analyzed: # Save the directories I already have tested in order to # avoid testing them more than once... self._already_analyzed.add(url) self._run(url) def _run(self, url): """ Really run the plugin. :param url: The URL object I have to test. """ config = Config(self._cgi_dirs, self._admin_dirs, self._nuke, self._mutate_tests, self._users) for db_file in [self._db_file, self._extra_db_file]: parser = NiktoTestParser(db_file, config, url) # Send the requests using threads: self.worker_pool.map_multi_args(self._send_and_check, parser.test_generator(), chunksize=10) def _send_and_check(self, nikto_test): """ This method sends the request to the server. :return: True if the requested URI responded as expected. """ # # Small performance improvement. If all we want to know is if the # file exists or not, lets use HEAD instead of GET. In 99% of the # cases this will work as expected and we'll have a significant # performance improvement. # if nikto_test.is_vulnerable.checks_only_response_code(): try: http_response = self._uri_opener.HEAD(nikto_test.uri) except Exception: return else: if not nikto_test.is_vulnerable.check(http_response): return False function_ptr = getattr(self._uri_opener, nikto_test.method) try: http_response = function_ptr(nikto_test.uri) except BaseFrameworkException, e: msg = ('An exception was raised while requesting "%s", the error' ' message is: "%s".') om.out.error(msg % (nikto_test.uri, e)) return False if nikto_test.is_vulnerable.check(http_response) and \ not is_404(http_response): vdesc = ('pykto plugin found a vulnerability at URL: "%s".' ' Vulnerability description: "%s".') vdesc = vdesc % (http_response.get_url(), nikto_test.message) v = Vuln('Insecure URL', vdesc, severity.LOW, http_response.id, self.get_name()) v.set_uri(http_response.get_uri()) v.set_method(nikto_test.method) kb.kb.append(self, 'vuln', v) om.out.vulnerability(v.get_desc(), severity=v.get_severity()) fr = FuzzableRequest.from_http_response(http_response) self.output_queue.put(fr)
if is_404(http_response): return # # Looking good, but lets see if this is a false positive # or not... # dir_url = base_path.url_join(directory_name + rand_alnum(5) + '/') invalid_http_response = self._uri_opener.GET(dir_url, cache=False) if is_404(invalid_http_response): # # Good, the directory_name + rand_alnum(5) return a # 404, the original directory_name is not a false positive. # fr = FuzzableRequest.from_http_response(http_response) self.output_queue.put(fr) msg = ('dir_file_brute plugin found "%s" with HTTP response ' 'code %s and Content-Length: %s.') om.out.information( msg % (http_response.get_url(), http_response.get_code(), len(http_response.get_body()))) def _bruteforce_directories(self, base_path): """ :param base_path: The base path to use in the bruteforcing process, can be something like http://host.tld/ or http://host.tld/images/ .
def _analyze_crossdomain_clientaccesspolicy(self, url, response, file_name): # https://github.com/andresriancho/w3af/issues/14491 if file_name not in self.FILE_TAG_ATTR: return try: dom = xml.dom.minidom.parseString(response.get_body()) except Exception: # Report this, it may be interesting for the final user # not a vulnerability per-se... but... it's information after all if 'allow-access-from' in response.get_body() or \ 'cross-domain-policy' in response.get_body() or \ 'cross-domain-access' in response.get_body(): desc = 'The "%s" file at: "%s" is not a valid XML.' desc %= (file_name, response.get_url()) i = Info('Invalid RIA settings file', desc, response.id, self.get_name()) i.set_url(response.get_url()) kb.kb.append(self, 'info', i) om.out.information(i.get_desc()) return tag, attribute = self.FILE_TAG_ATTR.get(file_name) url_list = dom.getElementsByTagName(tag) for url in url_list: url = url.getAttribute(attribute) if url == '*': desc = 'The "%s" file at "%s" allows flash / silverlight'\ ' access from any site.' desc %= (file_name, response.get_url()) v = Vuln('Insecure RIA settings', desc, severity.LOW, response.id, self.get_name()) v.set_url(response.get_url()) v.set_method('GET') kb.kb.append(self, 'vuln', v) om.out.vulnerability(v.get_desc(), severity=v.get_severity()) fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) else: desc = 'The "%s" file at "%s" allows flash / silverlight'\ ' access from "%s".' desc %= (file_name, response.get_url(), url) i = Info('Cross-domain allow ACL', desc, response.id, self.get_name()) i.set_url(response.get_url()) i.set_method('GET') kb.kb.append(self, 'info', i) om.out.information(i.get_desc()) fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr)
# when scanning phpinfo in window box # the problem is generating a lot of results # due to all-the-same-for-windows files phpVersion.php, phpversion.php ..etc # Well, how to solve it? # Finding one phpinfo file is enough for auditing for the target # So, we report every phpinfo file found # but we do and report auditing once. Sounds logical? # # Feb/17/2009 by Andres Riancho: # Yes, that sounds ok for me. # Check if it's a phpinfo file if not is_404(response): # Create the fuzzable request and send it to the core fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) """ |Modified| old: regex_str = 'alt="PHP Logo" /></a><h1 class="p">PHP Version (.*?)</h1>' new: regex_str = '(<tr class="h"><td>\n|alt="PHP Logo" /></a>)<h1 class="p">PHP Version (.*?)</h1>' by aungkhant - I've been seeing phpinfo pages which don't print php logo image. One example, ning.com. """ regex_str = '(<tr class="h"><td>\n|alt="PHP Logo" /></a>)<h1'\ ' class="p">PHP Version (.*?)</h1>' php_version = re.search(regex_str, response.get_body(), re.I)
class phpinfo(CrawlPlugin): """ Search PHP Info file and if it finds it will determine the version of PHP. :author: Viktor Gazdag ( [email protected] ) :author: Aung Khant ( aungkhant[at]yehg.net ) """ PHP_VERSION_RE = re.compile('(<tr class="h"><td>\n|alt="PHP Logo" /></a>)' '<h1 class="p">PHP Version (.*?)</h1>', re.I) SYSTEM_RE = re.compile('System </td><td class="v">(.*?)</td></tr>', re.I) def __init__(self): CrawlPlugin.__init__(self) # Internal variables self._analyzed_dirs = DiskSet(table_prefix='phpinfo') self._has_audited = False def crawl(self, fuzzable_request): """ For every directory, fetch a list of files and analyze the response. :param fuzzable_request: A fuzzable_request instance that contains (among other things) the URL to test. """ for domain_path in fuzzable_request.get_url().get_directories(): if domain_path in self._analyzed_dirs: continue self._analyzed_dirs.add(domain_path) url_repeater = repeat(domain_path) args = izip(url_repeater, self._get_potential_phpinfos()) self.worker_pool.map_multi_args(self._check_and_analyze, args) def _get_potential_phpinfos(self): """ :return: Filename of the php info file. """ res = ['phpinfo.php', 'PhpInfo.php', 'PHPinfo.php', 'PHPINFO.php', 'phpInfo.php', 'info.php', 'test.php?mode=phpinfo', 'index.php?view=phpinfo', 'index.php?mode=phpinfo', 'TEST.php?mode=phpinfo', '?mode=phpinfo', '?view=phpinfo', 'install.php?mode=phpinfo', 'INSTALL.php?mode=phpinfo', 'admin.php?mode=phpinfo', 'phpversion.php', 'phpVersion.php', 'test1.php', 'phpinfo1.php', 'phpInfo1.php', 'info1.php', 'PHPversion.php', 'x.php', 'xx.php', 'xxx.php'] identified_os = kb.kb.raw_read('fingerprint_os', 'operating_system_str') if not isinstance(identified_os, basestring): identified_os = cf.cf.get('target_os') # pylint: disable=E1103 if 'windows' in identified_os.lower(): res = list(set([path.lower() for path in res])) # pylint: enable=E1103 return res def _check_and_analyze(self, domain_path, php_info_filename): """ Check if a php_info_filename exists in the domain_path. :return: None, everything is put() into the self.output_queue. """ php_info_url = domain_path.url_join(php_info_filename) try: response = self._uri_opener.GET(php_info_url, cache=True) except BaseFrameworkException, w3: msg = 'Failed to GET phpinfo file: "%s". Exception: "%s".' om.out.debug(msg % (php_info_url, w3)) return # Needs to exist if is_404(response): return # Create the fuzzable request and send it to the core fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) # Check if it's a phpinfo file php_version = self.PHP_VERSION_RE.search(response.get_body(), re.I) sysinfo = self.SYSTEM_RE.search(response.get_body(), re.I) if php_version and sysinfo: desc = ('The phpinfo() file was found at: %s. The version' ' of PHP is: "%s" and the system information is:' ' "%s".') desc %= (response.get_url(), php_version.group(2), sysinfo.group(1)) v = Vuln('phpinfo() file found', desc, severity.MEDIUM, response.id, self.get_name()) v.set_url(response.get_url()) kb.kb.append(self, 'phpinfo', v) om.out.vulnerability(v.get_desc(), severity=v.get_severity()) if not self._has_audited: self._has_audited = True self.audit_phpinfo(response)