Skip to content

Commit e07a122

Browse files
committed
fix npm affected package , add npm test
Signed-off-by: ziadhany <ziadhany2016@gmail.com>
1 parent 2babaf4 commit e07a122

13 files changed

Lines changed: 489 additions & 312 deletions

File tree

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ toml==0.10.2
106106
tomli==2.0.1
107107
traitlets==5.1.1
108108
typing_extensions==4.1.1
109-
univers==30.7.0
109+
univers==30.8.0
110110
urllib3==1.26.9
111111
wcwidth==0.2.5
112112
websocket-client==0.59.0

vulnerabilities/importers/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
from vulnerabilities.importers import github
1313
from vulnerabilities.importers import gitlab
1414
from vulnerabilities.importers import nginx
15+
from vulnerabilities.importers import npm
1516
from vulnerabilities.importers import nvd
1617
from vulnerabilities.importers import openssl
18+
from vulnerabilities.importers import pypa
1719
from vulnerabilities.importers import pysec
1820
from vulnerabilities.importers import redhat
1921

@@ -27,6 +29,8 @@
2729
pysec.PyPIImporter,
2830
debian.DebianImporter,
2931
gitlab.GitLabGitImporter,
32+
pypa.PyPaImporter,
33+
npm.NpmImporter,
3034
]
3135

3236
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}

vulnerabilities/importers/npm.py

Lines changed: 70 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99

1010
# Author: Navonil Das (@NavonilDas)
1111
import logging
12+
from pathlib import Path
1213
from typing import Iterable
1314
from typing import List
1415
from typing import Optional
15-
from typing import Set
16-
from typing import Tuple
1716
from urllib.parse import quote
1817

1918
import pytz
@@ -24,13 +23,11 @@
2423
from univers.versions import SemverVersion
2524

2625
from vulnerabilities.importer import AdvisoryData
27-
from vulnerabilities.importer import GitConfig
26+
from vulnerabilities.importer import AffectedPackage
2827
from vulnerabilities.importer import GitImporter
2928
from vulnerabilities.importer import Reference
3029
from vulnerabilities.package_managers import NpmVersionAPI
31-
from vulnerabilities.package_managers import PackageVersion
3230
from vulnerabilities.utils import load_json
33-
from vulnerabilities.utils import nearest_patched_package
3431

3532
NPM_URL = "https://registry.npmjs.org{}"
3633
logger = logging.getLogger(__name__)
@@ -39,163 +36,73 @@
3936
class NpmImporter(GitImporter):
4037
license_url = "https://github.com/nodejs/security-wg/blob/main/LICENSE.md"
4138
spdx_license_expression = "MIT"
42-
config = GitConfig(
43-
repository_url="https://github.com/nodejs/security-wg.git",
44-
working_directory="npm",
45-
branch="main",
46-
)
47-
cutoff_timestamp = 1
4839

4940
def __init__(self):
50-
super().__init__(config=self.config, cutoff_timestamp=self.cutoff_timestamp)
51-
self._added_files, self._updated_files = self.file_changes(
52-
recursive=True, file_ext="json", subdir="vuln/npm"
53-
)
54-
self.pkg_manager_api = NpmVersionAPI()
55-
56-
def parse_advisory_data(self, record) -> Optional[AdvisoryData]:
57-
package_name = record["module_name"].strip()
58-
59-
publish_date = parse(record["updated_at"])
60-
publish_date = publish_date.replace(tzinfo=pytz.UTC)
61-
62-
all_versions = self.pkg_manager_api.fetch(package_name)
63-
aff_range = record.get("vulnerable_versions")
64-
if not aff_range:
65-
aff_range = ""
66-
fixed_range = record.get("patched_versions")
67-
if not fixed_range:
68-
fixed_range = ""
69-
70-
# if aff_range == "*" or fixed_range == "*":
71-
# return None
72-
73-
impacted_versions, resolved_versions = categorize_versions(
74-
all_versions, aff_range, fixed_range
75-
)
76-
77-
impacted_purls = _versions_to_purls(package_name, impacted_versions)
78-
resolved_purls = _versions_to_purls(package_name, resolved_versions)
79-
80-
vuln_reference = [
81-
Reference(
82-
url=NPM_URL.format(f'/-/npm/v1/advisories/{record["id"]}'),
83-
reference_id=record["id"],
84-
)
85-
]
86-
cve_id = record.get("cves") or []
87-
88-
return AdvisoryData(
89-
aliases=cve_id,
90-
summary=record.get("overview", ""),
91-
affected_packages=nearest_patched_package(impacted_purls, resolved_purls),
92-
references=vuln_reference,
93-
date_published=publish_date,
94-
)
41+
super().__init__(repo_url="git+https://github.com/nodejs/security-wg.git")
9542

