├── 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 | }
--------------------------------------------------------------------------------