async def attack(self, request: Request): page = request.path for mutated_request, parameter, _payload, _flags in self.mutator.mutate( request): log_verbose(f"[¨] {mutated_request.url}") try: response = await self.crawler.async_send(mutated_request) except ReadTimeout: self.network_errors += 1 await self.add_anom_medium(request_id=request.path_id, category=Messages.RES_CONSUMPTION, request=mutated_request, parameter=parameter, info="Timeout (" + parameter + ")", wstg=RESOURCE_CONSUMPTION_WSTG_CODE) log_orange("---") log_orange(Messages.MSG_TIMEOUT, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(mutated_request.http_repr()) log_orange("---") except HTTPStatusError: self.network_errors += 1 logging.error( _("Error: The server did not understand this request")) except RequestError: self.network_errors += 1 else: if "wapiti" in response.headers: await self.add_vuln_low( request_id=request.path_id, category=NAME, request=mutated_request, parameter=parameter, info=_( "{0} via injection in the parameter {1}").format( self.MSG_VULN, parameter), wstg=WSTG_CODE) if parameter == "QUERY_STRING": injection_msg = Messages.MSG_QS_INJECT else: injection_msg = Messages.MSG_PARAM_INJECT log_red("---") log_red(injection_msg, self.MSG_VULN, page, parameter) log_red(Messages.MSG_EVIL_REQUEST) log_red(mutated_request.http_repr()) log_red("---")
async def attack(self, request: Request): page = request.path self.excluded_path.add(page) option_request = Request(page, "OPTIONS", referer=request.referer, link_depth=request.link_depth) log_verbose(f"[+] {option_request}") try: response = await self.crawler.async_send(option_request) except RequestError: self.network_errors += 1 return if response.is_success or response.is_redirect: methods = response.headers.get("allow", '').upper().split(',') methods = {method.strip() for method in methods if method.strip()} interesting_methods = sorted(methods - self.KNOWN_METHODS) if interesting_methods: log_orange("---") log_orange( _("Interesting methods allowed on {}: {}").format( page, ", ".join(interesting_methods))) await self.add_addition( category=NAME, request=option_request, info=_("Interesting methods allowed on {}: {}").format( page, ", ".join(interesting_methods)), wstg=WSTG_CODE) log_orange("---")
async def error_based_attack(self, request: Request): page = request.path saw_internal_error = False current_parameter = None vulnerable_parameter = False vulnerable_parameters = set() for mutated_request, parameter, __, __ in self.mutator.mutate(request): if current_parameter != parameter: # Forget what we know about current parameter current_parameter = parameter vulnerable_parameter = False elif vulnerable_parameter: # If parameter is vulnerable, just skip till next parameter continue log_verbose(f"[¨] {mutated_request}") try: response = await self.crawler.async_send(mutated_request) except RequestError: self.network_errors += 1 else: vuln_info = self._find_pattern_in_response(response.content) if vuln_info and not await self.is_false_positive(request): # An error message implies that a vulnerability may exists if parameter == "QUERY_STRING": vuln_message = Messages.MSG_QS_INJECT.format(vuln_info, page) else: vuln_message = _("{0} via injection in the parameter {1}").format(vuln_info, parameter) await self.add_vuln_critical( request_id=request.path_id, category=NAME, request=mutated_request, info=vuln_message, parameter=parameter, wstg=WSTG_CODE ) log_red("---") log_red( Messages.MSG_QS_INJECT if parameter == "QUERY_STRING" else Messages.MSG_PARAM_INJECT, vuln_info, page, parameter ) log_red(Messages.MSG_EVIL_REQUEST) log_red(mutated_request.http_repr()) log_red("---") # We reached maximum exploitation for this parameter, don't send more payloads vulnerable_parameter = True vulnerable_parameters.add(parameter) elif response.status == 500 and not saw_internal_error: saw_internal_error = True if parameter == "QUERY_STRING": anom_msg = Messages.MSG_QS_500 else: anom_msg = Messages.MSG_PARAM_500.format(parameter) await self.add_anom_high( request_id=request.path_id, category=Messages.ERROR_500, request=mutated_request, info=anom_msg, parameter=parameter, wstg=INTERNAL_ERROR_WSTG_CODE ) log_orange("---") log_orange(Messages.MSG_500, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(mutated_request.http_repr()) log_orange("---") return vulnerable_parameters
async def attempt_exploit(self, method, payloads, injection_request, parameter, taint, output_request): timeouted = False page = injection_request.path saw_internal_error = False output_url = output_request.url attack_mutator = Mutator(methods=method, payloads=payloads, qs_inject=self.must_attack_query_string, parameters=[parameter], skip=self.options.get("skipped_parameters")) for evil_request, xss_param, _xss_payload, xss_flags in attack_mutator.mutate( injection_request): log_verbose(f"[¨] {evil_request}") try: await self.crawler.async_send(evil_request) except ReadTimeout: self.network_errors += 1 if timeouted: continue log_orange("---") log_orange(Messages.MSG_TIMEOUT, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(evil_request.http_repr()) log_orange("---") if xss_param == "QUERY_STRING": anom_msg = Messages.MSG_QS_TIMEOUT else: anom_msg = Messages.MSG_PARAM_TIMEOUT.format(xss_param) await self.add_anom_medium( request_id=injection_request.path_id, category=Messages.RES_CONSUMPTION, request=evil_request, info=anom_msg, parameter=xss_param, wstg=RESOURCE_CONSUMPTION_WSTG_CODE) timeouted = True except RequestError: self.network_errors += 1 continue else: try: response = await self.crawler.async_send(output_request) except RequestError: self.network_errors += 1 continue if (response.status not in (301, 302, 303) and valid_xss_content_type(evil_request) and check_payload(self.DATA_DIR, self.PAYLOADS_FILE, self.external_endpoint, self.proto_endpoint, response, xss_flags, taint)): if page == output_request.path: description = _( "Permanent XSS vulnerability found via injection in the parameter {0}" ).format(xss_param) else: description = _( "Permanent XSS vulnerability found in {0} by injecting" " the parameter {1} of {2}").format( output_request.url, parameter, page) if has_strong_csp(response): description += ".\n" + _( "Warning: Content-Security-Policy is present!") await self.add_vuln_high( request_id=injection_request.path_id, category=NAME, request=evil_request, parameter=xss_param, info=description, wstg=WSTG_CODE) if xss_param == "QUERY_STRING": injection_msg = Messages.MSG_QS_INJECT else: injection_msg = Messages.MSG_PARAM_INJECT log_red("---") # TODO: a last parameter should give URL used to pass the vulnerable parameter log_red(injection_msg, self.MSG_VULN, output_url, xss_param) if has_strong_csp(response): log_red( _("Warning: Content-Security-Policy is present!")) log_red(Messages.MSG_EVIL_REQUEST) log_red(evil_request.http_repr()) log_red("---") # stop trying payloads and jump to the next parameter break if response.status == 500 and not saw_internal_error: if xss_param == "QUERY_STRING": anom_msg = Messages.MSG_QS_500 else: anom_msg = Messages.MSG_PARAM_500.format(xss_param) await self.add_anom_high( request_id=injection_request.path_id, category=Messages.ERROR_500, request=evil_request, info=anom_msg, parameter=xss_param, wstg=INTERNAL_ERROR_WSTG_CODE) log_orange("---") log_orange(Messages.MSG_500, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(evil_request.http_repr()) log_orange("---") saw_internal_error = True
async def attack(self, request: Request): timeouted = False page = request.path saw_internal_error = False current_parameter = None vulnerable_parameter = False if request.url not in self.attacked_urls: await self.attack_body(request) self.attacked_urls.add(request.url) if request.path_id in self.vulnerables: return if request.is_multipart: await self.attack_upload(request) if request.path_id in self.vulnerables: return for mutated_request, parameter, __, flags in self.mutator.mutate( request): if current_parameter != parameter: # Forget what we know about current parameter current_parameter = parameter vulnerable_parameter = False elif vulnerable_parameter: # If parameter is vulnerable, just skip till next parameter continue log_verbose(f"[¨] {mutated_request}") try: response = await self.crawler.async_send(mutated_request) except ReadTimeout: self.network_errors += 1 if timeouted: continue log_orange("---") log_orange(Messages.MSG_TIMEOUT, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(mutated_request.http_repr()) log_orange("---") if parameter == "QUERY_STRING": anom_msg = Messages.MSG_QS_TIMEOUT else: anom_msg = Messages.MSG_PARAM_TIMEOUT.format(parameter) await self.add_anom_medium(request_id=request.path_id, category=Messages.RES_CONSUMPTION, request=mutated_request, info=anom_msg, parameter=parameter, wstg=RESOURCE_CONSUMPTION_WSTG_CODE) timeouted = True except RequestError: self.network_errors += 1 continue else: pattern = search_pattern(response.content, self.flag_to_patterns(flags)) if pattern and not await self.false_positive(request, pattern): # An error message implies that a vulnerability may exists if parameter == "QUERY_STRING": vuln_message = Messages.MSG_QS_INJECT.format( self.MSG_VULN, page) else: vuln_message = _( "{0} via injection in the parameter {1}").format( self.MSG_VULN, parameter) await self.add_vuln_high(request_id=request.path_id, category=NAME, request=mutated_request, info=vuln_message, parameter=parameter, wstg=WSTG_CODE) log_red("---") log_red( Messages.MSG_QS_INJECT if parameter == "QUERY_STRING" else Messages.MSG_PARAM_INJECT, self.MSG_VULN, page, parameter) log_red(Messages.MSG_EVIL_REQUEST) log_red(mutated_request.http_repr()) log_red("---") # We reached maximum exploitation for this parameter, don't send more payloads vulnerable_parameter = True continue if response.status == 500 and not saw_internal_error: saw_internal_error = True if parameter == "QUERY_STRING": anom_msg = Messages.MSG_QS_500 else: anom_msg = Messages.MSG_PARAM_500.format(parameter) await self.add_anom_high(request_id=request.path_id, category=Messages.ERROR_500, request=mutated_request, info=anom_msg, parameter=parameter, wstg=INTERNAL_ERROR_WSTG_CODE) log_orange("---") log_orange(Messages.MSG_500, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(mutated_request.http_repr()) log_orange("---")
async def attack(self, request: Request): warned = False timeouted = False page = request.path saw_internal_error = False current_parameter = None vulnerable_parameter = False for mutated_request, parameter, __, flags in self.mutator.mutate( request): if current_parameter != parameter: # Forget what we know about current parameter current_parameter = parameter vulnerable_parameter = False elif vulnerable_parameter: # If parameter is vulnerable, just skip till next parameter continue if flags.payload_type == PayloadType.time and request.path_id in self.false_positive_timeouts: # If the original request is known to gives timeout and payload is time-based, just skip # and move to next payload continue log_verbose(f"[¨] {mutated_request}") try: response = await self.crawler.async_send(mutated_request) except ReadTimeout: if flags.payload_type == PayloadType.time: if await self.does_timeout(request): self.network_errors += 1 self.false_positive_timeouts.add(request.path_id) continue vuln_info = _("Blind command execution") if parameter == "QUERY_STRING": vuln_message = Messages.MSG_QS_INJECT.format( vuln_info, page) else: vuln_message = _( "{0} via injection in the parameter {1}").format( vuln_info, parameter) await self.add_vuln_critical(request_id=request.path_id, category=NAME, request=mutated_request, info=vuln_message, parameter=parameter, wstg=WSTG_CODE) log_red("---") log_red( Messages.MSG_QS_INJECT if parameter == "QUERY_STRING" else Messages.MSG_PARAM_INJECT, vuln_info, page, parameter) log_red(Messages.MSG_EVIL_REQUEST) log_red(mutated_request.http_repr()) log_red("---") vulnerable_parameter = True continue self.network_errors += 1 if timeouted: continue log_orange("---") log_orange(Messages.MSG_TIMEOUT, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(mutated_request.http_repr()) log_orange("---") if parameter == "QUERY_STRING": anom_msg = Messages.MSG_QS_TIMEOUT else: anom_msg = Messages.MSG_PARAM_TIMEOUT.format(parameter) await self.add_anom_medium(request_id=request.path_id, category=Messages.RES_CONSUMPTION, request=mutated_request, info=anom_msg, parameter=parameter, wstg=RESOURCE_CONSUMPTION_WSTG_CODE) timeouted = True except RequestError: self.network_errors += 1 else: # No timeout raised vuln_info, executed, warned = self._find_pattern_in_response( response.content, warned) if vuln_info: # An error message implies that a vulnerability may exists if parameter == "QUERY_STRING": vuln_message = Messages.MSG_QS_INJECT.format( vuln_info, page) log_message = Messages.MSG_QS_INJECT else: vuln_message = _( "{0} via injection in the parameter {1}").format( vuln_info, parameter) log_message = Messages.MSG_PARAM_INJECT await self.add_vuln_critical(request_id=request.path_id, category=NAME, request=mutated_request, info=vuln_message, parameter=parameter, wstg=WSTG_CODE) log_red("---") log_red(log_message, vuln_info, page, parameter) log_red(Messages.MSG_EVIL_REQUEST) log_red(mutated_request.http_repr()) log_red("---") if executed: # We reached maximum exploitation for this parameter, don't send more payloads vulnerable_parameter = True continue elif response.is_server_error and not saw_internal_error: saw_internal_error = True if parameter == "QUERY_STRING": anom_msg = Messages.MSG_QS_500 else: anom_msg = Messages.MSG_PARAM_500.format(parameter) await self.add_anom_high(request_id=request.path_id, category=Messages.ERROR_500, request=mutated_request, info=anom_msg, parameter=parameter, wstg=INTERNAL_ERROR_WSTG_CODE) log_orange("---") log_orange(Messages.MSG_500, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(mutated_request.http_repr()) log_orange("---")
async def attack(self, request: Request): page = request.path saw_internal_error = False current_parameter = None vulnerable_parameter = False for mutated_request, parameter, _payload, _flags in self.mutator.mutate( request): if current_parameter != parameter: # Forget what we know about current parameter current_parameter = parameter vulnerable_parameter = False elif vulnerable_parameter: # If parameter is vulnerable, just skip till next parameter continue log_verbose(f"[¨] {mutated_request}") try: response = await self.crawler.async_send(mutated_request) except ReadTimeout: # The request with time based payload did timeout, what about a regular request? if await self.does_timeout(request): self.network_errors += 1 logging.error( "[!] Too much lag from website, can't reliably test time-based blind SQL" ) break if parameter == "QUERY_STRING": vuln_message = Messages.MSG_QS_INJECT.format( self.MSG_VULN, page) log_message = Messages.MSG_QS_INJECT else: vuln_message = _( "{0} via injection in the parameter {1}").format( self.MSG_VULN, parameter) log_message = Messages.MSG_PARAM_INJECT await self.add_vuln_critical(request_id=request.path_id, category=NAME, request=mutated_request, info=vuln_message, parameter=parameter, wstg=WSTG_CODE) log_red("---") log_red(log_message, self.MSG_VULN, page, parameter) log_red(Messages.MSG_EVIL_REQUEST) log_red(mutated_request.http_repr()) log_red("---") # We reached maximum exploitation for this parameter, don't send more payloads vulnerable_parameter = True continue except RequestError: self.network_errors += 1 continue else: if response.is_server_error and not saw_internal_error: saw_internal_error = True if parameter == "QUERY_STRING": anom_msg = Messages.MSG_QS_500 else: anom_msg = Messages.MSG_PARAM_500.format(parameter) await self.add_anom_high(request_id=request.path_id, category=Messages.ERROR_500, request=mutated_request, info=anom_msg, parameter=parameter, wstg=INTERNAL_ERROR_WSTG_CODE) log_orange("---") log_orange(Messages.MSG_500, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(mutated_request.http_repr()) log_orange("---")
async def attack(self, request: Request): warned = False timeouted = False page = request.path saw_internal_error = False current_parameter = None vulnerable_parameter = False for mutated_request, parameter, payload, flags in self.mutator.mutate( request): if current_parameter != parameter: # Forget what we know about current parameter current_parameter = parameter vulnerable_parameter = False elif vulnerable_parameter: # If parameter is vulnerable, just skip till next parameter continue log_verbose(f"[¨] {mutated_request}") try: response = await self.crawler.async_send(mutated_request) except ReadTimeout: self.network_errors += 1 if timeouted: continue log_orange("---") log_orange(Messages.MSG_TIMEOUT, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(mutated_request.http_repr()) log_orange("---") if parameter == "QUERY_STRING": anom_msg = Messages.MSG_QS_TIMEOUT else: anom_msg = Messages.MSG_PARAM_TIMEOUT.format(parameter) await self.add_anom_medium(request_id=request.path_id, category=Messages.RES_CONSUMPTION, request=mutated_request, info=anom_msg, parameter=parameter, wstg=RESOURCE_CONSUMPTION_WSTG_CODE) timeouted = True except RequestError: self.network_errors += 1 continue else: file_warning = None # original_payload = self.payload_to_rules[flags.section] for rule in self.payload_to_rules[flags.section]: if rule in response.content: found_pattern = rule vulnerable_method = self.rules_to_messages[rule] inclusion_succeed = True break else: # No successful inclusion or directory traversal but perhaps we can control something inclusion_succeed = False file_warning = find_warning_message( response.content, payload) if file_warning: found_pattern = file_warning.pattern vulnerable_method = file_warning.function else: found_pattern = vulnerable_method = None if found_pattern: # Interesting pattern found, either inclusion or error message if await self.is_false_positive(request, found_pattern): continue if not inclusion_succeed: if warned: # No need to warn more than once continue # Mark as eventuality vulnerable_method = _("Possible {0} vulnerability" ).format(vulnerable_method) warned = True # An error message implies that a vulnerability may exists if parameter == "QUERY_STRING": vuln_message = Messages.MSG_QS_INJECT.format( vulnerable_method, page) else: vuln_message = _( "{0} via injection in the parameter {1}").format( vulnerable_method, parameter) constraint_message = "" if file_warning and file_warning.uri: constraints = has_prefix_or_suffix( payload, file_warning.uri) if constraints: constraint_message += _("Constraints: {}").format( ", ".join(constraints)) vuln_message += " (" + constraint_message + ")" await self.add_vuln_critical(request_id=request.path_id, category=NAME, request=mutated_request, info=vuln_message, parameter=parameter, wstg=WSTG_CODE) log_red("---") log_red( Messages.MSG_QS_INJECT if parameter == "QUERY_STRING" else Messages.MSG_PARAM_INJECT, vulnerable_method, page, parameter) if constraint_message: log_red(constraint_message) log_red(Messages.MSG_EVIL_REQUEST) log_red(mutated_request.http_repr()) log_red("---") if inclusion_succeed: # We reached maximum exploitation for this parameter, don't send more payloads vulnerable_parameter = True continue elif response.is_server_error and not saw_internal_error: saw_internal_error = True if parameter == "QUERY_STRING": anom_msg = Messages.MSG_QS_500 else: anom_msg = Messages.MSG_PARAM_500.format(parameter) await self.add_anom_high(request_id=request.path_id, category=Messages.ERROR_500, request=mutated_request, info=anom_msg, parameter=parameter, wstg=INTERNAL_ERROR_WSTG_CODE) log_orange("---") log_orange(Messages.MSG_500, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(mutated_request.http_repr()) log_orange("---")
def process_certificate_info(certinfo_result): for cert_deployment in certinfo_result.certificate_deployments: leaf_certificate = cert_deployment.received_certificate_chain[0] message = _("Certificate subject: {0}").format( get_common_name(leaf_certificate.subject)) log_blue(message) yield INFO_LEVEL, message message = _("Alt. names: {0}").format( extract_dns_subject_alternative_names(leaf_certificate)) log_blue(message) yield INFO_LEVEL, message message = _("Issuer: {0}").format( get_common_name(leaf_certificate.issuer)) log_blue(message) yield INFO_LEVEL, message if not cert_deployment.leaf_certificate_subject_matches_hostname: message = _( "Requested hostname doesn't match those in the certificate") log_red(message) yield CRITICAL_LEVEL, message if not cert_deployment.received_chain_has_valid_order: message = _("Certificate chain is in invalid order") log_orange(message) yield MEDIUM_LEVEL, message public_key = leaf_certificate.public_key() if isinstance(public_key, EllipticCurvePublicKey): key_size = public_key.curve.key_size else: key_size = public_key.key_size if public_key.__class__.__name__ == "_RSAPublicKey": algorithm = "RSA" elif public_key.__class__.__name__ == "_EllipticCurvePublicKey": algorithm = "ECC" else: algorithm = public_key.__class__.__name__ message = _("Key: {0} {1} bits").format(algorithm, key_size) log_blue(message) yield INFO_LEVEL, message message = _("Signature Algorithm: {0}").format( leaf_certificate.signature_hash_algorithm.name) log_blue(message) yield INFO_LEVEL, message # print(f"Valid from {leaf_certificate.not_valid_before} to {leaf_certificate.not_valid_after}") if leaf_certificate.not_valid_after > datetime.utcnow(): # We should add a method for humanize inside our language package # _t = humanize.i18n.activate("fr_FR") message = _("Certificate expires in ") + \ humanize.precisedelta(leaf_certificate.not_valid_after - datetime.utcnow()) log_green(message) yield INFO_LEVEL, message else: message = _("Certificate has expired at" ) + f" {leaf_certificate.not_valid_after}" log_red(message) yield CRITICAL_LEVEL, message if not cert_deployment.leaf_certificate_is_ev: message = _("Certificate doesn't use Extended Validation") log_orange(message) yield MEDIUM_LEVEL, message # https://en.wikipedia.org/wiki/OCSP_stapling if not cert_deployment.leaf_certificate_has_must_staple_extension: message = _("OCSP Must-Staple extension is missing") log_orange(message) yield MEDIUM_LEVEL, message if cert_deployment.leaf_certificate_signed_certificate_timestamps_count is None: message = _("Certificate transparency:") + " " + _( "Unknown (Your OpenSSL version is not recent enough)") log_orange(message) yield MEDIUM_LEVEL, message elif cert_deployment.leaf_certificate_signed_certificate_timestamps_count: message = _("Certificate transparency:") + " " + _("Yes") + \ f" ({cert_deployment.leaf_certificate_signed_certificate_timestamps_count} SCT)" log_green(message) yield INFO_LEVEL, message else: message = _("Certificate transparency:") + " " + _("No") log_red(message) yield HIGH_LEVEL, message if cert_deployment.verified_chain_has_sha1_signature: message = _( "One of the certificate in the chain is signed using SHA-1") log_red(message) yield HIGH_LEVEL, message for validation_result in cert_deployment.path_validation_results: if not validation_result.was_validation_successful: message = _( "Certificate is invalid for {} trust store: {}").format( validation_result.trust_store.name, validation_result.openssl_error_string) log_red(message) yield CRITICAL_LEVEL, message # Currently we stop at the first certificate of the server, maybe improve later # Right now several certificates generates too much confusion in report break
def analyze(hostname: str, port: int) -> List[Tuple[int, str]]: results = [] # Define the server that you want to scan try: server_location = ServerNetworkLocation(hostname, port) except ServerHostnameCouldNotBeResolved: log_red(_("Could not resolve {0}"), hostname) return results # Then queue some scan commands for the server scanner = Scanner() server_scan_req = ServerScanRequest( server_location=server_location, scan_commands={ ScanCommand.CERTIFICATE_INFO, ScanCommand.SSL_2_0_CIPHER_SUITES, ScanCommand.SSL_3_0_CIPHER_SUITES, ScanCommand.TLS_1_0_CIPHER_SUITES, ScanCommand.TLS_1_1_CIPHER_SUITES, ScanCommand.TLS_1_2_CIPHER_SUITES, ScanCommand.TLS_1_3_CIPHER_SUITES, ScanCommand.ROBOT, ScanCommand.HEARTBLEED, ScanCommand.TLS_COMPRESSION, ScanCommand.TLS_FALLBACK_SCSV, ScanCommand.TLS_1_3_EARLY_DATA, ScanCommand.OPENSSL_CCS_INJECTION, ScanCommand.SESSION_RENEGOTIATION, ScanCommand.HTTP_HEADERS }, network_configuration=ServerNetworkConfiguration( tls_server_name_indication=server_location.hostname, network_timeout=5, network_max_retries=2)) scanner.queue_scans([server_scan_req]) # TLS 1.2 / 1.3 results good_protocols = { ScanCommand.TLS_1_2_CIPHER_SUITES: "TLS v1.2", ScanCommand.TLS_1_3_CIPHER_SUITES: "TLS v1.3" } # https://blog.mozilla.org/security/2014/10/14/the-poodle-attack-and-the-end-of-ssl-3-0/ # https://blog.qualys.com/product-tech/2018/11/19/grade-change-for-tls-1-0-and-tls-1-1-protocols bad_protocols = { ScanCommand.SSL_2_0_CIPHER_SUITES: "SSL v2", ScanCommand.SSL_3_0_CIPHER_SUITES: "SSL v3", ScanCommand.TLS_1_0_CIPHER_SUITES: "TLS v1.0", ScanCommand.TLS_1_1_CIPHER_SUITES: "TLS v1.1" } # Then retrieve the results for result in scanner.get_results(): log_blue("\n" + _("Results for") + f" {result.server_location.hostname}:") deprecated_protocols = [] if result.connectivity_error_trace: # Stuff like connection timeout log_red(result.connectivity_error_trace) continue for scan_command in result.scan_result.__annotations__: scan_results = getattr(result.scan_result, scan_command) if scan_results.error_reason: log_red(scan_results.error_reason) continue if scan_results.status != ScanCommandAttemptStatusEnum.COMPLETED: continue if scan_command == ScanCommand.CERTIFICATE_INFO: for level, message in process_certificate_info( scan_results.result): results.append((level, message)) elif scan_command in bad_protocols: if scan_results.result.accepted_cipher_suites: deprecated_protocols.append(bad_protocols[scan_command]) elif scan_command == ScanCommand.ROBOT: if scan_results.result.robot_result in ( RobotScanResultEnum.VULNERABLE_WEAK_ORACLE, RobotScanResultEnum.VULNERABLE_STRONG_ORACLE): message = _("Server is vulnerable to ROBOT attack") log_red(message) results.append((CRITICAL_LEVEL, message)) elif scan_command == ScanCommand.HEARTBLEED: if scan_results.result.is_vulnerable_to_heartbleed: message = _("Server is vulnerable to Heartbleed attack") log_red(message) results.append((CRITICAL_LEVEL, message)) elif scan_command == ScanCommand.TLS_COMPRESSION: if scan_results.result.supports_compression: message = _( "Server is vulnerable to CRIME attack (compression is supported)" ) log_red(message) results.append((CRITICAL_LEVEL, message)) elif scan_command == ScanCommand.TLS_FALLBACK_SCSV: if not scan_results.result.supports_fallback_scsv: message = _( "Server is vulnerable to downgrade attacks (support for TLS_FALLBACK_SCSV is missing)" ) log_red(message) results.append((CRITICAL_LEVEL, message)) elif scan_command == ScanCommand.TLS_1_3_EARLY_DATA: # https://blog.trailofbits.com/2019/03/25/what-application-developers-need-to-know-about-tls-early-data-0rtt/ if scan_results.result.supports_early_data: message = _( "TLS 1.3 Early Data (0RTT) is vulnerable to replay attacks" ) log_orange(message) results.append((MEDIUM_LEVEL, message)) elif scan_command == ScanCommand.OPENSSL_CCS_INJECTION: if scan_results.result.is_vulnerable_to_ccs_injection: message = _( "Server is vulnerable to OpenSSL CCS (CVE-2014-0224)") log_red(message) results.append((CRITICAL_LEVEL, message)) elif scan_command == ScanCommand.SESSION_RENEGOTIATION: if scan_results.result.is_vulnerable_to_client_renegotiation_dos: message = _( "Server honors client-initiated renegotiations (vulnerable to DoS attacks)" ) log_red(message) results.append((HIGH_LEVEL, message)) if not scan_results.result.supports_secure_renegotiation: message = _("Server doesn't support secure renegotiations") log_orange(message) results.append((MEDIUM_LEVEL, message)) elif scan_command == ScanCommand.HTTP_HEADERS: if scan_results.result.strict_transport_security_header is None: message = _("Strict Transport Security (HSTS) is not set") log_red(message) results.append((HIGH_LEVEL, message)) elif scan_command in good_protocols: for level, message in process_cipher_suites( scan_results.result, good_protocols[scan_command]): results.append((level, message)) if deprecated_protocols: message = _("The following protocols are deprecated and/or insecure and should be deactivated:") + \ " " + ", ".join(deprecated_protocols) log_red(message) results.append((CRITICAL_LEVEL, message)) return results
async def attempt_exploit(self, method, payloads, original_request, parameter, taint): timeouted = False page = original_request.path saw_internal_error = False attack_mutator = Mutator(methods=method, payloads=payloads, qs_inject=self.must_attack_query_string, parameters=[parameter], skip=self.options.get("skipped_parameters")) for evil_request, xss_param, xss_payload, xss_flags in attack_mutator.mutate( original_request): log_verbose(f"[¨] {evil_request}") try: response = await self.crawler.async_send(evil_request) except ReadTimeout: self.network_errors += 1 if timeouted: continue log_orange("---") log_orange(Messages.MSG_TIMEOUT, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(evil_request.http_repr()) log_orange("---") if xss_param == "QUERY_STRING": anom_msg = Messages.MSG_QS_TIMEOUT else: anom_msg = Messages.MSG_PARAM_TIMEOUT.format(xss_param) await self.add_anom_medium(request_id=original_request.path_id, category=Messages.RES_CONSUMPTION, request=evil_request, info=anom_msg, parameter=xss_param, wstg=RESOURCE_CONSUMPTION_WSTG_CODE) timeouted = True except RequestError: self.network_errors += 1 else: if (not response.is_redirect and valid_xss_content_type(evil_request) and check_payload(self.DATA_DIR, self.PAYLOADS_FILE, self.external_endpoint, self.proto_endpoint, response, xss_flags, taint)): self.successful_xss[taint] = (xss_payload, xss_flags) message = _( "XSS vulnerability found via injection in the parameter {0}" ).format(xss_param) if has_strong_csp(response): message += ".\n" + _( "Warning: Content-Security-Policy is present!") await self.add_vuln_medium( request_id=original_request.path_id, category=NAME, request=evil_request, parameter=xss_param, info=message, wstg=WSTG_CODE) if xss_param == "QUERY_STRING": injection_msg = Messages.MSG_QS_INJECT else: injection_msg = Messages.MSG_PARAM_INJECT log_red("---") log_red(injection_msg, self.MSG_VULN, page, xss_param) if has_strong_csp(response): log_red( _("Warning: Content-Security-Policy is present!")) log_red(Messages.MSG_EVIL_REQUEST) log_red(evil_request.http_repr()) log_red("---") # stop trying payloads and jump to the next parameter break if response.is_server_error and not saw_internal_error: if xss_param == "QUERY_STRING": anom_msg = Messages.MSG_QS_500 else: anom_msg = Messages.MSG_PARAM_500.format(xss_param) await self.add_anom_high( request_id=original_request.path_id, category=Messages.ERROR_500, request=evil_request, info=anom_msg, parameter=xss_param, wstg=INTERNAL_ERROR_WSTG_CODE) log_orange("---") log_orange(Messages.MSG_500, page) log_orange(Messages.MSG_EVIL_REQUEST) log_orange(evil_request.http_repr()) log_orange("---") saw_internal_error = True