@secureport/core 0.3.0 → 0.4.0
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.
- package/README.md +87 -10
- package/dist/import/burp.d.ts +19 -0
- package/dist/import/burp.d.ts.map +1 -0
- package/dist/import/burp.js +114 -0
- package/dist/import/burp.js.map +1 -0
- package/dist/import/generic.d.ts +90 -0
- package/dist/import/generic.d.ts.map +1 -0
- package/dist/import/generic.js +159 -0
- package/dist/import/generic.js.map +1 -0
- package/dist/import/nessus.d.ts +32 -0
- package/dist/import/nessus.d.ts.map +1 -0
- package/dist/import/nessus.js +125 -0
- package/dist/import/nessus.js.map +1 -0
- package/dist/import/xml.d.ts +47 -0
- package/dist/import/xml.d.ts.map +1 -0
- package/dist/import/xml.js +157 -0
- package/dist/import/xml.js.map +1 -0
- package/dist/import/zap.d.ts +1 -1
- package/dist/import/zap.js +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/report/html.d.ts +21 -0
- package/dist/report/html.d.ts.map +1 -0
- package/dist/report/html.js +324 -0
- package/dist/report/html.js.map +1 -0
- package/dist/report/json.d.ts +81 -0
- package/dist/report/json.d.ts.map +1 -0
- package/dist/report/json.js +47 -0
- package/dist/report/json.js.map +1 -0
- package/dist/report/markdown.d.ts +24 -0
- package/dist/report/markdown.d.ts.map +1 -0
- package/dist/report/markdown.js +304 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/report/model.d.ts +215 -0
- package/dist/report/model.d.ts.map +1 -0
- package/dist/report/model.js +197 -0
- package/dist/report/model.js.map +1 -0
- package/dist/snapshot-builder.d.ts +8 -0
- package/dist/snapshot-builder.d.ts.map +1 -1
- package/dist/snapshot-builder.js +25 -1
- package/dist/snapshot-builder.js.map +1 -1
- package/package.json +3 -3
- package/src/import/burp.ts +126 -0
- package/src/import/generic.ts +258 -0
- package/src/import/nessus.ts +136 -0
- package/src/import/xml.ts +187 -0
- package/src/import/zap.ts +1 -1
- package/src/index.ts +19 -0
- package/src/report/html.ts +449 -0
- package/src/report/json.ts +134 -0
- package/src/report/markdown.ts +435 -0
- package/src/report/model.ts +462 -0
- package/src/snapshot-builder.ts +28 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @secureport/core
|
|
2
2
|
|
|
3
|
-
The shared domain model for [Secureport](https://
|
|
3
|
+
The shared domain model for [Secureport](https://secureport.io/).
|
|
4
4
|
The hosted API imports this package the same way a third party would — one
|
|
5
5
|
model, one implementation.
|
|
6
6
|
|
|
@@ -24,12 +24,53 @@ comes back after being fixed is a regression, not a discovery.
|
|
|
24
24
|
npm install @secureport/core
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## What it does
|
|
28
|
+
|
|
29
|
+
Everything below is pure: no I/O, no clock, no randomness. Time and ids are
|
|
30
|
+
arguments, which is what makes a report reproducible and a test a golden file.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { buildSnapshot, importNuclei, renderMarkdown, parseSnapshot } from '@secureport/core';
|
|
34
|
+
|
|
35
|
+
const findings = importNuclei(text, { orgId, runId, targetId, now, newId });
|
|
36
|
+
|
|
37
|
+
const snapshot = buildSnapshot({
|
|
38
|
+
target,
|
|
39
|
+
run,
|
|
40
|
+
findings,
|
|
41
|
+
previous: parseSnapshot(lastTime), // optional — this is what makes a retest
|
|
42
|
+
now,
|
|
43
|
+
newId,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
renderMarkdown(snapshot, { kind: 'retest', now });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| | |
|
|
50
|
+
| ------------------ | -------------------------------------------------------------------------- |
|
|
51
|
+
| **Importers** | `importNuclei`, `importZap`, `importBurp`, `importNessus`, `importGeneric` |
|
|
52
|
+
| **Fingerprint** | `fingerprint`, `vulnKey`, `normaliseLocation`, `FINGERPRINT_VERSION` |
|
|
53
|
+
| **Reconciliation** | `reconcile` — the only thing that changes issue state |
|
|
54
|
+
| **Coverage** | `coversLocation` — what makes auto-resolution safe |
|
|
55
|
+
| **Snapshots** | `buildSnapshot`, `parseSnapshot` |
|
|
56
|
+
| **Reports** | `renderMarkdown`, `renderHtml`, `renderJson`, `buildReportModel` |
|
|
57
|
+
|
|
58
|
+
Rendered examples of all five report kinds are at
|
|
59
|
+
[secureport.io](https://secureport.io/) — the source repository is private, so
|
|
60
|
+
the site is where they live.
|
|
61
|
+
|
|
62
|
+
For a command line rather than an API, see
|
|
63
|
+
[`@secureport/cli`](https://www.npmjs.com/package/@secureport/cli):
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
npx @secureport/cli report pen scan.jsonl --url https://app.example.com
|
|
67
|
+
```
|
|
68
|
+
|
|
27
69
|
## Status
|
|
28
70
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
model rather than computing it.
|
|
71
|
+
Pre-1.0. Every export is a semver contract already, and the shapes are settled
|
|
72
|
+
by five report templates and five importers built on them — but the deprecation
|
|
73
|
+
policy lands before `1.0.0` and until then a minor may still move something.
|
|
33
74
|
|
|
34
75
|
`0.2.x` had a placeholder `reconcile()` that matched issues by title. It is gone
|
|
35
76
|
rather than deprecated — matching by title is not a simplified version of
|
|
@@ -85,11 +126,47 @@ account.
|
|
|
85
126
|
|
|
86
127
|
The snapshot is the artefact; a PDF is a view of it.
|
|
87
128
|
|
|
88
|
-
##
|
|
129
|
+
## Public contracts
|
|
130
|
+
|
|
131
|
+
Every export is a semver contract. Two of them are worth stating separately,
|
|
132
|
+
because they outlive any one version of this package.
|
|
133
|
+
|
|
134
|
+
### `fingerprint_version`
|
|
135
|
+
|
|
136
|
+
An issue's identity is the pair `(fingerprintVersion, fingerprint)`, not the
|
|
137
|
+
fingerprint alone. `FINGERPRINT_VERSION` is `fp_v1` and it travels with every
|
|
138
|
+
fingerprint everywhere it is stored, so a fingerprint written today can always
|
|
139
|
+
be interpreted — including by code that has moved on.
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
fingerprint = sha256(targetId + '|' + vulnKey + '|' + normalisedLocation + '|' + (parameter ?? port ?? ''))
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`vulnKey` is the engine-independent name for a weakness: a mapping-table entry
|
|
146
|
+
where one exists, then the CWE, then the category, then `engine:ruleId`. That
|
|
147
|
+
chain is what makes two scanners reporting the same weakness one issue rather
|
|
148
|
+
than two.
|
|
149
|
+
|
|
150
|
+
**Changing any of this changes the version**, and a version change means a
|
|
151
|
+
migration that recomputes fingerprints while preserving history — `firstSeen`
|
|
152
|
+
is never reset — not a silent recomputation that orphans every issue. That is
|
|
153
|
+
the whole reason the version exists, and it is why it is a contract rather than
|
|
154
|
+
an implementation detail.
|
|
155
|
+
|
|
156
|
+
### The JSON report
|
|
157
|
+
|
|
158
|
+
`renderJson` emits a `JsonReport`, versioned by `reportVersion` (currently
|
|
159
|
+
`1`), for consumers rendering their own view. It carries the derived answers
|
|
160
|
+
beside the snapshot rather than only the snapshot, and two of its fields are
|
|
161
|
+
there for a specific reason:
|
|
162
|
+
|
|
163
|
+
- **`basis`** — how the findings were produced, as data. A consumer must not be
|
|
164
|
+
able to present automated results as manual testing simply by not reading the
|
|
165
|
+
prose.
|
|
166
|
+
- **`limitations`** — what the report does not establish, as data, for the same
|
|
167
|
+
reason.
|
|
89
168
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
changing the algorithm means a migration that preserves history rather than a
|
|
93
|
-
silent recomputation.
|
|
169
|
+
`reportVersion` bumps when the shape changes incompatibly. Adding a field does
|
|
170
|
+
not bump it, so read defensively.
|
|
94
171
|
|
|
95
172
|
A deprecation policy lands before `1.0.0`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Finding } from '../finding.js';
|
|
2
|
+
import type { ImportOptions } from './nuclei.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turns a Burp Suite XML export into {@link Finding}s.
|
|
5
|
+
*
|
|
6
|
+
* Burp exports a flat `<issues>` document, one `<issue>` per detection, with the
|
|
7
|
+
* host and path in separate elements — so the location is assembled rather than
|
|
8
|
+
* read. Prose fields arrive as CDATA containing HTML, which is stripped.
|
|
9
|
+
*
|
|
10
|
+
* `<type>` is Burp's numeric issue type and is used as the rule id, because it
|
|
11
|
+
* is stable across versions where the human-readable `<name>` is not.
|
|
12
|
+
*
|
|
13
|
+
* @param xml - The contents of a Burp XML export.
|
|
14
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
15
|
+
* @returns One finding per issue, in document order.
|
|
16
|
+
* @throws SyntaxError If the document is not well-formed XML.
|
|
17
|
+
*/
|
|
18
|
+
export declare function importBurp(xml: string, options: ImportOptions): Finding[];
|
|
19
|
+
//# sourceMappingURL=burp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"burp.d.ts","sourceRoot":"","sources":["../../src/import/burp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAwCjD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,EAAE,CA0DzE"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { fingerprint, vulnKey, FINGERPRINT_VERSION } from '../fingerprint.js';
|
|
2
|
+
import { childText, findAll, parseXml } from './xml.js';
|
|
3
|
+
/**
|
|
4
|
+
* Burp's severity vocabulary, mapped onto {@link Severity}.
|
|
5
|
+
*
|
|
6
|
+
* **Burp has no "critical" either.** Like ZAP it tops out at High, and
|
|
7
|
+
* `Information` is the bottom of the scale rather than a separate category.
|
|
8
|
+
*/
|
|
9
|
+
const BURP_SEVERITY = Object.freeze({
|
|
10
|
+
high: 'high',
|
|
11
|
+
medium: 'medium',
|
|
12
|
+
low: 'low',
|
|
13
|
+
information: 'advisory',
|
|
14
|
+
info: 'advisory',
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Burp reports confidence in words. Mapped to a number so the model does not
|
|
18
|
+
* have to carry a second vocabulary for it.
|
|
19
|
+
*/
|
|
20
|
+
const BURP_CONFIDENCE = Object.freeze({
|
|
21
|
+
certain: 1,
|
|
22
|
+
firm: 0.7,
|
|
23
|
+
tentative: 0.4,
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Burp writes the affected parameter into the location, not a field of its own.
|
|
27
|
+
*
|
|
28
|
+
* `/search [q parameter]` means the `q` parameter of `/search`. Pulling it out
|
|
29
|
+
* matters: the parameter is part of the fingerprint, so leaving it embedded in
|
|
30
|
+
* the path would make Burp's finding a different issue from ZAP's finding of the
|
|
31
|
+
* same weakness in the same place.
|
|
32
|
+
*/
|
|
33
|
+
function splitLocation(location) {
|
|
34
|
+
const match = /^(.*?)\s*\[\s*(.+?)\s+parameter\s*\]\s*$/iu.exec(location);
|
|
35
|
+
if (match)
|
|
36
|
+
return { path: match[1].trim(), parameter: match[2].trim() };
|
|
37
|
+
return { path: location.trim() };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Turns a Burp Suite XML export into {@link Finding}s.
|
|
41
|
+
*
|
|
42
|
+
* Burp exports a flat `<issues>` document, one `<issue>` per detection, with the
|
|
43
|
+
* host and path in separate elements — so the location is assembled rather than
|
|
44
|
+
* read. Prose fields arrive as CDATA containing HTML, which is stripped.
|
|
45
|
+
*
|
|
46
|
+
* `<type>` is Burp's numeric issue type and is used as the rule id, because it
|
|
47
|
+
* is stable across versions where the human-readable `<name>` is not.
|
|
48
|
+
*
|
|
49
|
+
* @param xml - The contents of a Burp XML export.
|
|
50
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
51
|
+
* @returns One finding per issue, in document order.
|
|
52
|
+
* @throws SyntaxError If the document is not well-formed XML.
|
|
53
|
+
*/
|
|
54
|
+
export function importBurp(xml, options) {
|
|
55
|
+
const root = parseXml(xml);
|
|
56
|
+
const findings = [];
|
|
57
|
+
for (const issue of findAll(root, 'issue')) {
|
|
58
|
+
const name = childText(issue, 'name');
|
|
59
|
+
const type = childText(issue, 'type');
|
|
60
|
+
if (name === undefined || type === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
const host = childText(issue, 'host') ?? '';
|
|
63
|
+
const raw = childText(issue, 'location') ?? childText(issue, 'path') ?? '';
|
|
64
|
+
const { path, parameter } = splitLocation(raw);
|
|
65
|
+
const location = path.startsWith('http') ? path : `${host}${path}`;
|
|
66
|
+
if (location === '')
|
|
67
|
+
continue;
|
|
68
|
+
const cwe = childText(issue, 'vulnerabilityClassifications')
|
|
69
|
+
?.match(/CWE-\d+/iu)?.[0]
|
|
70
|
+
?.toUpperCase();
|
|
71
|
+
const key = vulnKey({
|
|
72
|
+
sourceEngine: 'burp',
|
|
73
|
+
sourceRuleId: type,
|
|
74
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
75
|
+
});
|
|
76
|
+
const confidence = BURP_CONFIDENCE[childText(issue, 'confidence')?.toLowerCase() ?? ''];
|
|
77
|
+
const background = childText(issue, 'issueBackground');
|
|
78
|
+
const remediation = childText(issue, 'remediationBackground');
|
|
79
|
+
findings.push({
|
|
80
|
+
id: options.newId(),
|
|
81
|
+
orgId: options.orgId,
|
|
82
|
+
runId: options.runId,
|
|
83
|
+
fingerprint: fingerprint({
|
|
84
|
+
targetId: options.targetId,
|
|
85
|
+
vulnKey: key,
|
|
86
|
+
location,
|
|
87
|
+
...(parameter === undefined ? {} : { parameter }),
|
|
88
|
+
}),
|
|
89
|
+
fingerprintVersion: FINGERPRINT_VERSION,
|
|
90
|
+
title: name,
|
|
91
|
+
...(background === undefined ? {} : { description: stripHtml(background) }),
|
|
92
|
+
detectedSeverity: BURP_SEVERITY[childText(issue, 'severity')?.toLowerCase() ?? ''] ?? 'advisory',
|
|
93
|
+
severitySource: 'engine_default',
|
|
94
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
95
|
+
vulnKey: key,
|
|
96
|
+
location,
|
|
97
|
+
...(parameter === undefined ? {} : { parameter }),
|
|
98
|
+
...(remediation === undefined ? {} : { recommendation: stripHtml(remediation) }),
|
|
99
|
+
sourceEngine: 'burp',
|
|
100
|
+
sourceRuleId: type,
|
|
101
|
+
...(confidence === undefined ? {} : { confidence }),
|
|
102
|
+
createdAt: options.now,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return findings;
|
|
106
|
+
}
|
|
107
|
+
/** Burp's prose fields are HTML inside CDATA. */
|
|
108
|
+
function stripHtml(value) {
|
|
109
|
+
return value
|
|
110
|
+
.replace(/<[^>]*>/gu, ' ')
|
|
111
|
+
.replace(/\s+/gu, ' ')
|
|
112
|
+
.trim();
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=burp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"burp.js","sourceRoot":"","sources":["../../src/import/burp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Finding } from '../finding.js';
|
|
2
|
+
import type { Severity } from '../severity.js';
|
|
3
|
+
import type { ImportOptions } from './nuclei.js';
|
|
4
|
+
/**
|
|
5
|
+
* One finding in the generic Secureport format.
|
|
6
|
+
*
|
|
7
|
+
* **Written last, on purpose.** This is the shape the four scanner importers
|
|
8
|
+
* turned out to have in common, rather than a format designed in advance and
|
|
9
|
+
* then argued with. Everything an engine reliably supplies is here; everything
|
|
10
|
+
* only one of them had is not.
|
|
11
|
+
*
|
|
12
|
+
* Only `title` and `location` are required. A tool that knows nothing but "this
|
|
13
|
+
* is wrong, and it is here" can still produce a trackable finding — which is the
|
|
14
|
+
* point of having a generic format at all.
|
|
15
|
+
*/
|
|
16
|
+
export interface GenericFinding {
|
|
17
|
+
/** What is wrong. */
|
|
18
|
+
readonly title: string;
|
|
19
|
+
/** Where it is — a URL, a host, a `host:port`, or a file path. */
|
|
20
|
+
readonly location: string;
|
|
21
|
+
/** Fuller explanation. */
|
|
22
|
+
readonly description?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Severity, if the tool rates it.
|
|
25
|
+
*
|
|
26
|
+
* Ignored when `cvssScore` is present: a score is more precise than a word,
|
|
27
|
+
* and `severitySource` records which was used.
|
|
28
|
+
*/
|
|
29
|
+
readonly severity?: Severity;
|
|
30
|
+
/** CVSS base score, 0–10. Preferred over `severity` when both are given. */
|
|
31
|
+
readonly cvssScore?: number;
|
|
32
|
+
/** CVSS vector string. */
|
|
33
|
+
readonly cvssVector?: string;
|
|
34
|
+
/** CWE identifier, with or without the `CWE-` prefix. */
|
|
35
|
+
readonly cwe?: string;
|
|
36
|
+
/** CVE identifier. */
|
|
37
|
+
readonly cve?: string;
|
|
38
|
+
/** Broad grouping, e.g. `injection`. */
|
|
39
|
+
readonly category?: string;
|
|
40
|
+
/** The parameter implicated, where the weakness has one. */
|
|
41
|
+
readonly parameter?: string;
|
|
42
|
+
/** The port, for findings about a service rather than a path. */
|
|
43
|
+
readonly port?: number;
|
|
44
|
+
/** What produced it. Defaults to `generic`. */
|
|
45
|
+
readonly engine?: string;
|
|
46
|
+
/** The producing tool's own identifier for the rule. */
|
|
47
|
+
readonly ruleId?: string;
|
|
48
|
+
/** What to do about it. */
|
|
49
|
+
readonly recommendation?: string;
|
|
50
|
+
/** Further reading. */
|
|
51
|
+
readonly references?: readonly string[];
|
|
52
|
+
/** Pointers to stored evidence. */
|
|
53
|
+
readonly evidenceUri?: readonly string[];
|
|
54
|
+
/** The tool's confidence, 0–1. */
|
|
55
|
+
readonly confidence?: number;
|
|
56
|
+
}
|
|
57
|
+
/** A generic findings document. */
|
|
58
|
+
export interface GenericDocument {
|
|
59
|
+
/**
|
|
60
|
+
* Format version.
|
|
61
|
+
*
|
|
62
|
+
* Present so this file can change shape later without guessing. An unknown
|
|
63
|
+
* version is refused rather than parsed optimistically.
|
|
64
|
+
*/
|
|
65
|
+
readonly version: 1;
|
|
66
|
+
/** The findings. */
|
|
67
|
+
readonly findings: readonly GenericFinding[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Turns the generic Secureport format into {@link Finding}s.
|
|
71
|
+
*
|
|
72
|
+
* The escape hatch for everything with no importer of its own: a manual test, an
|
|
73
|
+
* internal tool, a scanner nobody has written a parser for. Fingerprinting,
|
|
74
|
+
* reconciliation and reports then work identically — a finding that arrives this
|
|
75
|
+
* way is not a second-class finding.
|
|
76
|
+
*
|
|
77
|
+
* Refuses the document rather than salvaging part of it. Unlike Nuclei's JSONL,
|
|
78
|
+
* where one bad line costs one record, this is a single document a human or a
|
|
79
|
+
* script wrote deliberately: a field in the wrong shape is a mistake worth
|
|
80
|
+
* hearing about, not one to route around silently.
|
|
81
|
+
*
|
|
82
|
+
* @param json - The contents of a generic findings document.
|
|
83
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
84
|
+
* @returns One finding per entry, in document order.
|
|
85
|
+
* @throws SyntaxError If the text is not valid JSON.
|
|
86
|
+
* @throws TypeError If the document is not the expected shape, naming the entry
|
|
87
|
+
* and field at fault.
|
|
88
|
+
*/
|
|
89
|
+
export declare function importGeneric(json: string, options: ImportOptions): Finding[];
|
|
90
|
+
//# sourceMappingURL=generic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic.d.ts","sourceRoot":"","sources":["../../src/import/generic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,gBAAgB,CAAC;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B,qBAAqB;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAE7B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,0BAA0B;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAE7B,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEtB,sBAAsB;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEtB,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEvB,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,wDAAwD;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,2BAA2B;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAEjC,uBAAuB;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAExC,mCAAmC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzC,kCAAkC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,mCAAmC;AACnC,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpB,oBAAoB;IACpB,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C;AAuCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,EAAE,CA8G7E"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { SEVERITY_ORDER, severityFromCvss } from '../severity.js';
|
|
2
|
+
import { fingerprint, vulnKey, FINGERPRINT_VERSION } from '../fingerprint.js';
|
|
3
|
+
const SEVERITIES = new Set(SEVERITY_ORDER);
|
|
4
|
+
/** A non-empty trimmed string, or nothing. */
|
|
5
|
+
const str = (v) => typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined;
|
|
6
|
+
/** A finite number, or nothing. */
|
|
7
|
+
const num = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
8
|
+
/**
|
|
9
|
+
* A title reduced to something usable as a weakness key.
|
|
10
|
+
*
|
|
11
|
+
* The last resort, and only for the generic format. A tool that reports nothing
|
|
12
|
+
* but "this is wrong, and it is here" has no CWE, no category and no rule id —
|
|
13
|
+
* and `vulnKey` would rightly refuse, because there is nothing to key on.
|
|
14
|
+
* Refusing would mean the minimal case cannot be imported at all, which defeats
|
|
15
|
+
* the point of having a generic format.
|
|
16
|
+
*
|
|
17
|
+
* **Titles are a poor key and this is not pretending otherwise:** reword the
|
|
18
|
+
* title and the issue becomes a different issue, and two tools describing the
|
|
19
|
+
* same weakness differently never collide. Supplying a `ruleId`, a `cwe` or a
|
|
20
|
+
* `category` is strictly better and all three are preferred over this.
|
|
21
|
+
*/
|
|
22
|
+
const titleKey = (title) => title
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[^a-z0-9]+/gu, '-')
|
|
25
|
+
.replace(/^-|-$/gu, '');
|
|
26
|
+
/** An array of non-empty strings, or nothing. */
|
|
27
|
+
const strArray = (v) => {
|
|
28
|
+
if (!Array.isArray(v))
|
|
29
|
+
return undefined;
|
|
30
|
+
const items = v.map(str).filter((x) => x !== undefined);
|
|
31
|
+
return items.length > 0 ? items : undefined;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Turns the generic Secureport format into {@link Finding}s.
|
|
35
|
+
*
|
|
36
|
+
* The escape hatch for everything with no importer of its own: a manual test, an
|
|
37
|
+
* internal tool, a scanner nobody has written a parser for. Fingerprinting,
|
|
38
|
+
* reconciliation and reports then work identically — a finding that arrives this
|
|
39
|
+
* way is not a second-class finding.
|
|
40
|
+
*
|
|
41
|
+
* Refuses the document rather than salvaging part of it. Unlike Nuclei's JSONL,
|
|
42
|
+
* where one bad line costs one record, this is a single document a human or a
|
|
43
|
+
* script wrote deliberately: a field in the wrong shape is a mistake worth
|
|
44
|
+
* hearing about, not one to route around silently.
|
|
45
|
+
*
|
|
46
|
+
* @param json - The contents of a generic findings document.
|
|
47
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
48
|
+
* @returns One finding per entry, in document order.
|
|
49
|
+
* @throws SyntaxError If the text is not valid JSON.
|
|
50
|
+
* @throws TypeError If the document is not the expected shape, naming the entry
|
|
51
|
+
* and field at fault.
|
|
52
|
+
*/
|
|
53
|
+
export function importGeneric(json, options) {
|
|
54
|
+
const doc = JSON.parse(json);
|
|
55
|
+
if (typeof doc !== 'object' || doc === null) {
|
|
56
|
+
throw new TypeError('generic findings document must be an object');
|
|
57
|
+
}
|
|
58
|
+
const record = doc;
|
|
59
|
+
if (record['version'] !== 1) {
|
|
60
|
+
throw new TypeError(`unsupported generic findings version: ${String(record['version'])}`);
|
|
61
|
+
}
|
|
62
|
+
const raw = record['findings'];
|
|
63
|
+
if (!Array.isArray(raw)) {
|
|
64
|
+
throw new TypeError('generic findings document has no `findings` array');
|
|
65
|
+
}
|
|
66
|
+
return raw.map((item, index) => {
|
|
67
|
+
const where = `findings[${String(index)}]`;
|
|
68
|
+
if (typeof item !== 'object' || item === null) {
|
|
69
|
+
throw new TypeError(`${where} is not an object`);
|
|
70
|
+
}
|
|
71
|
+
const entry = item;
|
|
72
|
+
const title = str(entry['title']);
|
|
73
|
+
const location = str(entry['location']);
|
|
74
|
+
if (title === undefined)
|
|
75
|
+
throw new TypeError(`${where}.title is required`);
|
|
76
|
+
if (location === undefined)
|
|
77
|
+
throw new TypeError(`${where}.location is required`);
|
|
78
|
+
const stated = str(entry['severity']);
|
|
79
|
+
if (stated !== undefined && !SEVERITIES.has(stated)) {
|
|
80
|
+
throw new TypeError(`${where}.severity is not a severity: ${stated}`);
|
|
81
|
+
}
|
|
82
|
+
const score = num(entry['cvssScore']);
|
|
83
|
+
let detectedSeverity;
|
|
84
|
+
let severitySource;
|
|
85
|
+
if (score !== undefined && score >= 0 && score <= 10) {
|
|
86
|
+
detectedSeverity = severityFromCvss(score);
|
|
87
|
+
severitySource = 'cvss';
|
|
88
|
+
}
|
|
89
|
+
else if (stated !== undefined) {
|
|
90
|
+
detectedSeverity = stated;
|
|
91
|
+
// Somebody stated it for this finding specifically, which is the
|
|
92
|
+
// strongest provenance there is.
|
|
93
|
+
severitySource = 'explicit';
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
detectedSeverity = 'advisory';
|
|
97
|
+
severitySource = 'engine_default';
|
|
98
|
+
}
|
|
99
|
+
const engine = str(entry['engine']) ?? 'generic';
|
|
100
|
+
const ruleId = str(entry['ruleId']);
|
|
101
|
+
const rawCwe = str(entry['cwe']);
|
|
102
|
+
const cwe = rawCwe === undefined ? undefined : `CWE-${rawCwe.replace(/^CWE-/iu, '')}`;
|
|
103
|
+
const category = str(entry['category']);
|
|
104
|
+
const parameter = str(entry['parameter']);
|
|
105
|
+
const port = num(entry['port']);
|
|
106
|
+
// Nothing classifies this finding, so key on the title rather than refuse
|
|
107
|
+
// it. `sourceRuleId` stays absent: the tool supplied none, and recording a
|
|
108
|
+
// slug as though it had would be a lie about provenance.
|
|
109
|
+
const key = cwe === undefined && category === undefined && ruleId === undefined
|
|
110
|
+
? `${engine}:${titleKey(title)}`
|
|
111
|
+
: vulnKey({
|
|
112
|
+
sourceEngine: engine,
|
|
113
|
+
...(ruleId === undefined ? {} : { sourceRuleId: ruleId }),
|
|
114
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
115
|
+
...(category === undefined ? {} : { category }),
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
id: options.newId(),
|
|
119
|
+
orgId: options.orgId,
|
|
120
|
+
runId: options.runId,
|
|
121
|
+
fingerprint: fingerprint({
|
|
122
|
+
targetId: options.targetId,
|
|
123
|
+
vulnKey: key,
|
|
124
|
+
location,
|
|
125
|
+
...(parameter === undefined ? {} : { parameter }),
|
|
126
|
+
...(port === undefined ? {} : { port }),
|
|
127
|
+
}),
|
|
128
|
+
fingerprintVersion: FINGERPRINT_VERSION,
|
|
129
|
+
title,
|
|
130
|
+
...(str(entry['description']) === undefined
|
|
131
|
+
? {}
|
|
132
|
+
: { description: str(entry['description']) }),
|
|
133
|
+
detectedSeverity,
|
|
134
|
+
severitySource,
|
|
135
|
+
...(score === undefined ? {} : { cvssScore: score }),
|
|
136
|
+
...(str(entry['cvssVector']) === undefined ? {} : { cvssVector: str(entry['cvssVector']) }),
|
|
137
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
138
|
+
...(str(entry['cve']) === undefined ? {} : { cve: str(entry['cve']).toUpperCase() }),
|
|
139
|
+
vulnKey: key,
|
|
140
|
+
...(category === undefined ? {} : { category: category.toLowerCase() }),
|
|
141
|
+
location,
|
|
142
|
+
...(parameter === undefined ? {} : { parameter }),
|
|
143
|
+
...(strArray(entry['evidenceUri']) === undefined
|
|
144
|
+
? {}
|
|
145
|
+
: { evidenceUri: strArray(entry['evidenceUri']) }),
|
|
146
|
+
...(str(entry['recommendation']) === undefined
|
|
147
|
+
? {}
|
|
148
|
+
: { recommendation: str(entry['recommendation']) }),
|
|
149
|
+
...(strArray(entry['references']) === undefined
|
|
150
|
+
? {}
|
|
151
|
+
: { references: strArray(entry['references']) }),
|
|
152
|
+
sourceEngine: engine,
|
|
153
|
+
...(ruleId === undefined ? {} : { sourceRuleId: ruleId }),
|
|
154
|
+
...(num(entry['confidence']) === undefined ? {} : { confidence: num(entry['confidence']) }),
|
|
155
|
+
createdAt: options.now,
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=generic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic.js","sourceRoot":"","sources":["../../src/import/generic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Finding } from '../finding.js';
|
|
2
|
+
import type { ImportOptions } from './nuclei.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turns a `.nessus` export into {@link Finding}s.
|
|
5
|
+
*
|
|
6
|
+
* Nessus is host-oriented: every `ReportItem` hangs off a `ReportHost`, and the
|
|
7
|
+
* port and protocol are attributes rather than part of a URL. The location is
|
|
8
|
+
* assembled as `host:port`, which is **the only place the port survives** —
|
|
9
|
+
* `00-DOMAIN.md` §3 defines no `port` on a finding, so anything not in the
|
|
10
|
+
* location is lost. A network finding with no path still fingerprints
|
|
11
|
+
* distinctly per service because the port is inside its location.
|
|
12
|
+
*
|
|
13
|
+
* A consequence worth knowing: the fingerprint's last component is
|
|
14
|
+
* `parameter ?? port ?? ''`, so a Nessus port is counted twice — once inside
|
|
15
|
+
* the location and once as that component. Harmless, but it means a port
|
|
16
|
+
* cannot be recovered from a finding except by parsing its location. Recorded
|
|
17
|
+
* as backlog **B42**.
|
|
18
|
+
*
|
|
19
|
+
* Severity comes from the CVSS v3 base score where Nessus supplies one, and from
|
|
20
|
+
* its own numeric rating otherwise.
|
|
21
|
+
*
|
|
22
|
+
* A `ReportItem` with severity `0` and no port — Nessus's host-inventory
|
|
23
|
+
* plugins — still imports: an advisory is still a finding, and dropping it here
|
|
24
|
+
* would make coverage look narrower than it was.
|
|
25
|
+
*
|
|
26
|
+
* @param xml - The contents of a `.nessus` file.
|
|
27
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
28
|
+
* @returns One finding per report item, in document order.
|
|
29
|
+
* @throws SyntaxError If the document is not well-formed XML.
|
|
30
|
+
*/
|
|
31
|
+
export declare function importNessus(xml: string, options: ImportOptions): Finding[];
|
|
32
|
+
//# sourceMappingURL=nessus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nessus.d.ts","sourceRoot":"","sources":["../../src/import/nessus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAK7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAgBjD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,EAAE,CAsF3E"}
|