Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎉 Improvements for wazuh importer #9248

Merged
merged 16 commits into from
Feb 6, 2024
Merged
46 changes: 45 additions & 1 deletion docs/content/en/integrations/parsers/file/wazuh.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,51 @@
title: "Wazuh Scanner"
toc_hide: true
---
Import JSON report.

### File Types
DefectDojo parser accepts a .json file from [Wazuh](https://wazuh.com). The export from Wazuh can be done via 2 ways. Choose the one which you prefer.

- export the Wazuh findings from API and upload them to DefectDojo. This method may be the easiest one but does export all known vulnerabilities at once. It is not possible to sort them after clients or any other categories. You will receive all vulnerabilities in one engagement. It also does not output the endpoint of a finding.
- export the findings via the script [available here](https://github.com/quirinziessler/wazuh-findings-exporter). The script fetches the findings by Wazuh client groups and saves them as json, ready for upload. You will receive one file per group allowing you to separate the clients via engagements in Wazuh. It also exports the endpoints hostname and displays them in DefectDojo UI.

Independent of your above choice: Have in mind to adjust the max file size via "DD_SCAN_FILE_MAX_SIZE" if you see files larger than the default value of 100MB. Depending on the amount and category of integrated devices, the file size jumps rapidly.

### Acceptable JSON Format
Parser expects a .json file structured as below.

~~~
{
"data": {
"affected_items": [
{
"architecture": "amd64",
"condition": "Package less than 4.3.2",
"cve": "CVE-1234-123123",
"cvss2_score": 0,
"cvss3_score": 5.5,
"detection_time": "2023-02-08T13:55:10Z",
"external_references": [
"https://nvd.nist.gov/vuln/detail/CVE-YYYY-XXXXX",
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-YYYY-XXXXX"
],
"name": "asdf",
"published": "2022-09-01",
"severity": "Medium",
"status": "VALID",
"title": "CVE-YYYY-XXXXX affects asdf",
"type": "PACKAGE",
"updated": "2022-09-07",
"version": "4.3.1"
}
],
"failed_items": [],
"total_affected_items": 1,
"total_failed_items": 0
},
"error": 0,
"message": "All selected vulnerabilities were returned"
}
~~~

### Sample Scan Data
Sample Wazuh Scanner scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/wazuh).
2 changes: 1 addition & 1 deletion dojo/settings/settings.dist.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Django settings for DefectDojo

Check warning on line 1 in dojo/settings/settings.dist.py

View check run for this annotation

DryRunSecurity / AI-powered Sensitive Files Check

Possible Sensitive File

Our AI-Powered Sensitive File checker believes it has discovered a sensitive file being modified in this PR. Extra care must be taken when modifying a file that is potentially security-sensitive. The following reason was provided: Contains sensitive settings
import os
from datetime import timedelta
from celery.schedules import crontab
Expand Down Expand Up @@ -1260,7 +1260,6 @@
'NeuVector (compliance)': ['title', 'vuln_id_from_tool', 'description'],
'Wpscan': ['title', 'description', 'severity'],
'Popeye Scan': ['title', 'description'],
'Wazuh Scan': ['title'],
'Nuclei Scan': ['title', 'cwe', 'severity'],
'KubeHunter Scan': ['title', 'description'],
'kube-bench Scan': ['title', 'vuln_id_from_tool', 'description'],
Expand Down Expand Up @@ -1473,6 +1472,7 @@
'kube-bench Scan': DEDUPE_ALGO_HASH_CODE,
'Threagile risks report': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE,
'Humble Json Importer': DEDUPE_ALGO_HASH_CODE,
'Wazuh Scan': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
'MSDefender Parser': DEDUPE_ALGO_HASH_CODE,
}

Expand Down
82 changes: 44 additions & 38 deletions dojo/tools/wazuh/parser.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import hashlib

Check warning on line 1 in dojo/tools/wazuh/parser.py

View check run for this annotation

DryRunSecurity / AI-powered Sensitive Files Check

Possible Sensitive File

Our AI-Powered Sensitive File checker believes it has discovered a sensitive file being modified in this PR. Extra care must be taken when modifying a file that is potentially security-sensitive. The following reason was provided: Contains sensitive parser
import json
from dojo.models import Finding
from dojo.models import Finding, Endpoint


class WazuhParser(object):
"""
Use Wazuh Vulnerability API to retrieve the findings
The vulnerabilities with condition "Package unfixed" are skipped because there is no fix out yet.
https://github.com/wazuh/wazuh/issues/14560
"""
Expand All @@ -18,54 +18,49 @@
def get_description_for_scan_types(self, scan_type):
return "Wazuh"

def get_findings(self, filename, test):
data = json.load(filename)
# Detect duplications
dupes = dict()
def get_findings(self, file, test):
data = json.load(file)

try:
vulnerability = data[next(iter(data.keys()))]["affected_items"]
except (KeyError, StopIteration):
return list()
if not data:
return []