9643
def advisory_data(self) -> Iterable[AdvisoryData]:
97-
files = self._updated_files.union(self._added_files)
44+
self.clone()
45+
path = Path(self.vcs_response.dest_dir)
46+
47+
glob = "vuln/npm/**/*.json" # subdir="vuln/npm"
48+
files = (p for p in path.glob(glob) if p.is_file())
9849
for file in files:
50+
print(file)
9951
record = load_json(file)
100-
yield self.parse_advisory_data(record)
101-
102-
103-
def _versions_to_purls(package_name, versions):
104-
purls = {f"pkg:npm/{quote(package_name)}@{v}" for v in versions}
105-
return [PackageURL.from_string(s) for s in purls]
106-
107-
108-
def normalize_ranges(version_range_string):
109-
"""
110-
- Splits version range strings with "||" operator into separate ranges.
111-
- Removes spaces between range operator and range operands
112-
- Normalizes 'x' ranges
113-
Example:
114-
>>> z = normalize_ranges(">=6.1.3 < 7.0.0 || >=7.0.3")
115-
>>> assert z == [">=6.1.3,<7.0.0", ">=7.0.3"]
116-
"""
117-
118-
version_ranges = version_range_string.split("||")
119-
version_ranges = list(map(str.strip, version_ranges))
120-
for id, version_range in enumerate(version_ranges):
121-
122-
# TODO: This is cryptic, simplify this if possible
123-
version_ranges[id] = ",".join(version_range.split())
124-
version_ranges[id] = version_ranges[id].replace(">=,", ">=")
125-
version_ranges[id] = version_ranges[id].replace("<=,", "<=")
126-
version_ranges[id] = version_ranges[id].replace("<=,", "<=")
127-
version_ranges[id] = version_ranges[id].replace("<,", "<")
128-
version_ranges[id] = version_ranges[id].replace(">,", ">")
129-
130-
# "x" is interpretted as wild card character here. These are not part of semver
131-
# spec. We replace the "x" with aribitarily large number to simulate the effect.
132-
if ".x." in version_ranges[id]:
133-
version_ranges[id] = version_ranges[id].replace(".x", ".10000.0")
134-
if ".x" in version_ranges[id]:
135-
version_ranges[id] = version_ranges[id].replace(".x", ".10000")
136-
137-
return version_ranges
138-
139-
140-
def categorize_versions(
141-
all_versions: Iterable[PackageVersion],
142-
affected_version_range: str,
143-
fixed_version_range: str,
144-
) -> Tuple[Set[SemverVersion], Set[SemverVersion]]:
145-
"""
146-
Seperate list of affected versions and unaffected versions from all versions
147-
using the ranges specified.
148-
149-
:return: impacted, resolved versions
150-
"""
151-
if not all_versions:
152-
# NPM registry has no data regarding this package, we skip these
153-
return set(), set()
154-
155-
aff_spec = []
156-
fix_spec = []
157-
158-
if affected_version_range:
159-
aff_spec = get_version_range(affected_version_range)
160-
161-
if fixed_version_range:
162-
fix_spec = get_version_range(fixed_version_range)
163-
164-
aff_ver, fix_ver = set(), set()
52+
yield parse_advisory_data(record)
16553

166-
# Unaffected version is that version which is in the fixed_version_range
167-
# or which is absent in the affected_version_range
168-
for ver in get_all_versions(all_versions):
16954

170-
if not any([ver in spec for spec in aff_spec]) or any([ver in spec for spec in fix_spec]):
171-
fix_ver.add(ver)
172-
else:
173-
aff_ver.add(ver)
55+
def parse_advisory_data(record) -> Optional[AdvisoryData]:
56+
cves = record.get("cves") or []
57+
overview = record.get("overview", "")
58+
package_name = record["module_name"].strip()
17459

175-
return aff_ver, fix_ver
60+
publish_date = parse(record["updated_at"])
61+
publish_date = publish_date.replace(tzinfo=pytz.UTC)
17662

63+
pkg_manager_api = NpmVersionAPI()
64+
all_versions = pkg_manager_api.fetch(package_name)
65+
aff_range = record.get("vulnerable_versions") or ""
66+
fixed_range = record.get("patched_versions") or ""
17767

178-
def get_version_range(version_range) -> List[NpmVersionRange]:
179-
fix_specs = normalize_ranges(version_range)
180-
ver_range_objs = []
181-
for spec in fix_specs:
182-
if len(spec) >= 3:
183-
try:
184-
ver_range_objs.append(NpmVersionRange.from_string(f"vers:npm/{spec}"))
185-
except InvalidVersion:
186-
logger.error(f"InvalidVersionRange {spec}")
68+
fixed_versions = get_fixed_version(
69+
map_all_versions(all_versions), NpmVersionRange.from_native(fixed_range)
70+
)
71+
# if aff_range == "*" or fixed_range == "*":
72+
# return None
18773

