├── Getting Started Guide.pdf ├── ZAP_Scripts ├── README.md ├── httpfuzzerprocessor │ ├── password_security_wordlist.txt.gz │ ├── default_accounts_wordlist.txt │ ├── 2-5-4-default-accounts.js │ ├── 3-2-1-new-session-token.js │ └── password_security.js ├── passive │ ├── 14-4-5-strict-transport-security-header.py │ ├── 3-4-3-cookie-samesite-attribute.py │ ├── 8-2-1-anti-cache-header.py │ ├── 14-4-2-api-content-disposition-header.py │ ├── 9-1-1-tls-communication.py │ ├── 3-4-2-cookie-httponly-attribute.py │ ├── 2-5-2-secret-questions.py │ ├── 5-1-1-HTTP parameter-pollution.py │ ├── 14-4-6-referrer-policy-header.py │ ├── 14-4-4-x-content-type-options-nosnif.py │ ├── 3-4-5-cookie-path-attribute.py │ ├── 4-1-2-Hidden-fields.py │ ├── 3-4-4-cookie-host-prefix.py │ ├── 3-4-1-cookie-secure-attribute.py │ ├── 14-3-3-server-version-info.py │ ├── 3-1-1-token-in-url.py │ ├── 5-2-4-eval-body.py │ ├── 13-1-3-key-in-api-url.py │ ├── 14-5-2-Origin-header.py │ ├── 3-2-2-session-token-entropy.py │ ├── 14-3-2-Debug-modes.py │ ├── 4-1-5-fail-securely.py │ ├── 14-4-7-x-frame-options-header.py │ ├── 14-4-3-csp-header.py │ ├── 5-3-2-preserve-encoding.py │ ├── 14-4-1-content-type-header.py │ ├── 8-3-1-sensitive-data-parameters.py │ ├── 14-2-3-Subresource-Integrity.py │ ├── 4-2-2-CSRF-tokens.py │ └── 14-3-1-Error-messages.py ├── active │ ├── 14-5-3-CORS-header.py │ ├── 5-2-7-svg-script-injection.py │ ├── 5-2-3-imap-injection.py │ ├── 14-5-1-HTTP-methods.py │ ├── 5-2-8-template-language-injection.py │ └── 5-3-6-json-injection.py └── standalone │ └── reformat-alerts.js ├── SECURITY.md ├── README.md ├── LICENSE └── ASVS.policy /Getting Started Guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YaleUniversity/ZAP_ASVS_Checks/HEAD/Getting Started Guide.pdf -------------------------------------------------------------------------------- /ZAP_Scripts/README.md: -------------------------------------------------------------------------------- 1 | This directory uses sub-directories that follow ZAP's naming convention for easy script integration (i.e., passive, active, and standalone). 2 | -------------------------------------------------------------------------------- /ZAP_Scripts/httpfuzzerprocessor/password_security_wordlist.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YaleUniversity/ZAP_ASVS_Checks/HEAD/ZAP_Scripts/httpfuzzerprocessor/password_security_wordlist.txt.gz -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Versions of the ZAC (ZAP_ASVS_Checks) project that are currently 6 | being supported include the following. 7 | 8 | | Version | Supported | 9 | | ------------- | ------------------ | 10 | | 0.0.x-alpha | :white_check_mark: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | To report a vulnerability or bug, please use this GitHub project's 'Issues' resource. 15 | Because ZAC is supported on a best-effort basis, updates on issues do not happen on a 16 | schedule. If an vulnerability/bug is accepted, it will be fixed. 17 | -------------------------------------------------------------------------------- /ZAP_Scripts/httpfuzzerprocessor/default_accounts_wordlist.txt: -------------------------------------------------------------------------------- 1 | admin 2 | administrator 3 | root 4 | sa 5 | tech 6 | operator 7 | debug 8 | adm 9 | user 10 | adminttd 11 | security 12 | manager 13 | recovery 14 | sysadmin 15 | system 16 | customer 17 | guest 18 | superuser 19 | super 20 | Admin 21 | Administrator 22 | Root 23 | Sa 24 | Tech 25 | Operator 26 | Debug 27 | Adm 28 | User 29 | Adminttd 30 | Security 31 | Manager 32 | Recovery 33 | Sysadmin 34 | System 35 | Customer 36 | Guest 37 | Superuser 38 | Super 39 | ADMIN 40 | ADMINISTRATOR 41 | ROOT 42 | SA 43 | TECH 44 | OPERATOR 45 | DEBUG 46 | ADM 47 | USER 48 | ADMINTTD 49 | SECURITY 50 | MANAGER 51 | RECOVERY 52 | SYSADMIN 53 | SYSTEM 54 | CUSTOMER 55 | GUEST 56 | SUPERUSER 57 | SUPER -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZAP_ASVS_Checks 2 | The ZAP_ASVS_Checks (ZAC) project aims to extend the [ZAP](https://owasp.org/www-project-zap/) vulnerability scanner with scripts to implement OWASP [ASVS](https://github.com/OWASP/ASVS) L1 controls checking. 3 | 4 | This project is inspired by the [OWASP ASVS 4.0 testing guide](https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide) project by BlazingWind, which provides a number of ZAP scripts and a BASH script for ASVS L1 checks. For additional information about the BlazingWind project, please see the blog post, [Automate checking ASVS controls using ZAP scripts](https://www.zaproxy.org/blog/2021-02-10-automate-checking-asvs-controls-using-zap-scripts/). 5 | 6 | In-depth instructions and demonstration on how to use our scripts can be [found on Vimeo.](https://vimeo.com/702190939) 7 | For a quick read, check out our [Getting Started Guide.](https://github.com/YaleUniversity/ZAP_ASVS_Checks/blob/main/Getting%20Started%20Guide.pdf) 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Yale University 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-4-5-strict-transport-security-header.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 14.4.5 control from OWASP ASVS 4.0: 4 | '14.4.5 Verify that a Strict-Transport-Security header is included on all responses 5 | and for all subdomains, such as Strict-Transport-Security: max- 6 | age=15724800; includeSubdomains.' 7 | 8 | The script will raise an alert if 'Strict-Transport-Security' header is not present. 9 | 10 | """ 11 | 12 | def scan(ps, msg, src): 13 | 14 | #find "Strict-Transport-Security" header 15 | header = str(msg.getResponseHeader().getHeader("Strict-Transport-Security")) 16 | 17 | #alert parameters 18 | alertRisk= 1 19 | alertConfidence = 2 20 | alertTitle = "14.4.5 Verify that a Strict-Transport-Security header is included on all responses." 21 | alertDescription = "Verify that a Strict-Transport-Security header is included on all responses and for all subdomains, such as Strict-Transport-Security: max-age=15724800; includeSubdomains." 22 | url = msg.getRequestHeader().getURI().toString() 23 | alertParam = "" 24 | alertAttack = "" 25 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html" 26 | alertSolution = "Add 'Strict-Transport-Security' header to all HTTP repsponses." 27 | alertEvidence = "" 28 | cweID = 523 29 | wascID = 0 30 | 31 | #if header is not present (equals "None"), raise alert 32 | if (header == "None"): 33 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 34 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 35 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/3-4-3-cookie-samesite-attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 3.4.3 control from OWASP ASVS 4.0: 4 | Verify that cookie-based session tokens utilize the 'SameSite' attribute to limit exposure to cross-site request forgery attacks. 5 | 6 | 7 | The script will raise an alert if 'SameSite' attribute is not present. 8 | 9 | """ 10 | 11 | def scan(ps, msg, src): 12 | 13 | #find "Set-Cookie" header 14 | headerCookie = str(msg.getResponseHeader().getHeader("Set-Cookie")) 15 | 16 | #alert parameters 17 | alertRisk= 1 18 | alertConfidence = 2 19 | alertTitle = "3.4.3 Verify that cookie-based session tokens utilize the 'SameSite' attribute." 20 | alertDescription = "SameSite prevents the browser from sending this cookie along with cross-site requests. The main goal is to mitigate the risk of cross-origin information leakage. It also provides some protection against cross-site request forgery attacks. Possible values for the flag are none, lax, or strict." 21 | url = msg.getRequestHeader().getURI().toString() 22 | alertParam = "" 23 | alertAttack = "" 24 | alertInfo = "https://owasp.org/www-community/SameSite" 25 | alertSolution = "Add 'SameSite' attribute when sending cookie." 26 | alertEvidence = "" 27 | cweID = 16 28 | wascID = 0 29 | 30 | #if "Set-Cookie" header does not have "samesite" attribute, raise alert 31 | if ((headerCookie != "None") and "samesite" not in headerCookie.lower()): 32 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 33 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 34 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/8-2-1-anti-cache-header.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 8.2.1 control from OWASP ASVS 4.0: 4 | 'Verify the application sets sufficient anti-caching headers so that sensitive 5 | data is not cached in modern browsers.' 6 | 7 | The script will raise an alert if 'Cache-Control' and 'Pragma' headers are not present. 8 | 9 | """ 10 | 11 | def scan(ps, msg, src): 12 | 13 | #find "Cache-Control" and "Pragma" headers 14 | header_cache = str(msg.getResponseHeader().getHeader("Cache-Control")) 15 | header_pragma = str(msg.getResponseHeader().getHeader("Pragma")) 16 | 17 | #alert parameters 18 | alertRisk= 1 19 | alertConfidence = 2 20 | alertTitle = "8.2.1 Verify the application sets sufficient anti-caching headers." 21 | alertDescription = "Verify the application sets sufficient anti-caching headers so that sensitive data is not cached in modern browsers." 22 | url = msg.getRequestHeader().getURI().toString() 23 | alertParam = "" 24 | alertAttack = "" 25 | alertInfo = "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/04-Authentication_Testing/06-Testing_for_Browser_Cache_Weaknesses" 26 | alertSolution = "Add anti caching headers to HTTP response (Cache-Control, Pragma)." 27 | alertEvidence = "" 28 | cweID = 525 29 | wascID = 0 30 | 31 | #if 'Cache-Control' and 'Pragma' headers are not present 32 | if (header_cache == "None" and header_pragma == "None"): 33 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 34 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 35 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-4-2-api-content-disposition-header.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 14.4.2 control from OWASP ASVS 4.0: 4 | 'Verify that all API responses contain a Content-Disposition: attachment; 5 | filename="api.json" header (or other appropriate filename for the content 6 | type).' 7 | 8 | The script will raise an alert if 'Content-Disposition' header is present but not follow the format - Content-Disposition: attachment; filename= 9 | 10 | """ 11 | 12 | def scan(ps, msg, src): 13 | 14 | #find "Content-Disposition" header 15 | header = str(msg.getResponseHeader().getHeader("Content-Disposition")) 16 | 17 | #alert parameters 18 | alertRisk= 1 19 | alertConfidence = 2 20 | alertTitle = "14.4.2 Verify that all API responses contain a Content-Disposition." 21 | alertDescription = "Verify that all API responses contain a Content-Disposition: attachment; filename='api.json'header (or other appropriate filename for the content type)." 22 | url = msg.getRequestHeader().getURI().toString() 23 | alertParam = "" 24 | alertAttack = "" 25 | alertInfo = "https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload" 26 | alertSolution = "Use the format 'Content-Disposition: attachment; filename=' for API responses" 27 | alertEvidence = "" 28 | cweID = 116 29 | wascID = 0 30 | 31 | # if "attachment; filename=" is not in "Content-Disposition" header, raise alert 32 | if ("attachment; filename=" not in header.lower()): 33 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 34 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 35 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/9-1-1-tls-communication.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 9.1.1 control from OWASP ASVS 4.0: 4 | 'Verify that TLS is used for all client connectivity, and does not fall back to insecure or unencrypted communications.' 5 | 6 | The script will raise an alert if the client is able to connect the application through http which has no encryption. 7 | 8 | """ 9 | import re 10 | 11 | def scan(ps, msg, src): 12 | 13 | #alert parameters 14 | alertRisk= 1 15 | alertConfidence = 1 16 | alertTitle = "9.1.1 Verify that TLS is used for all client connectivity." 17 | alertDescription = "Verify that TLS is used for all client connectivity, and does not fall back to insecure or unencrypted communications." 18 | url = msg.getRequestHeader().getURI().toString() 19 | alertParam = "" 20 | alertAttack = "" 21 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html" 22 | alertSolution = "A request was made with HTTP ---> " + url + "\n" + "Ensure at least version TLSv1.2 is used for client connectivity." 23 | alertEvidence = "" 24 | cweID = 319 25 | wascID = 0 26 | 27 | code = str(msg.getResponseHeader().getStatusCode()) # get status code 28 | pattern = re.compile(r"2[0-9]{2}") #regular expression for codes 200-209 29 | 30 | #if url contains http and msg returns a successful status code, raise alert 31 | if ("http://" in url and re.search(pattern,code)): 32 | alertEvidence = "Code: " + code 33 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 34 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 35 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/3-4-2-cookie-httponly-attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 3.4.2 control from OWASP ASVS 4.0: 4 | Verify that cookie-based session tokens have the 'HttpOnly' attribute set. 5 | 6 | The script will raise an alert if 'HttpOnly' attribute is not present. 7 | 8 | """ 9 | 10 | def scan(ps, msg, src): 11 | 12 | #find "Set-Cookie" header 13 | headerCookie = str(msg.getResponseHeader().getHeader("Set-Cookie")) 14 | 15 | #alert parameters 16 | alertRisk= 1 17 | alertConfidence = 2 18 | alertTitle = "3.4.2 Verify that cookie-based session tokens have the 'HttpOnly' attribute set." 19 | alertDescription = "If the HttpOnly flag (optional) is included in the HTTP response header, the cookie cannot be accessed through client side script (again if the browser supports this flag). As a result, even if a cross-site scripting (XSS) flaw exists, and a user accidentally accesses a link that exploits this flaw, the browser (primarily Internet Explorer) will not reveal the cookie to a third party." 20 | url = msg.getRequestHeader().getURI().toString() 21 | alertParam = "" 22 | alertAttack = "" 23 | alertInfo = "https://owasp.org/www-community/HttpOnly" 24 | alertSolution = "Add 'HttpOnly' attribute when sending cookie." 25 | alertEvidence = "" 26 | cweID = 1004 27 | wascID = 0 28 | 29 | #if "Set-Cookie" header does not have "httponly" attribute, raise alert 30 | if ((headerCookie != "None") and "httponly" not in headerCookie.lower()): 31 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 32 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 33 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/2-5-2-secret-questions.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 5.2.4 control from OWASP ASVS 4.0: 4 | 'Verify password hints or knowledge-based authentication (so called 'secret questions') are not present.' 5 | 6 | The script will raise an alert if 'secret question' is found in the response body. 7 | 8 | """ 9 | 10 | def scan(ps, msg, src): 11 | 12 | #alert parameters 13 | alertRisk= 0 14 | alertConfidence = 1 15 | alertTitle = "2.5.2 Verify password hints or knowledge-based authentication (so called 'secret questions') are not present." 16 | alertDescription = "Verify password hints or knowledge-based authentication (so called 'secret questions') are not present." 17 | url = msg.getRequestHeader().getURI().toString() 18 | alertParam = "" 19 | alertAttack = "" 20 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/Choosing_and_Using_Security_Questions_Cheat_Sheet.html" 21 | alertSolution = "Avoid using password hints and knowledge-based questions for account recovery." 22 | alertEvidence = "" 23 | cweID = 640 24 | wascID = 0 25 | 26 | parameters = ["secret question", "security question", "secretquestion", "securityquestion"] 27 | 28 | try: 29 | #get response body 30 | body = str(msg.getResponseBody()) 31 | 32 | for p in parameters: 33 | #if p is in response body, raise alert 34 | if (body != "None" and (p in body.lower())): 35 | alertEvidence = p + " was found." 36 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 37 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 38 | except UnicodeEncodeError: 39 | pass 40 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/5-1-1-HTTP parameter-pollution.py: -------------------------------------------------------------------------------- 1 | """ 2 | Credit to BlazingWind on Github for this script: https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide/blob/main/ZAP-scripts/5-1-1-HTTP%20parameter-pollution.py 3 | 4 | Helper script for 5.1.1 control from OWASP ASVS 4.0: 5 | 'Verify that the application has defenses against HTTP parameter pollution attacks, particularly if the application framework makes no distinction about the source of request parameters (GET, POST, cookies, headers, or environment variables).' 6 | 7 | The script looks for any parameters in URLs and in POST form fields and raises an alert on all parameters it has found. 8 | """ 9 | import re 10 | 11 | def scan(ps, msg, src): 12 | 13 | alertTitle = "5.1.1 Verify that the application has defenses against HTTP parameter pollution attacks, particularly if the application framework makes no distinction about the source of request parameters (GET, POST, cookies, headers, or environment variables)." 14 | alertDescription = "HTTP parameter pollution tests how the applications responds to multiple parameters with the same name. " 15 | param = msg.getParamNames() 16 | alertRisk = 0 17 | alertReliability = 1 18 | alertSolution = "Ensure proper input validation and input encoding." 19 | alertParam = "Query in URL or POST message" 20 | alertInfo = "Review parameters in the URL and POST form fields to assess control's success" 21 | cweID = 235 22 | wascID = 0 23 | 24 | if param: 25 | ps.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 26 | msg.getRequestHeader().getURI().toString(), 27 | alertParam, "", alertInfo, alertSolution, str(param), cweID, wascID, msg); -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-4-6-referrer-policy-header.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 14.4.6 control from OWASP ASVS 4.0: 4 | 'Verify that a suitable Referrer-Policy header is included to avoid exposing 5 | sensitive information in the URL through the Referer header to untrusted 6 | parties.' 7 | 8 | The script will raise an alert if 'Referrer-Policy' header is not present or does not contain 'strict-origin-when-cross-origin option'. 9 | 10 | """ 11 | 12 | def scan(ps, msg, src): 13 | 14 | #find "Referrer[Policy" header 15 | header = str(msg.getResponseHeader().getHeader("Referrer-Policy")) 16 | 17 | #alert parameters 18 | alertRisk= 1 19 | alertConfidence = 2 20 | alertTitle = "14.4.6 Verify that a suitable Referrer-Policy header is included." 21 | alertDescription = "Verify that a suitable Referrer-Policy header is included to avoid exposing sensitive information in the URL through the Referer header to untrusted parties." 22 | url = msg.getRequestHeader().getURI().toString() 23 | alertParam = "" 24 | alertAttack = "" 25 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#referrer-policy" 26 | alertSolution = "Add 'Referrer-Policy: strict-origin-when-cross-origin' header when sending HTTP response." 27 | alertEvidence = "" 28 | cweID = 116 29 | wascID = 0 30 | 31 | #if header is not present (equals "None") or does not contain "strict-origin-when-cross-origin", raise alert 32 | if ("strict-origin-when-cross-origin" not in header.lower()): 33 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 34 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 35 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-4-4-x-content-type-options-nosnif.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 14.4.4 control from OWASP ASVS 4.0: 4 | 'Verify that all responses contain a X-Content-Type-Options: nosniff header.' 5 | 6 | The script will raise an alert if 'X-Content-Type-Options: nosniff header is not present. 7 | 8 | """ 9 | 10 | def scan(ps, msg, src): 11 | 12 | #find "X-Content-Type-Options" header 13 | header = str(msg.getResponseHeader().getHeader("X-Content-Type-Options")) 14 | 15 | #alert parameters 16 | alertRisk= 1 17 | alertConfidence = 2 18 | alertTitle = "14.4.4 Verify that all responses contain a X-Content-Type-Options: nosniff header." 19 | alertDescription = "The X-Content-Type-Options response HTTP header is used by the server to prevent browsers from guessing the media type ( MIME type). This is known as MIME sniffing in which the browser guesses the correct MIME type by looking at the contents of the resource. The absence of this header might cause browsers to transform non-executable content into executable content." 20 | url = msg.getRequestHeader().getURI().toString() 21 | alertParam = "" 22 | alertAttack = "" 23 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html" 24 | alertSolution = "Add 'X-Content-Type-Options: nosniff header to all HTTP responses." 25 | alertEvidence = "X-Content-Type-Options" + header 26 | cweID = 116 27 | wascID = 0 28 | 29 | #if "no sniff" is not in header, raise alert 30 | if ("nosniff" not in header.lower()): 31 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 32 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 33 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/3-4-5-cookie-path-attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 3.4.5 control from OWASP ASVS 4.0: 4 | Verify that if the application is published under a domain name with other applications that set or use 5 | session cookies that might disclose the session cookies, set the path attribute in cookie-based session 6 | tokens using the most precise path possible. 7 | 8 | """ 9 | 10 | def scan(ps, msg, src): 11 | 12 | #find "Set-Cookie" header 13 | headerCookie = str(msg.getResponseHeader().getHeader("Set-Cookie")) 14 | 15 | #alert parameters 16 | alertRisk= 1 17 | alertConfidence = 2 18 | alertTitle = "3.4.5 Verify that cookie-based session tokens utilize the 'Path' attribute." 19 | alertDescription = "Verify that if the application is published under a domain name with other applications that set or use session cookies that might disclose the session cookies, set the path attribute in cookie-based session tokens using the most precise path possible." 20 | url = msg.getRequestHeader().getURI().toString() 21 | alertParam = "" 22 | alertAttack = "" 23 | alertInfo = "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/02-Testing_for_Cookies_Attributes" 24 | alertSolution = "If the application is published under a domain name with other applications, include the 'Path' Cookie attribute with the most specific path" 25 | alertEvidence = "" 26 | cweID = 16 27 | wascID = 0 28 | 29 | #if "Set-Cookie" header does not have "Path" attribute, raise alert 30 | if ((headerCookie != "None") and "path=" not in headerCookie.lower()): 31 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 32 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 33 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/4-1-2-Hidden-fields.py: -------------------------------------------------------------------------------- 1 | """ 2 | Credit to BlazingWind on Github for this script: https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide/blob/main/ZAP-scripts/4-1-2-Hidden-fields.py 3 | 4 | Helper script testing 4.1.2 control from OWASP ASVS 4.0: 5 | 'Verify that all user and data attributes and policy information used by access controls cannot be manipulated by end users unless specifically authorized.' 6 | 7 | The script looks for hidden fileds in the html of the website 8 | """ 9 | 10 | import re 11 | 12 | def scan(ps, msg, src): 13 | alertTitle = "4.1.2 Verify that all user and data attributes and policy information used by access controls cannot be manipulated by end users unless specifically authorized." 14 | alertDescription = "Among others, hidden fields should not be used for access control." 15 | alertRisk = 0 16 | alertReliability = 1 17 | header = str(msg.getResponseHeader().getHeader("Content-Type")) 18 | alertSolution = ["Use access control that is not accessible to users.", ""] 19 | alertInfo = "Review if hidden fields are used for access control, befor assessing control's success." 20 | cweID = 639 21 | wascID = 0 22 | 23 | # Search for regex match 24 | patternType = re.compile(r"text/.*|.*\+xml.*|application/xml.*") 25 | 26 | # Test the request and/or response here 27 | 28 | 29 | if (re.search(patternType,header)): 30 | body = msg.getResponseBody().toString() 31 | regexHidden = re.compile(r"type\s*=\s*['\"]?hidden['\"]?") #regex found in Hidden field passive tag rule in ZAP 32 | matchHidden = regexHidden.findall(body) 33 | for match in matchHidden: 34 | ps.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 35 | msg.getRequestHeader().getURI().toString(), 36 | str(match), "", alertInfo, alertSolution[0], match, cweID, wascID, msg); 37 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/3-4-4-cookie-host-prefix.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 3.4.4 control from OWASP ASVS 4.0: 4 | 5 | Verify that cookie-based session tokens use the "__Host-" prefix so cookies are only sent to the host that initially set the cookie. 6 | 7 | The script will raise an alert if "__Host-" prefix and secure attribute attribute are not present. 8 | 9 | """ 10 | 11 | def scan(ps, msg, src): 12 | 13 | #find "Set-Cookie" header 14 | setCookie = str(msg.getResponseHeader().getHeader("Set-Cookie")) 15 | cookie = setCookie.lower() 16 | 17 | #alert parameters 18 | alertRisk= 1 19 | alertConfidence = 2 20 | alertTitle = "3.4.4 Verify that cookie-based session tokens use the __Host- prefix." 21 | alertDescription = "3.4.4 Verify that cookie-based session tokens use the __Host- prefix so cookies are only sent to the host that initially set the cookie." 22 | url = msg.getRequestHeader().getURI().toString() 23 | alertParam = "" 24 | alertAttack = "" 25 | alertInfo = "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/02-Testing_for_Cookies_Attributes#:~:text=Host%20Prefix&text=The%20cookie%20must%20be%20set%20from%20a%20URI%20considered%20secure,every%20request%20to%20the%20host." 26 | alertSolution = "Rename cookie to include __Host- prefix if applicable." 27 | alertEvidence = "" 28 | cweID = 16 29 | wascID = 0 30 | 31 | #boolean value to check if the cookie contains the host prefix and secure attribute 32 | no_prefix_and_no_secure = "__host-" not in cookie and ("secure" not in cookie) 33 | 34 | 35 | #if no_prefix_and_no_secure is true, raise alert 36 | if ((cookie != "None") and no_prefix_and_no_secure): 37 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 38 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 39 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/3-4-1-cookie-secure-attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 3.4.1 control from OWASP ASVS 4.0: 4 | 'Verify that cookie-based session tokens have the 'Secure' attribute set.' 5 | 6 | The script will raise an alert if 'Secure' attribute is not present. 7 | 8 | """ 9 | 10 | def scan(ps, msg, src): 11 | 12 | #find "Set-Cookie" header 13 | headerCookie = str(msg.getResponseHeader().getHeader("Set-Cookie")) 14 | 15 | #alert parameters 16 | alertRisk= 1 17 | alertConfidence = 2 18 | alertTitle = "3.4.1 Verify that cookie-based session tokens have the 'Secure' attribute set." 19 | alertDescription = "The secure attribute is an option that can be set by the application server when sending a new cookie to the user within an HTTP Response. The purpose of the secure attribute is to prevent cookies from being observed by unauthorized parties due to the transmission of the cookie in clear text. To accomplish this goal, browsers which support the secure attribute will only send cookies with the secure attribute when the request is going to an HTTPS page. Said in another way, the browser will not send a cookie with the secure attribute set over an unencrypted HTTP request. By setting the secure attribute, the browser will prevent the transmission of a cookie over an unencrypted channel." 20 | url = msg.getRequestHeader().getURI().toString() 21 | alertParam = "" 22 | alertAttack = "" 23 | alertInfo = "https://owasp.org/www-community/controls/SecureCookieAttribute" 24 | alertSolution = "Add 'Secure' attribute when sending cookie." 25 | alertEvidence = "" 26 | cweID = 614 27 | wascID = 0 28 | 29 | #if "Set-Cookie" header does not have "secure" attribute, raise alert 30 | if ((headerCookie != "None") and "secure" not in headerCookie.lower()): 31 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 32 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 33 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-3-3-server-version-info.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 14.3.3 control from OWASP ASVS 4.0: 4 | 'Verify that the HTTP headers or any part of the HTTP response do not expose 5 | detailed version information of system components.' 6 | 7 | The script will raise an alert if 8 | 1. Server 9 | 2. X-Powered-By 10 | headers are present 11 | 12 | """ 13 | 14 | def scan(ps, msg, src): 15 | 16 | #search response header for "Server" and "X-Powered-By" headers 17 | header_server = str(msg.getResponseHeader().getHeader("Server")) 18 | header_xpowered = str(msg.getResponseHeader().getHeader("X-Powered-By")) 19 | 20 | #alert parameters 21 | alertRisk= 2 22 | alertConfidence = 2 23 | alertTitle = "14.3.3 Verify that the HTTP headers do not expose detailed version information of system components." 24 | alertDescription = "14.3.3 Verify that the HTTP headers or any part of the HTTP response do not expose detailed version information of system components." 25 | url = msg.getRequestHeader().getURI().toString() 26 | alertParam = "" 27 | alertAttack = "" 28 | alertInfo = "" 29 | solutions = ["Ensure Server header in HTTP response does not expose server version information.", "Ensure X-Powered-By header in HTTP response does not expose server version information."] 30 | alertSolution = "" 31 | alertEvidence = "" 32 | cweID = 200 33 | wascID = 0 34 | 35 | #if "Server" header is valid, add solution and evidence to alert 36 | if (header_server != "None"): 37 | alertSolution += solutions[0] 38 | alertEvidence += "Server: " + header_server 39 | #if "Server" header is valid, add solution and evidence to alert 40 | if (header_xpowered != "None"): 41 | alertSolution += solutions[1] 42 | alertEvidence += "X-Powered-By: " + header_xpowered 43 | 44 | #if the alert solution has been changed, raise alert 45 | if (alertSolution != ""): 46 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 47 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 48 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/3-1-1-token-in-url.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 3.1.1 control from OWASP ASVS 4.0: 4 | 'Verify the application never reveals session tokens in URL 5 | parameters.' 6 | 7 | The script will raise an alert if the following are found in the URL: 8 | 1. strings: "PHPSESSID", "JSESSIONID", "CFID", "CFTOKEN", "ASP.NET_SESSIONID", "ID", "COOKIE", "JWT", "SESSION" 9 | 2. actual token value from application (if sent) 10 | 11 | """ 12 | import ast 13 | 14 | #check response body for valid token and return it 15 | #return None if no token is found 16 | def getToken(msg): 17 | token = None 18 | try: 19 | body = str(msg.getResponseBody()) 20 | token = ast.literal_eval(body).get('authentication').get('token') 21 | except: 22 | pass 23 | return token 24 | 25 | def scan(ps, msg, src): 26 | 27 | #alert parameters 28 | alertRisk= 2 29 | alertConfidence = 1 30 | alertTitle = "3.1.1 Verify the application never reveals session tokens in URL parameters." 31 | alertDescription = "Verify the application never reveals session tokens in URL parameters." 32 | url = msg.getRequestHeader().getURI().toString() 33 | alertParam = "" 34 | alertAttack = "" 35 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-name-fingerprinting" 36 | alertSolution = "Remove session tokens from URL: " + url 37 | alertEvidence = url 38 | cweID = 598 39 | wascID = 0 40 | 41 | 42 | tokens = ["PHPSESSID", "JSESSIONID", "CFID", "CFTOKEN", "ASP.NET_SESSIONID", "ID", "COOKIE", "JWT", "SESSION"] 43 | 44 | #if valid token is found, make it uppercase 45 | app_token = getToken(msg) 46 | if (app_token is not None): 47 | tokens.append(app_token.upper()) 48 | 49 | #loop through tokens list and raise alert if it appears in the URL 50 | for t in tokens: 51 | if (t in url.upper()): #compare against uppercase URL to avoid case sensitivity 52 | alertParam = t 53 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 54 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 55 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/5-2-4-eval-body.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 5.2.4 and 5.5.4 controls from OWASP ASVS 4.0: 4 | 5 | 'Verify that the application avoids the use of eval() or other dynamic code 6 | execution features. Where there is no alternative, any user input being 7 | included must be sanitized or sandboxed before being executed.' 8 | 9 | 'Verify that when parsing JSON in browsers or JavaScript-based backends, 10 | JSON.parse is used to parse the JSON document. Do not use eval() to parse JSON.' 11 | 12 | The script will raise an alert if 'eval' or 'include' is present in the response body. 13 | 14 | """ 15 | 16 | def scan(ps, msg, src): 17 | 18 | 19 | 20 | #alert parameters 21 | alertRisk= 2 22 | alertConfidence = 1 23 | alertTitle = "5.2.4 & 5.5.4 Verify that the application avoids the use of eval() or other dynamic code execution features." 24 | alertDescription = " 5.2.4 Verify that the application avoids the use of eval() or other dynamic code execution features. Where there is no alternative, any user input being included must be sanitized or sandboxed before being executed." + "\n" + "5.5.4 Verify that when parsing JSON in browsers or JavaScript-based backends, JSON.parse is used to parse the JSON document. Do not use eval() to parse JSON." 25 | url = msg.getRequestHeader().getURI().toString() 26 | alertParam = "" 27 | alertAttack = "" 28 | alertInfo = "https://owasp.org/www-community/attacks/Direct_Dynamic_Code_Evaluation_Eval%20Injection" + "/n" "https://owasp.org/www-community/attacks/Code_Injection" 29 | alertSolution = "Ensure the use of eval() or include() do not expose the application to dynamic code execution injection. If eval() is being used to parse JSON, use JSON.parse instead." 30 | alertEvidence = "" 31 | cweID = 95 32 | wascID = 0 33 | 34 | try: 35 | #get response body 36 | body = str(msg.getResponseBody()) 37 | 38 | #if 'eval' or 'include' is in response body, raise alert 39 | if (body != "None" and ("eval(" in body.lower() or "include(" in body.lower())): 40 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 41 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 42 | except: 43 | pass 44 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/13-1-3-key-in-api-url.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 13.1.3 control from OWASP ASVS 4.0: 4 | 'Verify API URLs do not expose sensitive information, such as the API key, 5 | session tokens etc.' 6 | 7 | Note: this script is almost identical to 3-1-1-token-in-url.py except, it contains strings related to api keys as well. 8 | 9 | The script will raise an alert if the following are found in the URL: 10 | 1. strings: "PHPSESSID", "JSESSIONID", "CFID", "CFTOKEN", "ASP.NET_SESSIONID", "ID", "COOKIE", "JWT", "SESSION", "KEY", "API"] 11 | 2. actual token value from application (if sent) 12 | 13 | """ 14 | import ast 15 | 16 | #check response body for valid token and return it 17 | #return None if no token is found 18 | def getToken(msg): 19 | token = None 20 | try: 21 | body = str(msg.getResponseBody()) 22 | token = ast.literal_eval(body).get('authentication').get('token')#evaluate response body to find token {authetication: {token: ****}} 23 | except: 24 | pass 25 | return token 26 | 27 | def scan(ps, msg, src): 28 | 29 | #alert parameters 30 | alertRisk= 2 31 | alertConfidence = 1 32 | alertTitle = "13.1.3 Verify API URLs do not expose sensitive information." 33 | alertDescription = "Verify API URLs do not expose sensitive information, such as the API key, session tokens etc." 34 | url = msg.getRequestHeader().getURI().toString() 35 | alertParam = "" 36 | alertAttack = "" 37 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html" 38 | alertSolution = "Please review the following url for session tokens or api keys: " + url 39 | alertEvidence = url 40 | cweID = 116 41 | wascID = 0 42 | 43 | tokens = ["PHPSESSID", "JSESSIONID", "CFID", "CFTOKEN", "ASP.NET_SESSIONID", "ID", "COOKIE", "JWT", "SESSION", "KEY", "API"] 44 | 45 | #if valid token is found, make it uppercase 46 | app_token = getToken(msg) 47 | if (app_token is not None): 48 | tokens.append(app_token.upper()) 49 | 50 | #loop through tokens list and raise alert if it appears in the URL 51 | for t in tokens: 52 | if (t in url.upper()): #compare against uppercase URL to avoid case sensitivity 53 | alertParam = t 54 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 55 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 56 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-5-2-Origin-header.py: -------------------------------------------------------------------------------- 1 | """ 2 | Credit to BlazingWind on Github for this script: https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide/blob/main/ZAP-scripts/14-5-2-Origin-header.py 3 | 4 | Script testing 14.5.2 control from OWASP ASVS 4.0: 5 | 'Verify that the supplied Origin header is not used for authentication or access control decisions, as the Origin header can easily be changed by an attacker.' 6 | 7 | The script looks for CSP header and if there are none found, the control is failed. 8 | Additionally, the script test if the CSP is configured with the directives: 'unsafe-inline', 'unsafe-eval' and wildcards. 9 | """ 10 | import re 11 | 12 | def scan(ps, msg, src): 13 | #Passively scans the message sent/received through ZAP. 14 | 15 | #Args: 16 | # ps (ScriptsPassiveScanner): The helper class to raise alerts and add tags to the message. 17 | # msg (HttpMessage): The HTTP message being scanned. 18 | # src (Source): The HTML source of the message (if any). 19 | 20 | alertTitle = "14.5.2 Verify that the supplied Origin header is not used for authentication or access control decisions, as the Origin header can easily be changed by an attacker." 21 | alertDescription = "Origin header is a part of CORS mechanism. Oftentimes a websitedies not maintain a whitelist of websites" 22 | alertRisk = 0 23 | alertReliability = 1 24 | header = str(msg.getResponseHeader().getHeader("Content-Security-Policy")) 25 | alertSolution = ["Add CSP header to the server's configuration.","Ensure that CSP is not configured with the directives: 'unsafe-inline', 'unsafe-eval' and wildcards."] 26 | alertParam = "Content-Security-Policy header" 27 | alertInfo = "Control failure" 28 | cweID = 1021 29 | wascID = 0 30 | 31 | # Search for regex match 32 | pattern = re.compile(r"'unsafe-inline|'unsafe-eval'|\*") 33 | directives = re.search(pattern,header) 34 | 35 | if (header == "None"): 36 | ps.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 37 | msg.getRequestHeader().getURI().toString(), 38 | alertParam, "", alertInfo, alertSolution[0], header, cweID, wascID, msg); 39 | elif directives: 40 | ps.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 41 | msg.getRequestHeader().getURI().toString(), 42 | alertParam, "", alertInfo, alertSolution[1], header, cweID, wascID, msg); 43 | 44 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/3-2-2-session-token-entropy.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 3.2.2 control from OWASP ASVS 4.0: 4 | 'Verify that session tokens possess at least 64 bits of entropy.' 5 | 6 | From OWASP: A session token with 64 bits of entropy must be at least 128 bits in length. The script will raise an alert if a session token is less than 128 bits. The Randomly Selected Passwords formula by Claude Shannon will be used to calculate the bit legth of the token. 7 | 8 | Credit to 4k1 on GitHub for thier implementation of this formula in python. https://gist.github.com/4k1/6fbe670807db1d48407685d6cc46b0af 9 | 10 | """ 11 | 12 | import ast, math 13 | 14 | #check response body for valid token and return it 15 | #return None if no token is found 16 | def getToken(msg): 17 | token = None 18 | try: 19 | body = str(msg.getResponseBody()) 20 | token = ast.literal_eval(body).get('authentication').get('token') 21 | except: 22 | pass 23 | return token 24 | 25 | #calculate entropy of a given token 26 | def calculateEntropy(token): 27 | n = len(list(set(list(token)))) #n = set of possible characters for token 28 | l = len(token) #l = length of token 29 | h = math.log(math.pow(n, l), 2) #h = log2(n^l) = entropy 30 | return h 31 | 32 | 33 | def scan(ps, msg, src): 34 | 35 | #alert parameters 36 | alertRisk= 1 37 | alertConfidence = 1 38 | alertTitle = "3.2.2 Verify that session tokens possess at least 64 bits of entropy." 39 | alertDescription = "Session identifiers should be at least 128 bits long to prevent brute-force session guessing attacks." 40 | url = msg.getRequestHeader().getURI().toString() 41 | alertParam = "" 42 | alertAttack = "" 43 | alertInfo = "https://owasp.org/www-community/vulnerabilities/Insufficient_Session-ID_Length" 44 | alertSolution = "Ensure any session token is at least 128 bits long." + "\n" + "Note: a single token that does not meet the entropy requirement is not sufficient to show the token generation is insecure." 45 | alertEvidence = "" 46 | cweID = 331 47 | wascID = 0 48 | 49 | token = getToken(msg) 50 | 51 | if token: #if token is not None 52 | entropy = calculateEntropy(token) 53 | if (entropy < 128): #raise alert if entropy is less than 128 54 | alertEvidence = "Token " + str(token) + "/n" + "Entropy " + entropy 55 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 56 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-3-2-Debug-modes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Credit to BlazingWind on Github for this script: https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide/blob/main/ZAP-scripts/14-3-2-Debug-modes.py 3 | 4 | Script testing 14.3.2 control from OWASP ASVS 4.0: 5 | 'Verify that web or application server and application framework debug modes are disabled in production to eliminate debug features, developer consoles, and unintended security disclosures.' 6 | 7 | The script searches the body of the response for words such as 'debug', 'medio' and raises an alert if it did find them. 8 | """ 9 | import re 10 | 11 | def scan(ps, msg, src): 12 | #Passively scans the message sent/received through ZAP. 13 | #Args: 14 | # ps (ScriptsPassiveScanner): The helper class to raise alerts and add tags to the message. 15 | # msg (HttpMessage): The HTTP message being scanned. 16 | # src (Source): The HTML source of the message (if any). 17 | 18 | alertTitle = "14.3.2 Verify that web or application server and application framework debug modes are disabled in production to eliminate debug features, developer consoles, and unintended security disclosures." 19 | alertDescription = "Debug mode often allows for much more functionality and employs poorer security practices. It should not be present in production. The script searches the body of the response for words such as 'debug', 'medio' and raises an alert if it did find them." 20 | alertRisk = 0 21 | alertReliability = 1 22 | body = msg.getResponseBody().toString() 23 | alertSolution = ["Ensure that the application does not contain any leftover debug code",""] 24 | alertParam = "'debug', 'medio' in body of the response" 25 | alertInfo = "Control failure" 26 | cweID = 497 27 | wascID = 0 28 | 29 | pattern = re.compile(r"(?i)debug|medio") 30 | # Search for regex match 31 | words = re.search(pattern,body) 32 | 33 | # Test the request and/or response here 34 | if words: 35 | # Change to a test which detects the vulnerability 36 | # raiseAlert(risk, int reliability, String name, String description, String uri, 37 | # String param, String attack, String otherInfo, String solution, String evidence, 38 | # int cweId, int wascId, HttpMessage msg) 39 | # risk: 0: info, 1: low, 2: medium, 3: high 40 | # reliability: 0: falsePositive, 1: suspicious, 2: warning 41 | ps.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 42 | msg.getRequestHeader().getURI().toString(), 43 | alertParam, "", alertInfo, alertSolution[0], "", cweID, wascID, msg); 44 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/4-1-5-fail-securely.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 4.1.5 control from OWASP ASVS 4.0: 4 | 5 | 'Verify that access controls fail securely including when an exception occurs.' 6 | 7 | The script will raise an alert if an error status code is returned 400-599 and 8 | sensitive data is leaked in the response body like ssn, email, file_path, zip_code, ip, netid or version information 9 | 10 | """ 11 | import re 12 | 13 | def scan(ps, msg, src): 14 | 15 | #alert parameters 16 | alertRisk= 2 17 | alertConfidence = 1 18 | alertTitle = "4.1.5 Verify that access controls fail securely." 19 | alertDescription = "Verify that access controls fail securely including when an exception occurs." 20 | url = msg.getRequestHeader().getURI().toString() 21 | alertParam = "" 22 | alertAttack = "" 23 | alertInfo = "https://owasp.org/www-community/Fail_securely" 24 | alertSolution = "" 25 | alertEvidence = "" 26 | cweID = 285 27 | wascID = 0 28 | 29 | 30 | try: 31 | code = str(msg.getResponseHeader().getStatusCode()) # get status code 32 | body = str(msg.getResponseBody()) #get response body 33 | 34 | error_pattern = re.compile(r"[4-5][0-9]{2}") #regular expression for codes 400-599 35 | 36 | #regular expressions for sensitive data 37 | ssn = re.compile(r"[0-9]{3}-[0-9]{2}-[0-9]{4}") 38 | email = re.compile(r"^[\w\.=-]+@[\w\.-]+\.[\w]{2,3}$") 39 | file_path = re.compile(r"\\[^\\]+$") 40 | zip_code = re.compile(r"^((\d{5}-\d{4})|(\d{5})|([A-Z]\d[A-Z]\s\d[A-Z]\d))$") 41 | ip = re.compile(r"^\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3}$") 42 | netid = re.compile(r"^([a-z]{2,3})([2-9]{1,5})$") 43 | version = re.compile(r"[a-zA-Z]{1}\d{1,2}\.\d{1,2}\.\d{1,3}") 44 | 45 | patterns = [(ssn, "ssn"), (email, "email"), (file_path, "file path"), (zip_code, "zip code"), (ip, "ip address"), (netid, "netid"), (version, "version")] 46 | 47 | error_code = re.search(error_pattern,code) 48 | 49 | #if the response code is 400-599 and loop through the list of patterns 50 | #and if the response body contains one of the regex, raise an alert 51 | if (error_code): 52 | for pat in patterns: 53 | match = re.search(pat[0],body) 54 | if (match): 55 | alertEvidence = "Error triggered. Status Code: " + code + "\n" + "Possible " + pat[1] + " found in response body: "+ match.group(0) 56 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 57 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 58 | except: 59 | pass 60 | 61 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-4-7-x-frame-options-header.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 14.4.7 control from OWASP ASVS 4.0: 4 | 'Verify that the content of a web application cannot be embedded in a third- 5 | party site by default and that embedding of the exact resources is only 6 | allowed where necessary by using suitable Content-Security-Policy: frame- 7 | ancestors and X-Frame-Options response headers.' 8 | 9 | The script will raise an alert if 10 | 1. X-Frame-Options: deny or X-Frame-Options: sameorigin 11 | 2. Content-Security-Policy: frame-ancestors ‘none’ or Content-Security-Policy: frame-ancestors 12 | is not present. 13 | 14 | """ 15 | 16 | def scan(ps, msg, src): 17 | 18 | #find "X-Frame-Options" and "Content-Security-Policy" headers 19 | header_xframe = str(msg.getResponseHeader().getHeader("X-Frame-Options")) 20 | header_csp = str(msg.getResponseHeader().getHeader("Content-Security-Policy")) 21 | 22 | #alert parameters 23 | alertRisk= 1 24 | alertConfidence = 2 25 | alertTitle = "14.4.7 Verify that the content of a web application cannot be embedded in a third- party site." 26 | alertDescription = "Verify that the content of a web application cannot be embedded in a third- party site by default and that embedding of the exact resources is only allowed where necessary by using suitable Content-Security-Policy: frame-ancestors and X-Frame-Options response headers." 27 | url = msg.getRequestHeader().getURI().toString() 28 | alertParam = "" 29 | alertAttack = "" 30 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-frame-options" + "/n" + "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#content-security-policy" 31 | solutions = ["Add proper X-Frame-Options header to HTTP responses (deny or sameorigin).", "Add proper Content-Security-Policy: frame-ancestors header to HTTP responses."] 32 | alertSolution = "" 33 | alertEvidence = "" 34 | cweID = 1021 35 | wascID = 0 36 | 37 | #if "X-Frame-Options" is not set to "sameorigin" or "deny", change alert solution 38 | if (header_xframe.lower() not in ["sameorigin", "deny"]): 39 | alertSolution = solutions[0] 40 | 41 | #if "Content-Security-Policy" is not set to "frame-ancestors", change alert solution 42 | elif (header_csp.lower() not in ["frame-ancestors"]): 43 | alertSolution = solutions[1] 44 | 45 | #if alert solution has changed, raise alert 46 | if (alertSolution != ""): 47 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 48 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 49 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-4-3-csp-header.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 14.4.3 control from OWASP ASVS 4.0: 4 | 'Verify that a Content Security Policy (CSP) response header is in place that 5 | helps mitigate impact for XSS attacks like HTML, DOM, JSON, and JavaScript 6 | injection vulnerabilities.' 7 | 8 | The script will raise an alert if: 9 | 1. There is no Content-Security-Policy or Content-Security-Policy-Report-Only header 10 | 2. X-Content-Security-Policy or X-WebKit-CSP is used 11 | 12 | """ 13 | #return valid header from ["Content-Security-Policy", "Content-Security-Policy-Report-Only", "X-Content-Security-Policy", "X-WebKit-CSP"] 14 | def findHeaderType(msg): 15 | headers = ["Content-Security-Policy", "Content-Security-Policy-Report-Only", "X-Content-Security-Policy", "X-WebKit-CSP"] 16 | headerType = "" 17 | for h in headers: 18 | msg_header = str(msg.getResponseHeader().getHeader(h)) 19 | if (msg_header != "None"): 20 | headerType = h 21 | return headerType 22 | 23 | def scan(ps, msg, src): 24 | 25 | #alert parameters 26 | alertRisk= 1 27 | alertConfidence = 2 28 | alertTitle = "14.4.3 Verify that a Content Security Policy (CSP) response header is in place." 29 | alertDescription = "Verify that a Content Security Policy (CSP) response header is in place that helps mitigate impact for XSS attacks like HTML, DOM, JSON, and JavaScript injection vulnerabilities." 30 | url = msg.getRequestHeader().getURI().toString() 31 | alertParam = "" 32 | alertAttack = "" 33 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html" 34 | solutions = ["Add Content Security Policy (CSP) header in HTTP response.", "DO NOT use X-Content-Security-Policy or X-WebKit-CSP. Their implementations are obsolete (since Firefox 23, Chrome 25), limited, inconsistent, and incredibly buggy."] 35 | alertSolution = "" 36 | alertEvidence = "" 37 | cweID = 1021 38 | wascID = 0 39 | 40 | headerType = findHeaderType(msg) 41 | 42 | #if header is "X-Content-Security-Policy" or "X-WebKit-CSP", change alert solution and evidence 43 | if (headerType in ["X-Content-Security-Policy", "X-WebKit-CSP"]): 44 | alertSolution = solutions[1] 45 | alertEvidence = str(msg.getResponseHeader().getHeader(headerType)) 46 | #if there is no valid header, change alert solution 47 | elif (headerType == ""): 48 | alertSolution = solutions[0] 49 | 50 | #if alert solution has changed, raise alert 51 | if (alertSolution != ""): 52 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 53 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 54 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/5-3-2-preserve-encoding.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 5.3.2 control from OWASP ASVS 4.0: 4 | 'Verify that output encoding preserves the user's chosen character set and locale, 5 | such that any Unicode character point is valid and safely handled.' 6 | 7 | The script will raise an alert if the response header "Content-Type" does not retain 8 | the charcter set specified in the request header "Accept" 9 | 10 | """ 11 | #try to parse the headers to capture charset values and return as dictionary 12 | #if charset is not in header, return 0 13 | def get_char_sets(req, resp): 14 | try: 15 | req_index = req.index("charset=") + 8 16 | resp_index = resp.index("charset=") + 8 17 | return {"req" : req[req_index:], "resp": resp[resp_index:]} 18 | except: 19 | return 0 20 | 21 | def diff_encoding(req, resp): 22 | sets = get_char_sets(req, resp) #get character sets if listed 23 | if ("*/*" in req or req == "None"): #if request will accept any encoding or is not specified, return false 24 | return False 25 | elif ((resp == "None") or (sets != 0 and (sets["resp"] not in sets["req"]))): #if there is no response header or the character sets dont match, return true 26 | return True 27 | return False #all else, return false 28 | 29 | def scan(ps, msg, src): 30 | 31 | #alert parameters 32 | alertRisk= 0 33 | alertConfidence = 1 34 | alertTitle = "5.3.2 Verify that output encoding preserves the user's chosen character set and locale." 35 | alertDescription = "Verify that output encoding preserves the user's chosen character set and locale, such that any Unicode character point is valid and safely handled." 36 | url = msg.getRequestHeader().getURI().toString() 37 | alertParam = "" 38 | alertAttack = "" 39 | alertInfo = "" 40 | alertSolution = "Ensure the application preserves the user's chosen character set (located in the 'Accept' Header) by responding with the appropriate 'Content-Type' Header" 41 | alertEvidence = "" 42 | cweID = 176 43 | wascID = 0 44 | 45 | #get request and response headers for 'Accept' and 'Content-Type' 46 | request_header = str(msg.getResponseHeader().getHeader("Accept")) 47 | response_header = str(msg.getResponseHeader().getHeader("Content-Type")) 48 | 49 | 50 | #if the request header and response header dont have matching character sets, raise alert 51 | if (diff_encoding(request_header, response_header)): 52 | alertEvidence = "Character set requested: " + request_header + "\n" + "Character set sent: " + response_header 53 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 54 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 55 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-4-1-content-type-header.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 14.4.1 control from OWASP ASVS 4.0: 4 | 'Verify that every HTTP response contains a Content-Type header. Also 5 | specify a safe character set (e.g., UTF-8, ISO-8859-1) if the content types are 6 | text/*, /+xml and application/xml. Content must match with the provided 7 | Content-Type header.' 8 | 9 | The script will raise an alert if: 10 | 1. There is no Content-Type header in the HTTP response 11 | 2. The content types are text/*, /+xml and application/xml but safe character sets: UTF-8, ISO-8859-1 are not used 12 | 13 | """ 14 | 15 | #return true if: 16 | #xml is in the header but utf-8 and utf-16 are not 17 | #text is in the header but utf-8, utf-16 and iso-8859-1 are not 18 | def useSafeCharacters(header): 19 | not_utf8 = "utf-8" not in header 20 | not_utf16 = "utf-16" not in header 21 | not_iso88591 = "iso-8859-1" not in header 22 | text = "text/" in header 23 | xml = "xml" in header 24 | 25 | if xml and (not_utf8 and not_utf16): 26 | return True 27 | elif text and ((not_utf8 and not_utf16) and not_iso88591): 28 | return True 29 | return False 30 | 31 | 32 | def scan(ps, msg, src): 33 | 34 | #find "Content-Type" header 35 | header = str(msg.getResponseHeader().getHeader("Content-Type")) 36 | 37 | #alert parameters 38 | alertRisk= 1 39 | alertConfidence = 2 40 | alertTitle = "14.4.1 Verify that every HTTP response contains a Content-Type header." 41 | alertDescription = "The Content-Type representation header is used to indicate the original media type of the resource (before any content encoding is applied for sending)." 42 | url = msg.getRequestHeader().getURI().toString() 43 | alertParam = "" 44 | alertAttack = "" 45 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html" 46 | solutions = ["Add 'Content-Type' header in HTTP response.", "Specify a safe character set (UTF-8, UTF-16) if the content types are /+xml or application/xml and (UTF-8, UTF-16, ISO-8859-1) if the content type is text/*"] 47 | alertSolution = "" 48 | alertEvidence = "Content-Type: " + header 49 | cweID = 173 50 | wascID = 0 51 | 52 | #if there is no header, change alert solution 53 | if (header == "None"): 54 | alertSolution = solutions[0] 55 | 56 | #if header needs to use safe character sets, change alert solution 57 | elif (useSafeCharacters(header.lower())): 58 | alertSolution = solutions[1] 59 | 60 | #if alert solution has changed, raise alert 61 | if (alertSolution != ""): 62 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 63 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 64 | -------------------------------------------------------------------------------- /ZAP_Scripts/active/14-5-3-CORS-header.py: -------------------------------------------------------------------------------- 1 | """ 2 | Credit to BlazingWind on Github for this script: https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide/blob/main/ZAP-scripts/14-5-3-CORS-header.py 3 | 4 | Script testing 14.5.3 control from OWASP ASVS 4.0: 5 | 'Verify that the cross-domain resource sharing (CORS) Access-Control-Allow-Origin header uses a strict white-list of trusted domains to match against and does not support the "null" origin.' 6 | 7 | The script sends a CORS request from an inexisitent domain to determine if the Access-Control-Allow-Origin header is configured with a wildcard, if it reflected the Origin header or if the request was blocked. 8 | """ 9 | import re 10 | 11 | alertTitle = '14.5.3 Verify that the cross-domain resource sharing (CORS) Access-Control-Allow-Origin header uses a strict white-list of trusted domains to match against and does not support the "null" origin.' 12 | alertDescription = "This controls checks if CORS policy is properly configured." 13 | alertRisk = 0 14 | alertReliability = 1 15 | alertSolution = ["Use a strict whitelist of sites allowed to request resources of your domain", ""] 16 | alertInfo = "Control failure" 17 | cweID = 346 18 | wascID = 0 19 | 20 | origin = "exampletestsite.com" 21 | 22 | def scanNode(sas, msg): 23 | origMsg = msg; 24 | # Copy requests before reusing them 25 | msg = origMsg.cloneRequest(); 26 | 27 | # GET resource that doesn't exist 28 | msg.getRequestHeader().setHeader("Origin", origin) 29 | 30 | # sendAndReceive(msg, followRedirect, handleAntiCSRFtoken) 31 | if (sas.isStop()): 32 | return 33 | sas.sendAndReceive(msg, True, False); 34 | 35 | header = str(msg.getResponseHeader().getHeader("Access-Control-Allow-Origin")) 36 | header 37 | if (header == "*"): 38 | alertParam = "wildcard directive in Access-Control-Allow-Origin" 39 | sas.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 40 | msg.getRequestHeader().getURI().toString(), 41 | alertParam, "", alertInfo, alertSolution[0], "", cweID, wascID, msg); 42 | elif (header == origin): 43 | alertParam = "Access-Control-Allow-Origin reflects Origin header" 44 | sas.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 45 | msg.getRequestHeader().getURI().toString(), 46 | alertParam, "", alertInfo, alertSolution[0], "", cweID, wascID, msg); 47 | elif (header == "null"): 48 | alertParam = "Access-Control-Allow-Origin is null" 49 | sas.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 50 | msg.getRequestHeader().getURI().toString(), 51 | alertParam, "", alertInfo, alertSolution[0], "", cweID, wascID, msg); 52 | 53 | def scan(sas, msg, param, value): 54 | pass 55 | 56 | -------------------------------------------------------------------------------- /ZAP_Scripts/active/5-2-7-svg-script-injection.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 5.2.7 control from OWASP ASVS 4.0: 4 | 'Verify that the application sanitizes, disables, or sandboxes user-supplied Scalable Vector Graphics (SVG) scriptable content, 5 | especially as they relate to XSS resulting from inline scripts, and foreignObject.' 6 | 7 | 8 | This script will inject an SVG XSS payload to see if it is returned in the response body without being sanatized or escaped or, 9 | causes the server to return an error. 10 | 11 | """ 12 | 13 | def scanNode(sas, msg): 14 | pass 15 | 16 | def scan(sas, msg, param, value): 17 | #alert parameters 18 | alertRisk= 3 19 | alertConfidence = 2 20 | alertTitle = "5.2.7 Verify that the application sanitizes, disables, or sandboxes user-supplied Scalable Vector Graphics (SVG) scriptable content." 21 | alertDescription = "Verify that the application sanitizes, disables, or sandboxes user-supplied Scalable Vector Graphics (SVG) scriptable content, especially as they relate to XSS resulting from inline scripts, and foreignObject." 22 | url = msg.getRequestHeader().getURI().toString() 23 | alertParam = "" 24 | alertAttack = "" 25 | alertInfo = "https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html#svg-object-tag" 26 | alertSolution = "" 27 | alertEvidence = "" 28 | cweID = 159 29 | wascID = 0 30 | 31 | #SVG payload 32 | attack = ' ' 33 | 34 | #clone message 35 | msg = msg.cloneRequest(); 36 | 37 | # setParam (message, parameterName, newValue) 38 | sas.setParam(msg, param, attack); 39 | 40 | # sendAndReceive(msg, followRedirect, handleAntiCSRFtoken) 41 | sas.sendAndReceive(msg, False, False); 42 | 43 | code = str(msg.getResponseHeader().getStatusCode()) # get status code 44 | 45 | 46 | #check if attack payload is reflected back in the response body or server errror, if so raise alert 47 | try: # use try/except to avoid parsing issues from invalid response bodies 48 | body = str(msg.getResponseBody()) 49 | if (attack in body): 50 | alertAttack = attack 51 | alertEvidence = attack + " in Response Body" 52 | sas.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 53 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 54 | elif (code == '500'): #check for server error code (500) 55 | alertAttack = attack 56 | alertEvidence = "Status Code: " + code + "\n" + "Attack triggered server error." 57 | sas.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 58 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 59 | except: 60 | pass 61 | 62 | -------------------------------------------------------------------------------- /ZAP_Scripts/httpfuzzerprocessor/2-5-4-default-accounts.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Script testing 2.5.4 from OWASP ASVS 4.0: 4 | 5 | Verify shared or default accounts are not present (e.g. "root", "admin", or "sa"). 6 | 7 | How to run this script: 8 | 1. Enable the script from the scripts tab 9 | 2. Manually login with a user account in the application 10 | 3. In the history window, right click the login entry, usually it will be a POST message (for juice shop the url is http://localhost:3000/rest/user/login) 11 | 4. Select Attack > Fuzz 12 | 5. If the fuzz location menu, highlight the username value in the JSON object in the request body (ex: highlight test@test.com in {"username":"test@test.com", "password":"test123"}) 13 | 6. Select add > add and import a wordlist of valid usernames 14 | 8. Go to the manage processors tab and select add 15 | 9. Ensure the type is Fuzzer HTTP Processor (Script) and from the script drop down select 2-5-4-default-account.js 16 | 10. Start fuzzer 17 | 18 | The script will add a custom state in the fuzzer window if the http response contains "invalid password" rather than "invalid username or password". 19 | This lets us know that an account with the fuzzed username exists. 20 | 21 | */ 22 | 23 | 24 | // Auxiliary variables/constants needed for processing. 25 | var count = 1; 26 | 27 | function processMessage(utils, message) { 28 | message.getRequestHeader().setHeader("X-Unique-Id", count); 29 | count++; 30 | } 31 | 32 | //ran after the fuzzed msg is sent 33 | function processResult(utils, fuzzResult){ 34 | 35 | //testing variables 36 | var response_body = fuzzResult.getHttpMessage().getResponseBody().toString().toLowerCase(); //set to lowercase for conditional 37 | var payload = utils.getPayloads().toString(); 38 | 39 | //alert info 40 | var risk= 1; 41 | var confidence = 1; 42 | var name = "2.5.4 - Verify shared or default accounts are not present (e.g. root, admin, or sa)."; 43 | var description = "2.5.4 - Verify shared or default accounts are not present (e.g. root, admin, or sa)." + "\n" + "Account found: " + payload + "cweID: " + "16" + "\n" + "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/04-Authentication_Testing/02-Testing_for_Default_Credentials"; 44 | 45 | 46 | if (response_body.indexOf("invalid password") !== -1){//if "invalid pasword" is found in http response body 47 | fuzzResult.addCustomState("Key Custom State", "2.5.4 - Verify shared or default accounts are not present (e.g. root, admin, or sa)"); 48 | utils.raiseAlert(risk, confidence, name, description); 49 | } 50 | return true; 51 | } 52 | 53 | function getRequiredParamsNames(){ 54 | return []; 55 | } 56 | 57 | function getOptionalParamsNames(){ 58 | return []; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /ZAP_Scripts/active/5-2-3-imap-injection.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 5.2.3 control from OWASP ASVS 4.0: 4 | 'Verify that the application sanitizes user input before passing to mail systems 5 | to protect against SMTP or IMAP injection.' 6 | 7 | 8 | This script will inject a payload to see if it is returned in the response body without being sanatized or escaped or, 9 | causes the server to return an error. 10 | 11 | """ 12 | 13 | def scanNode(sas, msg): 14 | pass 15 | 16 | def scan(sas, msg, param, value): 17 | #alert parameters 18 | alertRisk= 3 19 | alertConfidence = 2 20 | alertTitle = "5.2.3 Verify that the application sanitizes user input before passing to mail systems to protect against SMTP or IMAP injection." 21 | alertDescription = "Verify that the application sanitizes user input before passing to mail systems to protect against SMTP or IMAP injection." 22 | url = msg.getRequestHeader().getURI().toString() 23 | alertParam = "" 24 | alertAttack = "" 25 | alertInfo = "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/10-Testing_for_IMAP_SMTP_Injection" 26 | alertSolution = "" 27 | alertEvidence = "" 28 | cweID = 147 29 | wascID = 0 30 | 31 | common_imap_elements = ["imap attack", "", "\", "'", "@", "#", "!", "|", '"'] 32 | 33 | 34 | # Copy requests before reusing them 35 | msg = msg.cloneRequest(); 36 | sas.sendAndReceive(msg, False, False); 37 | 38 | for element in common_imap_elements: 39 | attack = + element 40 | 41 | # setParam (message, parameterName, newValue) 42 | sas.setParam(msg, param, attack); 43 | 44 | # sendAndReceive(msg, followRedirect, handleAntiCSRFtoken) 45 | sas.sendAndReceive(msg, False, False); 46 | 47 | code = str(msg.getResponseHeader().getStatusCode()) # get status code 48 | 49 | #check if attack payload is reflected back in the response body or server errror, if so raise alert 50 | try: # use try/except to avoid parsing issues from invalid response bodies 51 | body = str(msg.getResponseBody()) 52 | if (attack in body): 53 | alertAttack = attack 54 | alertEvidence = attack + " found in Response Body" 55 | sas.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 56 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 57 | elif (code == '500'): #check for server error code (500) 58 | alertAttack = attack 59 | alertEvidence = "Status Code: " + code + "\n" + "Attack triggered server error." 60 | sas.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 61 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 62 | 63 | except: 64 | pass 65 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/8-3-1-sensitive-data-parameters.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 8.3.1 control from OWASP ASVS 4.0: 4 | 5 | 'Verify that sensitive data is sent to the server in the HTTP message body or 6 | headers, and that query string parameters from any HTTP verb do not contain 7 | sensitive data.' 8 | 9 | 10 | The script will raise an alert if any parameter value matches the regex for ssn, emails, file paths, zip codes or ip addresses. 11 | 12 | """ 13 | import re 14 | 15 | def get_parameters(url): 16 | 17 | try: 18 | query = str(url.split("?")[1]) 19 | freq = query.count("&") 20 | values= [] 21 | for x in range(freq - 1): 22 | sets = query.split('&', 1) 23 | values.append(sets[0].split('=')[1]) 24 | query = str(sets[1]) 25 | sets = query.split('&', 1) 26 | values.append(sets[0].split('=')[1]) 27 | values.append(sets[1].split('=')[1]) 28 | return values 29 | except: 30 | return "" 31 | 32 | def scan(ps, msg, src): 33 | 34 | #alert parameters 35 | alertRisk= 2 36 | alertConfidence = 1 37 | alertTitle = "8.3.1 Verify that sensitive data is sent to the server in the HTTP message body or headers." 38 | alertDescription = "8.3.1 Verify that sensitive data is sent to the server in the HTTP message body or headers, and that query string parameters from any HTTP verb do not contain sensitive data." 39 | url = msg.getRequestHeader().getURI().toString() 40 | alertParam = "" 41 | alertAttack = "" 42 | alertInfo = "https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url" 43 | alertSolution = "" 44 | alertEvidence = "" 45 | cweID = 319 46 | wascID = 0 47 | 48 | #get parameters from url 49 | parameters = get_parameters(url) 50 | 51 | #regular expressions for sensitive data 52 | ssn = re.compile(r"[0-9]{3}-[0-9]{2}-[0-9]{4}") 53 | email = re.compile(r"^[\w\.=-]+@[\w\.-]+\.[\w]{2,3}$") 54 | file_path = re.compile(r"\\[^\\]+$") 55 | zip_code = re.compile(r"^((\d{5}-\d{4})|(\d{5})|([A-Z]\d[A-Z]\s\d[A-Z]\d))$") 56 | ip = re.compile(r"^\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3}$") 57 | netid = re.compile(r"^([a-z]{2,3})([2-9]{1,5})$") 58 | version = re.compile(r"[a-zA-Z]{1}\d{1,2}\.\d{1,2}\.\d{1,3}") 59 | 60 | patterns = [(ssn, "ssn"), (email, "email"), (file_path, "file path"), (zip_code, "zip code"), (ip, "ip address"), (netid, "netid"), (version, "version")] 61 | 62 | #if there are parameters, loop through them and the patters 63 | if (parameters != ""): 64 | for par in parameters: 65 | for pat in patterns: 66 | match = re.search(pat[0],par) 67 | #if pattern if found in parameter, raise alert 68 | if (match): 69 | alertParam = par 70 | alertEvidence = "Possible " + pat[1] + " found in url parameter: " + match.group(0) 71 | ps.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 72 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 73 | -------------------------------------------------------------------------------- /ZAP_Scripts/active/14-5-1-HTTP-methods.py: -------------------------------------------------------------------------------- 1 | """ 2 | Credit to BlazingWind on Github for this script. https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide/blob/main/ZAP-scripts/14-5-1-HTTP-methods.py 3 | 4 | Script testing 14.5.1 and 13.2.1 controls from OWASP ASVS 4.0: 5 | 6 | 'Verify that the application server only accepts the HTTP methods in use by the application or API, including pre-flight OPTIONS.' 7 | The script attempts to connect using HTTP methods: HEAD, POST, PUT, DELETE, OPTIONS, TRACE, PATCH and OPTIONS. A website or an API should only allow the methods that it is using. 8 | 9 | 'Verify that enabled RESTful HTTP methods are a valid choice for the user or action, such as preventing normal users using DELETE or PUT on protected API or resources.' 10 | 11 | A website should only support use of GET, HEAD, POST and OPTIONS. If use of any other methods is allowed, the script will raise an alert. 12 | 13 | """ 14 | import re 15 | #import time 16 | alertTitle = "14.5.1 Verify that the application server only accepts the HTTP methods in use by the application or API, including pre-flight OPTIONS." 17 | alertDescription = "Several HTTP methods have known exploits for them and should not be used." + "/n" + "14.5.1 Verify that the application server only accepts the HTTP methods in use by the application or API, including pre-flight OPTIONS."+ "\n" + "13.2.1 Verify that enabled RESTful HTTP methods are a valid choice for the user or action, such as preventing normal users using DELETE or PUT on protected API or resources." 18 | alertRisk = 2 19 | alertReliability = 2 20 | alertSolution = ["Ensure that only the required HTTP methods are allowed", ""] 21 | alertInfo = "Control failure" 22 | cweID = 749 23 | wascID = 0 24 | 25 | methods = ["HEAD", "POST", "PUT", "DELETE", "TRACE", "PATCH", "OPTIONS", "PATATATA"] 26 | pattern = re.compile(r"2[0-9]{2}") 27 | 28 | # If you get an error java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: host parameter is null 29 | #http://deepakmodi2006.blogspot.com/2011/05/javalangillegalargumentexception-host.html - it comes from CONNECT method 30 | #hf=new HostConfiguration(); 31 | #hf.setHost("http://localhost", 22); 32 | def scanNode(sas, msg): 33 | origMsg = msg; 34 | # Copy requests before reusing them 35 | if (sas.isStop()): 36 | return 37 | 38 | for i in methods: 39 | msg = origMsg.cloneRequest(); 40 | #print(i, methods[i]) 41 | #alertParam = methods[i] 42 | msg.mutateHttpMethod(i) 43 | # sendAndReceive(msg, followRedirect, handleAntiCSRFtoken) 44 | sas.sendAndReceive(msg, False, False); 45 | 46 | code = str(msg.getResponseHeader().getStatusCode()) 47 | if (re.search(pattern,code)): 48 | #set response and request body to empy JSON so it can be parsed correctly 49 | msg.setResponseBody("{}") 50 | msg.setRequestBody("{}") 51 | 52 | sas.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 53 | msg.getRequestHeader().getURI().toString(), 54 | i, "", alertInfo, alertSolution[0], "", cweID, wascID, msg); 55 | #time.sleep(1) 56 | 57 | def scan(sas, msg, param, value): 58 | pass -------------------------------------------------------------------------------- /ASVS.policy: -------------------------------------------------------------------------------- 1 | 2 | 3 | ASVS 4 | 5 | MEDIUM 6 | MEDIUM 7 | 8 | 9 | 10 | false 11 | OFF 12 | 13 | 14 | true 15 | MEDIUM 16 | 17 | 18 | false 19 | OFF 20 | 21 | 22 | false 23 | OFF 24 | 25 | 26 | false 27 | OFF 28 | 29 | 30 | true 31 | MEDIUM 32 | 33 | 34 | true 35 | MEDIUM 36 | 37 | 38 | true 39 | MEDIUM 40 | 41 | 42 | false 43 | OFF 44 | 45 | 46 | true 47 | MEDIUM 48 | 49 | 50 | true 51 | MEDIUM 52 | 53 | 54 | false 55 | OFF 56 | 57 | 58 | false 59 | OFF 60 | 61 | 62 | false 63 | OFF 64 | 65 | 66 | false 67 | OFF 68 | 69 | 70 | false 71 | OFF 72 | 73 | 74 | false 75 | OFF 76 | 77 | 78 | true 79 | MEDIUM 80 | 81 | 82 | true 83 | MEDIUM 84 | 85 | 86 | true 87 | MEDIUM 88 | 89 | 90 | true 91 | MEDIUM 92 | 93 | 94 | false 95 | OFF 96 | 97 | 98 | true 99 | MEDIUM 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-2-3-Subresource-Integrity.py: -------------------------------------------------------------------------------- 1 | """ 2 | Credit to BlazingWind on Github for this script: https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide/blob/main/ZAP-scripts/14-2-3-Subresource-Integrity.py 3 | 4 | Script testing 14.2.3 control from OWASP ASVS 4.0: 5 | 'Verify that if application assets, such as JavaScript libraries, CSS stylesheets or web fonts, are hosted externally on a content delivery network (CDN) or external provider, Subresource Integrity (SRI) is used to validate the integrity of the asset.' 6 | 7 | The script checks if integrity attribute is present in ') 37 | bbcode = ('BBCode', '[color=#ff0000;xss:expression(alert(String.fromCharCode(88,83,83)));]XSS[/color]') 38 | 39 | 40 | attacks = [markdown, css, xsl, bbcode] 41 | 42 | #clone message 43 | msg = msg.cloneRequest(); 44 | 45 | #loop through attacks 46 | for pair in attacks: 47 | attack = pair[1] 48 | 49 | # setParam (message, parameterName, newValue) 50 | sas.setParam(msg, param, attack); 51 | 52 | # sendAndReceive(msg, followRedirect, handleAntiCSRFtoken) 53 | sas.sendAndReceive(msg, False, False); 54 | 55 | code = str(msg.getResponseHeader().getStatusCode()) # get status code 56 | 57 | #check if attack payload is reflected back in the response body or server errror, if so raise alert 58 | try: # use try/except to avoid parsing issues from invalid response bodies 59 | body = str(msg.getResponseBody()) 60 | if (attack in body): 61 | alertAttack = attack 62 | alertEvidence = pair[0] + " Payload: " + attack + " found in Response Body" 63 | sas.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 64 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 65 | elif (code == '500'): #check for server error code (500) 66 | alertAttack = attack 67 | alertEvidence = "Status Code: " + code + "\n" + "Attack triggered server error." 68 | sas.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 69 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 70 | except: 71 | pass 72 | 73 | -------------------------------------------------------------------------------- /ZAP_Scripts/active/5-3-6-json-injection.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Script testing 5.3.6, 5.5.3 and 13.3.2 controls from OWASP ASVS 4.0: 4 | 'Verify that the application protects against JSON injection attacks, 5 | JSON eval attacks, and JavaScript expression evaluation.' 6 | 7 | 'Verify that deserialization of untrusted data is avoided or is protected in 8 | both custom code and third-party libraries (such as JSON, XML and YAML parsers).' 9 | 10 | 'Verify that JSON schema validation is in place and verified before accepting input.' 11 | 12 | The scan function will check if a message returns JSON data by checking for "application/json" in the Content-Type response header. 13 | If it does, it will try injecting various common JSON elements like [, ], {, }, etc... that might be used in a JSON injection attack. 14 | The payload will include a key word along with the element. This way, when we check the response body for the payload, we can reudence 15 | the number of false positives from properly formatted JSON data. 16 | 17 | The script will raise an alert if the payload is echoed in the response body (no sanitization or encoding) or the server returns an error. 18 | 19 | """ 20 | 21 | def scanNode(sas, msg): 22 | pass 23 | 24 | def scan(sas, msg, param, value): 25 | #alert parameters 26 | alertRisk= 3 27 | alertConfidence = 2 28 | alertTitle = "5.3.6, 5.5.3, 13.2.2 Verify that the application protects against JSON injection attacks, JSON eval attacks, and JavaScript expression evaluation." 29 | alertDescription = " 5.3.6 Verify that the application protects against JSON injection attacks, JSON eval attacks, and JavaScript expression evaluation." + "\n" " 5.5.3 Verify that deserialization of untrusted data is avoided or is protected in both custom code and third-party libraries (such as JSON, XML and YAML parsers)." + "\n" + "13.2.2 Verify that JSON schema validation is in place and verified before accepting input." 30 | url = msg.getRequestHeader().getURI().toString() 31 | alertParam = "" 32 | alertAttack = "" 33 | alertInfo = "https://owasp.org/www-project-json-sanitizer/migrated_content" + "\n" + "https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data" 34 | alertSolution = "" 35 | alertEvidence = "" 36 | cweID = 830 37 | wascID = 0 38 | 39 | common_json_elements = ["[", "]", "{", "}", ",", ":", '"'] 40 | 41 | 42 | # Copy requests before reusing them 43 | msg = msg.cloneRequest(); 44 | sas.sendAndReceive(msg, False, False); 45 | 46 | #check if msg response includes json object 47 | response_header = msg.getResponseHeader().getHeader("Content-Type") 48 | if ((response_header != None) and "application/json" in response_header): 49 | for element in common_json_elements: 50 | attack = "json attack" + element 51 | 52 | # setParam (message, parameterName, newValue) 53 | sas.setParam(msg, param, attack); 54 | 55 | # sendAndReceive(msg, followRedirect, handleAntiCSRFtoken) 56 | sas.sendAndReceive(msg, False, False); 57 | 58 | code = str(msg.getResponseHeader().getStatusCode()) # get status code 59 | 60 | #check if attack payload is reflected back in the response body or server errror, if so raise alert 61 | try: # use try/except to avoid parsing issues from invalid response bodies 62 | body = str(msg.getResponseBody()) 63 | if (attack in body): 64 | alertAttack = attack 65 | alertEvidence = attack + " found in Response Body" 66 | sas.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 67 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 68 | elif (code == '500'): #check for server error code (500) 69 | alertAttack = attack 70 | alertEvidence = "Status Code: " + code + "\n" + "Attack triggered server error." 71 | sas.raiseAlert(alertRisk, alertConfidence, alertTitle, alertDescription, 72 | url, alertParam, alertAttack, alertInfo, alertSolution, alertEvidence, cweID, wascID, msg); 73 | 74 | except: 75 | pass 76 | -------------------------------------------------------------------------------- /ZAP_Scripts/passive/14-3-1-Error-messages.py: -------------------------------------------------------------------------------- 1 | """ 2 | Credit to BlazingWind on Github for this script:https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide/blob/main/ZAP-scripts/14-3-1-Error-messages.py 3 | 4 | Script testing 14.3.1 and 7.4.1 controls from OWASP ASVS 4.0: 5 | 'Verify that web or application server and framework error messages are configured to deliver user actionable, customized responses to eliminate any unintended security disclosures.' 6 | 7 | 'Verify that a generic message is shown when an unexpected or security sensitive error occurs, potentially with a unique ID which support personnel can use to investigate. (C10)' 8 | 9 | The script attempts to trigger errors in a web application and checks the response for any software component disclosure - if it finds one, it raises an alert. Most of the attacks are based on OWASP WSTG v4.1 10 | chapter 8.1 Testing for Error Code. 11 | """ 12 | import re 13 | 14 | alertTitle = "14.3.1 Verify that web or application server and framework error messages are configured to deliver user actionable, customized responses to eliminate any unintended security disclosures." 15 | alertDescription = "Default error pages has been found which may disclose information about the underlying software and web server in use." 16 | alertRisk = 0 17 | alertReliability = 1 18 | alertSolution = ["Configure the web server to diplay a custom, user-actionable error page instead.", ""] 19 | alertInfo = "Control failure" 20 | cweID = "209 and 210" 21 | wascID = 0 22 | 23 | pattern = re.compile(r"[4-5][0-9]{2}") 24 | evidence = re.compile(r"(?i)Bad request|Unauthorized|Payment Required|Forbidden|Not Found|Apache|nginx|IIS|stack|40[1-3]|40[5-9]|5[0-9]{2}") 25 | 26 | def scanNode(sas, msg): 27 | origMsg = msg; 28 | # Copy requests before reusing them 29 | msg = origMsg.cloneRequest(); 30 | 31 | # GET resource that doesn't exist 32 | alertParam = "/patatata" 33 | msg.getRequestHeader().getURI().setPath(alertParam) 34 | # sendAndReceive(msg, followRedirect, handleAntiCSRFtoken) 35 | sendMsg(sas, msg, alertParam) 36 | 37 | # Use TRACE method that shouldn't be allowed 38 | msg = origMsg.cloneRequest() 39 | alertParam = "TRACE method" 40 | msg.getRequestHeader().setMethod("TRACE") 41 | sendMsg(sas, msg, alertParam) 42 | 43 | # Use an HTTP method that doesn't exist 44 | msg = origMsg.cloneRequest() 45 | alertParam = "Method that doesn't exist" 46 | msg.getRequestHeader().setMethod("PATATATA") 47 | sendMsg(sas, msg, alertParam) 48 | 49 | # Use an older version of HTTP 50 | msg = origMsg.cloneRequest() 51 | alertParam = "HTTP/0.9" 52 | msg.getRequestHeader().setVersion(alertParam) 53 | sendMsg(sas, msg, alertParam) 54 | 55 | # Use a protocol that doesn't exist - does not work bc of regex check which uses HTTP 56 | #msg = origMsg.cloneRequest() 57 | #alertParam = "Invalid protocol" 58 | #msg.setRequestHeader("GET " + msg.getRequestHeader().getURI().toString() + " INVALID/1.1") 59 | #sendMsg(sas, msg, alertParam) 60 | 61 | # Set Host header to localhost - ZAP enforces the header, can't be changed. See https://github.com/zaproxy/zaproxy/issues/1318 62 | #msg = origMsg.cloneRequest() 63 | #alertParam = "Host: localhost header" 64 | #prime = msg.getRequestHeader().getPrimeHeader() 65 | #msg.setRequestHeader(prime) 66 | #msg.getRequestHeader().addHeader("Host", "localhost") 67 | #sendMsg(sas, msg, alertParam) 68 | 69 | # Send only the first line of the request (ZAP adds Content-Length and Host headers and thus this may not trigger anything) 70 | msg = origMsg.cloneRequest() 71 | alertParam = "Prime line of the request" 72 | prime = str(msg.getRequestHeader().getPrimeHeader()) 73 | msg.setRequestHeader(prime + "\r\n\r\n") 74 | sendMsg(sas, msg, alertParam) 75 | 76 | def scan(sas, msg, param, value): 77 | pass 78 | 79 | def sendMsg(sas, msg, alertParam): 80 | if (sas.isStop()): 81 | return 82 | sas.sendAndReceive(msg, False, False); 83 | 84 | code = str(msg.getResponseHeader().getStatusCode()) 85 | if (re.search(pattern,code)): 86 | body = msg.getResponseBody().toString() 87 | if (re.search(evidence,body)): 88 | sas.raiseAlert(alertRisk, alertReliability, alertTitle, alertDescription, 89 | msg.getRequestHeader().getURI().toString(), 90 | alertParam, "", alertInfo, alertSolution[0], "", cweID, wascID, msg); 91 | 92 | -------------------------------------------------------------------------------- /ZAP_Scripts/httpfuzzerprocessor/password_security.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Script testing the following password security controls from OWASP ASVS 4.0: 4 | 5 | 2.1.1 - Verify that user set passwords are at least 12 characters in length (after multiple spaces are combined). 6 | 7 | 2.1.2 - Verify that passwords of at least 64 characters are permitted, and that passwords of more than 128 characters are denied. 8 | 9 | 2.1.4 - Verify that any printable Unicode character, including language neutral characters such as spaces and Emojis are permitted in passwords. 10 | 11 | 2.1.7 - Verify that passwords submitted during account registration, login, and password change are checked against a set of breached passwords 12 | either locally (such as the top 1,000 or 10,000 most common passwords which match the system's password policy) or using an external API. 13 | If using an API a zero knowledge proof or other mechanism should be used to ensure that the plain text password is not sent or used in verifying the breach status of the password. 14 | If the password is breached, the application must require the user to set a new non-breached password. 15 | 16 | 2.1.9 - Verify that there are no password composition rules limiting the type of characters permitted. There should be no requirement for upper or lower case or numbers or special characters. 17 | 18 | 19 | Once the fuzzer is run with the provided wordlist, this script will check the response from each fuzzed request. 20 | 21 | If the reponse status code is successful (200-209), the script will check the payload and add a custom status code if following criteria apply: 22 | 1. Payload is less than 12 characters (2.1.1) 23 | 2. Payload is greater than 128 characters (2.1.2) 24 | 3. Payload is from top 1,000 most common passwords, taken from rockyou.txt (2.1.7) 25 | *This condition is true by default if the payload is greater than 12 and less than 128 characters long. This is because the wordlist contains only payloads from rockyou.txt and payloads that are too short or too long. 26 | *If changes are made to the wordlist or another one is used, false positive may be triggered so please review your results. 27 | 28 | If the response status code is NOT successful, the script will check the payload and add a custom status code if following criteria apply (Note: these conditions are more prone to false positives): 29 | 1. Payload is between 64 and 128 characters (2.1.2) 30 | 2. Payload is valid length and contains special character (2.1.9) 31 | 3. Payload is valid length and contains a space or emoji (2.1.4) 32 | 33 | */ 34 | 35 | 36 | // Auxiliary variables/constants needed for processing. 37 | var count = 1; 38 | 39 | //function to determine if string contains a special character using regex 40 | function containsSpecial(str){ 41 | var regex = /[ !@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/g; 42 | return regex.test(str); 43 | } 44 | 45 | //function to determine if string contains an emoji or space since the only payloads in the wordlist that contain those characters start with DGGVss 46 | function containsEmoji(str){ 47 | var index = str.indexOf("DGGVss"); 48 | return (index != -1); 49 | 50 | } 51 | 52 | function processMessage(utils, message) { 53 | message.getRequestHeader().setHeader("X-Unique-Id", count); 54 | count++; 55 | } 56 | 57 | function processResult(utils, fuzzResult){ 58 | 59 | //testing variables 60 | var payload = utils.getPayloads().toString(); 61 | var codes = [200, 201, 202, 203, 204, 205, 206, 207, 208, 209]; 62 | 63 | var response_code = fuzzResult.getHttpMessage().getResponseHeader().getStatusCode(); 64 | var index = codes.indexOf(response_code); 65 | var length = payload.length; 66 | 67 | 68 | //alert info 69 | var risk= 0; 70 | var confidence = 1; 71 | var name = "Fuzzer: Password Security"; 72 | var description = "Please check the state column in the Fuzzer window to see which ASVS control was triggered for each payload." + "\n" + "Status Code: " + response_code + "\n" + "cweID: " + "521" + "\n" + "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/04-Authentication_Testing/07-Testing_for_Weak_Password_Policy"; 73 | 74 | 75 | if (index != -1){//if, status code is found in list of successful codes (permitted) 76 | if (length < 12){//Payload is less than 12 characters (2.1.1) 77 | fuzzResult.addCustomState("Key Custom State", "2.1.1 - Verify that user set passwords are at least 12 characters in length (after multiple spaces are combined)."); 78 | risk= 1; 79 | utils.raiseAlert(risk, confidence, name, description); 80 | }else if (length > 128){ //Payload is greater than 128 characters (2.1.2) 81 | fuzzResult.addCustomState("Key Custom State", "2.1.2 - Verify that passwords of at least 64 characters are permitted, and that passwords of more than 128 characters are denied.") 82 | risk= 1; 83 | utils.raiseAlert(risk, confidence, name, description); 84 | }else{//Payload is from top 1,000 most common passwords, taken from rockyou.txt (2.1.7) 85 | fuzzResult.addCustomState("Key Custom State", "2.1.7 - Verify that passwords submitted during account registration, login, and password change are checked against a set of breached passwords" 86 | + "either locally (such as the top 1,000 or 10,000 most common passwords which match the system's password policy) or using an external API. " 87 | + "If using an API a zero knowledge proof or other mechanism should be used to ensure that the plain text password is not sent or used in verifying the breach status of the password. " 88 | + "If the password is breached, the application must require the user to set a new non-breached password.") 89 | risk= 1; 90 | utils.raiseAlert(risk, confidence, name, description); 91 | } 92 | 93 | }else{//if denied/error (response code is not 200-209) 94 | if (length > 63 && length < 129){//Payload is between 64 and 128 characters (2.1.2) but was denied 95 | fuzzResult.addCustomState("Key Custom State", "2.1.2 - Verify that passwords of at least 64 characters are permitted, and that passwords of more than 128 characters are denied.") 96 | utils.raiseAlert(risk, confidence, name, description); 97 | }else if ((length > 11 && length < 129) && containsEmoji){//Payload is valid length and contains emoji or space (2.1.4) but was denied 98 | fuzzResult.addCustomState("Key Custom State", "2.1.4 - Verify that any printable Unicode character, including language neutral characters such as spaces and Emojis are permitted in passwords.") 99 | utils.raiseAlert(risk, confidence, name, description); 100 | }else if ((length > 11 && length < 129) && containsSpecial(payload)){//Payload is valid length and contains special character (2.1.9) but was denied 101 | fuzzResult.addCustomState("Key Custom State", "2.1.9 - Verify that there are no password composition rules limiting the type of characters permitted. There should be no requirement for upper or lower case or numbers or special characters.") 102 | utils.raiseAlert(risk, confidence, name, description); 103 | } 104 | 105 | } 106 | return true; 107 | } 108 | 109 | function getRequiredParamsNames(){ 110 | return []; 111 | } 112 | 113 | function getOptionalParamsNames(){ 114 | return []; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /ZAP_Scripts/standalone/reformat-alerts.js: -------------------------------------------------------------------------------- 1 | //Credit to BlazingWind on Github for thier implementation of this script https://github.com/BlazingWind/OWASP-ASVS-4.0-testing-guide 2 | 3 | //Script testing the following controls from OWASP ASVS 4.0: 4 | //5.3.4 5 | //5.3.5 6 | //5.3.10 7 | //5.3.3 8 | //5.3.7 9 | //5.3.8 10 | //5.1.2 11 | //6.2.1 12 | //5.3.9 13 | //5.5.2 14 | //14.2.1 15 | //4.3.2 16 | 17 | 18 | //The script checks loops through all the alerts looking for plugin ids that correspond to the ASVS controls 19 | //and changes the title and description to match the requirement 20 | 21 | //alert plugin ids can be found here: 22 | //https://www.zaproxy.org/docs/alerts/ 23 | 24 | 25 | extAlert = org.parosproxy.paros.control.Control.getSingleton(). 26 | getExtensionLoader().getExtension( 27 | org.zaproxy.zap.extension.alert.ExtensionAlert.NAME) 28 | 29 | 30 | if (extAlert != null) { 31 | // var Alert = org.parosproxy.paros.core.scanner.Alert 32 | var alerts = extAlert.getAllAlerts() 33 | // cycle thorugh all alerts 34 | for (var i = 0; i < alerts.length; i++) { 35 | 36 | var alert = alerts[i] 37 | var id = alert.getPluginId(); // get plugin id for alert 38 | 39 | switch (id){ //set up cases for each id to change alert format to match ASVS 40 | case 40018: //sql injection 41 | description = alert.getDescription() 42 | alert.setName("5.3.4 & 5.3.5 Verify that where parameterized or safer mechanisms are not present, context-specific output encoding is used."); 43 | alert.setDescription("5.3.4 Verify that data selection or database queries (e.g. SQL, HQL, ORM, NoSQL) use parameterized queries, ORMs, entity frameworks, or are otherwise protected from database injection attacks." + "\n" +"5.3.5 Verify that where parameterized or safer mechanisms are not present, context-specific output encoding is used to protect against injection attacks, such as the use of SQL escaping to protect against SQL injection." + "\n" + description) 44 | extAlert.updateAlert(alert); 45 | break; 46 | case 90029: //soap xml injection 47 | description = alert.getDescription() 48 | alert.setName("5.3.10 Verify that the application protects against XML injection attacks."); 49 | alert.setDescription('Verify that the application protects against XML injection attacks.' + "\n" + description) 50 | extAlert.updateAlert(alert); 51 | break; 52 | case 90021: //xpath injection 53 | description = alert.getDescription() 54 | alert.setName("5.3.10 Verify that the application protects against XPath injection attacks."); 55 | alert.setDescription('Verify that the application protects against XPath injection attacks.' + "\n" + description) 56 | extAlert.updateAlert(alert); 57 | break; 58 | case 40012: //reflected xss 59 | description = alert.getDescription() 60 | alert.setName("5.3.3 Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against reflected XSS."); 61 | alert.setDescription('Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against reflected XSS.' + "\n" + description) 62 | extAlert.updateAlert(alert); 63 | break; 64 | case 40014: //persistent xss 65 | description = alert.getDescription() 66 | alert.setName("5.3.3 Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against stored XSS."); 67 | alert.setDescription('Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against stored XSS.' + "\n" + description) 68 | extAlert.updateAlert(alert); 69 | break; 70 | case 40016: //persistent xss - prime 71 | description = alert.getDescription() 72 | alert.setName("5.3.3 Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against stored XSS."); 73 | alert.setDescription('Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against stored XSS.' + "\n" + description) 74 | extAlert.updateAlert(alert); 75 | break; 76 | case 40017: //persistent xss - spider 77 | description = alert.getDescription() 78 | alert.setName("5.3.3 Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against stored XSS."); 79 | alert.setDescription('Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against stored XSS.' + "\n" + description) 80 | extAlert.updateAlert(alert); 81 | break; 82 | case 40026: //dom based xss 83 | description = alert.getDescription() 84 | alert.setName("5.3.3 Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against dom-based XSS."); 85 | alert.setDescription('Verify that context-aware, preferably automated - or at worst, manual - output escaping protects against dom-based XSS.' + "\n" + description) 86 | extAlert.updateAlert(alert); 87 | break; 88 | case 40015: //ldap injection 89 | description = alert.getDescription() 90 | alert.setName("5.3.7 Verify that the application protects against LDAP injection vulnerabilities."); 91 | alert.setDescription('Verify that the application protects against LDAP injection vulnerabilities, or that specific security controls to prevent LDAP injection have been implemented.' + "\n" + description) 92 | extAlert.updateAlert(alert); 93 | break; 94 | case 90020: //remote os command injection 95 | description = alert.getDescription() 96 | alert.setName("5.3.8 Verify that the application protects against OS command injection."); 97 | alert.setDescription('Verify that the application protects against OS command injection and that operating system calls use parameterized OS queries or use contextual command line output encoding.' + "\n" + description) 98 | extAlert.updateAlert(alert); 99 | break; 100 | case 20014: //parameter pollution 101 | description = alert.getDescription() 102 | alert.setName("5.1.2 Verify that frameworks protect against mass parameter assignment attacks."); 103 | alert.setDescription('Verify that frameworks protect against mass parameter assignment attacks, or that the application has countermeasures to protect against unsafe parameter assignment, such as marking fields private or similar.' + "\n" + description) 104 | extAlert.updateAlert(alert); 105 | break; 106 | case 90024: //generic padding oracle attack 107 | description = alert.getDescription() 108 | alert.setName("6.2.1 Verify that all cryptographic modules fail securely, and errors are handled in a way that does not enable Padding Oracle attacks."); 109 | alert.setDescription('Verify that all cryptographic modules fail securely, and errors are handled in a way that does not enable Padding Oracle attacks.' + "\n" + description) 110 | extAlert.updateAlert(alert); 111 | break; 112 | case 4: //rfi 113 | description = alert.getDescription() 114 | alert.setName("5.3.9 & 13.1.1 Verify that the application protects against Local File Inclusion (LFI) or Remote File Inclusion (RFI) attacks."); 115 | alert.setDescription("5.3.9 Verify that the application protects against Local File Inclusion (LFI) or Remote File Inclusion (RFI) attacks." + "\n" + "13.1.1 Verify that all application components use the same encodings and parsers to avoid parsing attacks that exploit different URI or file parsing behavior that could be used in SSRF and RFI attacks." + "\n" + description) 116 | extAlert.updateAlert(alert); 117 | break; 118 | case 90023: //xml external entity attack 119 | description = alert.getDescription() 120 | alert.setName("5.5.2 Verify that the application correctly restricts XML parsers."); 121 | alert.setDescription('Verify that the application correctly restricts XML parsers to only use the most restrictive configuration possible and to ensure that unsafe features such as resolving external entities are disabled to prevent XML eXternal Entity (XXE) attacks.' + "\n" + description) 122 | extAlert.updateAlert(alert); 123 | break; 124 | case 10003: //up to date components 125 | description = alert.getDescription() 126 | info = alert.getOtherInfo() 127 | alert.setDescription('Using older versions of software packages, for example jquery, may allow for exploitation of e.g. XSS on a website. '+ description + ' Vulnerable to: \n' +info) 128 | alert.setName('14.2.1 Verify that all components are up to date, preferably using a dependency checker during build or compile time.') 129 | extAlert.updateAlert(alert); 130 | break; 131 | case 0: //directory browsing 132 | description = alert.getDescription() 133 | alert.setDescription('A directory listing was found, which may reveals sensitive data.') 134 | alert.setName('4.3.2 Verify that directory browsing is disabled unless deliberately desired. Additionally, applications should not allow discovery or disclosure of file or directory metadata, such as Thumbs.db, .DS_Store, .git or .svn folders.') 135 | extAlert.updateAlert(alert); 136 | break; 137 | default: 138 | break; 139 | } 140 | } 141 | } --------------------------------------------------------------------------------