From c1d178fae95f9c29135ce33f28018abafe43f01c Mon Sep 17 00:00:00 2001 From: Oussama Benghechoua Date: Thu, 27 Jul 2023 17:03:20 +0200 Subject: [PATCH] Update CSP module adding more information about the CSP directives and the unsafe values to avoid --- wapitiCore/attack/mod_csp.py | 85 +++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/wapitiCore/attack/mod_csp.py b/wapitiCore/attack/mod_csp.py index 85751c3d8..4be93b6fb 100644 --- a/wapitiCore/attack/mod_csp.py +++ b/wapitiCore/attack/mod_csp.py @@ -21,16 +21,68 @@ from wapitiCore.attack.attack import Attack from wapitiCore.net import Request from wapitiCore.net.response import Response -from wapitiCore.net.csp_utils import csp_header_to_dict, CSP_CHECK_LISTS, check_policy_values +from wapitiCore.net.csp_utils import csp_header_to_dict, CSP_CHECK_LISTS from wapitiCore.definitions.csp import NAME, WSTG_CODE from wapitiCore.main.log import log_red MSG_NO_CSP = "CSP is not set" -MSG_CSP_MISSING = "CSP attribute \"{0}\" is missing" -MSG_CSP_UNSAFE = "CSP \"{0}\" value is not safe" +INFO_UNSAFE_INLINE = "\"unsafe-inline\" in \"{0}\" directive allows the execution of unsafe in-page scripts and event\ + handlers." +INFO_UNSAFE_EVAL = "\"unsafe-eval\" in \"{0}\" directive allows the execution of code injected into DOM APIs such as\ + eval()." +INFO_DATA_HTTP_HTTPS = "value \"{0}\" URI in \"{1}\" allows the execution of unsafe scripts." +INFO_ALLOW_ALL = "\"{0}\" directive should not allow \"*\" as source" +INFO_UNSAFE_OBJECT_SRC = "unsafe values \"{0}\" other then \"none\" identified in \"object-src\"" +INFO_UNSAFE_BASE_URI = "unsafe values \"{0}\" other then \"none\" and \"self\" identified in \"base-uri\"" +INFO_UNDEFINED_DIRECTIVE = "directive \"{0}\" is not defined" # This module check the basics recommendations of CSP +def check_policy(policy_name, csp_dict): + """ + This function return the unsafe values for each directive of the tested CSP + """ + info = "" + + if policy_name not in csp_dict and "default-src" not in csp_dict: + log_red(INFO_UNDEFINED_DIRECTIVE.format(policy_name)) + info += INFO_UNDEFINED_DIRECTIVE.format(policy_name) + "\n" + + # The HTTP CSP "default-src" directive serves as a fallback for the other CSP fetch directives. + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src + policy_values = csp_dict.get(policy_name) or csp_dict["default-src"] + info = "" + # If the tested element is default-src or script-src, we must ensure that none of this unsafe values are present + if policy_name in ["default-src", "script-src"]: + for unsafe_value in CSP_CHECK_LISTS[policy_name]: + if unsafe_value in policy_values: + if unsafe_value == "unsafe-inline": + log_red(INFO_UNSAFE_INLINE.format(policy_name)) + info += INFO_UNSAFE_INLINE.format(policy_name) + "\n" + elif unsafe_value in ("data:", "http:", "https:"): + log_red(INFO_DATA_HTTP_HTTPS.format(unsafe_value, policy_name)) + info += INFO_DATA_HTTP_HTTPS.format(unsafe_value, policy_name) + "\n" + elif unsafe_value == "*": + log_red(INFO_ALLOW_ALL.format(policy_name)) + info += INFO_ALLOW_ALL.format(policy_name) + "\n" + elif unsafe_value == "unsafe-eval": + log_red(INFO_UNSAFE_EVAL.format(policy_name)) + info += INFO_UNSAFE_EVAL.format(policy_name) + "\n" + + # If the tested element is none of the previous list, we must ensure that one of this safe values is present + else: + for safe_value in CSP_CHECK_LISTS[policy_name]: + if safe_value not in policy_values: + if policy_name == "object-src": + log_red(INFO_UNSAFE_OBJECT_SRC.format(policy_values)) + info += INFO_UNSAFE_OBJECT_SRC.format(policy_values) + "\n" + elif policy_name == "base-uri": + log_red(INFO_UNSAFE_BASE_URI.format(policy_values)) + info += INFO_UNSAFE_BASE_URI.format(policy_values) + "\n" + + return info + + class ModuleCsp(Attack): """Evaluate the security level of Content Security Policies of the web server.""" name = "csp" @@ -65,21 +117,14 @@ async def attack(self, request: Request, response: Optional[Response] = None): ) else: csp_dict = csp_header_to_dict(response.headers["Content-Security-Policy"]) - + info = "" for policy_name in CSP_CHECK_LISTS: - result = check_policy_values(policy_name, csp_dict) - - if result <= 0: - if result == -1: - info = MSG_CSP_MISSING.format(policy_name) - else: # result == 0 - info = MSG_CSP_UNSAFE.format(policy_name) - - log_red(info) - await self.add_vuln_low( - category=NAME, - request=request_to_root, - info=info, - wstg=WSTG_CODE, - response=response - ) + info += check_policy(policy_name, csp_dict) + + await self.add_vuln_low( + category=NAME, + request=request, + info=info, + wstg=WSTG_CODE, + response=response + )