Skip to content

Commit 2b076a5

Browse files
Adding support for vulnerability aliases
Signed-off-by: Steve Springett <[email protected]>
1 parent 4a495a2 commit 2b076a5

File tree

5 files changed

+231
-36
lines changed

5 files changed

+231
-36
lines changed

src/i18n/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@
225225
"comments": "Comments",
226226
"affected_projects": "Affected Projects",
227227
"weakness": "Weakness",
228+
"alias": "Alias",
229+
"aliases": "Aliases",
228230
"references": "References",
229231
"credits": "Credits",
230232
"component_vulnerabilities": "Component Vulnerabilities",

src/shared/common.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,82 @@ $common.formatAnalyzerLabel = function formatAnalyzerLabel(analyzer, vulnSource,
127127
return `<span class="label label-source label-analyzer" style="white-space:nowrap;">${analyzerLabel}</span>`;
128128
};
129129

130+
/**
131+
* Given the source of a vulnerability (vulnSource) and the vulnerabilities identifier, create
132+
* a value object which also contains the proper name of the source and the URL to the vulnerability
133+
* hosted by the source.
134+
* @param vulnSource the source of a vulnerability
135+
* @param vulnId the unique identifier
136+
* @returns a SourceInfo object
137+
*/
138+
$common.resolveSourceVulnInfo = function resolveSourceVulnInfo(vulnSource, vulnId) {
139+
let sourceInfo = {};
140+
sourceInfo.source = vulnSource;
141+
sourceInfo.vulnId = vulnId;
142+
switch (vulnSource) {
143+
case "INTERNAL":
144+
// TODO
145+
break;
146+
case "NVD":
147+
sourceInfo.name = "National Vulnerability Database";
148+
sourceInfo.url = "https://nvd.nist.gov/vuln/detail/" + vulnId;
149+
break;
150+
case "GITHUB":
151+
sourceInfo.name = "GitHub Advisories";
152+
sourceInfo.url = "https://github.com/advisories/" + vulnId;
153+
break;
154+
case "OSSINDEX":
155+
sourceInfo.name = "OSS Index";
156+
sourceInfo.url = "https://ossindex.sonatype.org/vuln/" + vulnId;
157+
break;
158+
case "SNYK":
159+
sourceInfo.name = "Snyk";
160+
sourceInfo.url = "https://security.snyk.io/vuln/" + vulnId;
161+
break;
162+
case "OSV":
163+
sourceInfo.name = "Open Source Vulnerability Database";
164+
sourceInfo.url = "https://osv.dev/vulnerability/" + vulnId;
165+
break;
166+
case "GSD":
167+
sourceInfo.name = "Global Security Database";
168+
sourceInfo.url = "https://github.com/cloudsecurityalliance/gsd-database";
169+
break;
170+
case "VULNDB":
171+
sourceInfo.name = "VulnDB";
172+
sourceInfo.url = "https://vulndb.cyberriskanalytics.com/vulnerabilities/" + vulnId;
173+
break;
174+
}
175+
return sourceInfo;
176+
}
177+
178+
/**
179+
* Given the source of a vulnerability (vulnSource) and an alias of the vulnerability, normalizes
180+
* the return object.
181+
* @param vulnSource the source of a Vulnerability object
182+
* @param alias a VulnerabilityAlias response object for the given Vulnerability
183+
* @returns A resolved and normalized object with metadata
184+
*/
185+
$common.resolveVulnAliasInfo = function resolveVulnAliasInfo(vulnSource, alias) {
186+
if (!vulnSource || !alias) return;
187+
if (vulnSource !== "INTERNAL" && alias.internalId) {
188+
return $common.resolveSourceVulnInfo("INTERNAL", alias.internalId);
189+
} else if (vulnSource !== "NVD" && alias.cveId) {
190+
return $common.resolveSourceVulnInfo("NVD", alias.cveId);
191+
} else if (vulnSource !== "GITHUB" && alias.ghsaId) {
192+
return $common.resolveSourceVulnInfo("GITHUB", alias.ghsaId);
193+
} else if (vulnSource !== "OSSINDEX" && alias.sonatypeId) {
194+
return $common.resolveSourceVulnInfo("OSSINDEX", alias.sonatypeId);
195+
} else if (vulnSource !== "SNYK" && alias.snykId) {
196+
return $common.resolveSourceVulnInfo("SNYK", alias.snykId);
197+
} else if (vulnSource !== "OSV" && alias.osvId) {
198+
return $common.resolveSourceVulnInfo("OSV", alias.osvId);
199+
} else if (vulnSource !== "GSD" && alias.gsdId) {
200+
return $common.resolveSourceVulnInfo("GSD", alias.gsdId);
201+
} else if (vulnSource !== "VULNDB" && alias.vulnDbId) {
202+
return $common.resolveSourceVulnInfo("VULNDB", alias.vulnDbId);
203+
}
204+
}
205+
130206
/**
131207
*
132208
* @param {*} i18n - VueI18n instance with $t translate function available
@@ -328,6 +404,9 @@ $common.toBoolean = function(string) {
328404
if (!string) {
329405
return false;
330406
}
407+
if (typeof string == "boolean") {
408+
return string;
409+
}
331410
switch(string.toLowerCase().trim()) {
332411
case "true": case "yes": case "1": return true;
333412
case "false": case "no": case "0": case null: return false;
@@ -353,6 +432,8 @@ module.exports = {
353432
formatCweLabel: $common.formatCweLabel,
354433
formatCweShortLabel: $common.formatCweShortLabel,
355434
formatAnalyzerLabel: $common.formatAnalyzerLabel,
435+
resolveSourceVulnInfo: $common.resolveSourceVulnInfo,
436+
resolveVulnAliasInfo: $common.resolveVulnAliasInfo,
356437
makeAnalysisStateLabelFormatter: $common.makeAnalysisStateLabelFormatter,
357438
makeAnalysisJustificationLabelFormatter: $common.makeAnalysisJustificationLabelFormatter,
358439
componentClassifierLabelFormatter: $common.componentClassifierLabelFormatter,

src/views/portfolio/projects/ProjectFindings.vue

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@
107107
return common.formatSourceLabel(row.vulnerability.source) + ` <a href="${url}">${xssFilters.inHTMLData(value)}</a>`;
108108
}
109109
},
110+
{
111+
title: this.$t('message.aliases'),
112+
field: "vulnerability.aliases",
113+
sortable: true,
114+
visible: false,
115+
formatter(value, row, index) {
116+
if (typeof value !== 'undefined') {
117+
let label = "";
118+
for (let i=0; i<value.length; i++) {
119+
let alias = common.resolveVulnAliasInfo(row.vulnerability.source, value[i]);
120+
let url = xssFilters.uriInUnQuotedAttr("../vulnerabilities/" + alias.source + "/" + alias.vulnId);
121+
label += common.formatSourceLabel(alias.source) + ` <a href="${url}">${xssFilters.inHTMLData(alias.vulnId)}</a>`
122+
if (i < value.length-1) label += ", "
123+
}
124+
return label;
125+
}
126+
}
127+
},
110128
{
111129
title: this.$t('message.cwe'),
112130
field: "vulnerability.cwes",
@@ -195,6 +213,16 @@
195213
template: `
196214
<b-row class="expanded-row">
197215
<b-col sm="6">
216+
<div v-if="finding.vulnerability.aliases && finding.vulnerability.aliases.length > 0">
217+
<label>Aliases</label>
218+
<b-card class="font-weight-bold">
219+
<b-card-text>
220+
<span v-for="alias in finding.vulnerability.aliases">
221+
<b-link style="margin-right:1.0rem" :href="'/vulnerabilities/' + aliasLabel(finding.vulnerability.source, alias).source + '/' + aliasLabel(finding.vulnerability.source, alias).vulnId">{{aliasLabel(finding.vulnerability.source, alias).vulnId}}</b-link>
222+
</span>
223+
</b-card-text>
224+
</b-card>
225+
</div>
198226
<b-form-group v-if="finding.vulnerability.title" id="fieldset-1" :label="this.$t('message.title')" label-for="input-1">
199227
<b-form-input id="input-1" v-model="finding.vulnerability.title" class="form-control disabled" readonly trim />
200228
</b-form-group>
@@ -305,6 +333,9 @@
305333
},
306334
mixins: [permissionsMixin],
307335
methods: {
336+
aliasLabel: function(vulnSource, alias) {
337+
return common.resolveVulnAliasInfo(vulnSource, alias);
338+
},
308339
getAnalysis: function() {
309340
let queryString = "?project=" + projectUuid + "&component=" + this.finding.component.uuid + "&vulnerability=" + this.finding.vulnerability.uuid;
310341
let url = `${this.$api.BASE_URL}/${this.$api.URL_ANALYSIS}` + queryString;

src/views/portfolio/vulnerabilities/Vulnerability.vue

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@
3131
<b-link class="font-weight-bold font-xs btn-block text-muted" v-b-modal.vulnerabilityDetailsModal>{{ $t('message.view_details') }} <i class="fa fa-angle-right float-right font-lg"></i></b-link>
3232
</div>
3333
</b-card>
34+
35+
<div v-if="vulnerability.published || vulnerability.aliases">
36+
<b-card>
37+
<b-card-text>
38+
<span v-if="vulnerability.published" class="font-weight-bold font-xs">Published: </span>
39+
<span v-if="vulnerability.published" class="font-weight-bold font-xs" style="margin-right:1.2rem">{{prettyTimestamp}}</span>
40+
<span v-if="vulnerability.aliases && vulnerability.aliases.length > 0" class="font-weight-bold font-xs">
41+
Aliases:
42+
<span v-for="alias in vulnerability.aliases">
43+
<b-link style="margin-right:1.0rem" :href="`/vulnerabilities/${aliasLabel(alias).source}/${aliasLabel(alias).vulnId}`">{{ aliasLabel(alias).vulnId }}</b-link>
44+
</span>
45+
</span>
46+
</b-card-text>
47+
</b-card>
48+
</div>
49+
3450
<b-tabs class="body-bg-color" style="border-left: 0; border-right:0; border-top:0 ">
3551
<b-tab class="body-bg-color overview-chart" style="border-left: 0; border-right:0; border-top:0 " active>
3652
<template v-slot:title><i class="fa fa-line-chart"></i> {{ $t('message.overview') }}</template>
@@ -108,7 +124,7 @@
108124
<affected-projects :key="this.uuid" :source="source" :vulnId="vulnId" v-on:total="totalAffectedProjects = $event" />
109125
</b-tab>
110126
</b-tabs>
111-
<vulnerability-details-modal :vulnProp="cloneDeep(vulnerability)" v-on:vulnerabilityUpdated="syncVulnerabilityFields"/>
127+
<vulnerability-details-modal :vulnProp="clonedVulnerability" v-on:vulnerabilityUpdated="syncVulnerabilityFields"/>
112128
</div>
113129
</template>
114130

@@ -145,6 +161,9 @@
145161
cvssExploitScore () {
146162
return common.valueWithDefault(((this.vulnerability.cvssV3ExploitabilitySubScore) ? this.vulnerability.cvssV3ExploitabilitySubScore : this.vulnerability.cvssV2ExploitabilitySubScore), 0);
147163
},
164+
prettyTimestamp() {
165+
return common.formatTimestamp(this.vulnerability.published);
166+
},
148167
sourceLabel () {
149168
switch (this.vulnerability.source) {
150169
case 'NVD':
@@ -206,13 +225,11 @@
206225
source: null,
207226
vulnId: null,
208227
vulnerability: {},
228+
clonedVulnerability: {},
209229
totalAffectedProjects: 0
210230
}
211231
},
212232
methods: {
213-
cloneDeep: function(vulnerability) {
214-
return cloneDeep(vulnerability);
215-
},
216233
getStyle: function(style) {
217234
return getStyle(style);
218235
},
@@ -224,34 +241,56 @@
224241
},
225242
cweLink: function(cwe) {
226243
return `https://cwe.mitre.org/data/definitions/${cwe.cweId}`;
244+
},
245+
aliasLabel: function(alias) {
246+
return common.resolveVulnAliasInfo(this.source, alias);
247+
},
248+
loadData: function () {
249+
let url = "";
250+
if (this.uuid) {
251+
url = `${this.$api.BASE_URL}/${this.$api.URL_VULNERABILITY}/${this.uuid}`;
252+
} else {
253+
url = `${this.$api.BASE_URL}/${this.$api.URL_VULNERABILITY}/source/${this.source}/vuln/${this.vulnId}`;
254+
}
255+
this.axios.get(url).then((response) => {
256+
this.vulnerability = response.data;
257+
if (!this.source) {
258+
this.source = response.data.source;
259+
}
260+
if (!this.vulnerability.affectedComponents) {
261+
this.vulnerability.affectedComponents = []; // Initialize array so that components may be added to it.
262+
}
263+
if (!this.vulnId) {
264+
this.vulnId = response.data.vulnId;
265+
}
266+
EventBus.$emit('addCrumb', this.vulnLabel);
267+
this.$title = `${this.$t('message.vulnerability')} ${this.vulnLabel}`;
268+
this.clonedVulnerability = cloneDeep(this.vulnerability);
269+
});
270+
},
271+
initializeData: function() {
272+
this.uuid = this.$route.params.uuid;
273+
this.source = this.$route.params.source;
274+
this.vulnId = this.$route.params.vulnId;
275+
}
276+
},
277+
watch: {
278+
'$route.params.uuid'(newValue) {
279+
EventBus.$emit('crumble');
280+
this.initializeData();
281+
this.loadData();
282+
},
283+
'$route.params.vulnId'(newValue) {
284+
EventBus.$emit('crumble');
285+
this.initializeData();
286+
this.loadData();
227287
}
228288
},
229289
beforeMount() {
230-
this.uuid = this.$route.params.uuid;
231-
this.source = this.$route.params.source;
232-
this.vulnId = this.$route.params.vulnId;
290+
this.initializeData();
233291
},
234292
mounted() {
235-
let url = "";
236-
if (this.uuid) {
237-
url = `${this.$api.BASE_URL}/${this.$api.URL_VULNERABILITY}/${this.uuid}`;
238-
} else {
239-
url = `${this.$api.BASE_URL}/${this.$api.URL_VULNERABILITY}/source/${this.source}/vuln/${this.vulnId}`;
240-
}
241-
this.axios.get(url).then((response) => {
242-
this.vulnerability = response.data;
243-
if (!this.source) {
244-
this.source = response.data.source;
245-
}
246-
if (!this.vulnerability.affectedComponents) {
247-
this.vulnerability.affectedComponents = []; // Initialize array so that components may be added to it.
248-
}
249-
if (!this.vulnId) {
250-
this.vulnId = response.data.vulnId;
251-
}
252-
EventBus.$emit('addCrumb', this.vulnLabel);
253-
this.$title = `${this.$t('message.vulnerability')} ${this.vulnLabel}`;
254-
});
293+
this.loadData();
255294
},
256295
destroyed() {
257296
EventBus.$emit('crumble');

0 commit comments

Comments
 (0)