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

🐛 Yarn Audit: Add CI importer support #9478

Merged
merged 2 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 139 additions & 74 deletions dojo/tools/yarn_audit/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,94 +17,159 @@ def get_description_for_scan_types(self, scan_type):
def get_findings(self, json_output, test):
if json_output is None:
return list()
tree = (json.loads(line) for line in json_output)
return self.get_items(tree, test)
tree = None
lines = json_output.read()
if isinstance(lines, bytes):
lines = lines.decode("utf-8") # passes in unittests, but would fail in production
if '"type"' in lines:
lines = lines.split('\n')
tree = (json.loads(line) for line in lines if "{" in line)
return self.get_items_yarn(tree, test)
else:
tree = json.loads(lines)
return self.get_items_auditci(tree, test)

def get_items(self, tree, test):
def get_items_yarn(self, tree, test):
items = {}
for element in tree:
if element.get("type") == "auditAdvisory":
node = element.get("data").get("advisory")
item = get_item(node, test)
item = self.get_item_yarn(node, test)
unique_key = str(node.get("id")) + str(node.get("module_name"))
items[unique_key] = item
elif element.get("type") == "error":
error = element.get("data")
raise ValueError(
"yarn audit report contains errors: %s", error
)

return list(items.values())

def get_items_auditci(self, tree, test): # https://github.com/DefectDojo/django-DefectDojo/issues/6495
items = []
for element in tree.get("advisories"):
findings = "**findings:** " + str(tree.get("advisories").get(element).get("findings"))
metadata = "**metadata:** " + str(tree.get("advisories").get(element).get("metadata"))
vulnerable_versions = "**vulnerable_versions:** " + str(tree.get("advisories").get(element).get("vulnerable_versions"))
github_advisory_id = "**github_advisory_id:** " + str(tree.get("advisories").get(element).get("github_advisory_id"))
access = "**access:** " + str(tree.get("advisories").get(element).get("access"))
patched_versions = "**patched_versions:** " + str(tree.get("advisories").get(element).get("patched_versions"))
cvss = "**cvss:** " + str(tree.get("advisories").get(element).get("cvss"))
found_by = "**found_by:** " + str(tree.get("advisories").get(element).get("found_by"))
deleted = "**deleted:** " + str(tree.get("advisories").get(element).get("deleted"))
id = "**id:** " + str(tree.get("advisories").get(element).get("id"))
references = "**references:** " + str(tree.get("advisories").get(element).get("references"))
created = "**created:** " + str(tree.get("advisories").get(element).get("created"))
reported_by = "**reported_by:** " + str(tree.get("advisories").get(element).get("reported_by"))
title = "**title:** " + str(tree.get("advisories").get(element).get("title"))
npm_advisory_id = "**npm_advisory_id:** " + str(tree.get("advisories").get(element).get("npm_advisory_id"))
overview = "**overview:** " + str(tree.get("advisories").get(element).get("overview"))
url = "**url:** " + str(tree.get("advisories").get(element).get("url"))
description = ""
description += findings + "\n"
description += metadata + "\n"
description += vulnerable_versions + "\n"
description += github_advisory_id + "\n"
description += access + "\n"
description += patched_versions + "\n"
description += cvss + "\n"
description += found_by + "\n"
description += deleted + "\n"
description += id + "\n"
description += created + "\n"
description += reported_by + "\n"
description += title + "\n"
description += npm_advisory_id + "\n"
description += overview + "\n"
dojo_finding = Finding(
title=tree.get("advisories").get(element).get("cves")[0] + "_" + tree.get("advisories").get(element).get("module_name"),
test=test,
severity=self.severitytranslator(severity=tree.get("advisories").get(element).get("severity")),
description=description,
cve=tree.get("advisories").get(element).get("cves")[0],
mitigation=tree.get("advisories").get(element).get("recommendation"),
references=url + "\n" + references,
component_name=tree.get("advisories").get(element).get("module_name"),
component_version=tree.get("advisories").get(element).get("findings")[0]["version"],
false_p=False,
duplicate=False,
out_of_scope=False,
mitigated=None,
impact="No impact provided",
static_finding=True,
dynamic_finding=False,
)
if tree.get("advisories").get(element).get("cwe") != []:
dojo_finding.cwe = tree.get("advisories").get(element).get("cwe")[0].strip("CWE-")
items.append(dojo_finding)
return items

