From 852860f286549d48be914c3188dca183fd040b6e Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Thu, 20 Jun 2024 20:31:50 +0200 Subject: [PATCH 1/8] :bug: fix qualys webapp scan request body (#10422) * :bug: fix qualys webapp scan request body * add unittest --- dojo/tools/qualys_webapp/parser.py | 2 + .../scans/qualys_webapp/discussion_10239.xml | 837 ++++++++++++++++++ unittests/tools/test_qualys_webapp_parser.py | 11 + 3 files changed, 850 insertions(+) create mode 100644 unittests/scans/qualys_webapp/discussion_10239.xml diff --git a/dojo/tools/qualys_webapp/parser.py b/dojo/tools/qualys_webapp/parser.py index 52b008364f..729fc76d28 100644 --- a/dojo/tools/qualys_webapp/parser.py +++ b/dojo/tools/qualys_webapp/parser.py @@ -151,6 +151,8 @@ def get_request(request): for head in headers.iter("HEADER"): header += str(head.findtext("key")) + ": " header += str(head.findtext("value")) + "\n" + if request.findtext("BODY") is not None: + header += "BODY: " + str(request.findtext("BODY")) + "\n" return str(header) return "" diff --git a/unittests/scans/qualys_webapp/discussion_10239.xml b/unittests/scans/qualys_webapp/discussion_10239.xml new file mode 100644 index 0000000000..a827023155 --- /dev/null +++ b/unittests/scans/qualys_webapp/discussion_10239.xml @@ -0,0 +1,837 @@ + + +
+ Scan Report + Vulnerabilities of all selected scans are consolidated into one report so that you can view their evolution. + 01 Apr 2020 01:15PM GMT-0400 + + Example Co. +
2600 Dialtone Way
+ Metropolis + Virginia + United States of America + 20121 +
+ + Eddie Example + eexample + +
+ + + REMEDIATION + Include patched findings + + + REMEDIATION + Show ignored findings + + + + Web Application Vulnerability Scan - Web Service Integration - TEST - 2020-03-12 + + + + Medium + 2 + 0 + 13 + + + + Web Application Vulnerability Scan - Web Service Integration - TEST - 2020-03-12 + 12 Mar 2020 + 0 + 0 + 1 + 0 + 0 + 0 + 13 + + + + + + + 20f4ba90-7bb0-11ea-ac65-5fc1d89b8cc7 + 101588229 + 6074202 + 150085 + https://example.com/vulnerable-path + post_param + https://example.com/vulnerable/path + + https://example.com/ + https://www.example.com/ + https://www.example.com/vulnerable + https://www.example.com/vulnerable/path + + false + Not Required + 14 May 2024 09:35AM GMT+0200 + false + + + 1 + post_param=malicious_code_here + + POST + https://example.com/vulnerable/path + +
+ Referer + +
+
+ Host + +
+
+ User-Agent + +
+
+ Accept + +
+
+ Content-Length + +
+
+ Content-Type + +
+
+ post_param=malicious_code_here +
+ + Y29tbWVudDogClJlc3BvbnNlIGNvbnRlbnQtdHlwZTogdGV4dC9odG1sCgoJCQkJPC9kaXY+CgkJCQkJPCEtLS8ubW9kLWlubmVyLS0+CgoJCQkJPC9kaXY+CgkJCQk8IS0tLyNoZWFkZXItcGFnZS1iLS0+CgoJCQkJPGRpdiBjbGFzcz0iZnJhbmphU3VibWVudSI+CgkJCQkJPGRpdiBjbGFzcz0ibW9kLWlubmVyIj4KCQkJCQk8L2Rpdj4KCQkJCTwvZGl2PgoJCQkJPCEtLS8uZnJhbmphU3VibWVudS0tPgoKCQkJPC9oZWFkZXI+Cgo8c2VjdGlvbiBpZD0iY29udGVudCIgZGF0YS1wb3N0X3BhcmFtPSIxMVwiXCc+bWFsaWNpb3VzX2NvZGVfaW5qZWN0ZWQiPgoKCQoJCQoJCTxhcnRpY2xlIGNsYXNzPSJwYXJyYWxheCIgc3R5bGU9ImJhY2tncm91bmQ6dXJsKGh0dHBzOi8vd3d3LmV4YW1wbGUuY29tL2ltZy5wbmcpIDBweCAwcHggbm8tcmVwZWF0Ij4KCQkJPGRpdiBjbGFzcz0icGFycmFsYXgtaW5uZXIiPgoJCQkJPGRpdiBjbGFzcz0ibW9kLWlubmVyIj4KCQkJCQk8dWwgY2xhc3M9InBhdGgiPjxsaT5Fc3Tvv73vv71zIA== + bWFsaWNpb3VzX2NvZGVfYWN0aW9uX2V2aWRlbmNl + +
+
+ false +
+
+
+ + + + 6 + Information Gathered + 1 + DNS Host Name + IG-DIAG + The fully qualified domain name of this host, if it was obtained from a DNS server, is displayed in the RESULT section. + N/A + N/A + + + 45017 + Information Gathered + 2 + Operating System Detected + IG-DIAG + +1) TCP/IP Fingerprint: The operating system of a host can be identified from a remote system using TCP/IP fingerprinting. All underlying operating system TCP/IP stacks have subtle differences that can be seen in their responses to specially-crafted TCP packets. According to the results of this "fingerprinting" technique, the OS version is among those listed below. +

+Note that if one or more of these subtle differences are modified by a firewall or a packet filtering device between the scanner and the host, the fingerprinting technique may fail. Consequently, the version of the OS may not be detected correctly. If the host is behind a proxy-type firewall, the version of the operating system detected may be that of the firewall instead of the host being scanned. +

+2) NetBIOS: Short for Network Basic Input Output System, an application programming interface (API) that augments the DOS BIOS by adding special functions for local-area networks (LANs). Almost all LANs for PCs are based on the NetBIOS. Some LAN manufacturers have even extended it, adding additional network capabilities. NetBIOS relies on a message format called Server Message Block (SMB). +

+3) PHP Info: PHP is a hypertext pre-processor, an open-source, server-side, HTML-embedded scripting language used to create dynamic Web pages. Under some configurations it is possible to call PHP functions like phpinfo() and obtain operating system information. +

+4) SNMP: The Simple Network Monitoring Protocol is used to monitor hosts, routers, and the networks to which they attach. The SNMP service maintains Management Information Base (MIB), a set of variables (database) that can be fetched by Managers. These include "MIB_II.system.sysDescr" for the operating system. +]]> + Not applicable. + Not applicable. + + + 45038 + Information Gathered + 1 + Host Scan Time + IG-DIAG + +The Host Scan Time does not have a direct correlation to the Duration time as displayed in the Report Summary section of a scan results report. The Duration is the period of time it takes the service to perform a scan task. The Duration includes the time it takes the service to scan all hosts, which may involve parallel scanning. It also includes the time it takes for a scanner appliance to pick up the scan task and transfer the results back to the service's Secure Operating Center. Further, when a scan task is distributed across multiple scanners, the Duration includes the time it takes to perform parallel host scanning on all scanners. +

