Skip to content

Commit 6e90f26

Browse files
authored
ReversingLabs SpectraAssure rl-json parser for DefectDojo (#12579)
* ReversingLabs SpectraAssure rl-json parser for DefectDojo * add title to doc file after bot complain * fix the readme format for the verifier * remove TODO; fix scan type name * readme after internal review * remove unique_id_from_tool * update readme after removal of unique-id-from-tool * add documentation to parsing logic; cleanup short names
1 parent b6913df commit 6e90f26

10 files changed

Lines changed: 88106 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
title: "ReversingLabs Spectra Assure"
3+
toc_hide: true
4+
---
5+
6+
# ReversingLabs Spectra Assure Parser
7+
8+
The Spectra Assure platform is a set of [ReversingLabs](https://www.reversinglabs.com/) solutions primarily designed for software assurance and software supply chain security use-cases.
9+
Spectra Assure products analyze compiled software packages, their components and third-party dependencies to detect exposures, reduce vulnerabilities, and eliminate threats before reaching production.
10+
11+
Every Spectra Assure analysis (software scan) produces a set of reports and the overall CI status (pass or fail) for the analyzed software package.
12+
The reports are created in multiple different formats, with different level of detail and scope of information about the analysis results.
13+
The official documentation describes all [supported report formats](https://docs.secure.software/concepts/analysis-reports) in Spectra Assure.
14+
15+
**The primary purpose of this parser is extracting known vulnerabilities (CVEs) that are present in the `components` and `dependencies` sections of the `rl-json` report.**
16+
17+
### File Types
18+
19+
The parser accepts only `report.rl.json` files (the Spectra Assure [rl-json report](https://docs.secure.software/concepts/analysis-reports#rl-json)).
20+
21+
You can find instructions for exporting the `rl-json` report in the documentation of the Spectra Assure product you're using.
22+
23+
- [Spectra Assure CLI](https://docs.secure.software/cli/commands/report).
24+
- [Spectra Assure Portal](https://docs.secure.software/api-reference/#tag/Version/operation/getVersionReport).
25+
- [docker:rl-scanner](https://hub.docker.com/r/reversinglabs/rl-scanner).
26+
- [docker:rl-scanner-cloud](https://hub.docker.com/r/reversinglabs/rl-scanner-cloud).
27+
28+
29+
### Total Fields in Reversinglabs Spectra Assure rl-json
30+
31+
For the specification of the `rl-json` report, consult the official Spectra Assure documentation:
32+
33+
- [rl-json report schema](https://docs.secure.software/cli/rl-json-schema)
34+
- [Analysis reports: rl-json](https://docs.secure.software/concepts/analysis-reports#rl-json).
35+
36+
37+
### Field Mapping Details
38+
39+
#### Title
40+
41+
##### Component
42+
43+
For a component, the title includes:
44+
45+
- the CVE
46+
- the type: `Component`
47+
- the `purl` of the `Component` if present; otherwise name and version
48+
49+
50+
##### Dependency
51+
52+
For a dependency, the title includes:
53+
54+
- the CVE
55+
- the type: `Dependency`
56+
- the `purl` of the `Dependency` if present; otherwise name and version
57+
58+
#### Description
59+
60+
##### Component
61+
62+
For a component, the description repeats the information from the [title](#title) and includes the SHA256 hash of the component.
63+
64+
The SHA256 is included because sometimes a file scan may have multiple items with the same name and version, but with different hashes.
65+
Typically this happens with multi-language Windows installer packages.
66+
67+
68+
##### Dependency
69+
70+
For a dependency, the description repeats the information from the [title](#title) and includes the component path, `component-name` and `component-hash`.
71+
For duplicates, the description includes an additional line showing the title and component of each duplicate.
72+
73+
#### Vulnerabilities
74+
75+
For vulnerabilities, the following information is retrieved:
76+
77+
- the CVE unique ID
78+
- CVSS version
79+
- CVSS base score
80+
81+
From the CVSS base score, we map the severity into:
82+
83+
- Info
84+
- Low
85+
- Medium
86+
- High
87+
- Critical
88+
89+
If no mapping is matched, the default severity is `Info`.
90+
91+
##### Notes
92+
93+
- Currently, no endpoints are created.
94+
- Deduplication is done with sha256 if the title.
95+
- On detecting a duplicate dependency, we increment the number of occurrences. Components have no duplicates, so the number of occurrences is always 1.
96+
- We extract the scan date, the Spectra Assure scanner version, and set a static scanner name.
97+
98+
### Sample Scan Data or Unit Tests
99+
100+
- [Sample Scan Data Folder](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/reversinglabs_spectraassure)
101+
102+
### Link To Tool
103+
104+
- [Spectra Assure Cli](https://docs.secure.software/cli/)
105+
- [Spectra Assure Portal](https://docs.secure.software/portal/)
106+
- [docker:rl-scanner](https://docs.secure.software/cli/integrations/docker-image)
107+
- [docker:rl-scanner-cloud](https://docs.secure.software/portal/docker-image)

dojo/tools/reversinglabs_spectraassure/__init__.py

Whitespace-only changes.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
SCAN_TYPE = "ReversingLabs Spectra Assure"
13+
14+
"""
15+
The actual parsing is done by `RlJsonInfo` and it stores data as a collection of `CveInfoNode`
16+
A `CveInfoNode` matches a dd.Finding more closely and makes the collection of Findings easy.
17+
18+
"""
19+
20+
21+
class ReversinglabsSpectraassureParser:
22+
23+
# --------------------------------------------
24+
# This class MUST have an empty constructor or no constructor
25+
26+
def _find_duplicate(self, key: str) -> Finding | None:
27+
logger.debug("")
28+
29+
if key in self._duplicates:
30+
return self._duplicates
31+
32+
return None
33+
34+
def _make_hash(
35+
self,
36+
data: str,
37+
) -> str:
38+
# Calculate SHA-256 hash
39+
d = data.encode()
40+
return hashlib.sha256(d).hexdigest()
41+
42+
def _one_finding(
43+
self,
44+
*,
45+
node: CveInfoNode,
46+
test: Any,
47+
) -> Finding:
48+
logger.debug("%s", node)
49+
50+
key = self._make_hash(node.title + " " + node.component_file_path)
51+
cve = node.cve
52+
finding = Finding(
53+
date=node.scan_date,
54+
title=node.title,
55+
description=node.title + " " + node.description + "\n",
56+
cve=cve,
57+
cvssv3_score=node.score,
58+
severity=node.score_severity,
59+
vuln_id_from_tool=node.vuln_id_from_tool,
60+
file_path=node.component_file_path,
61+
component_name=node.component_name,
62+
component_version=node.component_version,
63+
nb_occurences=1,
64+
hash_code=key, # sha256 on title
65+
references=None, # future: urls
66+
active=True, # this is the DefectDojo active field, nothing to do with node.active field
67+
test=test,
68+
static_finding=True,
69+
dynamic_finding=False,
70+
)
71+
finding.unsaved_vulnerability_ids = [cve]
72+
finding.unsaved_tags = node.tags
73+
finding.impact = node.impact
74+
75+
return finding
76+
77+
# --------------------------------------------
78+
# PUBLIC
79+
def get_scan_types(self) -> list[str]:
80+
return [SCAN_TYPE]
81+
82+
def get_label_for_scan_types(self, scan_type: str) -> str:
83+
return scan_type
84+
85+
def get_description_for_scan_types(self, scan_type: str) -> str:
86+
if scan_type == SCAN_TYPE:
87+
return "Import the SpectraAssure report.rl.json file."
88+
return f"Unknown Scan Type; {scan_type}"
89+
90+
def get_findings(
91+
self,
92+
file: Any,
93+
test: Any,
94+
) -> list[Finding]:
95+
# ------------------------------------
96+
rl_json_info_instance = RlJsonInfo(file_handle=file)
97+
rl_json_info_instance.get_cve_active_all()
98+
99+
self._findings: list[Finding] = []
100+
self._duplicates: dict[str, Finding] = {}
101+
102+
for cve_info_node_instance in rl_json_info_instance.get_results_list():
103+
finding = self._one_finding(
104+
node=cve_info_node_instance,
105+
test=test,
106+
)
107+
if finding is None:
108+
continue
109+
110+
key = finding.hash_code
111+
if key not in self._duplicates:
112+
self._findings.append(finding)
113+
self._duplicates[key] = finding
114+
continue
115+
116+
dup = self._duplicates[key]
117+
if dup:
118+
dup.description += finding.description
119+
dup.nb_occurences += 1
120+
121+
# ------------------------------------
122+
return self._findings

0 commit comments

Comments
 (0)