Skip to content

Commit 6b15218

Browse files
committed
Add CWE support for gitlab and redhat
Signed-off-by: ziadhany <ziadhany2016@gmail.com>
1 parent 3aff154 commit 6b15218

16 files changed

Lines changed: 146 additions & 21 deletions

vulnerabilities/importers/gitlab.py

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99

1010
import logging
1111
import traceback
12+
from datetime import datetime
1213
from pathlib import Path
1314
from typing import Iterable
1415
from typing import List
16+
from typing import Mapping
1517
from typing import Optional
1618

1719
import pytz
1820
import saneyaml
1921
from dateutil import parser as dateparser
22+
from django.db.models.query import QuerySet
2023
from packageurl import PackageURL
2124
from univers.version_range import RANGE_CLASS_BY_SCHEMES
2225
from univers.version_range import VersionRange
@@ -27,11 +30,23 @@
2730
from vulnerabilities.importer import AffectedPackage
2831
from vulnerabilities.importer import GitImporter
2932
from vulnerabilities.importer import Reference
33+
from vulnerabilities.importer import UnMergeablePackageError
34+
from vulnerabilities.improver import Improver
35+
from vulnerabilities.improver import Inference
36+
from vulnerabilities.models import Advisory
37+
from vulnerabilities.package_managers import VERSION_API_CLASSES_BY_PACKAGE_TYPE
38+
from vulnerabilities.package_managers import GoproxyVersionAPI
39+
from vulnerabilities.package_managers import VersionAPI
40+
from vulnerabilities.package_managers import get_api_package_name
41+
from vulnerabilities.utils import AffectedPackage as LegacyAffectedPackage
3042
from vulnerabilities.utils import build_description
43+
from vulnerabilities.utils import get_affected_packages_by_patched_package
44+
from vulnerabilities.utils import get_cwe_id
45+
from vulnerabilities.utils import nearest_patched_package
46+
from vulnerabilities.utils import resolve_version_range
3147

3248
logger = logging.getLogger(__name__)
3349

34-
3550
PURL_TYPE_BY_GITLAB_SCHEME = {
3651
# "conan": "conan",
3752
"gem": "gem",
@@ -44,7 +59,6 @@
4459
"pypi": "pypi",
4560
}
4661

47-
4862
GITLAB_SCHEME_BY_PURL_TYPE = {v: k for k, v in PURL_TYPE_BY_GITLAB_SCHEME.items()}
4963

5064