if vulnerability is None:
return list()
# Detect duplications
dupes = dict()

for item in vulnerability:
# Loop through each element in the list
vulnerabilities = data.get("data", {}).get("affected_items", [])
for item in vulnerabilities:
if (
item["condition"] != "Package unfixed"
and item["severity"] != "Untriaged"
):
id = item.get("cve")
cve = item.get("cve")
package_name = item.get("name")
package_version = item.get("version")
description = item.get("condition")
if item.get("severity") == "Untriaged":
severity = "Info"
else:
severity = item.get("severity")
if item.get("status") == "VALID":
active = True
else:
active = False
severity = item.get("severity").capitalize()
agent_ip = item.get("agent_ip")
links = item.get("external_references")
title = (
item.get("title") + " (version: " + package_version + ")"
)
severity = item.get("severity", "info").capitalize()
cvssv3_score = item.get("cvss3_score")
publish_date = item.get("published")
agent_name = item.get("agent_name")
agent_ip = item.get("agent_ip")
detection_time = item.get("detection_time")

if links:
references = ""
for link in links:
references += f"{link}\n"
references = "\n".join(links)
else:
references = None

if id and id.startswith("CVE"):
vulnerability_id = id
else:
vulnerability_id = None
title = (
item.get("title") + " (version: " + package_version + ")"
)

dupe_key = title
if agent_name:
dupe_key = title + cve + agent_name + package_name + package_version
else:
dupe_key = title + cve + package_name + package_version
dupe_key = hashlib.sha256(dupe_key.encode('utf-8')).hexdigest()

if dupe_key in dupes:
find = dupes[dupe_key]
Expand All @@ -77,14 +72,25 @@
test=test,
description=description,
severity=severity,
active=active,
mitigation="mitigation",
references=references,
static_finding=True,
component_name=package_name,
component_version=package_version,
cvssv3_score=cvssv3_score,
publish_date=publish_date,
unique_id_from_tool=dupe_key,
date=detection_time,
)
if vulnerability_id:
find.unsaved_vulnerability_ids = [vulnerability_id]

# in some cases the agent_ip is not the perfect way on how to identify a host. Thus prefer the agent_name, if existant.
if agent_name:
find.unsaved_endpoints = [Endpoint(host=agent_name)]
elif agent_ip:
find.unsaved_endpoints = [Endpoint(host=agent_ip)]

if id:
find.unsaved_vulnerability_ids = cve

dupes[dupe_key] = find

return list(dupes.values())
29 changes: 29 additions & 0 deletions unittests/scans/wazuh/one_finding_with_endpoint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"data": {
"affected_items": [
{
"name": "asdf",
"version": "1",
"cve": "CVE-1234-1234",
"cvss2_score": 0,
"title": "CVE-1234-1234 affects curl",
"published": "2023-12-07",
"architecture": "amd64",
"status": "VALID",
"cvss3_score": 6.5,
"external_references": [
"https://nvd.nist.gov/vuln/detail/CVE-1234-1234",
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-1234-1234"
],
"updated": "2023-12-24",
"severity": "Medium",
"type": "PACKAGE",
"detection_time": "2023-12-13T22:11:57+00:00",
"condition": "Package less than 2",
"agent_ip": "111.111.111.111",
"agent_name": "agent-1"
}
],
"total_affected_items": 1
}
}
21 changes: 20 additions & 1 deletion unittests/tools/test_wazuh_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ def test_parse_one_finding(self):
endpoint.clean()
self.assertEqual(1, len(findings))
self.assertEqual("Medium", finding.severity)
self.assertEqual("CVE-1234-123123", finding.unsaved_vulnerability_ids[0])
self.assertEqual("CVE-1234-123123", finding.unsaved_vulnerability_ids)
self.assertEqual("asdf", finding.component_name)
self.assertEqual("4.3.1", finding.component_version)
self.assertEqual(5.5, finding.cvssv3_score)

def test_parse_many_finding(self):
testfile = open("unittests/scans/wazuh/many_findings.json")
Expand All @@ -30,3 +33,19 @@ def test_parse_many_finding(self):
for endpoint in finding.unsaved_endpoints:
endpoint.clean()
self.assertEqual(6, len(findings))

def test_parse_one_finding_with_endpoint(self):
testfile = open("unittests/scans/wazuh/one_finding_with_endpoint.json")
parser = WazuhParser()
findings = parser.get_findings(testfile, Test())
for finding in findings:
for endpoint in finding.unsaved_endpoints:
endpoint.clean()
self.assertEqual(1, len(findings))
self.assertEqual("Medium", finding.severity)
self.assertEqual("CVE-1234-1234", finding.unsaved_vulnerability_ids)
self.assertEqual(6.5, finding.cvssv3_score)
endpoint = finding.unsaved_endpoints[0]
self.assertEqual("agent-1", endpoint.host)
self.assertEqual("asdf", finding.component_name)
self.assertEqual("1", finding.component_version)