Skip to content

Commit 5f0b3cb

Browse files
committed
Fixed Issues
Signed-off-by: Shrish0098 <shrish409@gmail.com>
1 parent aeb07ec commit 5f0b3cb

File tree

11 files changed

+725
-450
lines changed

11 files changed

+725
-450
lines changed

vulnerabilities/importer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def to_dict(self):
111111
def from_dict(cls, ref: dict):
112112
return cls(
113113
reference_id=ref["reference_id"],
114-
reference_type=ref["reference_type"],
114+
reference_type=ref.get("reference_type") or "",
115115
url=ref["url"],
116116
severities=[
117117
VulnerabilitySeverity.from_dict(severity) for severity in ref["severities"]

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
9-
109
from vulnerabilities.importers import apache_httpd
1110
from vulnerabilities.importers import apache_kafka
1211
from vulnerabilities.importers import apache_tomcat
@@ -33,6 +32,7 @@
3332
from vulnerabilities.importers import ubuntu_usn
3433
from vulnerabilities.importers import vulnrichment
3534
from vulnerabilities.importers import xen
35+
from vulnerabilities.importers import liferay
3636
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipeline
3737
from vulnerabilities.pipelines import alpine_linux_importer
3838
from vulnerabilities.pipelines import github_importer
@@ -78,6 +78,7 @@
7878
nvd_importer.NVDImporterPipeline,
7979
pysec_importer.PyPIImporterPipeline,
8080
alpine_linux_importer.AlpineLinuxImporterPipeline,
81+
anchore_importer.AnchoreImporterPipeline,
8182
]
8283

8384
IMPORTERS_REGISTRY = {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
import requests
10+
from bs4 import BeautifulSoup
11+
from packageurl import PackageURL
12+
13+
from vulnerabilities.importer import AdvisoryData
14+
from vulnerabilities.importer import Importer
15+
from vulnerabilities.importer import VulnerabilityReference
16+
17+
18+
class LiferayImporter(Importer):
19+
"""
20+
Importer for Liferay advisories.
21+
"""
22+
spdx_license_identifier = "CC-BY-SA-4.0" # License for Liferay's data
23+
24+
def fetch(self):
25+
"""
26+
Fetches the HTML content from the Liferay Known Vulnerabilities page.
27+
"""
28+
url = "https://liferay.dev/portal/security/known-vulnerabilities"
29+
response = requests.get(url)
30+
response.raise_for_status()
31+
return response.text
32+
33+
def parse(self, html):
34+
"""
35+
Parses the fetched HTML and extracts vulnerability data.
36+
Returns a list of AdvisoryData objects.
37+
"""
38+
soup = BeautifulSoup(html, "html.parser")
39+
advisories = []
40+
41+
# Locate the table. (Adjust the selector if the page structure changes.)
42+
table = soup.find("table")
43+
if not table:
44+
return advisories
45+
46+
# Iterate over each row in the table body.
47+
tbody = table.find("tbody")
48+
if not tbody:
49+
return advisories
50+
51+
for row in tbody.find_all("tr"):
52+
cells = row.find_all("td")
53+
if len(cells) < 5:
54+
continue
55+
56+
# Extract each field by cell order.
57+
vulnerability_id = cells[0].get_text(strip=True)
58+
affected_versions = cells[1].get_text(strip=True)
59+
description = cells[2].get_text(strip=True)
60+
severity = cells[3].get_text(strip=True)
61+
62+
# Extract references – there may be multiple links in the cell.
63+
references = []
64+
for a in cells[4].find_all("a", href=True):
65+
ref_url = a["href"].strip()
66+
if ref_url:
67+
references.append(VulnerabilityReference(url=ref_url))
68+
69+
# Create PackageURL objects for affected versions.
70+
affected_packages = []
71+
for version in affected_versions.split(","):
72+
version = version.strip()
73+
if version:
74+
affected_packages.append(
75+
PackageURL(
76+
type="liferay",
77+
name="liferay-portal",
78+
version=version,
79+
)
80+
)
81+
82+
# Create an AdvisoryData object.
83+
advisories.append(
84+
AdvisoryData(
85+
aliases=[vulnerability_id],
86+
summary=description,
87+
affected_packages=affected_packages,
88+
references=references,
89+
severity=severity,
90+
)
91+
)
92+
93+
return advisories
94+
95+
def advisory_data(self):
96+
"""
97+
Fetches and parses the data, returning a list of AdvisoryData objects.
98+
"""
99+
html = self.fetch()
100+
return self.parse(html)

vulnerabilities/models.py

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,23 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10+
import csv
1011
import hashlib
1112
import json
1213
import logging
14+
import xml.etree.ElementTree as ET
1315
from contextlib import suppress
1416
from functools import cached_property
17+
from itertools import groupby
18+
from operator import attrgetter
1519
from typing import Union
1620

21+
from cvss.exceptions import CVSS2MalformedError
22+
from cvss.exceptions import CVSS3MalformedError
23+
from cvss.exceptions import CVSS4MalformedError
1724
from cwe2.database import Database
25+
from cwe2.mappings import xml_database_path
26+
from cwe2.weakness import Weakness as DBWeakness
1827
from django.contrib.auth import get_user_model
1928
from django.contrib.auth.models import UserManager
2029
from django.core import exceptions
@@ -41,8 +50,8 @@
4150
from univers.version_range import AlpineLinuxVersionRange
4251
from univers.versions import Version
4352

44-
from aboutcode import hashid
4553
from vulnerabilities import utils
54+
from vulnerabilities.severity_systems import EPSS
4655
from vulnerabilities.severity_systems import SCORING_SYSTEMS
4756
from vulnerabilities.utils import normalize_purl
4857
from vulnerabilities.utils import purl_to_dict
@@ -371,6 +380,127 @@ def get_related_purls(self):
371380
"""
372381
return [p.package_url for p in self.packages.distinct().all()]
373382

383+
def aggregate_fixed_and_affected_packages(self):
384+
from vulnerabilities.utils import get_purl_version_class
385+
386+
sorted_fixed_by_packages = self.fixed_by_packages.filter(is_ghost=False).order_by(
387+
"type", "namespace", "name", "qualifiers", "subpath"
388+
)
389+
390+
if sorted_fixed_by_packages:
391+
sorted_fixed_by_packages.first().calculate_version_rank
392+
393+
sorted_affected_packages = self.affected_packages.all()
394+
395+
if sorted_affected_packages:
396+
sorted_affected_packages.first().calculate_version_rank
397+
398+
grouped_fixed_by_packages = {
399+
key: list(group)
400+
for key, group in groupby(
401+
sorted_fixed_by_packages,
402+
key=attrgetter("type", "namespace", "name", "qualifiers", "subpath"),
403+
)
404+
}
405+
406+
all_affected_fixed_by_matches = []
407+
408+
for sorted_affected_package in sorted_affected_packages:
409+
affected_fixed_by_matches = {
410+
"affected_package": sorted_affected_package,
411+
"matched_fixed_by_packages": [],
412+
}
413+
414+
# Build the key to find matching group
415+
key = (
416+
sorted_affected_package.type,
417+
sorted_affected_package.namespace,
418+
sorted_affected_package.name,
419+
sorted_affected_package.qualifiers,
420+
sorted_affected_package.subpath,
421+
)
422+
423+
# Get matching group from pre-grouped fixed_by_packages
424+
matching_fixed_packages = grouped_fixed_by_packages.get(key, [])
425+
426+
# Get version classes for comparison
427+
affected_version_class = get_purl_version_class(sorted_affected_package)
428+
affected_version = affected_version_class(sorted_affected_package.version)
429+
430+
# Compare versions and filter valid matches
431+
matched_fixed_by_packages = [
432+
fixed_by_package.purl
433+
for fixed_by_package in matching_fixed_packages
434+
if get_purl_version_class(fixed_by_package)(fixed_by_package.version)
435+
> affected_version
436+
]
437+
438+
affected_fixed_by_matches["matched_fixed_by_packages"] = matched_fixed_by_packages
439+
all_affected_fixed_by_matches.append(affected_fixed_by_matches)
440+
return sorted_fixed_by_packages, sorted_affected_packages, all_affected_fixed_by_matches
441+
442+
def get_severity_vectors_and_values(self):
443+
"""
444+
Collect severity vectors and values, excluding EPSS scoring systems and handling errors gracefully.
445+
"""
446+
severity_vectors = []
447+
severity_values = set()
448+
449+
# Exclude EPSS scoring system
450+
base_severities = self.severities.exclude(scoring_system=EPSS.identifier)
451+
452+
# QuerySet for severities with valid scoring_elements and scoring_system in SCORING_SYSTEMS
453+
valid_scoring_severities = base_severities.filter(
454+
scoring_elements__isnull=False, scoring_system__in=SCORING_SYSTEMS.keys()
455+
)
456+
457+
for severity in valid_scoring_severities:
458+
try:
459+
vector_values = SCORING_SYSTEMS[severity.scoring_system].get(
460+
severity.scoring_elements
461+
)
462+
if vector_values:
463+
severity_vectors.append(vector_values)
464+
except (
465+
CVSS2MalformedError,
466+
CVSS3MalformedError,
467+
CVSS4MalformedError,
468+
NotImplementedError,
469+
) as e:
470+
logging.error(f"CVSSMalformedError for {severity.scoring_elements}: {e}")
471+
472+
valid_value_severities = base_severities.filter(value__isnull=False).exclude(value="")
473+
474+
severity_values.update(valid_value_severities.values_list("value", flat=True))
475+
476+
return severity_vectors, severity_values
477+
478+
479+
def get_cwes(self):
480+
"""Yield CWE Weakness objects"""
481+
for cwe_category in self.cwe_files:
482+
cwe_category.seek(0)
483+
reader = csv.DictReader(cwe_category)
484+
for row in reader:
485+
yield DBWeakness(*list(row.values())[0:-1])
486+
tree = ET.parse(xml_database_path)
487+
root = tree.getroot()
488+
for tag_num in [1, 2]: # Categories , Views
489+
tag = root[tag_num]
490+
for child in tag:
491+
yield DBWeakness(
492+
*[
493+
child.attrib["ID"],
494+
child.attrib.get("Name"),
495+
None,
496+
child.attrib.get("Status"),
497+
child[0].text,
498+
]
499+
)
500+
501+
502+
Database.get_cwes = get_cwes
503+
374504

375505
class Weakness(models.Model):
376506
"""
@@ -379,7 +509,15 @@ class Weakness(models.Model):
379509

380510
cwe_id = models.IntegerField(help_text="CWE id")
381511
vulnerabilities = models.ManyToManyField(Vulnerability, related_name="weaknesses")
382-
db = Database()
512+
513+
cwe_by_id = {}
514+
515+
def get_cwe(self, cwe_id):
516+
if not self.cwe_by_id:
517+
db = Database()
518+
for weakness in db.get_cwes():
519+
self.cwe_by_id[str(weakness.cwe_id)] = weakness
520+
return self.cwe_by_id[cwe_id]
383521

384522
@property
385523
def cwe(self):
@@ -391,7 +529,7 @@ def weakness(self):
391529
Return a queryset of Weakness for this vulnerability.
392530
"""
393531
try:
394-
weakness = self.db.get(self.cwe_id)
532+
weakness = self.get_cwe(str(self.cwe_id))
395533
return weakness
396534
except Exception as e:
397535
logger.warning(f"Could not find CWE {self.cwe_id}: {e}")

0 commit comments

Comments
 (0)