188-
return ver_range_objs
74+
vuln_reference = [
75+
Reference(
76+
url=NPM_URL.format(f'/-/npm/v1/advisories/{record["id"]}'),
77+
reference_id=record["id"],
78+
)
79+
]
80+
81+
return AdvisoryData(
82+
aliases=cves,
83+
summary=overview,
84+
affected_packages=[
85+
AffectedPackage(
86+
package=PackageURL.from_string(f"pkg:npm/{quote(package_name)}"),
87+
affected_version_range=NpmVersionRange.from_native(aff_range),
88+
fixed_version=fixed_version,
89+
)
90+
for fixed_version in fixed_versions
91+
],
92+
references=vuln_reference,
93+
date_published=publish_date,
94+
)
18995

19096

191-
def get_all_versions(all_versions) -> List[SemverVersion]:
97+
def map_all_versions(all_versions) -> List[SemverVersion]:
19298
"""
99+
map all versions from PackageVersion to SemverVersion
193100
194-
Args:
195-
all_versions:
101+
Parameters:
102+
all_versions (PackageVersion): List of PackageVersion
196103
197104
Returns:
198-
105+
List[SemverVersion]: return a list of SemverVersion
199106
"""
200107
ver_objs = []
201108
for ver in all_versions:
@@ -204,3 +111,22 @@ def get_all_versions(all_versions) -> List[SemverVersion]:
204111
except InvalidVersion:
205112
logger.error(f"InvalidVersion {ver.value}")
206113
return ver_objs
114+
115+
116+
def get_fixed_version(
117+
all_versions: List[SemverVersion], aff_range: NpmVersionRange
118+
) -> List[SemverVersion]:
119+
"""
120+
return a list of SemverVersion fixed versions
121+
"""
122+
try:
123+
fixed_versions = []
124+
if not all_versions or not all_versions:
125+
return fixed_versions
126+
for v in all_versions:
127+
if v in aff_range:
128+
fixed_versions.append(v)
129+
return fixed_versions
130+
except Exception as e:
131+
logger.error(e)
132+
return []
-35.5 KB
Binary file not shown.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"aliases": [],
3+
"summary": "Versions of `@hapi/subtext` prior to 6.1.3 or 7.0.3 are vulnerable to Denial of Service. The Content-Encoding HTTP header parser has a vulnerability which will cause the function to throw a system error if the header contains some invalid values. Because hapi rethrows system errors (as opposed to catching expected application errors), the error is thrown all the way up the stack. If no unhandled exception handler is available, the application will exist, allowing an attacker to shut down services.",
4+
"affected_packages": [
5+
{
6+
"package": {
7+
"type": "npm",
8+
"namespace": "@hapi",
9+
"name": "subtext",
10+
"version": null,
11+
"qualifiers": null,
12+
"subpath": null
13+
},
14+
"affected_version_range": "vers:npm/>=4.1.0|<6.1.3|>=7.0.0|<7.0.3",
15+
"fixed_version": "6.1.3"
16+
},
17+
{
18+
"package": {
19+
"type": "npm",
20+
"namespace": "@hapi",
21+
"name": "subtext",
22+
"version": null,
23+
"qualifiers": null,
24+
"subpath": null
25+
},
26+
"affected_version_range": "vers:npm/>=4.1.0|<6.1.3|>=7.0.0|<7.0.3",
27+
"fixed_version": "7.0.3"
28+
},
29+
{
30+
"package": {
31+
"type": "npm",
32+
"namespace": "@hapi",
33+
"name": "subtext",
34+
"version": null,
35+
"qualifiers": null,
36+
"subpath": null
37+
},
38+
"affected_version_range": "vers:npm/>=4.1.0|<6.1.3|>=7.0.0|<7.0.3",
39+
"fixed_version": "7.0.4"
40+
},
41+
{
42+
"package": {
43+
"type": "npm",
44+
"namespace": "@hapi",
45+
"name": "subtext",
46+
"version": null,
47+
"qualifiers": null,
48+
"subpath": null
49+
},
50+
"affected_version_range": "vers:npm/>=4.1.0|<6.1.3|>=7.0.0|<7.0.3",
51+
"fixed_version": "8.0.0"
52+
}
53+
],
54+
"references": [
55+
{
56+
"reference_id": 1476,
57+
"url": "https://registry.npmjs.org/-/npm/v1/advisories/1476",
58+
"severities": []
59+
}
60+
],
61+
"date_published": "2020-02-18T18:00:29.843000+00:00"
62+
}

0 commit comments

Comments
 (0)