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 w3afException: 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()) for fr in self._create_fuzzable_requests(response): self.output_queue.put(fr)
def end(self): ''' This method is called when the plugin wont be used anymore. The real job of this plugin is done here, where I will try to see if one of the error_500 responses were not identified as a vuln by some of my audit plugins ''' all_vulns = kb.kb.get_all_vulns() all_vulns_tuples = [(v.get_uri(), v.get_dc()) for v in all_vulns] for request, error_500_response_id in self._error_500_responses: if (request.get_uri(), request.get_dc()) not in all_vulns_tuples: # Found a err 500 that wasnt identified !!! desc = 'An unidentified web application error (HTTP response'\ ' code 500) was found at: "%s". Enable all plugins and'\ ' try again, if the vulnerability still is not identified'\ ', please verify manually and report it to the w3af'\ ' developers.' desc = desc % request.get_url() v = Vuln('Unhandled error in web application', desc, severity.MEDIUM, error_500_response_id, self.get_name()) v.set_uri(request.get_uri()) self.kb_append_uniq(self, 'error_500', v, 'VAR') self._error_500_responses.cleanup()
def _analyze_headers(self, request, response): ''' Search for IP addresses in HTTP headers ''' # Get the headers string headers_string = response.dump_headers() # Match the regular expressions for regex in self._regex_list: for match in regex.findall(headers_string): # If i'm requesting 192.168.2.111 then I don't want to be # alerted about it if match not in self._ignore_if_match: desc = 'The URL: "%s" returned an HTTP header with a'\ ' private IP address: "%s".' desc = desc % (response.get_url(), match) v = Vuln('Private IP disclosure vulnerability', desc, severity.LOW, response.id, self.get_name()) v.set_url(response.get_url()) v['IP'] = match v.add_to_highlight(match) self.kb_append(self, 'header', v)
def _classic_worker(self, gh, search_term): ''' Perform the searches and store the results in the kb. ''' google_list = self._google_se.get_n_results(search_term, 9) for result in google_list: # I found a vuln in the site! response = self._uri_opener.GET(result.URL, cache=True) if not is_404(response): desc = 'ghdb plugin found a vulnerability at URL: "%s".' \ ' According to GHDB the vulnerability description'\ ' is "%s".' desc = desc % (response.get_url(), gh.desc) v = Vuln('Google hack database match', desc, severity.MEDIUM, 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=severity.LOW) # Create the fuzzable requests for fr in self._create_fuzzable_requests(response): self.output_queue.put(fr)
def _check_methods(self, url): ''' Perform some requests in order to check if we are able to retrieve some data with methods that may be wrongly enabled. ''' allowed_methods = [] for method in ['GET', 'POST', 'ABCD', 'HEAD']: method_functor = getattr(self._uri_opener, method) try: response = apply(method_functor, (url, ), {}) code = response.get_code() except: pass else: if code not in self.BAD_METHODS: allowed_methods.append((method, response.id)) if len(allowed_methods) > 0: response_ids = [i for m, i in allowed_methods] methods = ', '.join([m for m, i in allowed_methods]) + '.' desc = 'The resource: "%s" requires authentication but the access'\ ' is misconfigured and can be bypassed using these'\ ' methods: %s.' desc = desc % (url, methods) v = Vuln('Misconfigured access control', desc, severity.MEDIUM, response_ids, self.get_name()) v.set_url(url) v['methods'] = allowed_methods self.kb_append(self, 'auth', v)
def _PROPFIND(self, domain_path): ''' Test PROPFIND method ''' content = "<?xml version='1.0'?>\r\n" content += "<a:propfind xmlns:a='DAV:'>\r\n" content += "<a:prop>\r\n" content += "<a:displayname:/>\r\n" content += "</a:prop>\r\n" content += "</a:propfind>\r\n" hdrs = Headers([('Depth', '1')]) res = self._uri_opener.PROPFIND(domain_path, data=content, headers=hdrs) if "D:href" in res and res.get_code() in xrange(200, 300): msg = 'Directory listing with HTTP PROPFIND method was found at' \ ' directory: "%s".' % domain_path v = Vuln('Insecure DAV configuration', msg, severity.MEDIUM, res.id, self.get_name()) v.set_url(res.get_url()) v.set_method('PROPFIND') self.kb_append(self, 'dav', v)
def grep(self, request, response): ''' Plugin entry point, search for directory indexing. :param request: The HTTP request object. :param response: The HTTP response object :return: None ''' if not response.is_text_or_html(): return if response.get_url().get_domain_path() in self._already_visited: return self._already_visited.add(response.get_url().get_domain_path()) html_string = response.get_body() for dir_indexing_match in self._multi_in.query(html_string): desc = 'The URL: "%s" has a directory indexing vulnerability.' desc = desc % response.get_url() v = Vuln('Directory indexing', desc, severity.LOW, response.id, self.get_name()) v.set_url(response.get_url()) self.kb_append_uniq(self, 'directory', v, 'URL') break
def end(self): # If all URLs implement protection, don't report anything. if not self._vuln_count: return # If none of the URLs implement protection, simply report # ONE vulnerability that says that. if self._total_count == self._vuln_count: desc = 'The whole target web application has no protection (Pragma'\ ' and Cache-Control headers) against sensitive content'\ ' caching.' # If most of the URLs implement the protection but some # don't, report ONE vulnerability saying: "Most are protected, but x, y are not. if self._total_count > self._vuln_count: desc = 'Some URLs have no protection (Pragma and Cache-Control'\ ' headers) against sensitive content caching. Among them:\n' desc += ' '.join([str(url) + '\n' for url in self._vulns]) response_ids = [_id for _id in self._ids] v = Vuln('Missing cache control for HTTPS content', desc, severity.LOW, response_ids, self.get_name()) self.kb_append_uniq(self, 'cache_control', v, 'URL') self._vulns.cleanup() self._ids.cleanup()
def _universal_allow(self, forged_req, url, origin, response, allow_origin, allow_credentials, allow_methods): ''' Check if the allow_origin is set to *. :return: A list of vulnerability objects with the identified vulns (if any). ''' if allow_origin == '*': msg = 'The remote Web application, specifically "%s", returned' \ ' an %s header with the value set to "*" which is insecure'\ ' and leaves the application open to Cross-domain attacks.' msg = msg % (forged_req.get_url(), ACCESS_CONTROL_ALLOW_ORIGIN) v = Vuln('Access-Control-Allow-Origin set to "*"', msg, severity.LOW, response.get_id(), self.get_name()) v.set_url(forged_req.get_url()) self.kb_append(self, 'cors_origin', v) return self._filter_report('_universal_allow_counter', 'universal allow-origin', severity.MEDIUM, [ v, ]) return []
def _SEARCH(self, domain_path): ''' Test SEARCH method. ''' content = "<?xml version='1.0'?>\r\n" content += "<g:searchrequest xmlns:g='DAV:'>\r\n" content += "<g:sql>\r\n" content += "Select 'DAV:displayname' from scope()\r\n" content += "</g:sql>\r\n" content += "</g:searchrequest>\r\n" res = self._uri_opener.SEARCH(domain_path, data=content) content_matches = '<a:response>' in res or '<a:status>' in res or \ 'xmlns:a="DAV:"' in res if content_matches and res.get_code() in xrange(200, 300): msg = 'Directory listing with HTTP SEARCH method was found at' \ 'directory: "%s".' % domain_path v = Vuln('Insecure DAV configuration', msg, severity.MEDIUM, res.id, self.get_name()) v.set_url(res.get_url()) v.set_method('SEARCH') self.kb_append(self, 'dav', v)
def _not_secure_over_https(self, request, response, cookie_obj, cookie_header_value): ''' Checks if a cookie that does NOT have a secure flag is sent over https. :param request: The http request object :param response: The http response object :param cookie_obj: The cookie object to analyze :param cookie_header_value: The cookie, as sent in the HTTP response :return: None ''' # BUGBUG: See other reference in this file for http://bugs.python.org/issue1028088 if response.get_url().get_protocol().lower() == 'https' and \ not self.SECURE_RE.search(cookie_header_value): desc = 'A cookie without the secure flag was sent in an HTTPS' \ ' response at "%s". The secure flag prevents the browser' \ ' from sending a "secure" cookie over an insecure HTTP' \ ' channel, thus preventing potential session hijacking' \ ' attacks.' desc = desc % response.get_url() v = Vuln('Secure flag missing in HTTPS cookie', desc, severity.HIGH, response.id, self.get_name()) v.set_url(response.get_url()) self._set_cookie_to_rep(v, cobj=cookie_obj) kb.kb.append(self, 'security', v)
def grep(self, request, response): ''' Plugin entry point, find the SSN numbers. :param request: The HTTP request object. :param response: The HTTP response object :return: None. ''' uri = response.get_uri() if response.is_text_or_html() and response.get_code() == 200 \ and response.get_clear_text_body() is not None \ and uri not in self._already_inspected: # Don't repeat URLs self._already_inspected.add(uri) found_ssn, validated_ssn = self._find_SSN( response.get_clear_text_body()) if validated_ssn: desc = 'The URL: "%s" possibly discloses a US Social Security'\ ' Number: "%s".' desc = desc % (uri, validated_ssn) v = Vuln('US Social Security Number disclosure', desc, severity.LOW, response.id, self.get_name()) v.set_uri(uri) v.add_to_highlight(found_ssn) self.kb_append_uniq(self, 'ssn', v, 'URL')
def write_vuln_to_kb(vulnty, url, funcs): vulndata = php_sca.KB_DATA[vulnty] for f in funcs: vuln_sev = vulndata['severity'] desc = name = vulndata['name'] v = Vuln(name, desc, vuln_sev, 1, 'PHP Static Code Analyzer') v.set_uri(url) v.set_var(f.vulnsources[0]) args = list(vulndata['kb_key']) + [v] # TODO: Extract the method from the PHP code # $_GET == GET # $_POST == POST # $_REQUEST == GET v.set_method('GET') # TODO: Extract all the other variables that are # present in the PHP file using the SCA v.set_dc(DataContainer()) # # TODO: This needs to be checked! OS Commanding specific # attributes. v['os'] = 'unix' v['separator'] = '' kb.kb.append(*args)
def _send_and_check(self, repo_url, repo_get_files, repo, domain_path): ''' Check if a repository index exists in the domain_path. :return: None, everything is saved to the self.out_queue. ''' http_response = self.http_get_and_parse(repo_url) if not is_404(http_response): filenames = repo_get_files(http_response.get_body()) parsed_url_set = set() for filename in self._clean_filenames(filenames): test_url = domain_path.url_join(filename) if test_url not in self._analyzed_filenames: parsed_url_set.add(test_url) self._analyzed_filenames.add(filename) self.worker_pool.map(self.http_get_and_parse, parsed_url_set) if parsed_url_set: desc = 'A %s was found at: "%s"; this could indicate that'\ ' a %s is accessible. You might be able to download'\ ' the Web application source code.' desc = desc % (repo, http_response.get_url(), repo) v = Vuln('Source code repository', desc, severity.MEDIUM, http_response.id, self.get_name()) v.set_url(http_response.get_url()) kb.kb.append(self, repo, v) om.out.vulnerability(v.get_desc(), severity=v.get_severity())
def end(self): # If all URLs implement protection, don't report anything. if not self._vuln_count: return response_ids = [_id for _id in self._ids] # If none of the URLs implement protection, simply report # ONE vulnerability that says that. if self._total_count == self._vuln_count: desc = 'The whole target has no protection (X-Frame-Options'\ ' header) against Click-Jacking attacks' # If most of the URLs implement the protection but some # don't, report ONE vulnerability saying: "Most are protected, # but x, y are not. if self._total_count > self._vuln_count: desc = 'Some URLs have no protection (X-Frame-Options header) '\ 'against Click-Jacking attacks. Among them:\n '\ ' '.join([str(url) + '\n' for url in self._vulns]) v = Vuln('Click-Jacking vulnerability', desc, severity.MEDIUM, response_ids, self.get_name()) self.kb_append(self, 'click_jacking', v) self._vulns.cleanup() self._ids.cleanup()
def grep(self, request, response): ''' Plugin entry point. :param request: The HTTP request object. :param response: The HTTP response object :return: None, all results are saved in the kb. ''' uri = response.get_uri() if response.is_text_or_html() and uri not in self._already_inspected: # Don't repeat URLs self._already_inspected.add(uri) for regex in self._regex_list: for m in regex.findall(response.get_body()): user = m[0] desc = 'The URL: "%s" contains a SVN versioning signature'\ ' with the username "%s".' desc = desc % (uri, user) v = Vuln('SVN user disclosure vulnerability', desc, severity.LOW, response.id, self.get_name()) v.set_uri(uri) v['user'] = user v.add_to_highlight(user) self.kb_append_uniq(self, 'users', v, 'URL')
def _ssl_cookie_via_http(self, request, response): ''' Analyze if a cookie value, sent in a HTTPS request, is now used for identifying the user in an insecure page. Example: Login is done over SSL The rest of the page is HTTP ''' if request.get_url().get_protocol().lower() == 'https': return for cookie in kb.kb.get('analyze_cookies', 'cookies'): if cookie.get_url().get_protocol().lower() == 'https' and \ request.get_url().get_domain() == cookie.get_url().get_domain(): # The cookie was sent using SSL, I'll check if the current # request, is using these values in the POSTDATA / QS / COOKIE for key in cookie['cookie-object'].keys(): value = cookie['cookie-object'][key].value # This if is to create less false positives if len(value) > 6 and value in request.dump(): desc = 'Cookie values that were set over HTTPS, are' \ ' then sent over an insecure channel in a' \ ' request to "%s".' desc = desc % request.get_url() v = Vuln('Secure cookies over insecure channel', desc, severity.HIGH, response.id, self.get_name()) v.set_url(response.get_url()) self._set_cookie_to_rep(v, cobj=cookie['cookie-object']) kb.kb.append(self, 'security', v)
def _http_only(self, request, response, cookie_obj, cookie_header_value, fingerprinted): ''' Verify if the cookie has the httpOnly parameter set Reference: http://www.owasp.org/index.php/HTTPOnly http://en.wikipedia.org/wiki/HTTP_cookie :param request: The http request object :param response: The http response object :param cookie_obj: The cookie object to analyze :param cookie_header_value: The cookie, as sent in the HTTP response :param fingerprinted: True if the cookie was fingerprinted :return: None ''' if not self.HTTPONLY_RE.search(cookie_header_value): vuln_severity = severity.MEDIUM if fingerprinted else severity.LOW desc = 'A cookie without the HttpOnly flag was sent when requesting' \ ' "%s". The HttpOnly flag prevents potential intruders from' \ ' accessing the cookie value through Cross-Site Scripting' \ ' attacks.' desc = desc % response.get_url() v = Vuln('Cookie without HttpOnly', desc, vuln_severity, response.id, self.get_name()) v.set_url(response.get_url()) self._set_cookie_to_rep(v, cobj=cookie_obj) kb.kb.append(self, 'security', v)
def grep(self, request, response): ''' Plugin entry point, search for the code disclosures. Unit tests are available at plugins/grep/tests. :param request: The HTTP request object. :param response: The HTTP response object :return: None ''' if response.is_text_or_html() and \ response.get_url() not in self._already_added: match, lang = is_source_file(response.get_body()) if match: # Check also for 404 if not is_404(response): desc = 'The URL: "%s" has a %s code disclosure vulnerability.' desc = desc % (response.get_url(), lang) v = Vuln('Code disclosure vulnerability', desc, severity.LOW, response.id, self.get_name()) v.set_url(response.get_url()) v.add_to_highlight(match.group()) self.kb_append_uniq(self, 'code_disclosure', v, 'URL') self._already_added.add(response.get_url()) else: self._first_404 = False desc = 'The URL: "%s" has a %s code disclosure'\ ' vulnerability in the customized 404 script.' desc = desc % (v.get_url(), lang) v = Vuln('Code disclosure vulnerability in 404 page', desc, severity.LOW, response.id, self.get_name()) v.set_url(response.get_url()) v.add_to_highlight(match.group()) self.kb_append_uniq(self, 'code_disclosure', v, 'URL')
def _origin_echo(self, forged_req, url, origin, response, allow_origin, allow_credentials_str, allow_methods): ''' First check if the @allow_origin is set to the value we sent (@origin) and if the allow_credentials is set to True. If this test is successful (most important vulnerability) then do not check for the @allow_origin is set to the value we sent. :return: A list of vulnerability objects with the identified vulns (if any). ''' if allow_origin is not None: allow_origin = allow_origin.lower() allow_credentials = False if allow_credentials_str is not None: allow_credentials = 'true' in allow_credentials_str.lower() if origin in allow_origin: if allow_credentials: sev = severity.HIGH name = 'Insecure Access-Control-Allow-Origin with credentials' msg = 'The remote Web application, specifically "%s", returned' \ ' an %s header with the value set to the value sent in the'\ ' request\'s Origin header and a %s header with the value'\ ' set to "true", which is insecure and leaves the'\ ' application open to Cross-domain attacks which can' \ ' affect logged-in users.' msg = msg % (forged_req.get_url(), ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_CREDENTIALS) else: sev = severity.LOW name = 'Insecure Access-Control-Allow-Origin' msg = 'The remote Web application, specifically "%s", returned' \ ' an %s header with the value set to the value sent in the'\ ' request\'s Origin header, which is insecure and leaves'\ ' the application open to Cross-domain attacks.' msg = msg % (forged_req.get_url(), ACCESS_CONTROL_ALLOW_ORIGIN) v = Vuln(name, msg, sev, response.get_id(), self.get_name()) v.set_url(forged_req.get_url()) self.kb_append(self, 'cors_origin', v) return self._filter_report('_origin_echo_counter', 'origin echoed in allow-origin', severity.HIGH, [ v, ]) return []
def _PUT(self, domain_path): ''' Tests PUT method. ''' # upload url = domain_path.url_join(rand_alpha(5)) rnd_content = rand_alnum(6) put_response = self._uri_opener.PUT(url, data=rnd_content) # check if uploaded res = self._uri_opener.GET(url, cache=True) if res.get_body() == rnd_content: msg = 'File upload with HTTP PUT method was found at resource:' \ ' "%s". A test file was uploaded to: "%s".' msg = msg % (domain_path, res.get_url()) v = Vuln('Insecure DAV configuration', msg, severity.HIGH, [put_response.id, res.id], self.get_name()) v.set_url(url) v.set_method('PUT') self.kb_append(self, 'dav', v) # Report some common errors elif put_response.get_code() == 500: msg = 'DAV seems to be incorrectly configured. The web server' \ ' answered with a 500 error code. In most cases, this means'\ ' that the DAV extension failed in some way. This error was'\ ' found at: "%s".' % put_response.get_url() i = Info('DAV incorrect configuration', msg, res.id, self.get_name()) i.set_url(url) i.set_method('PUT') self.kb_append(self, 'dav', i) # Report some common errors elif put_response.get_code() == 403: msg = 'DAV seems to be correctly configured and allowing you to'\ ' use the PUT method but the directory does not have the'\ ' correct permissions that would allow the web server to'\ ' write to it. This error was found at: "%s".' msg = msg % put_response.get_url() i = Info('DAV incorrect configuration', msg, [put_response.id, res.id], self.get_name()) i.set_url(url) i.set_method('PUT') self.kb_append(self, 'dav', i)
def create_base_vuln(self): ''' :return: A vulnerability with some preconfigured settings ''' desc = 'This vulnerability was added to the knowledge-base by the'\ ' user and represents a "%s" vulnerability.' desc = desc % self.get_vulnerability_name() v = Vuln('Manually added vulnerability', desc, severity.HIGH, self.get_vuln_id(), 'manual') return v
def _parse_xssed_result(self, response): ''' Parse the result from the xssed site and create the corresponding info objects. :return: Fuzzable requests pointing to the XSS (if any) ''' html_body = response.get_body() if "<b>XSS:</b>" in html_body: # # Work! # regex_many_vulns = re.findall( "<a href='(/mirror/\d*/)' target='_blank'>", html_body) for mirror_relative_link in regex_many_vulns: mirror_url = self._xssed_url.url_join(mirror_relative_link) xss_report_response = self._uri_opener.GET(mirror_url) matches = re.findall("URL:.+", xss_report_response.get_body()) dxss = self._decode_xssed_url if self._fixed in xss_report_response.get_body(): vuln_severity = severity.LOW desc = 'This script contained a XSS vulnerability: "%s".' desc = desc % dxss(dxss(matches[0])) else: vuln_severity = severity.HIGH desc = 'According to xssed.com, this script contains a'\ ' XSS vulnerability: "%s".' desc = desc % dxss(dxss(matches[0])) v = Vuln('Potential XSS vulnerability', desc, vuln_severity, response.id, self.get_name()) v.set_url(mirror_url) kb.kb.append(self, 'xss', v) om.out.information(v.get_desc()) # # Add the fuzzable request, this is useful if I have the # XSS plugin enabled because it will re-test this and # possibly confirm the vulnerability # fuzzable_request_list = self._create_fuzzable_requests( xss_report_response) return fuzzable_request_list else: # Nothing to see here... om.out.debug('xssed_dot_com did not find any previously reported' ' XSS vulnerabilities.')
def end(self): ''' Perform global analysis for all vulnerabilities found. ''' #Check if vulns has been found if self._total_count == 0: return #Parse vulns collection vuln_already_reported = [] total_url_processed_count = len(self._urls) for vuln_store_item in self._vulns: for csp_directive_name, csp_vulns_list in vuln_store_item.csp_vulns.iteritems( ): for csp_vuln in csp_vulns_list: #Check if the current vuln is common (shared) to several url processed #and has been already reported if csp_vuln.desc in vuln_already_reported: continue #Search for current vuln occurences in order to know if #the vuln is common (shared) to several url processed occurences = self._find_occurences(csp_vuln.desc) v = None if len(occurences) > 1: #Shared vuln case v = Vuln('CSP vulnerability', csp_vuln.desc, csp_vuln.severity, occurences, self.get_name()) vuln_already_reported.append(csp_vuln.desc) else: #Isolated vuln case v = Vuln('CSP vulnerability', csp_vuln.desc, csp_vuln.severity, vuln_store_item.resp_id, self.get_name()) #Report vuln self.kb_append(self, 'csp', v) #Cleanup self._urls.cleanup() self._vulns.cleanup()
def _analyze_crossdomain_clientaccesspolicy(self, url, response, file_name): 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 = 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()) else: if (file_name == 'crossdomain.xml'): url_list = dom.getElementsByTagName("allow-access-from") attribute = 'domain' if (file_name == 'clientaccesspolicy.xml'): url_list = dom.getElementsByTagName("domain") attribute = 'uri' for url in url_list: url = url.getAttribute(attribute) desc = 'The "%s" file at "%s" allows flash/silverlight'\ ' access from any site.' desc = desc % (file_name, response.get_url()) if 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()) else: 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())
def _brute_worker(self, url, combination): ''' Try a user/password combination with HTTP basic authentication against a specific URL. :param url: A string representation of an URL :param combination: A tuple that contains (user,pass) ''' # Remember that this worker is called from a thread which lives in a # threadpool. If the worker finds something, it has to let the rest know # and the way we do that is by setting self._found. # # If one thread sees that we already bruteforced the access, the rest will # simply no-op if not self._found or not self._stop_on_first: user, passwd = combination raw_values = "%s:%s" % (user, passwd) auth = 'Basic %s' % base64.b64encode(raw_values).strip() headers = Headers([('Authorization', auth)]) try: response = self._uri_opener.GET(url, cache=False, grep=False, headers=headers) except w3afException, w3: msg = 'Exception while bruteforcing basic authentication,'\ ' error message: "%s".' om.out.debug(msg % w3) else: # GET was OK if response.get_code() != 401: self._found = True desc = 'Found authentication credentials to: "%s".'\ ' A valid user and password combination is: %s/%s .' desc = desc % (url, user, passwd) v = Vuln('Guessable credentials', desc, severity.HIGH, response.id, self.get_name()) v.set_url(url) v['user'] = user v['pass'] = passwd v['response'] = response kb.kb.append(self, 'auth', v) om.out.vulnerability(v.get_desc(), severity=v.get_severity())
def _parse_zone_h_result(self, response): ''' Parse the result from the zone_h site and create the corresponding info objects. :return: None ''' # # I'm going to do only one big "if": # # - The target site was hacked more than one time # - The target site was hacked only one time # # This is the string I have to parse: # in the zone_h response, they are two like this, the first has to be ignored! regex = 'Total notifications: <b>(\d*)</b> of which <b>(\d*)</b> single ip and <b>(\d*)</b> mass' regex_result = re.findall(regex, response.get_body()) try: total_attacks = int(regex_result[0][0]) except IndexError: om.out.debug( 'An error was generated during the parsing of the zone_h website.' ) else: # Do the if... if total_attacks > 1: desc = 'The target site was defaced more than one time in the'\ ' past. For more information please visit the following'\ ' URL: "%s".' % response.get_url() v = Vuln('Previous defacements', desc, severity.MEDIUM, response.id, self.get_name()) v.set_url(response.get_url()) kb.kb.append(self, 'defacements', v) om.out.information(v.get_desc()) elif total_attacks == 1: desc = 'The target site was defaced in the past. For more'\ ' information please visit the following URL: "%s".' desc = desc % response.get_url() i = Info('Previous defacements', desc, response.id, self.get_name()) i.set_url(response.get_url()) kb.kb.append(self, 'defacements', i) om.out.information(i.get_desc())
def _secure_over_http(self, request, response, cookie_obj, cookie_header_value): ''' Checks if a cookie marked as secure is sent over http. Reference: http://en.wikipedia.org/wiki/HTTP_cookie :param request: The http request object :param response: The http response object :param cookie_obj: The cookie object to analyze :param cookie_header_value: The cookie, as sent in the HTTP response :return: None ''' # BUGBUG: http://bugs.python.org/issue1028088 # # I workaround this issue by using the raw string from the HTTP # response instead of the parsed: # # cookie_obj_str = cookie_obj.output(header='') # # Bug can be reproduced like this: # >>> import Cookie # >>> cookie_object = Cookie.SimpleCookie() # >>> cookie_object.load('a=b; secure; httponly') # >>> cookie_object.output(header='') # ' a=b' # # Note the missing secure/httponly in the output return # And now, the code: if self.SECURE_RE.search(cookie_header_value) and \ response.get_url().get_protocol().lower() == 'http': desc = 'A cookie marked with the secure flag was sent over' \ ' an insecure channel (HTTP) when requesting the URL:'\ ' "%s", this usually means that the Web application was'\ ' designed to run over SSL and was deployed without'\ ' security or that the developer does not understand the'\ ' "secure" flag.' desc = desc % response.get_url() v = Vuln('Secure cookie over HTTP', desc, severity.HIGH, response.id, self.get_name()) v.set_url(response.get_url()) self._set_cookie_to_rep(v, cobj=cookie_obj) kb.kb.append(self, 'security', v)
def _filter_report(self, counter, section, vuln_severity, analysis_response): ''' :param counter: A string representing the name of the attr to increment when a vulnerability is found by the decorated method. :param section: A string with the section name to use in the description when there are too many vulnerabilities of this type. :param vuln_severity: One of the constants in the severity module. :param analysis_response: The vulnerability (if any) found by the analysis method. ''' if len(analysis_response): counter_val = getattr(self, counter) if counter_val <= self.MAX_REPEATED_REPORTS: counter_val += 1 setattr(self, counter, counter_val) return analysis_response else: if section not in self._reported_global: self._reported_global.add(section) response_id = analysis_response[0].get_id() msg = 'More than %s URLs in the Web application under analysis' \ ' returned a CORS response that triggered the %s' \ ' detection. Given that this seems to be an issue' \ ' that affects all of the site URLs, the scanner will' \ ' not report any other specific vulnerabilities of this'\ ' type.' msg = msg % (self.MAX_REPEATED_REPORTS, section) v = Vuln('Multiple CORS misconfigurations', msg, vuln_severity, response_id, self.get_name()) self.kb_append(self, 'cors_origin', v) return [ v, ] return []
def _universal_origin_allow_creds(self, forged_req, url, origin, response, allow_origin, allow_credentials_str, allow_methods): ''' Quote: "The above example would fail if the header was wildcarded as: Access-Control-Allow-Origin: *. Since the Access-Control-Allow-Origin explicitly mentions http://foo.example, the credential-cognizant content is returned to the invoking web content. Note that in line 23, a further cookie is set." https://developer.mozilla.org/en-US/docs/HTTP_access_control This method detects this bad implementation, which this is not a vuln it might be interesting for the developers and/or security admins. :return: Any implementation errors (as vuln objects) that might be found. ''' allow_credentials = False if allow_credentials_str is not None: allow_credentials = 'true' in allow_credentials_str.lower() if allow_credentials and allow_origin == '*': msg = 'The remote Web application, specifically "%s", returned' \ ' an %s header with the value set to "*" and an %s header'\ ' with the value set to "true" which according to Mozilla\'s'\ ' documentation is invalid. This implementation error might'\ ' affect the application behavior.' msg = msg % (forged_req.get_url(), ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_CREDENTIALS) v = Vuln('Incorrect withCredentials implementation', msg, severity.INFORMATION, response.get_id(), self.get_name()) v.set_url(forged_req.get_url()) self.kb_append(self, 'cors_origin', v) return self._filter_report( '_universal_origin_allow_creds_counter', 'withCredentials CORS implementation error', severity.INFORMATION, [ v, ]) return []