@@ -188,6 +202,10 @@ def parse_gitlab_advisory(file):
188202
summary = build_description(gitlab_advisory.get("title"), gitlab_advisory.get("description"))
189203
urls = gitlab_advisory.get("urls")
190204
references = [Reference.from_url(u) for u in urls]
205+
206+
cwe_ids = gitlab_advisory.get("cwe_ids") or []
207+
cwe_list = list(map(get_cwe_id, cwe_ids))
208+
191209
date_published = dateparser.parse(gitlab_advisory.get("pubdate"))
192210
date_published = date_published.replace(tzinfo=pytz.UTC)
193211
package_slug = gitlab_advisory.get("package_slug")
@@ -253,4 +271,102 @@ def parse_gitlab_advisory(file):
253271
references=references,
254272
date_published=date_published,
255273
affected_packages=affected_packages,
274+
weaknesses=cwe_list,
256275
)
276+
277+
278+
class GitLabBasicImprover(Improver):
279+
"""
280+
Get the nearest fixed_version and then resolve the version range with the help of all valid versions.
281+
Generate inference between all the affected packages and the fixed_version that fixes all those affected packages.
282+
283+
In case of gitlab advisory data we get a list of fixed_versions and a affected_version_range.
284+
Since we can not determine which package fixes which range.
285+
"""
286+
287+
def __init__(self) -> None:
288+
self.versions_fetcher_by_purl: Mapping[str, VersionAPI] = {}
289+
290+
@property
291+
def interesting_advisories(self) -> QuerySet:
292+
return Advisory.objects.filter(created_by=GitLabAPIImporter.qualified_name)
293+
294+
def get_package_versions(
295+
self, package_url: PackageURL, until: Optional[datetime] = None
296+
) -> List[str]:
297+
"""
298+
Return a list of `valid_versions` for the `package_url`
299+
"""
300+
api_name = get_api_package_name(purl=package_url)
301+
if not api_name:
302+
logger.error(f"Could not get versions for {package_url!r}")
303+
return []
304+
versions_fetcher = self.versions_fetcher_by_purl.get(package_url)
305+
if not versions_fetcher:
306+
versions_fetcher: VersionAPI = VERSION_API_CLASSES_BY_PACKAGE_TYPE[package_url.type]
307+
self.versions_fetcher_by_purl[package_url] = versions_fetcher()
308+
309+
versions_fetcher = self.versions_fetcher_by_purl[package_url]
310+
311+
self.versions_fetcher_by_purl[package_url] = versions_fetcher
312+
return versions_fetcher.get_until(package_name=api_name, until=until).valid_versions
313+
314+
def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]:
315+
"""
316+
Yield Inferences for the given advisory data
317+
"""
318+
if not advisory_data.affected_packages:
319+
return iter([])
320+
try:
321+
purl, affected_version_ranges, _ = AffectedPackage.merge(
322+
advisory_data.affected_packages
323+
)
324+
except UnMergeablePackageError:
325+
logger.error(f"Cannot merge with different purls {advisory_data.affected_packages!r}")
326+
return iter([])
327+
328+
pkg_type = purl.type
329+
pkg_namespace = purl.namespace
330+
pkg_name = purl.name
331+
if purl.type == "golang":
332+
# Problem with the Golang and Go that they provide full path
333+
# FIXME: We need to get the PURL subpath for Go module
334+
versions_fetcher = self.versions_fetcher_by_purl.get(purl)
335+
if not versions_fetcher:
336+
versions_fetcher = GoproxyVersionAPI()
337+
self.versions_fetcher_by_purl[purl] = versions_fetcher
338+
pkg_name = versions_fetcher.module_name_by_package_name.get(pkg_name, pkg_name)
339+
340+
valid_versions = self.get_package_versions(
341+
package_url=purl, until=advisory_data.date_published
342+
)
343+
for affected_version_range in affected_version_ranges:
344+
aff_vers, unaff_vers = resolve_version_range(
345+
affected_version_range=affected_version_range,
346+
package_versions=valid_versions,
347+
ignorable_versions=[],
348+
)
349+
affected_purls = [
350+
PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version)
351+
for version in aff_vers
352+
]
353+
354+
unaffected_purls = [
355+
PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version)
356+
for version in unaff_vers
357+
]
358+
359+
affected_packages: List[LegacyAffectedPackage] = nearest_patched_package(
360+
vulnerable_packages=affected_purls, resolved_packages=unaffected_purls
361+
)
362+
363+
for (
364+
fixed_package,
365+
affected_packages,
366+
) in get_affected_packages_by_patched_package(affected_packages).items():
367+
yield Inference.from_advisory_data(
368+
advisory_data, # We are getting all valid versions to get this inference
369+
confidence=100,
370+
affected_purls=affected_packages,
371+
fixed_purl=fixed_package,
372+
)

vulnerabilities/importers/redhat.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#
99

1010
import logging
11+
import re
1112
from typing import Dict
1213
from typing import Iterable
1314
from typing import List
@@ -23,6 +24,7 @@
2324
from vulnerabilities.importer import Reference
2425
from vulnerabilities.importer import VulnerabilitySeverity
2526
from vulnerabilities.rpm_utils import rpm_to_purl
27+
from vulnerabilities.utils import get_cwe_id
2628
from vulnerabilities.utils import get_item
2729
from vulnerabilities.utils import requests_with_5xx_retry
2830

