regscale-cli 6.20.10.0__py3-none-any.whl → 6.21.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/application.py +12 -5
- regscale/core/app/internal/set_permissions.py +58 -27
- regscale/integrations/commercial/nessus/scanner.py +2 -0
- regscale/integrations/commercial/sonarcloud.py +35 -36
- regscale/integrations/commercial/synqly/ticketing.py +51 -0
- regscale/integrations/integration_override.py +15 -6
- regscale/integrations/scanner_integration.py +163 -35
- regscale/models/integration_models/amazon_models/inspector_scan.py +32 -57
- regscale/models/integration_models/aqua.py +92 -78
- regscale/models/integration_models/cisa_kev_data.json +47 -4
- regscale/models/integration_models/defenderimport.py +64 -59
- regscale/models/integration_models/ecr_models/ecr.py +100 -147
- regscale/models/integration_models/flat_file_importer/__init__.py +52 -38
- regscale/models/integration_models/ibm.py +29 -47
- regscale/models/integration_models/nexpose.py +156 -68
- regscale/models/integration_models/prisma.py +46 -66
- regscale/models/integration_models/qualys.py +99 -93
- regscale/models/integration_models/snyk.py +229 -158
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/veracode.py +15 -20
- regscale/models/integration_models/xray.py +276 -82
- regscale/models/regscale_models/control_implementation.py +14 -12
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/rbac.py +22 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/RECORD +37 -36
- tests/fixtures/test_fixture.py +58 -2
- tests/regscale/core/test_app.py +5 -3
- tests/regscale/integrations/test_integration_mapping.py +522 -40
- tests/regscale/integrations/test_issue_due_date.py +1 -1
- tests/regscale/integrations/test_update_finding_dates.py +336 -0
- tests/regscale/models/test_asset.py +406 -50
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/top_level.txt +0 -0
|
@@ -33,7 +33,7 @@ from regscale.core.app.utils.parser_utils import safe_datetime_str
|
|
|
33
33
|
from regscale.integrations.scanner_integration import ScannerIntegration
|
|
34
34
|
from regscale.models import IssueStatus, Metadata, regscale_models
|
|
35
35
|
from regscale.models.app_models.mapping import Mapping
|
|
36
|
-
from regscale.models.regscale_models import Asset, File, Vulnerability
|
|
36
|
+
from regscale.models.regscale_models import Asset, File, IssueSeverity, Vulnerability
|
|
37
37
|
|
|
38
38
|
logger = logging.getLogger(__name__)
|
|
39
39
|
|
|
@@ -41,6 +41,10 @@ DT_FORMAT = "%Y-%m-%d"
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class FlatFileIntegration(ScannerIntegration):
|
|
44
|
+
"""
|
|
45
|
+
Flat File Integration
|
|
46
|
+
"""
|
|
47
|
+
|
|
44
48
|
title = "Flat File Integration"
|
|
45
49
|
# Required fields from ScannerIntegration
|
|
46
50
|
asset_identifier_field = "name"
|
|
@@ -53,13 +57,6 @@ class FlatFileIntegration(ScannerIntegration):
|
|
|
53
57
|
finding_severity_map: Optional[dict] = None,
|
|
54
58
|
**kwargs: Any,
|
|
55
59
|
):
|
|
56
|
-
"""
|
|
57
|
-
Initialize the FlatFileIntegration
|
|
58
|
-
|
|
59
|
-
:param int plan_id: The plan id
|
|
60
|
-
:param str asset_identifier_field: The asset identifier field to use, defaults to "name"
|
|
61
|
-
:param dict kwargs: Additional keyword arguments
|
|
62
|
-
"""
|
|
63
60
|
self.asset_identifier_field = asset_identifier_field
|
|
64
61
|
if finding_severity_map:
|
|
65
62
|
self.finding_severity_map = finding_severity_map
|
|
@@ -68,6 +65,7 @@ class FlatFileIntegration(ScannerIntegration):
|
|
|
68
65
|
"Critical": regscale_models.IssueSeverity.Critical,
|
|
69
66
|
"High": regscale_models.IssueSeverity.High,
|
|
70
67
|
"Medium": regscale_models.IssueSeverity.Moderate,
|
|
68
|
+
"Moderate": regscale_models.IssueSeverity.Moderate,
|
|
71
69
|
"Low": regscale_models.IssueSeverity.Low,
|
|
72
70
|
}
|
|
73
71
|
super().__init__(plan_id=plan_id, **kwargs)
|
|
@@ -126,20 +124,12 @@ class FlatFileImporter(ABC):
|
|
|
126
124
|
"Critical": regscale_models.IssueSeverity.Critical,
|
|
127
125
|
"High": regscale_models.IssueSeverity.High,
|
|
128
126
|
"Medium": regscale_models.IssueSeverity.Moderate,
|
|
127
|
+
"Moderate": regscale_models.IssueSeverity.Moderate,
|
|
129
128
|
"Low": regscale_models.IssueSeverity.Low,
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
|
|
133
|
-
if "parent_id" not in kwargs and "object_id" in kwargs:
|
|
134
|
-
kwargs["parent_id"] = kwargs["object_id"]
|
|
135
|
-
kwargs["plan_id"] = kwargs["object_id"]
|
|
136
|
-
if kwargs.get("is_component", False):
|
|
137
|
-
kwargs["parent_module"] = regscale_models.Component.get_module_string()
|
|
138
|
-
else:
|
|
139
|
-
kwargs["parent_module"] = regscale_models.SecurityPlan.get_module_string()
|
|
131
|
+
kwargs = self.update_kwargs(kwargs)
|
|
140
132
|
|
|
141
|
-
if "app" not in kwargs:
|
|
142
|
-
kwargs["app"] = Application()
|
|
143
133
|
# empty generator
|
|
144
134
|
self.integration_assets: Generator["IntegrationAsset", None, None] = (x for x in [])
|
|
145
135
|
self.integration_findings: Generator["IntegrationAsset", None, None] = (x for x in [])
|
|
@@ -209,7 +199,7 @@ class FlatFileImporter(ABC):
|
|
|
209
199
|
flat_int.num_assets_to_process = len(self.data["assets"])
|
|
210
200
|
if vuln_count:
|
|
211
201
|
flat_int.num_findings_to_process = vuln_count
|
|
212
|
-
elif isinstance(self.data["vulns"], list):
|
|
202
|
+
elif isinstance(self.data["vulns"], list) and not vuln_count:
|
|
213
203
|
flat_int.num_findings_to_process = len(self.data["vulns"])
|
|
214
204
|
flat_int.sync_assets(
|
|
215
205
|
plan_id=self.attributes.plan_id,
|
|
@@ -229,6 +219,31 @@ class FlatFileImporter(ABC):
|
|
|
229
219
|
)
|
|
230
220
|
self.clean_up()
|
|
231
221
|
|
|
222
|
+
def update_kwargs(self, kwargs: dict) -> dict:
|
|
223
|
+
"""
|
|
224
|
+
Update the kwargs with the default values
|
|
225
|
+
|
|
226
|
+
:param dict kwargs: The kwargs to update
|
|
227
|
+
:return: The updated kwargs
|
|
228
|
+
:rtype: dicta
|
|
229
|
+
"""
|
|
230
|
+
# Set the parent_id, parent_module, and plan_id if they are not provided
|
|
231
|
+
if "parent_id" not in kwargs and "object_id" in kwargs:
|
|
232
|
+
kwargs["parent_id"] = kwargs["object_id"]
|
|
233
|
+
kwargs["plan_id"] = kwargs["object_id"]
|
|
234
|
+
if kwargs.get("is_component", False):
|
|
235
|
+
kwargs["parent_module"] = regscale_models.Component.get_module_string()
|
|
236
|
+
else:
|
|
237
|
+
kwargs["parent_module"] = regscale_models.SecurityPlan.get_module_string()
|
|
238
|
+
|
|
239
|
+
# if plan id is still not set, set it to the object id
|
|
240
|
+
if "plan_id" not in kwargs or not kwargs["plan_id"]:
|
|
241
|
+
kwargs["plan_id"] = kwargs["object_id"]
|
|
242
|
+
|
|
243
|
+
if "app" not in kwargs:
|
|
244
|
+
kwargs["app"] = Application()
|
|
245
|
+
return kwargs
|
|
246
|
+
|
|
232
247
|
def parse_finding(self, vuln: Union[Vulnerability, "IntegrationFinding"]) -> Optional["IntegrationFinding"]:
|
|
233
248
|
"""
|
|
234
249
|
Parses a vulnerability object into an IntegrationFinding object
|
|
@@ -240,6 +255,8 @@ class FlatFileImporter(ABC):
|
|
|
240
255
|
from regscale.integrations.scanner_integration import IntegrationFinding
|
|
241
256
|
|
|
242
257
|
if isinstance(vuln, IntegrationFinding):
|
|
258
|
+
# We will store this in the properties subsystem of issue.
|
|
259
|
+
vuln.extra_data["source_file_path"] = self.attributes.file_path
|
|
243
260
|
return vuln
|
|
244
261
|
|
|
245
262
|
try:
|
|
@@ -543,21 +560,6 @@ class FlatFileImporter(ABC):
|
|
|
543
560
|
if asset not in self.data["assets"]:
|
|
544
561
|
self.data["assets"].append(asset)
|
|
545
562
|
|
|
546
|
-
def _check_vuln(self, vuln_to_check: Union[Vulnerability, "IntegrationFinding"]) -> None:
|
|
547
|
-
"""
|
|
548
|
-
Check if the vuln is in the data
|
|
549
|
-
|
|
550
|
-
:param Union[Vulnerability, IntegrationFinding] vuln_to_check: The vulnerability to check to prevent duplicates
|
|
551
|
-
:rtype: None
|
|
552
|
-
"""
|
|
553
|
-
from regscale.integrations.scanner_integration import IntegrationFinding
|
|
554
|
-
|
|
555
|
-
if isinstance(vuln_to_check, IntegrationFinding):
|
|
556
|
-
if vuln_to_check not in self.data["vulns"]:
|
|
557
|
-
self.data["vulns"].append(vuln_to_check)
|
|
558
|
-
elif (vuln_to_check and vuln_to_check not in self.data["vulns"]) and hasattr(vuln_to_check, "id"):
|
|
559
|
-
self.data["vulns"].append(vuln_to_check)
|
|
560
|
-
|
|
561
563
|
def create_vulns(self, func: Callable) -> None:
|
|
562
564
|
"""
|
|
563
565
|
Create vulns in RegScale from csv file
|
|
@@ -585,11 +587,12 @@ class FlatFileImporter(ABC):
|
|
|
585
587
|
if not vuln:
|
|
586
588
|
vuln_progress.advance(vuln_task, advance=1)
|
|
587
589
|
continue
|
|
588
|
-
|
|
589
|
-
|
|
590
|
+
|
|
591
|
+
if isinstance(vuln, IntegrationFinding):
|
|
592
|
+
self.data["vulns"].append(vuln)
|
|
590
593
|
if isinstance(vuln, list):
|
|
591
594
|
for v in vuln:
|
|
592
|
-
self.
|
|
595
|
+
self.data["vulns"].append(v)
|
|
593
596
|
if isinstance(vuln, Iterator):
|
|
594
597
|
self.integration_findings = vuln
|
|
595
598
|
self.data["vulns"] = vuln
|
|
@@ -936,10 +939,21 @@ class FlatFileImporter(ABC):
|
|
|
936
939
|
:return: The severity
|
|
937
940
|
:rtype: str
|
|
938
941
|
"""
|
|
942
|
+
mapping = {
|
|
943
|
+
"critical": IssueSeverity.Critical,
|
|
944
|
+
"high": IssueSeverity.High,
|
|
945
|
+
"medium": IssueSeverity.Moderate,
|
|
946
|
+
"moderate": IssueSeverity.Moderate,
|
|
947
|
+
"low": IssueSeverity.Low,
|
|
948
|
+
"informational": IssueSeverity.NotAssigned,
|
|
949
|
+
"none": IssueSeverity.NotAssigned,
|
|
950
|
+
"info": IssueSeverity.NotAssigned,
|
|
951
|
+
"unknown": IssueSeverity.NotAssigned,
|
|
952
|
+
}
|
|
939
953
|
severity = "info"
|
|
940
954
|
if s:
|
|
941
955
|
severity = s.lower()
|
|
942
|
-
return severity
|
|
956
|
+
return mapping.get(severity, IssueSeverity.NotAssigned)
|
|
943
957
|
|
|
944
958
|
@staticmethod
|
|
945
959
|
def map_status_to_issue_status(status: str) -> IssueStatus:
|
|
@@ -8,9 +8,10 @@ from urllib.parse import urlparse
|
|
|
8
8
|
from regscale.core.app.application import Application
|
|
9
9
|
from regscale.core.app.logz import create_logger
|
|
10
10
|
from regscale.core.app.utils.app_utils import epoch_to_datetime, get_current_datetime, is_valid_fqdn
|
|
11
|
-
from regscale.
|
|
11
|
+
from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
|
|
12
|
+
from regscale.models import ImportValidater
|
|
12
13
|
from regscale.models.integration_models.flat_file_importer import FlatFileImporter
|
|
13
|
-
from regscale.models.regscale_models import Asset,
|
|
14
|
+
from regscale.models.regscale_models import Asset, IssueSeverity, IssueStatus
|
|
14
15
|
|
|
15
16
|
ISSUE_TYPE = "Issue Type"
|
|
16
17
|
VULNERABILITY_TITLE = ISSUE_TYPE
|
|
@@ -53,74 +54,55 @@ class AppScan(FlatFileImporter):
|
|
|
53
54
|
**kwargs,
|
|
54
55
|
)
|
|
55
56
|
|
|
56
|
-
def create_asset(self, dat: Optional[dict] = None) ->
|
|
57
|
+
def create_asset(self, dat: Optional[dict] = None) -> IntegrationAsset:
|
|
57
58
|
"""
|
|
58
59
|
Create an asset from a row in the IBM csv file
|
|
59
60
|
|
|
60
61
|
:param Optional[dict] dat: Data row from CSV file, defaults to None
|
|
61
|
-
:return: RegScale
|
|
62
|
-
:rtype:
|
|
62
|
+
:return: RegScale IntegrationAsset object
|
|
63
|
+
:rtype: IntegrationAsset
|
|
63
64
|
"""
|
|
64
65
|
parsed_url = urlparse(self.mapping.get_value(dat, "URL"))
|
|
65
66
|
hostname: str = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
|
66
|
-
return
|
|
67
|
+
return IntegrationAsset(
|
|
67
68
|
**{
|
|
68
|
-
"id": 0,
|
|
69
69
|
"name": hostname,
|
|
70
|
-
"
|
|
70
|
+
"identifier": hostname,
|
|
71
71
|
"status": "Active (On Network)",
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"scanningTool": self.name,
|
|
76
|
-
"assetOwnerId": self.config["userId"],
|
|
77
|
-
"assetType": "Other",
|
|
72
|
+
"asset_category": "Software",
|
|
73
|
+
"scanning_tool": self.name,
|
|
74
|
+
"asset_type": "Other",
|
|
78
75
|
"fqdn": hostname if is_valid_fqdn(hostname) else None,
|
|
79
|
-
"systemAdministratorId": self.config["userId"],
|
|
80
|
-
"parentId": self.attributes.parent_id,
|
|
81
|
-
"parentModule": self.attributes.parent_module,
|
|
82
76
|
}
|
|
83
77
|
)
|
|
84
78
|
|
|
85
|
-
def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[
|
|
79
|
+
def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[IntegrationFinding]:
|
|
86
80
|
"""
|
|
87
|
-
Create
|
|
88
|
-
|
|
81
|
+
Create an IntegrationFinding from a row in the IBM csv file
|
|
89
82
|
:param Optional[dict] dat: Data row from CSV file, defaults to None
|
|
90
|
-
:return: RegScale
|
|
91
|
-
:rtype: Optional[
|
|
83
|
+
:return: RegScale IntegrationFinding object or None
|
|
84
|
+
:rtype: Optional[IntegrationFinding]
|
|
92
85
|
"""
|
|
86
|
+
|
|
93
87
|
regscale_vuln = None
|
|
94
88
|
parsed_url = urlparse(self.mapping.get_value(dat, "URL"))
|
|
95
89
|
hostname: str = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
|
96
90
|
description: str = self.mapping.get_value(dat, ISSUE_TYPE)
|
|
97
91
|
app_scan_severity = self.mapping.get_value(dat, "Severity")
|
|
98
92
|
severity = self.severity_map.get(app_scan_severity, "Informational")
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if dat and asset_match:
|
|
103
|
-
regscale_vuln = Vulnerability(
|
|
104
|
-
id=0,
|
|
105
|
-
scanId=0, # set later
|
|
106
|
-
parentId=asset.id,
|
|
107
|
-
parentModule="assets",
|
|
108
|
-
ipAddress="0.0.0.0", # No ip address available
|
|
109
|
-
lastSeen=get_current_datetime(),
|
|
110
|
-
firstSeen=epoch_to_datetime(self.create_epoch),
|
|
111
|
-
daysOpen=None,
|
|
112
|
-
dns=hostname,
|
|
113
|
-
mitigated=None,
|
|
114
|
-
severity=severity,
|
|
115
|
-
plugInName=description,
|
|
116
|
-
cve="",
|
|
117
|
-
vprScore=None,
|
|
118
|
-
tenantsId=0,
|
|
119
|
-
title=description[:255] if description else "No Title",
|
|
93
|
+
if dat:
|
|
94
|
+
return IntegrationFinding(
|
|
95
|
+
title=self.mapping.get_value(dat, self.vuln_title),
|
|
120
96
|
description=description,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
97
|
+
cve="",
|
|
98
|
+
severity=self.determine_severity(severity),
|
|
99
|
+
asset_identifier=hostname,
|
|
100
|
+
plugin_name=description,
|
|
101
|
+
plugin_text=description[:255],
|
|
102
|
+
control_labels=[],
|
|
103
|
+
category="Hardware",
|
|
104
|
+
status=IssueStatus.Open,
|
|
105
|
+
last_seen=get_current_datetime(),
|
|
106
|
+
first_seen=epoch_to_datetime(self.create_epoch),
|
|
125
107
|
)
|
|
126
108
|
return regscale_vuln
|
|
@@ -2,43 +2,47 @@
|
|
|
2
2
|
Nexpose Scan information
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from pathlib import Path
|
|
6
5
|
from typing import Optional
|
|
7
6
|
|
|
8
7
|
from regscale.core.app.application import Application
|
|
9
8
|
from regscale.core.app.logz import create_logger
|
|
10
|
-
from regscale.core.app.utils.app_utils import epoch_to_datetime,
|
|
9
|
+
from regscale.core.app.utils.app_utils import epoch_to_datetime, is_valid_fqdn
|
|
10
|
+
from regscale.core.utils.date import date_str
|
|
11
|
+
from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
|
|
11
12
|
from regscale.models import ImportValidater
|
|
12
|
-
from regscale.models.app_models.mapping import Mapping
|
|
13
13
|
from regscale.models.integration_models.flat_file_importer import FlatFileImporter
|
|
14
|
-
from regscale.models.regscale_models import Asset,
|
|
14
|
+
from regscale.models.regscale_models import Asset, IssueSeverity, IssueStatus
|
|
15
15
|
|
|
16
16
|
VULNERABILITY_TITLE = "Vulnerability Title"
|
|
17
17
|
VULNERABILITY_ID = "Vulnerability ID"
|
|
18
18
|
CVSS3_SCORE = "CVSSv3 Score"
|
|
19
|
-
|
|
19
|
+
CVSS2_SCORE = "CVSSv2 Score"
|
|
20
|
+
IP_ADDRESS = "IP_Address"
|
|
21
|
+
FIRST_SEEN = "first_seen"
|
|
22
|
+
SCAN_DATE = "scan_start_time"
|
|
23
|
+
CVE = "CVEs"
|
|
20
24
|
|
|
21
25
|
|
|
22
|
-
class Nexpose(FlatFileImporter):
|
|
26
|
+
class Nexpose(FlatFileImporter): # pylint: disable=too-many-instance-attributes
|
|
23
27
|
"""
|
|
24
|
-
|
|
28
|
+
Nexpose Scan information
|
|
25
29
|
"""
|
|
26
30
|
|
|
27
|
-
def __init__(self, **kwargs):
|
|
31
|
+
def __init__(self, **kwargs): # pylint: disable=R0902
|
|
28
32
|
self.name = kwargs.get("name")
|
|
29
33
|
self.vuln_title = VULNERABILITY_TITLE
|
|
30
34
|
self.vuln_id = VULNERABILITY_ID
|
|
31
35
|
self.cvss3_score = CVSS3_SCORE
|
|
36
|
+
self.first_seen = FIRST_SEEN
|
|
37
|
+
self.scan_date_field = SCAN_DATE
|
|
38
|
+
self.cve = CVE
|
|
32
39
|
self.required_headers = [
|
|
33
|
-
"IP Address",
|
|
34
40
|
"Hostname",
|
|
35
|
-
"OS",
|
|
36
41
|
"Vulnerability Title",
|
|
37
42
|
"Vulnerability ID",
|
|
38
43
|
"CVSSv2 Score",
|
|
39
44
|
"CVSSv3 Score",
|
|
40
45
|
"Description",
|
|
41
|
-
"Proof",
|
|
42
46
|
"Solution",
|
|
43
47
|
"CVEs",
|
|
44
48
|
]
|
|
@@ -60,81 +64,165 @@ class Nexpose(FlatFileImporter):
|
|
|
60
64
|
**kwargs,
|
|
61
65
|
)
|
|
62
66
|
|
|
63
|
-
def create_asset(self, dat: Optional[dict] = None) -> Optional[
|
|
67
|
+
def create_asset(self, dat: Optional[dict] = None) -> Optional[IntegrationAsset]:
|
|
64
68
|
"""
|
|
65
69
|
Create an asset from a row in the Nexpose csv file
|
|
66
70
|
|
|
67
71
|
:param Optional[dict] dat: Data row from CSV file, defaults to None
|
|
68
|
-
:return: RegScale
|
|
69
|
-
:rtype: Optional[
|
|
72
|
+
:return: RegScale IntegrationAsset object, if it has a hostname
|
|
73
|
+
:rtype: Optional[IntegrationAsset]
|
|
70
74
|
"""
|
|
71
75
|
if hostname := self.mapping.get_value(dat, "Hostname"):
|
|
72
|
-
return
|
|
76
|
+
return IntegrationAsset(
|
|
73
77
|
**{
|
|
74
|
-
"id": 0,
|
|
75
78
|
"name": hostname,
|
|
76
|
-
"
|
|
77
|
-
"
|
|
79
|
+
"ip_address": self.mapping.get_value(dat, IP_ADDRESS, "0.0.0.0"),
|
|
80
|
+
"identifier": hostname,
|
|
81
|
+
"other_tracking_number": hostname,
|
|
78
82
|
"status": "Active (On Network)",
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"scanningTool": self.name,
|
|
83
|
-
"assetOwnerId": self.config["userId"],
|
|
84
|
-
"assetType": "Other",
|
|
83
|
+
"asset_category": "Hardware",
|
|
84
|
+
"asset_type": "Other",
|
|
85
|
+
"scanning_tool": self.name,
|
|
85
86
|
"fqdn": hostname if is_valid_fqdn(hostname) else None,
|
|
86
|
-
"
|
|
87
|
-
"systemAdministratorId": self.config["userId"],
|
|
88
|
-
"parentId": self.attributes.parent_id,
|
|
89
|
-
"parentModule": self.attributes.parent_module,
|
|
87
|
+
"operating_system": Asset.find_os(self.mapping.get_value(dat, "OS")),
|
|
90
88
|
}
|
|
91
89
|
)
|
|
90
|
+
return None
|
|
92
91
|
|
|
93
|
-
|
|
92
|
+
@staticmethod
|
|
93
|
+
def determine_severity_from_cvss_score(cvss_score: float, cvss_version: str = "v3") -> IssueSeverity:
|
|
94
94
|
"""
|
|
95
|
-
|
|
95
|
+
Determine CVSS Severity Text from CVSS Base Score
|
|
96
|
+
|
|
97
|
+
:param float cvss_score: CVSS Base Score
|
|
98
|
+
:param str cvss_version: CVSS version ("v2" or "v3"), defaults to "v3"
|
|
99
|
+
:return: CVSS Severity Text
|
|
100
|
+
:rtype: IssueSeverity
|
|
101
|
+
"""
|
|
102
|
+
results = IssueSeverity.Low
|
|
103
|
+
|
|
104
|
+
if cvss_version.lower() == "v3":
|
|
105
|
+
# CVSSv3 severity ranges
|
|
106
|
+
if 4.0 <= cvss_score <= 6.9:
|
|
107
|
+
results = IssueSeverity.Moderate
|
|
108
|
+
elif 7.0 <= cvss_score <= 8.9:
|
|
109
|
+
results = IssueSeverity.High
|
|
110
|
+
elif cvss_score > 8.9:
|
|
111
|
+
results = IssueSeverity.Critical
|
|
112
|
+
elif cvss_version.lower() == "v2":
|
|
113
|
+
# CVSSv2 severity ranges
|
|
114
|
+
if 4.0 <= cvss_score <= 6.9:
|
|
115
|
+
results = IssueSeverity.Moderate
|
|
116
|
+
elif 7.0 <= cvss_score <= 10.0:
|
|
117
|
+
results = IssueSeverity.High
|
|
118
|
+
# CVSSv2 doesn't have a "Critical" category, High is the highest
|
|
119
|
+
|
|
120
|
+
return results
|
|
121
|
+
|
|
122
|
+
def severity_from_text(self, text_severity: str) -> Optional[IssueSeverity]:
|
|
123
|
+
"""
|
|
124
|
+
Determine severity from text severity
|
|
125
|
+
|
|
126
|
+
:param str text_severity: Text severity
|
|
127
|
+
:return: IssueSeverity or None
|
|
128
|
+
:rtype: Optional[IssueSeverity]
|
|
129
|
+
"""
|
|
130
|
+
if text_severity.lower() == "low":
|
|
131
|
+
return IssueSeverity.Low
|
|
132
|
+
if text_severity.lower() in ["medium", "moderate"]:
|
|
133
|
+
return IssueSeverity.Moderate
|
|
134
|
+
if text_severity.lower() == "high":
|
|
135
|
+
return IssueSeverity.High
|
|
136
|
+
if text_severity.lower() in ["critical", "severe"]:
|
|
137
|
+
return IssueSeverity.Critical
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
def _determine_severity(self, dat: Optional[dict] = None) -> IssueSeverity:
|
|
141
|
+
"""
|
|
142
|
+
Determine severity using the priority order: text severity > CVSSv3 > CVSSv2 > default
|
|
143
|
+
|
|
144
|
+
:param Optional[dict] dat: Data row from CSV file, defaults to None
|
|
145
|
+
:return: Determined IssueSeverity
|
|
146
|
+
:rtype: IssueSeverity
|
|
147
|
+
"""
|
|
148
|
+
# Default severity
|
|
149
|
+
severity = IssueSeverity.Low
|
|
150
|
+
|
|
151
|
+
# Extract CVSS scores
|
|
152
|
+
cvss3_score = self.mapping.get_value(dat, self.cvss3_score) or 0.0
|
|
153
|
+
cvss2_score = self.mapping.get_value(dat, CVSS2_SCORE) or 0.0
|
|
154
|
+
|
|
155
|
+
# Priority 1: Check for text-based severity (client-specific)
|
|
156
|
+
# This is client specific, need to be able to override the severity source
|
|
157
|
+
text_severity = self.severity_from_text(
|
|
158
|
+
self.mapping.get_value(dat, "adobe_severity")
|
|
159
|
+
) or self.severity_from_text(self.mapping.get_value(dat, "nexpose_severity"))
|
|
160
|
+
|
|
161
|
+
if text_severity:
|
|
162
|
+
severity = text_severity
|
|
163
|
+
else:
|
|
164
|
+
# Priority 2: Use CVSSv3 score if available and valid
|
|
165
|
+
if cvss3_score and cvss3_score > 0:
|
|
166
|
+
severity = self.determine_severity_from_cvss_score(float(cvss3_score), "v3")
|
|
167
|
+
# Priority 3: Fall back to CVSSv2 score if available and valid
|
|
168
|
+
elif cvss2_score and cvss2_score > 0:
|
|
169
|
+
severity = self.determine_severity_from_cvss_score(float(cvss2_score), "v2")
|
|
170
|
+
|
|
171
|
+
return severity
|
|
172
|
+
|
|
173
|
+
def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[IntegrationFinding]:
|
|
174
|
+
"""
|
|
175
|
+
Create an IntegrationFinding from a row in the Prisma/Nexpose csv file
|
|
96
176
|
|
|
97
177
|
:param Optional[dict] dat: Data row from CSV file, defaults to None
|
|
98
|
-
:
|
|
99
|
-
:
|
|
178
|
+
:param kwargs: Additional keyword arguments
|
|
179
|
+
:return: RegScale IntegrationFinding object or None
|
|
180
|
+
:rtype: Optional[IntegrationFinding]
|
|
100
181
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
182
|
+
regscale_finding = None
|
|
183
|
+
|
|
184
|
+
# Extract basic data
|
|
103
185
|
hostname: str = self.mapping.get_value(dat, "Hostname")
|
|
104
|
-
os: str = self.mapping.get_value(dat, "OS")
|
|
105
186
|
description: str = self.mapping.get_value(dat, "Description")
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
title=description[:255],
|
|
187
|
+
cvss3_score = self.mapping.get_value(dat, self.cvss3_score) or 0.0
|
|
188
|
+
|
|
189
|
+
# Determine severity using priority logic
|
|
190
|
+
severity = self._determine_severity(dat)
|
|
191
|
+
|
|
192
|
+
# Find matching asset
|
|
193
|
+
|
|
194
|
+
# Extract date information
|
|
195
|
+
first_seen = (
|
|
196
|
+
self.mapping.get_value(dat, self.first_seen)
|
|
197
|
+
or self.mapping.get_value(dat, "first_seen")
|
|
198
|
+
or epoch_to_datetime(self.create_epoch)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if scan_date := self.mapping.get_value(dat, SCAN_DATE):
|
|
202
|
+
self.scan_date = scan_date
|
|
203
|
+
cvss_score = self.mapping.get_value(dat, CVSS2_SCORE)
|
|
204
|
+
|
|
205
|
+
# Create IntegrationFinding if we have valid data and asset match
|
|
206
|
+
if dat:
|
|
207
|
+
return IntegrationFinding(
|
|
208
|
+
control_labels=[], # Add an empty list for control_labels
|
|
209
|
+
title=self.mapping.get_value(dat, self.vuln_title),
|
|
130
210
|
description=description,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
211
|
+
cve=self.mapping.get_value(dat, self.cve, "").upper(),
|
|
212
|
+
severity=severity,
|
|
213
|
+
asset_identifier=hostname,
|
|
214
|
+
plugin_name=self.mapping.get_value(dat, self.vuln_title),
|
|
215
|
+
plugin_id=str(self.mapping.get_value(dat, self.vuln_id)),
|
|
216
|
+
cvss_score=cvss_score or 0.0,
|
|
217
|
+
cvss_v3_score=cvss3_score or 0.0,
|
|
218
|
+
cvss_v2_score=cvss_score or 0.0,
|
|
219
|
+
plugin_text=description[:255],
|
|
220
|
+
remediation=self.mapping.get_value(dat, "Solution"),
|
|
221
|
+
category="Hardware",
|
|
222
|
+
status=IssueStatus.Open,
|
|
223
|
+
first_seen=self.scan_date or date_str(first_seen),
|
|
224
|
+
scan_date=date_str(scan_date) or self.scan_date,
|
|
225
|
+
vulnerability_type="Vulnerability Scan",
|
|
226
|
+
baseline=f"{self.name} Host",
|
|
139
227
|
)
|
|
140
|
-
return
|
|
228
|
+
return regscale_finding
|