Skip to content

Commit f745c9f

Browse files
author
Your Name
committed
add new parser ReversingLabs Spectra Assure
1 parent cfa7b97 commit f745c9f

File tree

9 files changed

+88021
-0
lines changed

9 files changed

+88021
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# ReversingLabs SpectraAssure Parser
2+
3+
## File Types
4+
5+
The parser accepts only `report.rl.json` files.
6+
You can find instructions how to export the `rl-json` report from the cli and portal scanners.
7+
8+
- [Spectra Assure Cli](https://docs.secure.software/cli/).
9+
- [Spectra Assure Portal](https://docs.secure.software/portal/).
10+
- [docker:rl-scanner](https://hub.docker.com/r/reversinglabs/rl-scanner).
11+
- [docker:rl-scanner-cloud](https://hub.docker.com/r/reversinglabs/rl-scanner-cloud).
12+
13+
14+
## Relevant Fields in report.rl.json
15+
16+
The format of the `rl-json` report is shown in:
17+
18+
- [analysis-reports:rl-json](https://docs.secure.software/concepts/analysis-reports#rl-json).
19+
20+
The RlJsonInfo module is the principal parser vor the `report.rl.json` data.
21+
The primary focus is on extracting vulnerabilities (CVE) that occur on rl-json `components` and rl-json `dependencies`. All items without vulnerabilities are currently ignored.
22+
23+
All data is stored only once in the rl-json file and it uses references to map relevant related data:
24+
25+
Component -> Vulnerabilities
26+
Component -> Dependencies -> Vulnerabilities
27+
28+
During the parsing we follow the references and add each item to a individual `CveInfoNode` record that has the vulnerablity and the component and the dependency data.
29+
30+
The `CveInfoNode` basically maps almost directly to the `DefectDojo Finding`.
31+
32+
The `title` and `description` are build using the collected data.
33+
34+
### Title
35+
36+
#### Component
37+
38+
For a Components, the title shows:
39+
40+
- the CVE.
41+
- the type: `Component`.
42+
- the `purl` of the `Component` if present, otherwise name and version.
43+
- the component-name.
44+
- the component-sha256.
45+
46+
The sha256 is added as sometimes a file scan my have multiple items with the same name and version but with a different hash.
47+
Typically this happens with multi language windows installeres.
48+
49+
#### Dependency
50+
51+
The title shows the:
52+
53+
- the CVE.
54+
- the type: `Dependecy`.
55+
- the `purl` of the `Dependency` if present, otherwise name and version.
56+
57+
### Description
58+
59+
#### Component
60+
61+
For a component we repeat the title.
62+
63+
#### Dependency
64+
65+
For a dependency we repeat the title and then add the component_name and component_hash.
66+
For duplicates we add one additional line to the description for each duplicate, showing its title and component.
67+
68+
### Vulnerabilities
69+
70+
From the vulnerability we fetch:
71+
72+
- the CVE unique id
73+
- cvss version
74+
- cvss.basescore
75+
76+
From the cvss.basescore we map the severity into:
77+
78+
- Info
79+
- Low
80+
- Medium
81+
- High
82+
- Critical
83+
84+
### Other
85+
86+
We extract the scan date and the scanner version and set a static scanner-name.
87+
88+
## Field Mapping Details
89+
90+
91+
- Currently no endpoints are created
92+
93+
- On detecting a duplicate `dependency` we increment the number of occurrences.
94+
`Components` have no duplicates so the nr of occurrences is always 1.
95+
96+
- Deduplication is done only on Dependencies and we use the title (cve + dependency_name and version) + the `component-path` as the hash_key to detect duplicates.
97+
98+
- The default severity if no mapping is matched is `Info`.
99+
100+
## Sample Scan Data or Unit Tests
101+
102+
- [Sample Scan Data Folder](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/reversinglabs_spectraassure)
103+
104+
## Link To Tools
105+
106+
- [Spectra Assure Cli](https://docs.secure.software/cli/)
107+
- [Spectra Assure Portal](https://docs.secure.software/portal/)
108+
- [docker:rl-scanner](https://docs.secure.software/cli/integrations/docker-image)
109+
- [docker:rl-scanner-cloud](https://docs.secure.software/portal/docker-image)
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
from typing import (
2+
List,
3+
Dict,
4+
Any,
5+
)
6+
import logging
7+
import hashlib
8+
9+
# any line after FIX_START and before FIX_END
10+
# will be rewritten when copy to the dev machine
11+
# commented lines will be uncommented
12+
# and uncommented lines will be commented
13+
14+
# FIX_START
15+
from dojo.models import Finding
16+
from dojo.tools.reversinglabs_spectraassure.rlJsonInfo import RlJsonInfo
17+
from dojo.tools.reversinglabs_spectraassure.rlJsonInfo.cve_info_node import CveInfoNode
18+
# from finding import Finding
19+
# from .rlJsonInfo import RlJsonInfo
20+
# from .rlJsonInfo.cve_info_node import CveInfoNode
21+
#
22+
# FIX_END
23+
24+
logger = logging.getLogger(__name__)
25+
26+
WHAT = "ReversingLabs Spectra Assure"
27+
28+
"""
29+
dedup on title:
30+
31+
if a finding is a Dependency:
32+
title:
33+
"<cve>, 'dep': <purl | name+version>"
34+
35+
description:
36+
"<title>\n<number> component: <purl | name+version>, <sha256>, 'id in report.rl.json': <dep-uuid>\n"
37+
38+
for each duplicate title: (so the same Dependency):
39+
increment occurrences and append to description of the first detected:
40+
41+
"<number+1> component: <purl | name+version>, <sha256>, 'id in report.rl.json': <dep-uuid>\n"
42+
43+
if a finding is a Component it is already unique.
44+
title:
45+
"<cve>, 'comp': <purl | name+version>, <sha256>"
46+
occurrences = 1
47+
48+
description:
49+
<title>\n
50+
51+
Note:
52+
We have components with the same name and version but different hash value.
53+
This is typical for windows installers with multi language support.
54+
A good example is: HxDSetup_2.5.0.exe
55+
"""
56+
57+
58+
class ReversinglabsSpectraassureParser(object):
59+
"""
60+
Parser for Spectra Assure rl-json files
61+
62+
This class MUST implement 3 methods:
63+
64+
- def get_scan_types(self)
65+
This function return a list of all the scan_type supported by your parser.
66+
These identifiers are used internally.
67+
Your parser can support more than one scan_type.
68+
e.g. some parsers use different identifier to modify the behavior of the parser (aggregate, filter, etc…)
69+
70+
- def get_label_for_scan_types(self, scan_type)
71+
This function return a string used to provide some text in the UI (short label)
72+
73+
- def get_description_for_scan_types(self, scan_type)
74+
This function return a string used to provide some text in the UI (long description)
75+
76+
- def get_findings(self, file, test)
77+
This function return a list of findings
78+
79+
If your parser has more than 1 scan_type (for detailed mode) you MUST implement:
80+
- def set_mode(self, mode) method
81+
"""
82+
83+
# --------------------------------------------
84+
# This class MUST have an empty constructor or no constructor
85+
86+
def _find_duplicate(self, key: str) -> Finding | None:
87+
logger.debug("")
88+
89+
if key in self._duplicates:
90+
return self._duplicates
91+
92+
return None
93+
94+
def _make_hash(
95+
self,
96+
data: str,
97+
) -> str:
98+
# Calculate SHA-256 hash
99+
d = data.encode()
100+
return hashlib.sha256(d).hexdigest()
101+
102+
def _one_finding(
103+
self,
104+
*,
105+
node: CveInfoNode,
106+
test: Any,
107+
) -> Finding:
108+
logger.debug("%s", node)
109+
110+
key = self._make_hash(node.title + " " + node.component_file_path)
111+
cve = node.cve
112+
finding = Finding(
113+
date=node.scan_date,
114+
title=node.title,
115+
description=node.title + " " + node.description + "\n",
116+
#
117+
cve=cve,
118+
cvssv3_score=node.score,
119+
severity=node.score_severity,
120+
#
121+
vuln_id_from_tool=node.vuln_id_from_tool,
122+
unique_id_from_tool=node.unique_id_from_tool, # purl if we have one ?
123+
#
124+
file_path=node.component_file_path,
125+
component_name=node.component_name,
126+
component_version=node.component_version,
127+
#
128+
nb_occurences=1,
129+
hash_code=key, # sha256 on title
130+
#
131+
references=None, # future urls
132+
#
133+
active=True, # this is the DefectDojo active field, nothing to do with node.active field
134+
test=test,
135+
static_finding=True,
136+
dynamic_finding=False,
137+
)
138+
finding.unsaved_vulnerability_ids = [cve]
139+
finding.unsaved_tags = node.tags
140+
finding.impact = node.impact
141+
142+
return finding
143+
144+
# --------------------------------------------
145+
# PUBLIC
146+
def get_scan_types(self) -> List[str]:
147+
return [WHAT]
148+
149+
def get_label_for_scan_types(self, scan_type: str) -> str:
150+
return scan_type
151+
152+
def get_description_for_scan_types(self, scan_type: str) -> str:
153+
if scan_type == WHAT:
154+
return "Import the SpectraAssure report.rl.json file."
155+
return f"Unknown Scan Type; {scan_type}"
156+
157+
def get_findings(
158+
self,
159+
file: Any,
160+
test: Any,
161+
) -> List[Finding]:
162+
# ------------------------------------
163+
rji = RlJsonInfo(file_handle=file)
164+
rji.get_cve_active_all()
165+
166+
self._findings: List[Finding] = []
167+
self._duplicates: Dict[str, Finding] = {}
168+
169+
for cin in rji.iter_results():
170+
finding = self._one_finding(
171+
node=cin,
172+
test=test,
173+
)
174+
if finding is None:
175+
continue
176+
177+
key = finding.hash_code
178+
if key not in self._duplicates:
179+
self._findings.append(finding)
180+
self._duplicates[key] = finding
181+
continue
182+
183+
dup = self._duplicates[key] # but that may be on a different component file, name, version
184+
assert dup is not None
185+
dup.description += finding.description
186+
dup.nb_occurences += 1
187+
188+
# ------------------------------------
189+
return self._findings

0 commit comments

Comments
 (0)