@@ -61,7 +63,6 @@ def get_data_from_url(url):
6163

6264

6365
class RedhatImporter(Importer):
64-
6566
spdx_license_expression = "CC-BY-4.0"
6667
license_url = "https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0/html/red_hat_security_data_api/legal-notice"
6768

@@ -164,6 +165,11 @@ def to_advisory(advisory_data):
164165
scoring_elements=cvssv3_vector,
165166
)
166167
)
168+
cwe_list = []
169+
# cwe_string : CWE-409","CWE-121->CWE-787","(CWE-401|CWE-404)","(CWE-190|CWE-911)->CWE-416"
170+
cwe_string = advisory_data.get("CWE")
171+
if cwe_string:
172+
cwe_list = list(map(get_cwe_id, re.findall("CWE-[0-9]+", cwe_string)))
167173

168174
aliases = []
169175
alias = advisory_data.get("CVE")
@@ -177,4 +183,5 @@ def to_advisory(advisory_data):
177183
summary=advisory_data.get("bugzilla_description") or "",
178184
affected_packages=affected_packages,
179185
references=references,
186+
weaknesses=cwe_list,
180187
)

vulnerabilities/improvers/default.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]:
5454
affected_purls=affected_purls,
5555
fixed_purl=None,
5656
references=advisory_data.references,
57+
weaknesses=advisory_data.weaknesses,
5758
)
5859
else:
5960
for fixed_purl in fixed_purls or []:
@@ -64,6 +65,7 @@ def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]:
6465
affected_purls=affected_purls,
6566
fixed_purl=fixed_purl,
6667
references=advisory_data.references,
68+
weaknesses=advisory_data.weaknesses,
6769
)
6870

6971
else:

vulnerabilities/tests/test_data/gitlab/composer-expected.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@
2525
}
2626
],
2727
"date_published": "2018-03-15T00:00:00+00:00",
28-
"weaknesses": []
28+
"weaknesses": [1035,937]
2929
}

vulnerabilities/tests/test_data/gitlab/composer-improver-expected.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@
2222
"severities": []
2323
}
2424
],
25-
"weaknesses": []
25+
"weaknesses": [1035,937]
2626
}
2727
]

vulnerabilities/tests/test_data/gitlab/golang-expected.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@
3131
}
3232
],
3333
"date_published": "2021-05-20T00:00:00+00:00",
34-
"weaknesses": []
34+
"weaknesses": [1035,937]
3535
}

vulnerabilities/tests/test_data/gitlab/golang-improver-expected.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"severities": []
3838
}
3939
],
40-
"weaknesses": []
40+
"weaknesses": [1035,937]
4141
},
4242
{
4343
"vulnerability_id": null,
@@ -68,6 +68,6 @@
6868
"severities": []
6969
}
7070
],
71-
"weaknesses": []
71+
"weaknesses": [1035,937]
7272
}
7373
]

vulnerabilities/tests/test_data/gitlab/maven-expected.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@
4646
}
4747
],
4848
"date_published": "2021-11-15T00:00:00+00:00",
49-
"weaknesses": []
49+
"weaknesses": [1035,937,94]
5050
}

vulnerabilities/tests/test_data/gitlab/maven-improver-expected.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
"severities": []
101101
}
102102
],
103-
"weaknesses": []
103+
"weaknesses": [1035,937,94]
104104
},
105105
{
106106
"vulnerability_id": null,
@@ -146,6 +146,6 @@
146146
"severities": []
147147
}
148148
],
149-
"weaknesses": []
149+
"weaknesses": [1035,937,94]
150150
}
151151
]

vulnerabilities/tests/test_data/gitlab/npm-expected.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@
3636
}
3737
],
3838
"date_published": "2020-06-05T00:00:00+00:00",
39-
"weaknesses": []
39+
"weaknesses": [1035,937]
4040
}

0 commit comments

Comments
 (0)