def get_item(item_node, test):
if item_node["severity"] == "low":
severity = "Low"
elif item_node["severity"] == "moderate":
severity = "Medium"
elif item_node["severity"] == "high":
severity = "High"
elif item_node["severity"] == "critical":
severity = "Critical"
else:
severity = "Info"
def severitytranslator(self, severity):
if severity == "low":
severity = "Low"
elif severity == "moderate":
severity = "Medium"
elif severity == "high":
severity = "High"
elif severity == "critical":
severity = "Critical"
else:
severity = "Info"
return severity

paths = ""
for finding in item_node["findings"]:
paths += (
"\n - "
+ str(finding["version"])
+ ":"
+ str(",".join(finding["paths"][:25]))
def get_item_yarn(self, item_node, test):
severity = self.severitytranslator(severity=item_node["severity"])
paths = ""
for finding in item_node["findings"]:
paths += (
"\n - "
+ str(finding["version"])
+ ":"
+ str(",".join(finding["paths"][:25]))
)
if len(finding["paths"]) > 25:
paths += "\n - ..... (list of paths truncated after 25 paths)"
cwe = get_npm_cwe(item_node)
dojo_finding = Finding(
title=item_node["title"]
+ " - "
+ "("
+ item_node["module_name"]
+ ", "
+ item_node["vulnerable_versions"]
+ ")",
test=test,
severity=severity,
file_path=item_node["findings"][0]["paths"][0],
description=item_node["url"]
+ "\n"
+ item_node["overview"]
+ "\n Vulnerable Module: "
+ item_node["module_name"]
+ "\n Vulnerable Versions: "
+ str(item_node["vulnerable_versions"])
+ "\n Patched Version: "
+ str(item_node["patched_versions"])
+ "\n Vulnerable Paths: "
+ str(paths)
+ "\n CWE: "
+ str(item_node["cwe"])
+ "\n Access: "
+ str(item_node["access"]),
cwe=cwe,
mitigation=item_node["recommendation"],
references=item_node["url"],
component_name=item_node["module_name"],
component_version=item_node["findings"][0]["version"],
false_p=False,
duplicate=False,
out_of_scope=False,
mitigated=None,
impact="No impact provided",
static_finding=True,
dynamic_finding=False,
)
if len(finding["paths"]) > 25:
paths += "\n - ..... (list of paths truncated after 25 paths)"

cwe = get_npm_cwe(item_node)

dojo_finding = Finding(
title=item_node["title"]
+ " - "
+ "("
+ item_node["module_name"]
+ ", "
+ item_node["vulnerable_versions"]
+ ")",
test=test,
severity=severity,
file_path=item_node["findings"][0]["paths"][0],
description=item_node["url"]
+ "\n"
+ item_node["overview"]
+ "\n Vulnerable Module: "
+ item_node["module_name"]
+ "\n Vulnerable Versions: "
+ str(item_node["vulnerable_versions"])
+ "\n Patched Version: "
+ str(item_node["patched_versions"])
+ "\n Vulnerable Paths: "
+ str(paths)
+ "\n CWE: "
+ str(item_node["cwe"])
+ "\n Access: "
+ str(item_node["access"]),
cwe=cwe,
mitigation=item_node["recommendation"],
references=item_node["url"],
component_name=item_node["module_name"],
component_version=item_node["findings"][0]["version"],
false_p=False,
duplicate=False,
out_of_scope=False,
mitigated=None,
impact="No impact provided",
static_finding=True,
dynamic_finding=False,
)

if len(item_node["cves"]) > 0:
dojo_finding.unsaved_vulnerability_ids = list()
for vulnerability_id in item_node["cves"]:
dojo_finding.unsaved_vulnerability_ids.append(vulnerability_id)

return dojo_finding
if len(item_node["cves"]) > 0:
dojo_finding.unsaved_vulnerability_ids = list()
for vulnerability_id in item_node["cves"]:
dojo_finding.unsaved_vulnerability_ids.append(vulnerability_id)
return dojo_finding
142 changes: 142 additions & 0 deletions unittests/scans/yarn_audit/issue_6495.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
{
"actions": [],
"advisories": {
"1068298": {
"findings": [
{
"version": "1.3.5",
"paths": [
"@angular/cli>ini",
"danger>parse-git-config>ini",
"@datorama/akita>schematics-utilities>@schematics/update>ini",
"@datorama/akita-ng-entity-service>@datorama/akita>schematics-utilities>@schematics/update>ini",
"nodemon>update-notifier>latest-version>package-json>registry-auth-token>rc>ini",
"@mikro-orm/cli>@mikro-orm/migrations>knex>liftoff>findup-sync>resolve-dir>global-modules>global-prefix>ini",
"@mikro-orm/cli>@mikro-orm/knex>@mikro-orm/migrations>knex>liftoff>findup-sync>resolve-dir>global-modules>global-prefix>ini",
"@mikro-orm/cli>@mikro-orm/entity-generator>@mikro-orm/knex>@mikro-orm/migrations>knex>liftoff>findup-sync>resolve-dir>global-modules>global-prefix>ini"
]
}
],
"metadata": null,
"vulnerable_versions": "<1.3.6",
"module_name": "ini",
"severity": "high",
"github_advisory_id": "GHSA-qqgx-2p2h-9c37",
"cves": [
"CVE-2020-7788"
],
"access": "public",
"patched_versions": ">=1.3.6",
"cvss": {
"score": 7.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L"
},
"updated": "2021-07-28T21:12:38.000Z",
"recommendation": "Upgrade to version 1.3.6 or later",
"cwe": [
"CWE-1321"
],
"found_by": null,
"deleted": null,
"id": 1068298,
"references": "- https://github.com/npm/ini/commit/56d2805e07ccd94e2ba0984ac9240ff02d44b6f1\n- https://www.npmjs.com/advisories/1589\n- https://snyk.io/vuln/SNYK-JS-INI-1048974\n- https://nvd.nist.gov/vuln/detail/CVE-2020-7788\n- https://lists.debian.org/debian-lts-announce/2020/12/msg00032.html\n- https://github.com/advisories/GHSA-qqgx-2p2h-9c37",
"created": "2020-12-10T16:53:45.000Z",
"reported_by": null,
"title": "Prototype Pollution",
"npm_advisory_id": null,
"overview": "### Overview\nThe `ini` npm package before version 1.3.6 has a Prototype Pollution vulnerability.\n\nIf an attacker submits a malicious INI file to an application that parses it with `ini.parse`, they will pollute the prototype on the application. This can be exploited further depending on the context.\n\n### Patches\n\nThis has been patched in 1.3.6\n\n### Steps to reproduce\n\npayload.ini\n```\n[__proto__]\npolluted = \"polluted\"\n```\n\npoc.js:\n```\nvar fs = require('fs')\nvar ini = require('ini')\n\nvar parsed = ini.parse(fs.readFileSync('./payload.ini', 'utf-8'))\nconsole.log(parsed)\nconsole.log(parsed.__proto__)\nconsole.log(polluted)\n```\n\n```\n> node poc.js\n{}\n{ polluted: 'polluted' }\n{ polluted: 'polluted' }\npolluted\n```",
"url": "https://github.com/advisories/GHSA-qqgx-2p2h-9c37"
},
"1075625": {
"findings": [
{
"version": "0.4.3",
"paths": [
"@playwright/test>jpeg-js",
"@playwright/test>playwright-core>jpeg-js"
]
}
],
"metadata": null,
"vulnerable_versions": "<0.4.4",
"module_name": "jpeg-js",
"severity": "high",
"github_advisory_id": "GHSA-xvf7-4v9q-58w6",
"cves": [
"CVE-2022-25851"
],
"access": "public",
"patched_versions": ">=0.4.4",
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"updated": "2022-06-20T21:58:36.000Z",
"recommendation": "Upgrade to version 0.4.4 or later",
"cwe": [
"CWE-835"
],
"found_by": null,
"deleted": null,
"id": 1075625,
"references": "- https://nvd.nist.gov/vuln/detail/CVE-2022-25851\n- https://github.com/jpeg-js/jpeg-js/issues/105\n- https://github.com/jpeg-js/jpeg-js/pull/106/\n- https://github.com/jpeg-js/jpeg-js/commit/9ccd35fb5f55a6c4f1902ac5b0f270f675750c27\n- https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-2860295\n- https://snyk.io/vuln/SNYK-JS-JPEGJS-2859218\n- https://github.com/advisories/GHSA-xvf7-4v9q-58w6",
"created": "2022-06-11T00:00:17.000Z",
"reported_by": null,
"title": "Infinite loop in jpeg-js",
"npm_advisory_id": null,
"overview": "The package jpeg-js before 0.4.4 is vulnerable to Denial of Service (DoS) where a particular piece of input will cause the program to enter an infinite loop and never return.",
"url": "https://github.com/advisories/GHSA-xvf7-4v9q-58w6"
},
"1075701": {
"findings": [
{
"version": "9.6.0",
"paths": [
"nodemon>update-notifier>latest-version>package-json>got"
]
}
],
"metadata": null,
"vulnerable_versions": "<11.8.5",
"module_name": "got",
"severity": "moderate",
"github_advisory_id": "GHSA-pfrx-2q88-qq97",
"cves": [
"CVE-2022-33987"
],
"access": "public",
"patched_versions": ">=11.8.5",
"cvss": {
"score": 0,
"vectorString": null
},
"updated": "2022-06-27T17:09:23.000Z",
"recommendation": "Upgrade to version 11.8.5 or later",
"cwe": [],
"found_by": null,
"deleted": null,
"id": 1075701,
"references": "- https://nvd.nist.gov/vuln/detail/CVE-2022-33987\n- https://github.com/sindresorhus/got/pull/2047\n- https://github.com/sindresorhus/got/compare/v12.0.3...v12.1.0\n- https://github.com/sindresorhus/got/commit/861ccd9ac2237df762a9e2beed7edd88c60782dc\n- https://github.com/sindresorhus/got/releases/tag/v11.8.5\n- https://github.com/sindresorhus/got/releases/tag/v12.1.0\n- https://github.com/advisories/GHSA-pfrx-2q88-qq97",
"created": "2022-06-19T00:00:21.000Z",
"reported_by": null,
"title": "Got allows a redirect to a UNIX socket",
"npm_advisory_id": null,
"overview": "The got package before 11.8.5 and 12.1.0 for Node.js allows a redirect to a UNIX socket.",
"url": "https://github.com/advisories/GHSA-pfrx-2q88-qq97"
}
},
"muted": [],
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 1,
"high": 10,
"critical": 0
},
"dependencies": 2236,
"devDependencies": 121,
"optionalDependencies": 0,
"totalDependencies": 2357
}
}
8 changes: 8 additions & 0 deletions unittests/tools/test_yarn_audit_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,11 @@ def test_yarn_audit_parser_empty_with_error(self):
"yarn audit report contains errors:" in str(context.exception)
)
self.assertTrue("ECONNREFUSED" in str(context.exception))

def test_yarn_audit_parser_issue_6495(self):
testfile = open("unittests/scans/yarn_audit/issue_6495.json")
parser = YarnAuditParser()
findings = parser.get_findings(testfile, self.get_test())
testfile.close()
self.assertEqual(3, len(findings))
self.assertEqual(findings[0].cwe, "1321")
Loading