Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Applicability evidence to non-applicable vulnerabilities #497

Merged
merged 12 commits into from
Nov 20, 2024
24 changes: 17 additions & 7 deletions src/main/scanLogic/scanRunners/applicabilityScan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface ApplicabilityScanResponse {
// All the cve that have applicable issues
applicableCve: { [cve_id: string]: CveApplicableDetails };
// All the cve that have non-applicable issues
nonapplicableCve: string[];
nonapplicableCve: { [cve_id: string]: CveApplicableDetails };
dortam888 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -268,21 +268,31 @@ export class ApplicabilityRunner extends JasRunner {
let applicableCvesIdToDetails: Map<string, CveApplicableDetails> = new Map<string, CveApplicableDetails>(
Object.entries(scanResponse.applicableCve)
);
let notApplicableCvesIdToDetails: Map<string, CveApplicableDetails> = new Map<string, CveApplicableDetails>(
Object.entries(scanResponse.nonapplicableCve)
);
let relevantScannedCve: string[] = [];
let relevantApplicableCve: Map<string, CveApplicableDetails> = new Map<string, CveApplicableDetails>();
let relevantNonApplicableCve: Map<string, CveApplicableDetails> = new Map<string, CveApplicableDetails>();

for (let scannedCve of scanResponse.scannedCve) {
if (relevantCve.has(scannedCve)) {
relevantScannedCve.push(scannedCve);
let potential: CveApplicableDetails | undefined = applicableCvesIdToDetails.get(scannedCve);
if (potential) {
relevantApplicableCve.set(scannedCve, potential);
continue
}
potential = notApplicableCvesIdToDetails.get(scannedCve);
if (potential) {
relevantNonApplicableCve.set(scannedCve, potential);
}
}
}
return {
scannedCve: Array.from(relevantScannedCve),
applicableCve: Object.fromEntries(relevantApplicableCve.entries())
applicableCve: Object.fromEntries(relevantApplicableCve.entries()),
nonapplicableCve: Object.fromEntries(relevantNonApplicableCve.entries())
} as ApplicabilityScanResponse;
}

Expand All @@ -298,10 +308,11 @@ export class ApplicabilityRunner extends JasRunner {
// Prepare
const analyzerScanRun: AnalyzerScanRun = response.runs[0];
const applicable: Map<string, CveApplicableDetails> = new Map<string, CveApplicableDetails>();
const nonapplicable: string[] = [];
const nonapplicable: Map<string, CveApplicableDetails> = new Map<string, CveApplicableDetails>();
const scanned: Set<string> = new Set<string>();
const rulesFullDescription: Map<string, string> = new Map<string, string>();
const applicabilityStatues: Map<string, ApplicabilityStatus> = new Map<string, ApplicabilityStatus>();

for (const rule of analyzerScanRun.tool.driver.rules) {
if (rule.fullDescription) {
rulesFullDescription.set(rule.id, rule.fullDescription.text);
Expand All @@ -328,18 +339,17 @@ export class ApplicabilityRunner extends JasRunner {
let fileIssues: FileIssues = this.getOrCreateFileIssues(applicableDetails, location.physicalLocation.artifactLocation.uri);
fileIssues.locations.push(location.physicalLocation.region);
});
scanned.add(this.getCveFromRuleId(analyzeIssue.ruleId));
} else if (status === ApplicabilityStatus.NOT_APPLICABLE) {
nonapplicable.push(this.getCveFromRuleId(analyzeIssue.ruleId));
scanned.add(this.getCveFromRuleId(analyzeIssue.ruleId));
this.getOrCreateApplicableDetails(analyzeIssue, nonapplicable, rulesFullDescription.get(analyzeIssue.ruleId));
}
scanned.add(this.getCveFromRuleId(analyzeIssue.ruleId));
});
}
// Convert data to a response
return {
scannedCve: Array.from(scanned),
applicableCve: Object.fromEntries(applicable.entries()),
nonapplicableCve: nonapplicable
nonapplicableCve: Object.fromEntries(nonapplicable.entries())
} as ApplicabilityScanResponse;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class ProjectDependencyTreeNode extends FileTreeNode {
private _scannedCve?: Set<string> | undefined;
// Is applicable if key in here
private _applicableCve?: Map<string, CveApplicableDetails> | undefined;
private _notApplicableCve?: Map<string, CveApplicableDetails> | undefined;
protected _dependencyScanTimeStamp?: number;
protected _applicableScanTimeStamp?: number;

Expand Down Expand Up @@ -133,6 +134,14 @@ export class ProjectDependencyTreeNode extends FileTreeNode {
this._applicableCve = value;
}

public get notApplicableCve(): Map<string, CveApplicableDetails> | undefined {
return this._notApplicableCve;
}

public set notApplicableCve(value: Map<string, CveApplicableDetails> | undefined) {
this._notApplicableCve = value;
}

public get applicableScanTimeStamp(): number | undefined {
return this._applicableScanTimeStamp;
}
Expand Down
20 changes: 17 additions & 3 deletions src/main/treeDataProviders/utils/analyzerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ export class AnalyzerUtils {
descriptorNode.applicableCve = new Map<string, CveApplicableDetails>(
dependencyScanResults.applicableIssues ? Object.entries(dependencyScanResults.applicableIssues.applicableCve) : []
);
descriptorNode.notApplicableCve = new Map<string, CveApplicableDetails>(
dependencyScanResults.applicableIssues ? Object.entries(dependencyScanResults.applicableIssues.nonapplicableCve) : []
);
descriptorNode.applicableScanTimeStamp = dependencyScanResults.applicableScanTimestamp;

// Populate related CodeFile nodes with issues and update the descriptor CVE applicability details
Expand All @@ -232,10 +235,10 @@ export class AnalyzerUtils {
}
for (const node of nodes) {
if (node instanceof CveTreeNode) {
let evidences: IEvidence[] = [];
let potential: CveApplicableDetails | undefined = descriptorNode.applicableCve?.get(node.labelId);
if (potential) {
let details: CveApplicableDetails = potential;
let evidences: IEvidence[] = [];
// Populate code file issues for workspace
details.fileEvidences.forEach((fileEvidence: FileIssues) => {
let fileNode: CodeFileTreeNode = this.getOrCreateCodeFileNode(root, fileEvidence.full_path);
Expand All @@ -248,9 +251,20 @@ export class AnalyzerUtils {
evidence: evidences
} as IApplicableDetails;
} else {
// Not applicable
// Not Applicable
dortam888 marked this conversation as resolved.
Show resolved Hide resolved
let notApplicableApplicableDetails: CveApplicableDetails | undefined = descriptorNode.notApplicableCve?.get(node.labelId);
if (!notApplicableApplicableDetails) {
continue
}
evidences.push({
reason: notApplicableApplicableDetails.fixReason
} as IEvidence);
node.severity = SeverityUtils.notApplicable(node.severity);
node.applicableDetails = { isApplicable: false } as IApplicableDetails;
node.applicableDetails = {
isApplicable: false,
searchTarget: notApplicableApplicableDetails.fullDescription,
evidence: evidences
} as IApplicableDetails;
}
}
}
Expand Down
19 changes: 14 additions & 5 deletions src/test/resources/applicableScan/npm/expectedScanResponse.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,18 @@
"fullDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `util.setProperty` with external input to its 2nd (`path`) or 3rd (`value`) arguments.\n* `ReflectionObject.setParsedOption` with external input to its 2nd (`name`) or 3rd (`value`) arguments.\n* `parse` with external input to its 1st (`source`) argument.\n* `load`\n* `loadSync`\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present."
}
},
"nonapplicableCve": [
"CVE-2021-3807",
"CVE-2021-3918",
"CVE-2021-44228"
]
"nonapplicableCve": {
"CVE-2021-3807": {
"fixReason": "The scanner checks whether the vulnerable function `ansi-regex` is called.",
"fullDescription": "The scanner checks whether the vulnerable function `ansi-regex` is called."
},
"CVE-2021-3918":{
"fixReason": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present.",
"fullDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present."
},
"CVE-2021-44228":{
"fixReason": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `info` with external input to any of its arguments.\n* `fatal` with external input to any of its arguments.\n* `log` with external input to any of its arguments.\n* `warn` with external input to any of its arguments.\n* `trace` with external input to any of its arguments.\n* `error` with external input to any of its arguments.\n* `debug` with external input to any of its arguments.",
"fullDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `info` with external input to any of its arguments.\n* `fatal` with external input to any of its arguments.\n* `log` with external input to any of its arguments.\n* `warn` with external input to any of its arguments.\n* `trace` with external input to any of its arguments.\n* `error` with external input to any of its arguments.\n* `debug` with external input to any of its arguments."
}
}
}
14 changes: 10 additions & 4 deletions src/test/resources/applicableScan/python/expectedScanResponse.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@
"fullDescription": "The scanner checks whether the vulnerable function `open` is called with external input to its 1st (`name`) argument."
}
},
"nonapplicableCve": [
"CVE-2021-3918",
"CVE-2019-15605"
]
"nonapplicableCve": {
"CVE-2021-3918": {
"fixReason": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present.",
"fullDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present."
},
"CVE-2019-15605": {
"fixReason": "The scanner checks whether Express.js, which is the vulnerability's main remote attack vector, is running.",
"fullDescription": "The scanner checks whether Express.js, which is the vulnerability's main remote attack vector, is running."
}
}
}
2 changes: 1 addition & 1 deletion src/test/tests/integration/applicability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('Applicability Integration Tests', async () => {
});

it('Check all expected nonapplicable CVE detected', () => {
assert.sameDeepMembers(response.nonapplicableCve, expectedContent.nonapplicableCve);
assert.includeDeepMembers(Object.keys(response.nonapplicableCve), Object.keys(expectedContent.nonapplicableCve));
});

describe('Applicable details data validations', () => {
Expand Down
Loading