@secureport/core 0.3.0 → 1.0.0-rc.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 +116 -11
- 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/reconcile.d.ts.map +1 -1
- package/dist/reconcile.js +35 -0
- package/dist/reconcile.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/reconcile.ts +37 -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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { SEVERITY_ORDER } from '../severity.js';
|
|
2
|
+
/** Works out what a run established about the issues carried into it. */
|
|
3
|
+
function describeRetest(snapshot) {
|
|
4
|
+
const { baseline } = snapshot;
|
|
5
|
+
if (baseline === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
const entries = snapshot.issues.map((issue) => ({
|
|
8
|
+
issue,
|
|
9
|
+
verdict: verdictFor(issue),
|
|
10
|
+
}));
|
|
11
|
+
// `SEVERITY_ORDER` rather than `severityRank`, which counts the other way:
|
|
12
|
+
// it returns 4 for `critical` so that a larger number is a worse problem.
|
|
13
|
+
// Sorting on it ascending put the advisories at the top of the table, which
|
|
14
|
+
// is how this shipped until a golden file showed it.
|
|
15
|
+
entries.sort((a, b) => {
|
|
16
|
+
const bySeverity = SEVERITY_ORDER.indexOf(a.issue.issue.effectiveSeverity) -
|
|
17
|
+
SEVERITY_ORDER.indexOf(b.issue.issue.effectiveSeverity);
|
|
18
|
+
return bySeverity === 0
|
|
19
|
+
? b.issue.issue.lastSeen.getTime() - a.issue.issue.lastSeen.getTime()
|
|
20
|
+
: bySeverity;
|
|
21
|
+
});
|
|
22
|
+
const counts = {
|
|
23
|
+
fixed: 0,
|
|
24
|
+
still_present: 0,
|
|
25
|
+
returned: 0,
|
|
26
|
+
not_retested: 0,
|
|
27
|
+
new: 0,
|
|
28
|
+
};
|
|
29
|
+
for (const entry of entries)
|
|
30
|
+
counts[entry.verdict]++;
|
|
31
|
+
const checkable = counts.fixed + counts.still_present + counts.returned;
|
|
32
|
+
return {
|
|
33
|
+
baseline,
|
|
34
|
+
entries,
|
|
35
|
+
counts,
|
|
36
|
+
fixRate: checkable === 0 ? null : counts.fixed / checkable,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The verdict for one issue.
|
|
41
|
+
*
|
|
42
|
+
* Turns on whether this run produced evidence for it, not on its status alone:
|
|
43
|
+
* an issue left open because the run never covered it looks identical to one
|
|
44
|
+
* left open because the run found it again, and telling a reader those are the
|
|
45
|
+
* same thing is the failure this report exists to avoid.
|
|
46
|
+
*/
|
|
47
|
+
function verdictFor(entry) {
|
|
48
|
+
if (entry.change === 'resolved')
|
|
49
|
+
return 'fixed';
|
|
50
|
+
if (entry.change === 'regressed')
|
|
51
|
+
return 'returned';
|
|
52
|
+
if (entry.change === 'new')
|
|
53
|
+
return 'new';
|
|
54
|
+
return entry.findings.length > 0 ? 'still_present' : 'not_retested';
|
|
55
|
+
}
|
|
56
|
+
/** Works out what a report cannot establish, from the run rather than a template. */
|
|
57
|
+
function describeLimitations(snapshot, basis) {
|
|
58
|
+
const limitations = [];
|
|
59
|
+
if (!basis.includesManual) {
|
|
60
|
+
limitations.push('This is evidence of automated security testing. It is not a human-led penetration ' +
|
|
61
|
+
'test and does not carry the assurance of one.');
|
|
62
|
+
}
|
|
63
|
+
limitations.push('Findings should be verified by a qualified technician before this document is relied ' +
|
|
64
|
+
'upon as audit evidence.');
|
|
65
|
+
limitations.push(`Testing covered only ${snapshot.run.coverage.paths.join(', ')}. Anything outside that ` +
|
|
66
|
+
'was not examined, and its absence from this document is not evidence that it is sound.');
|
|
67
|
+
limitations.push(`This reflects the state of the target as of ${snapshot.run.createdAt
|
|
68
|
+
.toISOString()
|
|
69
|
+
.slice(0, 10)}. It says nothing about the target before or after that date.`);
|
|
70
|
+
limitations.push('Automated testing cannot establish the absence of a vulnerability. A clean result means ' +
|
|
71
|
+
'nothing was detected, not that nothing is there.');
|
|
72
|
+
if (snapshot.suppressed.length > 0) {
|
|
73
|
+
limitations.push(`${snapshot.suppressed.length} finding${snapshot.suppressed.length === 1 ? ' has' : 's have'} been ` +
|
|
74
|
+
'suppressed and excluded from the counts above. They are listed in full in the appendix.');
|
|
75
|
+
}
|
|
76
|
+
limitations.push('This document does not certify compliance with any standard, framework or regulation.');
|
|
77
|
+
return limitations;
|
|
78
|
+
}
|
|
79
|
+
const DEFAULT_TITLES = Object.freeze({
|
|
80
|
+
pen: 'Penetration Test Report',
|
|
81
|
+
vap: 'Vulnerability Assessment Report',
|
|
82
|
+
exec: 'Executive Summary',
|
|
83
|
+
attest: 'Attestation of Security Testing',
|
|
84
|
+
retest: 'Retest Report',
|
|
85
|
+
});
|
|
86
|
+
/** Works out how the findings were produced, from the run rather than a claim. */
|
|
87
|
+
function describeBasis(snapshot) {
|
|
88
|
+
const kind = snapshot.run.kind;
|
|
89
|
+
const engines = snapshot.run.engines;
|
|
90
|
+
const includesManual = snapshot.issues.some((i) => i.issue.origin === 'manual');
|
|
91
|
+
const automated = kind === 'scan';
|
|
92
|
+
const uploaded = kind === 'upload';
|
|
93
|
+
const parts = [];
|
|
94
|
+
if (automated) {
|
|
95
|
+
parts.push('Findings were produced by automated vulnerability scanning against the scope described below.');
|
|
96
|
+
}
|
|
97
|
+
if (uploaded) {
|
|
98
|
+
parts.push('Findings were imported from an external testing tool and reconciled against previous runs.');
|
|
99
|
+
}
|
|
100
|
+
if (kind === 'manual')
|
|
101
|
+
parts.push('Findings were recorded manually by a tester.');
|
|
102
|
+
if (includesManual && kind !== 'manual') {
|
|
103
|
+
parts.push('Some findings were recorded manually by a tester.');
|
|
104
|
+
}
|
|
105
|
+
if (!includesManual && kind !== 'manual') {
|
|
106
|
+
parts.push('No part of this assessment constitutes a manual penetration test.');
|
|
107
|
+
}
|
|
108
|
+
if (automated || uploaded) {
|
|
109
|
+
parts.push('Automated findings should be verified by a qualified technician before this report is ' +
|
|
110
|
+
'submitted as audit evidence.');
|
|
111
|
+
}
|
|
112
|
+
return { includesManual, automated, uploaded, engines, statement: parts.join(' ') };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Derives everything a report shows, once, from a snapshot.
|
|
116
|
+
*
|
|
117
|
+
* Both renderers consume this rather than the snapshot, so a number in the
|
|
118
|
+
* Markdown and the same number in the HTML cannot drift apart — they are the
|
|
119
|
+
* same value formatted twice.
|
|
120
|
+
*
|
|
121
|
+
* Suppressed issues are already separated by the snapshot and stay separated
|
|
122
|
+
* here: they never reach {@link ReportModel.sections}, are excluded from
|
|
123
|
+
* {@link ReportModel.outstanding} and from the exposure score, and appear only
|
|
124
|
+
* in the suppressed appendix (invariant 7).
|
|
125
|
+
*
|
|
126
|
+
* @param snapshot - Issue state as of a run.
|
|
127
|
+
* @param options - Title, attribution, and the render time.
|
|
128
|
+
* @returns The model both renderers format.
|
|
129
|
+
*/
|
|
130
|
+
export function buildReportModel(snapshot, options) {
|
|
131
|
+
const outstanding = {
|
|
132
|
+
critical: 0,
|
|
133
|
+
high: 0,
|
|
134
|
+
medium: 0,
|
|
135
|
+
low: 0,
|
|
136
|
+
advisory: 0,
|
|
137
|
+
};
|
|
138
|
+
const bySeverity = new Map();
|
|
139
|
+
const resolved = [];
|
|
140
|
+
let breached = 0;
|
|
141
|
+
for (const entry of snapshot.issues) {
|
|
142
|
+
if (entry.issue.status === 'resolved') {
|
|
143
|
+
resolved.push(entry);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const severity = entry.issue.effectiveSeverity;
|
|
147
|
+
const group = bySeverity.get(severity);
|
|
148
|
+
if (group)
|
|
149
|
+
group.push(entry);
|
|
150
|
+
else
|
|
151
|
+
bySeverity.set(severity, [entry]);
|
|
152
|
+
if (entry.issue.status === 'open' || entry.issue.status === 'regressed') {
|
|
153
|
+
outstanding[severity]++;
|
|
154
|
+
if (entry.slaStatus === 'breached')
|
|
155
|
+
breached++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
resolved.sort((a, b) => b.issue.lastSeen.getTime() - a.issue.lastSeen.getTime());
|
|
159
|
+
const sections = [];
|
|
160
|
+
for (const severity of SEVERITY_ORDER) {
|
|
161
|
+
const issues = bySeverity.get(severity);
|
|
162
|
+
if (issues === undefined || issues.length === 0)
|
|
163
|
+
continue;
|
|
164
|
+
sections.push({
|
|
165
|
+
severity,
|
|
166
|
+
// Most recently seen first: what a reader wants at the top of a section
|
|
167
|
+
// is what the latest run actually found.
|
|
168
|
+
issues: [...issues].sort((a, b) => b.issue.lastSeen.getTime() - a.issue.lastSeen.getTime()),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const outstandingTotal = SEVERITY_ORDER.reduce((sum, s) => sum + outstanding[s], 0);
|
|
172
|
+
const basis = describeBasis(snapshot);
|
|
173
|
+
const retest = describeRetest(snapshot);
|
|
174
|
+
// Rendering a retest of nothing is the over-claim §7 warns about: the
|
|
175
|
+
// document's whole content is a comparison, and one run does not have one.
|
|
176
|
+
if (options.kind === 'retest' && retest === undefined) {
|
|
177
|
+
throw new TypeError('a retest report needs a baseline: build the snapshot with `previous` set to the run being retested');
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
kind: options.kind,
|
|
181
|
+
title: options.title ?? DEFAULT_TITLES[options.kind],
|
|
182
|
+
snapshot,
|
|
183
|
+
generatedAt: options.now,
|
|
184
|
+
...(options.preparedBy === undefined ? {} : { preparedBy: options.preparedBy }),
|
|
185
|
+
...(options.preparedFor === undefined ? {} : { preparedFor: options.preparedFor }),
|
|
186
|
+
basis,
|
|
187
|
+
limitations: describeLimitations(snapshot, basis),
|
|
188
|
+
sections,
|
|
189
|
+
resolved,
|
|
190
|
+
outstanding,
|
|
191
|
+
outstandingTotal,
|
|
192
|
+
exposureScore: snapshot.run.exposureScore,
|
|
193
|
+
breached,
|
|
194
|
+
...(retest === undefined ? {} : { retest }),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/report/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -62,9 +62,17 @@ export declare function buildSnapshot(input: BuildSnapshotInput): Snapshot;
|
|
|
62
62
|
*
|
|
63
63
|
* Use this rather than `JSON.parse` for any snapshot read back from disk.
|
|
64
64
|
*
|
|
65
|
+
* **It checks the shape, because this is where untrusted JSON enters.** A
|
|
66
|
+
* snapshot arrives from a file somebody chose, and the signature promises a
|
|
67
|
+
* `Snapshot` — so returning whatever parsed and letting the caller discover
|
|
68
|
+
* the difference is the function lying about its own return type. The check is
|
|
69
|
+
* deliberately shallow: the top-level shape, which is enough to tell a
|
|
70
|
+
* snapshot from any other JSON document and to make the failure say so.
|
|
71
|
+
*
|
|
65
72
|
* @param json - The contents of a snapshot file.
|
|
66
73
|
* @returns The snapshot, with dates revived.
|
|
67
74
|
* @throws SyntaxError If the text is not valid JSON.
|
|
75
|
+
* @throws TypeError If the JSON parsed but is not a snapshot.
|
|
68
76
|
*/
|
|
69
77
|
export declare function parseSnapshot(json: string): Snapshot;
|
|
70
78
|
//# sourceMappingURL=snapshot-builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-builder.d.ts","sourceRoot":"","sources":["../src/snapshot-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAe,QAAQ,EAAkC,MAAM,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAG9E;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAElB,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IAEtC;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAE7B,oCAAoC;IACpC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IAEnB,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC;IAE7B,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAE/B,4DAA4D;IAC5D,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAEvC,6BAA6B;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAuBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,QAAQ,CAyEjE;AAiBD
|
|
1
|
+
{"version":3,"file":"snapshot-builder.d.ts","sourceRoot":"","sources":["../src/snapshot-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAe,QAAQ,EAAkC,MAAM,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAG9E;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAElB,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IAEtC;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAE7B,oCAAoC;IACpC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IAEnB,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC;IAE7B,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAE/B,4DAA4D;IAC5D,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAEvC,6BAA6B;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAuBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,QAAQ,CAyEjE;AAiBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CA0BpD"}
|
package/dist/snapshot-builder.js
CHANGED
|
@@ -131,12 +131,20 @@ const DATE_FIELDS = new Set([
|
|
|
131
131
|
*
|
|
132
132
|
* Use this rather than `JSON.parse` for any snapshot read back from disk.
|
|
133
133
|
*
|
|
134
|
+
* **It checks the shape, because this is where untrusted JSON enters.** A
|
|
135
|
+
* snapshot arrives from a file somebody chose, and the signature promises a
|
|
136
|
+
* `Snapshot` — so returning whatever parsed and letting the caller discover
|
|
137
|
+
* the difference is the function lying about its own return type. The check is
|
|
138
|
+
* deliberately shallow: the top-level shape, which is enough to tell a
|
|
139
|
+
* snapshot from any other JSON document and to make the failure say so.
|
|
140
|
+
*
|
|
134
141
|
* @param json - The contents of a snapshot file.
|
|
135
142
|
* @returns The snapshot, with dates revived.
|
|
136
143
|
* @throws SyntaxError If the text is not valid JSON.
|
|
144
|
+
* @throws TypeError If the JSON parsed but is not a snapshot.
|
|
137
145
|
*/
|
|
138
146
|
export function parseSnapshot(json) {
|
|
139
|
-
|
|
147
|
+
const parsed = JSON.parse(json, (key, value) => {
|
|
140
148
|
if (DATE_FIELDS.has(key) && typeof value === 'string') {
|
|
141
149
|
const parsed = new Date(value);
|
|
142
150
|
if (!Number.isNaN(parsed.getTime()))
|
|
@@ -144,5 +152,21 @@ export function parseSnapshot(json) {
|
|
|
144
152
|
}
|
|
145
153
|
return value;
|
|
146
154
|
});
|
|
155
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
156
|
+
throw new TypeError('not a snapshot: expected a JSON object');
|
|
157
|
+
}
|
|
158
|
+
const record = parsed;
|
|
159
|
+
for (const field of ['run', 'target']) {
|
|
160
|
+
const value = record[field];
|
|
161
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
162
|
+
throw new TypeError(`not a snapshot: \`${field}\` is missing`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const field of ['issues', 'suppressed']) {
|
|
166
|
+
if (!Array.isArray(record[field])) {
|
|
167
|
+
throw new TypeError(`not a snapshot: \`${field}\` is missing`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return parsed;
|
|
147
171
|
}
|
|
148
172
|
//# sourceMappingURL=snapshot-builder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-builder.js","sourceRoot":"","sources":["../src/snapshot-builder.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"snapshot-builder.js","sourceRoot":"","sources":["../src/snapshot-builder.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@secureport/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-rc.0",
|
|
4
4
|
"description": "Shared domain model for Secureport. Imported by the hosted API the same way a third party would.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"security",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"nessus",
|
|
19
19
|
"security-reporting"
|
|
20
20
|
],
|
|
21
|
-
"homepage": "https://
|
|
21
|
+
"homepage": "https://secureport.io/",
|
|
22
22
|
"bugs": {
|
|
23
|
-
"
|
|
23
|
+
"email": "hello@secureport.io"
|
|
24
24
|
},
|
|
25
25
|
"type": "module",
|
|
26
26
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Finding } from '../finding.js';
|
|
2
|
+
import type { Severity } from '../severity.js';
|
|
3
|
+
import { fingerprint, vulnKey, FINGERPRINT_VERSION } from '../fingerprint.js';
|
|
4
|
+
import { childText, findAll, parseXml } from './xml.js';
|
|
5
|
+
import type { ImportOptions } from './nuclei.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Burp's severity vocabulary, mapped onto {@link Severity}.
|
|
9
|
+
*
|
|
10
|
+
* **Burp has no "critical" either.** Like ZAP it tops out at High, and
|
|
11
|
+
* `Information` is the bottom of the scale rather than a separate category.
|
|
12
|
+
*/
|
|
13
|
+
const BURP_SEVERITY: Readonly<Record<string, Severity>> = Object.freeze({
|
|
14
|
+
high: 'high',
|
|
15
|
+
medium: 'medium',
|
|
16
|
+
low: 'low',
|
|
17
|
+
information: 'advisory',
|
|
18
|
+
info: 'advisory',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Burp reports confidence in words. Mapped to a number so the model does not
|
|
23
|
+
* have to carry a second vocabulary for it.
|
|
24
|
+
*/
|
|
25
|
+
const BURP_CONFIDENCE: Readonly<Record<string, number>> = Object.freeze({
|
|
26
|
+
certain: 1,
|
|
27
|
+
firm: 0.7,
|
|
28
|
+
tentative: 0.4,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Burp writes the affected parameter into the location, not a field of its own.
|
|
33
|
+
*
|
|
34
|
+
* `/search [q parameter]` means the `q` parameter of `/search`. Pulling it out
|
|
35
|
+
* matters: the parameter is part of the fingerprint, so leaving it embedded in
|
|
36
|
+
* the path would make Burp's finding a different issue from ZAP's finding of the
|
|
37
|
+
* same weakness in the same place.
|
|
38
|
+
*/
|
|
39
|
+
function splitLocation(location: string): { path: string; parameter?: string } {
|
|
40
|
+
const match = /^(.*?)\s*\[\s*(.+?)\s+parameter\s*\]\s*$/iu.exec(location);
|
|
41
|
+
if (match) return { path: match[1].trim(), parameter: match[2].trim() };
|
|
42
|
+
return { path: location.trim() };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Turns a Burp Suite XML export into {@link Finding}s.
|
|
47
|
+
*
|
|
48
|
+
* Burp exports a flat `<issues>` document, one `<issue>` per detection, with the
|
|
49
|
+
* host and path in separate elements — so the location is assembled rather than
|
|
50
|
+
* read. Prose fields arrive as CDATA containing HTML, which is stripped.
|
|
51
|
+
*
|
|
52
|
+
* `<type>` is Burp's numeric issue type and is used as the rule id, because it
|
|
53
|
+
* is stable across versions where the human-readable `<name>` is not.
|
|
54
|
+
*
|
|
55
|
+
* @param xml - The contents of a Burp XML export.
|
|
56
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
57
|
+
* @returns One finding per issue, in document order.
|
|
58
|
+
* @throws SyntaxError If the document is not well-formed XML.
|
|
59
|
+
*/
|
|
60
|
+
export function importBurp(xml: string, options: ImportOptions): Finding[] {
|
|
61
|
+
const root = parseXml(xml);
|
|
62
|
+
const findings: Finding[] = [];
|
|
63
|
+
|
|
64
|
+
for (const issue of findAll(root, 'issue')) {
|
|
65
|
+
const name = childText(issue, 'name');
|
|
66
|
+
const type = childText(issue, 'type');
|
|
67
|
+
if (name === undefined || type === undefined) continue;
|
|
68
|
+
|
|
69
|
+
const host = childText(issue, 'host') ?? '';
|
|
70
|
+
const raw = childText(issue, 'location') ?? childText(issue, 'path') ?? '';
|
|
71
|
+
const { path, parameter } = splitLocation(raw);
|
|
72
|
+
const location = path.startsWith('http') ? path : `${host}${path}`;
|
|
73
|
+
if (location === '') continue;
|
|
74
|
+
|
|
75
|
+
const cwe = childText(issue, 'vulnerabilityClassifications')
|
|
76
|
+
?.match(/CWE-\d+/iu)?.[0]
|
|
77
|
+
?.toUpperCase();
|
|
78
|
+
|
|
79
|
+
const key = vulnKey({
|
|
80
|
+
sourceEngine: 'burp',
|
|
81
|
+
sourceRuleId: type,
|
|
82
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const confidence = BURP_CONFIDENCE[childText(issue, 'confidence')?.toLowerCase() ?? ''];
|
|
86
|
+
const background = childText(issue, 'issueBackground');
|
|
87
|
+
const remediation = childText(issue, 'remediationBackground');
|
|
88
|
+
|
|
89
|
+
findings.push({
|
|
90
|
+
id: options.newId(),
|
|
91
|
+
orgId: options.orgId,
|
|
92
|
+
runId: options.runId,
|
|
93
|
+
fingerprint: fingerprint({
|
|
94
|
+
targetId: options.targetId,
|
|
95
|
+
vulnKey: key,
|
|
96
|
+
location,
|
|
97
|
+
...(parameter === undefined ? {} : { parameter }),
|
|
98
|
+
}),
|
|
99
|
+
fingerprintVersion: FINGERPRINT_VERSION,
|
|
100
|
+
title: name,
|
|
101
|
+
...(background === undefined ? {} : { description: stripHtml(background) }),
|
|
102
|
+
detectedSeverity:
|
|
103
|
+
BURP_SEVERITY[childText(issue, 'severity')?.toLowerCase() ?? ''] ?? 'advisory',
|
|
104
|
+
severitySource: 'engine_default',
|
|
105
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
106
|
+
vulnKey: key,
|
|
107
|
+
location,
|
|
108
|
+
...(parameter === undefined ? {} : { parameter }),
|
|
109
|
+
...(remediation === undefined ? {} : { recommendation: stripHtml(remediation) }),
|
|
110
|
+
sourceEngine: 'burp',
|
|
111
|
+
sourceRuleId: type,
|
|
112
|
+
...(confidence === undefined ? {} : { confidence }),
|
|
113
|
+
createdAt: options.now,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return findings;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Burp's prose fields are HTML inside CDATA. */
|
|
121
|
+
function stripHtml(value: string): string {
|
|
122
|
+
return value
|
|
123
|
+
.replace(/<[^>]*>/gu, ' ')
|
|
124
|
+
.replace(/\s+/gu, ' ')
|
|
125
|
+
.trim();
|
|
126
|
+
}
|