Skip to content

Commit 1345ea9

Browse files
committed
ReversingLabs SpectraAssure rl-json parser for DefectDojo
1 parent 469ca7b commit 1345ea9

10 files changed

Lines changed: 87938 additions & 0 deletions

File tree

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)

dojo/tools/reversinglabs_spectraassure/__init__.py

Whitespace-only changes.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# noqa: RUF100
2+
import hashlib
3+
import logging
4+
from typing import Any
5+
6+
from dojo.models import Finding
7+
from dojo.tools.reversinglabs_spectraassure.rlJsonInfo import RlJsonInfo
8+
from dojo.tools.reversinglabs_spectraassure.rlJsonInfo.cve_info_node import CveInfoNode
9+
10+
logger = logging.getLogger(__name__)
11+
12+
WHAT = "ReversingLabs Spectra Assure"
13+
14+
15+
class ReversinglabsSpectraassureParser:
16+
17+
# --------------------------------------------
18+
# This class MUST have an empty constructor or no constructor
19+
20+
def _find_duplicate(self, key: str) -> Finding | None:
21+
logger.debug("")
22+
23+
if key in self._duplicates:
24+
return self._duplicates
25+
26+
return None
27+
28+
def _make_hash(
29+
self,
30+
data: str,
31+
) -> str:
32+
# Calculate SHA-256 hash
33+
d = data.encode()
34+
return hashlib.sha256(d).hexdigest()
35+
36+
def _one_finding(
37+
self,
38+
*,
39+
node: CveInfoNode,
40+
test: Any,
41+
) -> Finding:
42+
logger.debug("%s", node)
43+
44+
key = self._make_hash(node.title + " " + node.component_file_path)
45+
cve = node.cve
46+
finding = Finding(
47+
date=node.scan_date,
48+
title=node.title,
49+
description=node.title + " " + node.description + "\n",
50+
cve=cve,
51+
cvssv3_score=node.score,
52+
severity=node.score_severity,
53+
vuln_id_from_tool=node.vuln_id_from_tool,
54+
unique_id_from_tool=node.unique_id_from_tool, # purl if we have one ?
55+
file_path=node.component_file_path,
56+
component_name=node.component_name,
57+
component_version=node.component_version,
58+
nb_occurences=1,
59+
hash_code=key, # sha256 on title
60+
references=None, # future urls
61+
active=True, # this is the DefectDojo active field, nothing to do with node.active field
62+
test=test,
63+
static_finding=True,
64+
dynamic_finding=False,
65+
)
66+
finding.unsaved_vulnerability_ids = [cve]
67+
finding.unsaved_tags = node.tags
68+
finding.impact = node.impact
69+
70+
return finding
71+
72+
# --------------------------------------------
73+
# PUBLIC
74+
def get_scan_types(self) -> list[str]:
75+
return [WHAT]
76+
77+
def get_label_for_scan_types(self, scan_type: str) -> str:
78+
return scan_type
79+
80+
def get_description_for_scan_types(self, scan_type: str) -> str:
81+
if scan_type == WHAT:
82+
return "Import the SpectraAssure report.rl.json file."
83+
return f"Unknown Scan Type; {scan_type}"
84+
85+
def get_findings(
86+
self,
87+
file: Any,
88+
test: Any,
89+
) -> list[Finding]:
90+
# ------------------------------------
91+
rji = RlJsonInfo(file_handle=file)
92+
rji.get_cve_active_all()
93+
94+
self._findings: list[Finding] = []
95+
self._duplicates: dict[str, Finding] = {}
96+
97+
for cin in rji.get_results_list():
98+
finding = self._one_finding(
99+
node=cin,
100+
test=test,
101+
)
102+
if finding is None:
103+
continue
104+
105+
key = finding.hash_code
106+
if key not in self._duplicates:
107+
self._findings.append(finding)
108+
self._duplicates[key] = finding
109+
continue
110+
111+
dup = self._duplicates[key] # but that may be on a different component file, name, version
112+
if dup:
113+
dup.description += finding.description
114+
dup.nb_occurences += 1
115+
116+
# ------------------------------------
117+
return self._findings

0 commit comments

Comments
 (0)