+For host running the Qualys Windows agent this QID reports the time taken by the agent to collect the host metadata used for the most recent assessment scan.]]> + N/A + N/A + + + 150009 + Information Gathered + 1 + Links Crawled + IG-DIAG + NOTE: This list also includes +- All the unique links that are reported in QID 150140 (Redundant links/URL paths crawled and not crawled) +- All the forms reported in QID 150152 (Forms Crawled), +- All the forms in QID 150115 (Authentication Form Found) and +- Certain requests from QID 150172 (Requests Crawled)]]> + N/A + N/A + + + 150018 + Information Gathered + 2 + Connection Error Occurred During Web Application Scan + IG-DIAG + +

  • A disturbance in network connectivity between the scanner and the web application occurred.
  • +
  • The web server or application server hosting the application was taken down in the midst of a scan.
  • +
  • The web application experienced an overload, possibly due to load generated by the scan.
  • +
  • An error occurred in the SSL/TLS handshake (applies to HTTPS web applications only).
  • +
  • A security device, such as an IDS/IPS or web application firewall (WAF), began to drop or reject the HTTP connections from the scanner.
  • +
  • Very large files like PDFs, videos, etc. are present on the site and caused timeouts when accessed by the scanner.
  • +]]>
    + Some of the links were not crawled or scanned. Results may be incomplete or incorrect. + First, confirm that the server was not taken down in the midst of the scan. After that, investigate the root cause by reviewing the listed links and examining web server logs, application server logs, or IDS/IPS/WAF logs. If the errors are caused due to load generated by the scanner then try reducing the scan intensity (this could increase the scan duration). If the errors are due to specific URLs being tested by the scanner or due to specific form data sent by the scanner, then configure blacklists in the scan configuration as needed to avoid such requests. If timeouts or connection errors are a persistent issue but you want the scan to run to completion, change the Behavior Settings in the option profile to increase the error thresholds or disable the error checks entirely. +
    + + 150021 + Information Gathered + 1 + Scan Diagnostics + IG-DIAG + This check provides various details of the scan's performance and behavior. In some cases, this check can be used to identify problems that the scanner encountered when crawling the target Web application. + The scan diagnostics data provides technical details about the crawler's performance and behavior. This information does not necessarily imply problems with the Web application. + No action is required. + + + 150042 + Information Gathered + 3 + Server Returns HTTP 500 Message For Request + IG-DIAG + WASC-14 + During the scanning engine's crawl phase, the Web server responded with an HTTP 500 message for each link listed below. The HTTP 500 message indicates a server error. + The presence of an HTTP 500 error during the crawl phase indicates that some problem exists in the Web site that will be encountered during normal usage of the Web application. + Review each link to determine why the server encountered an error when responding to the link. + + + 150079 + Potential Vulnerability + 3 + Slow HTTP headers vulnerability + INFO + A6 + WASC-10 + CWE-772 + 6.1 + 5.5 + Slowloris HTTP DoS. ]]> + All other services remain intact but the web server itself becomes completely inaccessible. + here. +Countermeasures for Apache are described here. +Easy to use tool for intrusive testing is available here. +]]> + + + 150085 + Potential Vulnerability + 3 + Slow HTTP POST vulnerability + INFO + A6 + WASC-10 + CWE-772 + 6.1 + 5.5 + +The attack holds server connections open by sending properly crafted HTTP POST headers that contain a Content-Length header with a large value to inform the web server how much of data to expect. After the HTTP POST headers are fully sent, the HTTP POST message body is sent at slow speeds to prolong the completion of the connection and lock up server resources. By waiting for the complete request body, the server is helping clients with slow or intermittent connections to complete requests, but is also exposing itself to abuse. +

    +Further information can be found under BlackHat_DC_2011_Brennan_Denial_Service-Slides.pdf. ]]> + All other services remain intact but the web server itself becomes inaccessible. + here. +A tool that demonstrates this vulnerability in a more intrusive manner is available here.]]> + + + 150152 + Information Gathered + 1 + Forms Crawled + IG-DIAG + NOTE: The regular expression specified under 'Redundant Links' are not applied to forms. Forms (unique or redundant) are not reported under QID 150140.]]> + N/A + N/A + + + 150202 + Information Gathered + 2 + Missing header: X-Content-Type-Options + IG-WEAK + A6 + WASC-15 + The X-Content-Type-Options response header is not present. WAS reports missing X-Content-Type-Options header on each crawled link for both static and dynamic responses. The scanner performs the check not only on 200 responses but 4xx and 5xx responses as well. It's also possible the QID will be reported on directory-level links. + + + + + 150204 + Information Gathered + 1 + Missing header: X-XSS-Protection + IG-WEAK + A6 + WASC-15 + + +Note that HTTP 4xx and 5xx responses can also be susceptible to attacks such as XSS. For better security the X-XSS-Protection header should be set on 4xx and 5xx responses as well.]]> + NOTE: The X-XSS-Protection header is not supported by all browsers. Google Chrome and Safari are some of the browsers which support it, Firefox on the other hand does not support the header. X-XSS-Protection header does not guarantee a complete protection against XSS. For better protection against XSS attacks, the web application should use secure coding principles. Also, consider leveraging the Content-Security-Policy (CSP) header, which is supported by all browsers. + +

    Using X-XSS-Protection could have unintended side effects, please understand the implications carefully before using it. + +

    References:
    +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
    +- https://blog.innerht.ml/the-misunderstood-x-xss-protection/
    +- https://www.mbsd.jp/blog/20160407.html
    +- https://www.chromium.org/developers/design-documents/xss-auditor]]> + + + 150206 + Information Gathered + 2 + Content-Security-Policy Not Implemented + IG-WEAK + A6 + WASC-15 + + +HTTP 4xx and 5xx responses can also be susceptible to attacks such as XSS. For better security it's important to set appropriate CSP policies on 4xx and 5xx responses as well. +]]> + + +The absence of Content Security Policy in the response will allow the attacker to exploit vulnerabilities as the protection provided by the browser is not at all leveraged by the Web application. If secure CSP configuration is not implemented, browsers will not be able to block content-injection attacks such as Cross-Site Scripting and Clickjacking. + +]]> + + +References:
    +- https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html
    +- https://developers.google.com/web/fundamentals/security/csp/ + + + +]]>
    +
    + + 150208 + Information Gathered + 2 + Missing header: Referrer-Policy + IG-WEAK + A6 + WASC-15 + +1) no-referrer
    +2) no-referrer-when-downgrade
    +3) same-origin
    +4) origin
    +5) origin-when-cross-origin
    +6) strict-origin
    +7) strict-origin-when-cross-origin
    +

    +If the Referrer Policy header is not found , WAS checks in response body for meta tag containing tag name as "referrer" and one of the above Referrer Policy.]]> + + + +References:
    +- https://www.w3.org/TR/referrer-policy/
    +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy +]]>
    + + + 150262 + Information Gathered + 2 + Missing header: Feature-Policy + IG-WEAK + A6 + WASC-15 + The Feature-Policy response header is not present. + +These policies restrict what APIs the site can access or modify the browser's default behavior for certain features.]]> + + +References:
    +- https://www.w3.org/TR/feature-policy/
    +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy]]>
    +
    + + + + PATH + Path Disclosure + VULNERABILITY + + + CC + Credit Card Numbers + SENSITIVE_CONTENT + + + IG-DIAG + Scan Diagnostics + INFORMATION_GATHERED + + + SSN-US + Social Security Numbers (US) + SENSITIVE_CONTENT + + + IG-WEAK + Security Weaknesses + INFORMATION_GATHERED + + + CUSTOM + Custom Contents + SENSITIVE_CONTENT + + + XSS + Cross-Site Scripting + VULNERABILITY + + + INFO + Information Disclosure + VULNERABILITY + + + SQL + SQL Injection + VULNERABILITY + + + + + A1 + Injection + + + A2 + Broken Authentication + + + A3 + Sensitive Data Exposure + + + A4 + XML External Entities (XXE) + + + A5 + Broken Access Control + + + A6 + Security Misconfiguration + + + A7 + Cross-Site Scripting (XSS) + + + A8 + Insecure Deserialization + + + A9 + Using Components with Known Vulnerabilities + + + A10 + + + + + + WASC-1 + INSUFFICIENT AUTHENTICATION + + + WASC-2 + INSUFFICIENT AUTHORIZATION + + + WASC-3 + INTEGER OVERFLOWS + + + WASC-4 + INSUFFICIENT TRANSPORT LAYER PROTECTION + + + WASC-5 + REMOTE FILE INCLUSION + + + WASC-6 + FORMAT STRING + + + WASC-7 + BUFFER OVERFLOW + + + WASC-8 + CROSS-SITE SCRIPTING + + + WASC-9 + CROSS-SITE REQUEST FORGERY + + + WASC-10 + DENIAL OF SERVICE + + + WASC-11 + BRUTE FORCE + + + WASC-12 + CONTENT SPOOFING + + + WASC-13 + INFORMATION LEAKAGE + + + WASC-14 + SERVER MISCONFIGURATION + + + WASC-15 + APPLICATION MISCONFIGURATION + + + WASC-16 + DIRECTORY INDEXING + + + WASC-17 + IMPROPER FILESYSTEM PERMISSIONS + + + WASC-18 + CREDENTIAL/SESSION PREDICTION + + + WASC-19 + SQL INJECTION + + + WASC-20 + IMPROPER INPUT HANDLING + + + WASC-21 + INSUFFICIENT ANTI-AUTOMATION + + + WASC-22 + IMPROPER OUTPUT HANDLING + + + WASC-23 + XML INJECTION + + + WASC-24 + HTTP REQUEST SPLITTING + + + WASC-25 + HTTP RESPONSE SPLITTING + + + WASC-26 + HTTP REQUEST SMUGGLING + + + WASC-27 + HTTP RESPONSE SMUGGLING + + + WASC-28 + NULL BYTE INJECTION + + + WASC-29 + LDAP INJECTION + + + WASC-30 + MAIL COMMAND INJECTION + + + WASC-31 + OS COMMANDING + + + WASC-32 + ROUTING DETOUR + + + WASC-33 + PATH TRAVERSAL + + + WASC-34 + PREDICTABLE RESOURCE LOCATION + + + WASC-35 + SOAP ARRAY ABUSE + + + WASC-36 + SSI INJECTION + + + WASC-37 + SESSION FIXATION + + + WASC-38 + URL REDIRECTOR ABUSE + + + WASC-39 + XPATH INJECTION + + + WASC-40 + INSUFFICIENT PROCESS VALIDATION + + + WASC-41 + XML ATTRIBUTE BLOWUP + + + WASC-42 + ABUSE OF FUNCTIONALITY + + + WASC-43 + XML EXTERNAL ENTITIES (XXE) + + + WASC-44 + XML ENTITY EXPANSION + + + WASC-45 + FINGERPRINTING + + + WASC-46 + XQUERY INJECTION + + + WASC-47 + INSUFFICIENT SESSION EXPIRATION + + + WASC-48 + INSECURE INDEXING + + + WASC-49 + INSUFFICIENT PASSWORD RECOVERY + + + + + + + Web Application Vulnerability Scan - Web Service Integration - TEST - 2020-03-12 + was/1584045746255.2267109 + 12 Mar 2020 04:42PM GMT-0400 + 12 Mar 2020 04:54PM GMT-0400 + Vulnerability + 1 + Manual + Web Service Integration - TEST + None + Initial WAS Options + External (IP: 64.39.99.13, Scanner: 11.8.30-1, WAS: 7.6.34-1, Signatures: 2.4.841-2) + Finished + No Authentication specified + + + + BOTH + false + 300 + + Initial Parameters + true + false + 5 + false + 100 + 300 + LOW + MINIMAL + CORE + No + false + false + Off + + + 57568730 + Web Service Integration - TEST + https://services.example.com + Sally Example (sexample) + Ubuntu / Tiny Core Linux / Linux 2.6.x + Limit to URL hostname and specified domains + + + + Confirmed Vulnerability + Vulnerabilities (QIDs) are design flaws, programming errors, or mis-configurations that make your web application and web application platform susceptible to malicious attacks. Depending on the level of the security risk, the successful exploitation of a vulnerability can vary from the disclosure of information to a complete compromise of the web application and/or the web application platform. Even if the web application isn't fully compromised, an exploited vulnerability could still lead to the web application being used to launch attacks against users of the site. + + + 1 + Minimal + Basic information disclosure (e.g. web server type, programming language) might enable intruders to discover other vulnerabilities, but lack of this information does not make the vulnerability harder to find. + + + 2 + Medium + Intruders may be able to collect sensitive information about the application platform, such as the precise version of software used. With this information, intruders can easily exploit known vulnerabilities specific to software versions. Other types of sensitive information might disclose a few lines of source code or hidden directories. + + + 3 + Serious + Vulnerabilities at this level typically disclose security-related information that could result in misuse or an exploit. Examples include source code disclosure or transmitting authentication credentials over non-encrypted channels. + + + 4 + Critical + Intruders can exploit the vulnerability to gain highly sensitive content or affect other users of the web application. Examples include certain types of cross-site scripting and SQL injection attacks. + + + 5 + Urgent + Intruders can exploit the vulnerability to compromise the web application's data store, obtain information from other users' accounts, or obtain command execution on a host in the web application's architecture. + + + + + Potential Vulnerability + Potential Vulnerabilities indicate that the scanner observed a weakness or error that is commonly used to attack a web application, and the scanner was unable to confirm if the weakness or error could be exploited. Where possible, the QID's description and results section include information and hints for following-up with manual analysis. For example, the exploitability of a QID may be influenced by characteristics that the scanner cannot confirm, such as the web application's network architecture, or the test to confirm exploitability requires more intrusive testing than the scanner is designed to conduct. + + + 1 + Minimal + Presence of this vulnerability is indicative of basic information disclosure (e.g. web server type, programming language) and might enable intruders to discover other vulnerabilities. For example in this scenario, information such as web server type, programming language, passwords or file path references can be disclosed. + + + 2 + Medium + Presence of this vulnerability is indicative of basic information disclosure (e.g. web server type, programming language) and might enable intruders to discover other vulnerabilities. For example version of software or session data can be disclosed, which could be used to exploit. + + + 3 + Serious + Presence of this vulnerability might give access to security-related information to intruders who are bound to misuse or exploit. Examples of what could happen if this vulnerability was exploited include bringing down the server or causing hindrance to the regular service. + + + 4 + Critical + Presence of this vulnerability might give intruders the ability to gain highly sensitive content or affect other users of the web application. + + + 5 + Urgent + Presence of this vulnerability might enable intruders to compromise the web application's data store, obtain information from other users' accounts, or obtain command execution on a host in the web application's architecture. For example in this scenario, the web application users can potentially be targeted if the application is exploited. + + + + + Sensitive Content + Sensitive content may be detected based on known patterns (credit card numbers, social security numbers) or custom patterns (strings, regular expressions), depending on the option profile used. Intruders may gain access to sensitive content that could result in misuse or other exploits. + + + 1 + Minimal + Sensitive content was found in the web server response. During our scan of the site form(s) were found with field(s) for credit card number or social security number. This information disclosure could result in a confidentiality breach and could be a target for intruders. For this reason we recommend caution. + + + 2 + Medium + Sensitive content was found in the web server response. Specifically our service found a certain sensitive content pattern (defined in the option profile). This information disclosure could result in a confidentiality breach and could be a target for intruders. For this reason we recommend caution. + + + 3 + Serious + Sensitive content was found in the web server response - a valid social security number or credit card information. This infomation disclosure could result in a confidentiality breach, and it gives intruders access to valid sensitive content that could be misused. + + + + + Information Gathered + Information Gathered issues (QIDs) include visible information about the web application's platform, code, or architecture. It may also include information about users of the web application. + + + 1 + Minimal + Intruders may be able to retrieve sensitive information related to the web application platform. + + + 2 + Medium + Intruders may be able to retrieve sensitive information related to internal functionality or business logic of the web application. + + + 3 + Serious + Intruders may be able to detect highly sensitive data, such as personally identifiable information (PII) about other users of the web application. + + + + + + diff --git a/unittests/tools/test_qualys_webapp_parser.py b/unittests/tools/test_qualys_webapp_parser.py index 3805487c29..456b8f5a80 100644 --- a/unittests/tools/test_qualys_webapp_parser.py +++ b/unittests/tools/test_qualys_webapp_parser.py @@ -57,3 +57,14 @@ def test_qualys_webapp_parser_info_is_vuln(self): # 21 non-info findings, 21 total self.assertEqual(21, len([x for x in findings if x.severity != "Info"])) self.assertEqual(21, len(findings)) + + def test_discussion_10239(self): + testfile = open( + get_unit_tests_path() + "/scans/qualys_webapp/discussion_10239.xml" + ) + parser = QualysWebAppParser() + findings = parser.get_findings(testfile, Test(), True) + testfile.close() + self.assertEqual(1, len(findings)) + finding = findings[0] + self.assertEqual(finding.unsaved_req_resp[0].get('req'), "POST: https://example.com/vulnerable/path\nReferer: https://example.com/\n\nHost: www.example.com\n\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15\n\nAccept: */*\n\nContent-Length: 39\n\nContent-Type: application/x-www-form-urlencoded REQUEST_ONE\n\nBODY: post_param=malicious_code_here\n") From f9dfd2917dba5726758316288fb17f14b3c22685 Mon Sep 17 00:00:00 2001 From: CC <1499184+ccronca@users.noreply.github.com> Date: Thu, 20 Jun 2024 21:09:56 +0200 Subject: [PATCH 2/8] Fix create notification for group of findings (#10433) * Fix create notification execution for group of findings * Create notification for comment for group of findings if findings exist * Update notification title when a new comment is added for group of findings --------- Co-authored-by: Camilo Cota --- dojo/jira_link/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dojo/jira_link/views.py b/dojo/jira_link/views.py index 4e2a033305..6ae03f8d4c 100644 --- a/dojo/jira_link/views.py +++ b/dojo/jira_link/views.py @@ -241,8 +241,10 @@ def check_for_and_create_comment(parsed_json): findings = [jissue.finding] create_notification(event='jira_comment', title=f'JIRA incoming comment - {jissue.finding}', finding=jissue.finding, url=reverse("view_finding", args=(jissue.finding.id,)), icon='check') elif jissue.finding_group: - findings = [jissue.finding_group.findings.all()] - create_notification(event='jira_comment', title=f'JIRA incoming comment - {jissue.finding}', finding=jissue.finding, url=reverse("view_finding_group", args=(jissue.finding_group.id,)), icon='check') + findings = jissue.finding_group.findings.all() + first_finding_group = findings.first() + if first_finding_group: + create_notification(event='jira_comment', title=f'JIRA incoming comment - {jissue.finding_group}', finding=first_finding_group, url=reverse("view_finding_group", args=(jissue.finding_group.id,)), icon='check') elif jissue.engagement: return webhook_responser_handler("debug", "Comment for engagement ignored") else: From dd4985b3931aebd1b745acec9da99b6a7edee0c9 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Thu, 20 Jun 2024 20:03:31 -0500 Subject: [PATCH 3/8] Finding Reports: Support string based filtering (#10426) * Finding Reports: Support string based filtering * Adding a few more fields * Manage object level reports a bit better * Accommodate hidden fields better * Update dojo/filters.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update dojo/filters.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update dojo/filters.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update dojo/filters.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> --------- Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> --- dojo/api_v2/views.py | 15 +- dojo/filters.py | 206 ++++++++++++++++-- dojo/reports/views.py | 32 ++- dojo/reports/widgets.py | 12 +- .../templates/dojo/report_filter_snippet.html | 5 +- 5 files changed, 227 insertions(+), 43 deletions(-) diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index 588f5bb1ce..cbe2c9cb58 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -59,6 +59,7 @@ ApiTemplateFindingFilter, ApiTestFilter, ReportFindingFilter, + ReportFindingFilterWithoutObjectLookups, ) from dojo.finding.queries import ( get_authorized_findings, @@ -2866,13 +2867,15 @@ def report_generate(request, obj, options): include_finding_images = options.get("include_finding_images", False) include_executive_summary = options.get("include_executive_summary", False) include_table_of_contents = options.get("include_table_of_contents", False) + filter_string_matching = get_system_setting("filter_string_matching", False) + report_finding_filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter if type(obj).__name__ == "Product_Type": product_type = obj report_name = "Product Type Report: " + str(product_type) - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, prod_type=product_type, queryset=prefetch_related_findings_for_report( @@ -2901,7 +2904,7 @@ def report_generate(request, obj, options): report_name = "Product Report: " + str(product) - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, product=product, queryset=prefetch_related_findings_for_report( @@ -2915,7 +2918,7 @@ def report_generate(request, obj, options): elif type(obj).__name__ == "Engagement": engagement = obj - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, engagement=engagement, queryset=prefetch_related_findings_for_report( @@ -2932,7 +2935,7 @@ def report_generate(request, obj, options): elif type(obj).__name__ == "Test": test = obj - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, engagement=test.engagement, queryset=prefetch_related_findings_for_report( @@ -2948,7 +2951,7 @@ def report_generate(request, obj, options): endpoints = Endpoint.objects.filter( host=host, product=endpoint.product ).distinct() - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, queryset=prefetch_related_findings_for_report( Finding.objects.filter(endpoints__in=endpoints) @@ -2956,7 +2959,7 @@ def report_generate(request, obj, options): ) elif type(obj).__name__ == "CastTaggedQuerySet": - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, queryset=prefetch_related_findings_for_report(obj).distinct(), ) diff --git a/dojo/filters.py b/dojo/filters.py index 0b12cc3961..9d3a94a43d 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -595,6 +595,10 @@ class FindingTagStringFilter(FilterSet): help_text="Search for tags on a Product that are an exact match, and exclude them", exclude=True) + def delete_tags_from_form(self, tag_list: list): + for tag in tag_list: + self.form.fields.pop(tag, None) + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -1716,12 +1720,12 @@ class FindingFilterWithoutObjectLookups(FindingFilterHelper, FindingTagStringFil label="Engagement name Contains", help_text="Search for Engagement names that contain a given pattern") test__name = CharFilter( - field_name="test__engagement__name", + field_name="test__name", lookup_expr="iexact", label="Test Name", help_text="Search for Test names that are an exact match") test__name_contains = CharFilter( - field_name="test__engagement__name", + field_name="test__name", lookup_expr="icontains", label="Test name Contains", help_text="Search for Test names that contain a given pattern") @@ -2877,42 +2881,31 @@ class Meta: exclude = ['product'] -class ReportFindingFilter(FindingTagFilter): +class ReportFindingFilterHelper(FilterSet): title = CharFilter(lookup_expr='icontains', label='Name') date = DateFromToRangeFilter(field_name='date', label="Date Discovered") - test__engagement__product = ModelMultipleChoiceFilter( - queryset=Product.objects.none(), label="Product") - test__engagement__product__prod_type = ModelMultipleChoiceFilter( - queryset=Product_Type.objects.none(), - label="Product Type") - test__engagement__product__lifecycle = MultipleChoiceFilter(choices=Product.LIFECYCLE_CHOICES, label="Product Lifecycle") - test__engagement = ModelMultipleChoiceFilter(queryset=Engagement.objects.none(), label="Engagement") severity = MultipleChoiceFilter(choices=SEVERITY_CHOICES) active = ReportBooleanFilter() is_mitigated = ReportBooleanFilter() mitigated = DateRangeFilter(label="Mitigated Date") verified = ReportBooleanFilter() false_p = ReportBooleanFilter(label="False Positive") - risk_acceptance = ReportRiskAcceptanceFilter( - label="Risk Accepted") - # queryset will be restricted in __init__, here we don't have access to the logged in user + risk_acceptance = ReportRiskAcceptanceFilter(label="Risk Accepted") duplicate = ReportBooleanFilter() - duplicate_finding = ModelChoiceFilter(queryset=Finding.objects.filter(original_finding__isnull=False).distinct()) out_of_scope = ReportBooleanFilter() outside_of_sla = FindingSLAFilter(label="Outside of SLA") - file_path = CharFilter(lookup_expr='icontains') class Meta: model = Finding # exclude sonarqube issue as by default it will show all without checking permissions exclude = ['date', 'cwe', 'url', 'description', 'mitigation', 'impact', - 'references', 'sonarqube_issue', - 'thread_id', 'notes', + 'references', 'sonarqube_issue', 'duplicate_finding', + 'thread_id', 'notes', 'inherited_tags', 'endpoints', 'numerical_severity', 'reporter', 'last_reviewed', 'jira_creation', 'jira_change', 'files'] - def __init__(self, *args, **kwargs): + def manage_kwargs(self, kwargs): self.prod_type = None self.product = None self.engagement = None @@ -2926,6 +2919,24 @@ def __init__(self, *args, **kwargs): if 'test' in kwargs: self.test = kwargs.pop('test') + @property + def qs(self): + parent = super().qs + return get_authorized_findings(Permissions.Finding_View, parent) + + +class ReportFindingFilter(ReportFindingFilterHelper, FindingTagFilter): + test__engagement__product = ModelMultipleChoiceFilter( + queryset=Product.objects.none(), label="Product") + test__engagement__product__prod_type = ModelMultipleChoiceFilter( + queryset=Product_Type.objects.none(), + label="Product Type") + test__engagement__product__lifecycle = MultipleChoiceFilter(choices=Product.LIFECYCLE_CHOICES, label="Product Lifecycle") + test__engagement = ModelMultipleChoiceFilter(queryset=Engagement.objects.none(), label="Engagement") + duplicate_finding = ModelChoiceFilter(queryset=Finding.objects.filter(original_finding__isnull=False).distinct()) + + def __init__(self, *args, **kwargs): + self.manage_kwargs(kwargs) super().__init__(*args, **kwargs) # duplicate_finding queryset needs to restricted in line with permissions @@ -2961,10 +2972,161 @@ def __init__(self, *args, **kwargs): if 'test__engagement' in self.form.fields: self.form.fields['test__engagement'].queryset = get_authorized_engagements(Permissions.Engagement_View) - @property - def qs(self): - parent = super().qs - return get_authorized_findings(Permissions.Finding_View, parent) + +class ReportFindingFilterWithoutObjectLookups(ReportFindingFilterHelper, FindingTagStringFilter): + test__engagement__product__prod_type = NumberFilter(widget=HiddenInput()) + test__engagement__product = NumberFilter(widget=HiddenInput()) + test__engagement = NumberFilter(widget=HiddenInput()) + test = NumberFilter(widget=HiddenInput()) + endpoint = NumberFilter(widget=HiddenInput()) + reporter = CharFilter( + field_name="reporter__username", + lookup_expr="iexact", + label="Reporter Username", + help_text="Search for Reporter names that are an exact match") + reporter_contains = CharFilter( + field_name="reporter__username", + lookup_expr="icontains", + label="Reporter Username Contains", + help_text="Search for Reporter names that contain a given pattern") + reviewers = CharFilter( + field_name="reviewers__username", + lookup_expr="iexact", + label="Reviewer Username", + help_text="Search for Reviewer names that are an exact match") + reviewers_contains = CharFilter( + field_name="reviewers__username", + lookup_expr="icontains", + label="Reviewer Username Contains", + help_text="Search for Reviewer usernames that contain a given pattern") + last_reviewed_by = CharFilter( + field_name="last_reviewed_by__username", + lookup_expr="iexact", + label="Last Reviewed By Username", + help_text="Search for Last Reviewed By names that are an exact match") + last_reviewed_by_contains = CharFilter( + field_name="last_reviewed_by__username", + lookup_expr="icontains", + label="Last Reviewed By Username Contains", + help_text="Search for Last Reviewed By usernames that contain a given pattern") + review_requested_by = CharFilter( + field_name="review_requested_by__username", + lookup_expr="iexact", + label="Review Requested By Username", + help_text="Search for Review Requested By names that are an exact match") + review_requested_by_contains = CharFilter( + field_name="review_requested_by__username", + lookup_expr="icontains", + label="Review Requested By Username Contains", + help_text="Search for Review Requested By usernames that contain a given pattern") + mitigated_by = CharFilter( + field_name="mitigated_by__username", + lookup_expr="iexact", + label="Mitigator Username", + help_text="Search for Mitigator names that are an exact match") + mitigated_by_contains = CharFilter( + field_name="mitigated_by__username", + lookup_expr="icontains", + label="Mitigator Username Contains", + help_text="Search for Mitigator usernames that contain a given pattern") + defect_review_requested_by = CharFilter( + field_name="defect_review_requested_by__username", + lookup_expr="iexact", + label="Requester of Defect Review Username", + help_text="Search for Requester of Defect Review names that are an exact match") + defect_review_requested_by_contains = CharFilter( + field_name="defect_review_requested_by__username", + lookup_expr="icontains", + label="Requester of Defect Review Username Contains", + help_text="Search for Requester of Defect Review usernames that contain a given pattern") + test__engagement__product__prod_type__name = CharFilter( + field_name="test__engagement__product__prod_type__name", + lookup_expr="iexact", + label="Product Type Name", + help_text="Search for Product Type names that are an exact match") + test__engagement__product__prod_type__name_contains = CharFilter( + field_name="test__engagement__product__prod_type__name", + lookup_expr="icontains", + label="Product Type Name Contains", + help_text="Search for Product Type names that contain a given pattern") + test__engagement__product__name = CharFilter( + field_name="test__engagement__product__name", + lookup_expr="iexact", + label="Product Name", + help_text="Search for Product names that are an exact match") + test__engagement__product__name_contains = CharFilter( + field_name="test__engagement__product__name", + lookup_expr="icontains", + label="Product name Contains", + help_text="Search for Product names that contain a given pattern") + test__engagement__name = CharFilter( + field_name="test__engagement__name", + lookup_expr="iexact", + label="Engagement Name", + help_text="Search for Engagement names that are an exact match") + test__engagement__name_contains = CharFilter( + field_name="test__engagement__name", + lookup_expr="icontains", + label="Engagement name Contains", + help_text="Search for Engagement names that contain a given pattern") + test__name = CharFilter( + field_name="test__name", + lookup_expr="iexact", + label="Test Name", + help_text="Search for Test names that are an exact match") + test__name_contains = CharFilter( + field_name="test__name", + lookup_expr="icontains", + label="Test name Contains", + help_text="Search for Test names that contain a given pattern") + + def __init__(self, *args, **kwargs): + self.manage_kwargs(kwargs) + super().__init__(*args, **kwargs) + + product_type_refs = [ + "test__engagement__product__prod_type__name", + "test__engagement__product__prod_type__name_contains", + ] + product_refs = [ + "test__engagement__product__name", + "test__engagement__product__name_contains", + "test__engagement__product__tags", + "test__engagement__product__tags_contains", + "not_test__engagement__product__tags", + "not_test__engagement__product__tags_contains", + ] + engagement_refs = [ + "test__engagement__name", + "test__engagement__name_contains", + "test__engagement__tags", + "test__engagement__tags_contains", + "not_test__engagement__tags", + "not_test__engagement__tags_contains", + ] + test_refs = [ + "test__name", + "test__name_contains", + "test__tags", + "test__tags_contains", + "not_test__tags", + "not_test__tags_contains", + ] + + if self.test: + self.delete_tags_from_form(product_type_refs) + self.delete_tags_from_form(product_refs) + self.delete_tags_from_form(engagement_refs) + self.delete_tags_from_form(test_refs) + elif self.engagement: + self.delete_tags_from_form(product_type_refs) + self.delete_tags_from_form(product_refs) + self.delete_tags_from_form(engagement_refs) + elif self.product: + self.delete_tags_from_form(product_type_refs) + self.delete_tags_from_form(product_refs) + elif self.prod_type: + self.delete_tags_from_form(product_type_refs) class UserFilter(DojoFilter): diff --git a/dojo/reports/views.py b/dojo/reports/views.py index 99d5480b77..113b70dbd1 100644 --- a/dojo/reports/views.py +++ b/dojo/reports/views.py @@ -18,7 +18,13 @@ from dojo.authorization.authorization import user_has_permission_or_403 from dojo.authorization.authorization_decorators import user_is_authorized from dojo.authorization.roles_permissions import Permissions -from dojo.filters import EndpointFilter, EndpointFilterWithoutObjectLookups, EndpointReportFilter, ReportFindingFilter +from dojo.filters import ( + EndpointFilter, + EndpointFilterWithoutObjectLookups, + EndpointReportFilter, + ReportFindingFilter, + ReportFindingFilterWithoutObjectLookups, +) from dojo.finding.queries import get_authorized_findings from dojo.finding.views import BaseListFindings from dojo.forms import ReportOptionsForm @@ -73,7 +79,9 @@ def get(self, request: HttpRequest) -> HttpResponse: def get_findings(self, request: HttpRequest): findings = get_authorized_findings(Permissions.Finding_View) - return ReportFindingFilter(self.request.GET, queryset=findings) + filter_string_matching = get_system_setting("filter_string_matching", False) + filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter + return filter_class(self.request.GET, queryset=findings) def get_endpoints(self, request: HttpRequest): endpoints = Endpoint.objects.filter(finding__active=True, @@ -162,8 +170,9 @@ def get_context(self): def report_findings(request): findings = Finding.objects.filter() - - findings = ReportFindingFilter(request.GET, queryset=findings) + filter_string_matching = get_system_setting("filter_string_matching", False) + filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter + findings = filter_class(request.GET, queryset=findings) title_words = get_words_for_field(Finding, 'title') component_words = get_words_for_field(Finding, 'component_name') @@ -415,14 +424,15 @@ def generate_report(request, obj, host_view=False): disclaimer = 'Please configure in System Settings.' generate = "_generate" in request.GET report_name = str(obj) + filter_string_matching = get_system_setting("filter_string_matching", False) + report_finding_filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter add_breadcrumb(title="Generate Report", top_level=False, request=request) if type(obj).__name__ == "Product_Type": product_type = obj template = "dojo/product_type_pdf_report.html" report_name = "Product Type Report: " + str(product_type) report_title = "Product Type Report" - - findings = ReportFindingFilter(request.GET, prod_type=product_type, queryset=prefetch_related_findings_for_report(Finding.objects.filter( + findings = report_finding_filter_class(request.GET, prod_type=product_type, queryset=prefetch_related_findings_for_report(Finding.objects.filter( test__engagement__product__prod_type=product_type))) products = Product.objects.filter(prod_type=product_type, engagement__test__finding__in=findings.qs).distinct() @@ -472,7 +482,7 @@ def generate_report(request, obj, host_view=False): template = "dojo/product_pdf_report.html" report_name = "Product Report: " + str(product) report_title = "Product Report" - findings = ReportFindingFilter(request.GET, product=product, queryset=prefetch_related_findings_for_report(Finding.objects.filter( + findings = report_finding_filter_class(request.GET, product=product, queryset=prefetch_related_findings_for_report(Finding.objects.filter( test__engagement__product=product))) ids = set(finding.id for finding in findings.qs) # noqa: C401 engagements = Engagement.objects.filter(test__finding__id__in=ids).distinct() @@ -499,7 +509,7 @@ def generate_report(request, obj, host_view=False): elif type(obj).__name__ == "Engagement": logger.debug('generating report for Engagement') engagement = obj - findings = ReportFindingFilter(request.GET, engagement=engagement, + findings = report_finding_filter_class(request.GET, engagement=engagement, queryset=prefetch_related_findings_for_report(Finding.objects.filter(test__engagement=engagement))) report_name = "Engagement Report: " + str(engagement) template = 'dojo/engagement_pdf_report.html' @@ -528,7 +538,7 @@ def generate_report(request, obj, host_view=False): elif type(obj).__name__ == "Test": test = obj - findings = ReportFindingFilter(request.GET, engagement=test.engagement, + findings = report_finding_filter_class(request.GET, engagement=test.engagement, queryset=prefetch_related_findings_for_report(Finding.objects.filter(test=test))) template = "dojo/test_pdf_report.html" report_name = "Test Report: " + str(test) @@ -561,7 +571,7 @@ def generate_report(request, obj, host_view=False): endpoints = Endpoint.objects.filter(pk=endpoint.id).distinct() report_title = "Endpoint Report" template = 'dojo/endpoint_pdf_report.html' - findings = ReportFindingFilter(request.GET, + findings = report_finding_filter_class(request.GET, queryset=prefetch_related_findings_for_report(Finding.objects.filter(endpoints__in=endpoints))) context = {'endpoint': endpoint, @@ -580,7 +590,7 @@ def generate_report(request, obj, host_view=False): 'host': report_url_resolver(request), 'user_id': request.user.id} elif type(obj).__name__ in ["QuerySet", "CastTaggedQuerySet", "TagulousCastTaggedQuerySet"]: - findings = ReportFindingFilter(request.GET, queryset=prefetch_related_findings_for_report(obj).distinct()) + findings = report_finding_filter_class(request.GET, queryset=prefetch_related_findings_for_report(obj).distinct()) report_name = 'Finding' template = 'dojo/finding_pdf_report.html' report_title = "Finding Report" diff --git a/dojo/reports/widgets.py b/dojo/reports/widgets.py index 665b7758b4..b6593a6a0d 100644 --- a/dojo/reports/widgets.py +++ b/dojo/reports/widgets.py @@ -11,7 +11,12 @@ from django.utils.html import format_html from django.utils.safestring import mark_safe -from dojo.filters import EndpointFilter, EndpointFilterWithoutObjectLookups, ReportFindingFilter +from dojo.filters import ( + EndpointFilter, + EndpointFilterWithoutObjectLookups, + ReportFindingFilter, + ReportFindingFilterWithoutObjectLookups, +) from dojo.forms import CustomReportOptionsForm from dojo.models import Endpoint, Finding from dojo.utils import get_page_items, get_system_setting, get_words_for_field @@ -427,8 +432,9 @@ def report_widget_factory(json_data=None, request=None, user=None, finding_notes d.appendlist(item['name'], item['value']) else: d[item['name']] = item['value'] - - findings = ReportFindingFilter(d, queryset=findings) + filter_string_matching = get_system_setting("filter_string_matching", False) + filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter + findings = filter_class(d, queryset=findings) user_id = user.id if user is not None else None selected_widgets[list(widget.keys())[0] + '-' + str(idx)] = FindingList(request=request, findings=findings, finding_notes=finding_notes, diff --git a/dojo/templates/dojo/report_filter_snippet.html b/dojo/templates/dojo/report_filter_snippet.html index de1c68f58e..8018544457 100644 --- a/dojo/templates/dojo/report_filter_snippet.html +++ b/dojo/templates/dojo/report_filter_snippet.html @@ -5,7 +5,10 @@ {% endblock %}

    - {% for field in form %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + {% for field in form.visible_fields %}
    {{ field.errors }} {{ field.label_tag }} {{ field }} From 5bcf23220276746c7a4515d51edea396d20f227b Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 21 Jun 2024 23:14:46 +0200 Subject: [PATCH 4/8] :bug: fix acunetix360 parser #10435 (#10440) --- dojo/tools/acunetix/parse_acunetix360_json.py | 28 +++++++++--- unittests/scans/acunetix/issue_10435.json | 45 +++++++++++++++++++ unittests/tools/test_acunetix_parser.py | 6 +++ 3 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 unittests/scans/acunetix/issue_10435.json diff --git a/dojo/tools/acunetix/parse_acunetix360_json.py b/dojo/tools/acunetix/parse_acunetix360_json.py index 17b7f5a664..4398870542 100644 --- a/dojo/tools/acunetix/parse_acunetix360_json.py +++ b/dojo/tools/acunetix/parse_acunetix360_json.py @@ -29,16 +29,30 @@ def get_findings(self, filename, test): sev = item["Severity"] if sev not in ["Info", "Low", "Medium", "High", "Critical"]: sev = "Info" - mitigation = text_maker.handle(item.get("RemedialProcedure", "")) - references = text_maker.handle(item.get("RemedyReferences", "")) + if item["RemedialProcedure"] is not None: + mitigation = text_maker.handle(item.get("RemedialProcedure", "")) + else: + mitigation = None + if item["RemedyReferences"] is not None: + references = text_maker.handle(item.get("RemedyReferences", "")) + else: + references = None if "LookupId" in item: lookupId = item["LookupId"] - references = ( - f"https://online.acunetix360.com/issues/detail/{lookupId}\n" - + references - ) + if references is None: + references = ( + f"https://online.acunetix360.com/issues/detail/{lookupId}\n" + ) + else: + references = ( + f"https://online.acunetix360.com/issues/detail/{lookupId}\n" + + references + ) url = item["Url"] - impact = text_maker.handle(item.get("Impact", "")) + if item["Impact"] is not None: + impact = text_maker.handle(item.get("Impact", "")) + else: + impact = None dupe_key = title request = item["HttpRequest"]["Content"] if request is None or len(request) <= 0: diff --git a/unittests/scans/acunetix/issue_10435.json b/unittests/scans/acunetix/issue_10435.json new file mode 100644 index 0000000000..0406df1807 --- /dev/null +++ b/unittests/scans/acunetix/issue_10435.json @@ -0,0 +1,45 @@ +{ + "Generated": "19/06/2024 04:48 PM", + "Target": { + "Duration": "00:00:58.0662477", + "Initiated": "23/02/2023 02:30 PM", + "ScanId": "cb10809365d246d0881eafb202e63e55", + "Url": "XXXX" + }, + "Vulnerabilities": [ + { + "Certainty": 90, + "Classification": null, + "Confirmed": false, + "Description": "This vulnerability is removed or expired.", + "ExploitationSkills": null, + "ExternalReferences": null, + "ExtraInformation": [], + "FirstSeenDate": "02/06/2022 05:25 PM", + "HttpRequest": { + "Content": "GET /cgi-bin/ HTTP/1.1\r\nHost: XXXX\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nCache-Control: no-cache\r\nCookie: PHPSESSID=1adegrqlm9oebggs8mghcs78gt\r\nReferer: https://ics-monitoring.amersports.int/cgi-bin/\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.62 Safari/537.36\r\nX-Scanner: Acunetix 360\r\n\r\n", + "Method": "GET", + "Parameters": [] + }, + "HttpResponse": { + "Content": "HTTP/1.1 403 Forbidden\r\nServer: Apache\r\nConnection: Keep-Alive\r\nKeep-Alive: timeout=5, max=100\r\nContent-Length: 199\r\nContent-Type: text/html; charset=iso-8859-1\r\nDate: Thu, 23 Feb 2023 09:45:56 GMT\r\n\r\n\n\n403 Forbidden\n\n

    Forbidden

    \n

    You don't have permission to access this resource.

    \n\n", + "Duration": 668.4901, + "StatusCode": 403 + }, + "LookupId": "7ce8d15a-e5f0-4dd5-1f83-aea8034f4105", + "Impact": null, + "KnownVulnerabilities": [], + "LastSeenDate": "23/02/2023 10:46 AM", + "Name": "MissingXFrameOptionsHeader", + "ProofOfConcept": null, + "RemedialActions": null, + "RemedialProcedure": null, + "RemedyReferences": null, + "Severity": "Low", + "State": "Present", + "Type": "MissingXFrameOptionsHeader", + "Url": "XXXX", + "Tags": [] + } + ] + } \ No newline at end of file diff --git a/unittests/tools/test_acunetix_parser.py b/unittests/tools/test_acunetix_parser.py index 55c3e0800b..cd11c874fb 100644 --- a/unittests/tools/test_acunetix_parser.py +++ b/unittests/tools/test_acunetix_parser.py @@ -330,3 +330,9 @@ def test_parse_file_issue_10370(self): parser = AcunetixParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(1, len(findings)) + + def test_parse_file_issue_10435(self): + with open("unittests/scans/acunetix/issue_10435.json") as testfile: + parser = AcunetixParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(1, len(findings)) From a4fc79c4802a1e5a7b3eb8287ebfd3a06a10849e Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 24 Jun 2024 17:34:59 +0000 Subject: [PATCH 5/8] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/package.json b/components/package.json index 688f4f5d52..f6e63072c2 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.36.0-dev", + "version": "2.35.4", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index 423f4050e5..d87d6e4c64 100644 --- a/dojo/__init__.py +++ b/dojo/__init__.py @@ -4,6 +4,6 @@ # Django starts so that shared_task will use this app. from .celery import app as celery_app # noqa: F401 -__version__ = '2.36.0-dev' +__version__ = '2.35.4' __url__ = 'https://github.com/DefectDojo/django-DefectDojo' __docs__ = 'https://documentation.defectdojo.com' diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index a7f5252610..f8489d347c 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.36.0-dev" +appVersion: "2.35.4" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.136-dev +version: 1.6.136 icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap From 1bb36842e0ff11c3739ae2edd41713cc0f7c2991 Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 24 Jun 2024 18:25:44 +0000 Subject: [PATCH 6/8] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/package.json b/components/package.json index f6e63072c2..688f4f5d52 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.35.4", + "version": "2.36.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index d87d6e4c64..423f4050e5 100644 --- a/dojo/__init__.py +++ b/dojo/__init__.py @@ -4,6 +4,6 @@ # Django starts so that shared_task will use this app. from .celery import app as celery_app # noqa: F401 -__version__ = '2.35.4' +__version__ = '2.36.0-dev' __url__ = 'https://github.com/DefectDojo/django-DefectDojo' __docs__ = 'https://documentation.defectdojo.com' diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index f8489d347c..44d22f1295 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.35.4" +appVersion: "2.36.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.136 +version: 1.6.137-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap From 324f0df045d4ea76a912dc63f3da84b90d13d1a8 Mon Sep 17 00:00:00 2001 From: DefectDojo Date: Mon, 24 Jun 2024 18:32:03 +0000 Subject: [PATCH 7/8] Update helm lock file Signed-off-by: DefectDojo --- helm/defectdojo/Chart.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/helm/defectdojo/Chart.lock b/helm/defectdojo/Chart.lock index b485770474..8774f3f689 100644 --- a/helm/defectdojo/Chart.lock +++ b/helm/defectdojo/Chart.lock @@ -4,15 +4,15 @@ dependencies: version: 9.19.1 - name: postgresql repository: https://charts.bitnami.com/bitnami - version: 15.5.1 + version: 15.5.9 - name: postgresql-ha repository: https://charts.bitnami.com/bitnami version: 9.4.11 - name: rabbitmq repository: https://charts.bitnami.com/bitnami - version: 14.3.1 + version: 14.3.3 - name: redis repository: https://charts.bitnami.com/bitnami - version: 19.5.0 -digest: sha256:f7d84de0e09aa04522aca1b64fb2a297ad028c507144046f16da47d6750007dd -generated: "2024-05-30T16:46:40.020662037Z" + version: 19.5.5 +digest: sha256:8b025085ec71a05b7b62f4b85a1afdabf884d5fa878494671989ec03939872c8 +generated: "2024-06-24T18:31:53.752292333Z" From 5941b6341802a96ddd84f0f9a1308b7f8aa7cf76 Mon Sep 17 00:00:00 2001 From: DefectDojo Date: Mon, 24 Jun 2024 18:57:05 +0000 Subject: [PATCH 8/8] Update helm lock file Signed-off-by: DefectDojo --- helm/defectdojo/Chart.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/helm/defectdojo/Chart.lock b/helm/defectdojo/Chart.lock index 28c8097606..74468c83ba 100644 --- a/helm/defectdojo/Chart.lock +++ b/helm/defectdojo/Chart.lock @@ -4,15 +4,15 @@ dependencies: version: 9.19.1 - name: postgresql repository: https://charts.bitnami.com/bitnami - version: 15.5.6 + version: 15.5.9 - name: postgresql-ha repository: https://charts.bitnami.com/bitnami version: 9.4.11 - name: rabbitmq repository: https://charts.bitnami.com/bitnami - version: 14.4.3 + version: 14.4.4 - name: redis repository: https://charts.bitnami.com/bitnami - version: 19.5.4 -digest: sha256:dfc4c827cf13859928ebf770e5a384721ea67ecedab22fd7baf0e6ede28c6fd4 -generated: "2024-06-17T17:37:48.301414062Z" + version: 19.5.5 +digest: sha256:7ad88ea953ebef3acbd1270eeae206e4e650f2fb20f754e0d912688795500b18 +generated: "2024-06-24T18:56:55.876075791Z"