Skip to content

Commit 6046218

Browse files
authored
Merge branch 'main' into add-almalinux-advisories
2 parents 6300da1 + 432a7d4 commit 6046218

40 files changed

+2464
-666
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ else
4242
SUDO_POSTGRES=
4343
endif
4444

45+
ifeq ($(UNAME), Darwin)
46+
GET_SECRET_KEY=`head /dev/urandom | base64 | head -c50`
47+
endif
48+
4549
virtualenv:
4650
@echo "-> Bootstrap the virtualenv with PYTHON_EXE=${PYTHON_EXE}"
4751
@${PYTHON_EXE} ${VIRTUALENV_PYZ} --never-download --no-periodic-update ${VENV}

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ version: "3"
33
services:
44
db:
55
image: postgres:13
6+
command: -c config_file=/etc/postgresql/postgresql.conf
67
env_file:
78
- docker.env
89
volumes:
910
- db_data:/var/lib/postgresql/data/
11+
- ./etc/postgresql/postgresql.conf:/etc/postgresql/postgresql.conf
1012

1113
vulnerablecode:
1214
build: .

etc/postgresql/postgresql.conf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Default configuration for development build
2+
# DB Version: 13
3+
# OS Type: linux
4+
# DB Type: development
5+
# Data Storage: local
6+
7+
listen_addresses = '*'
8+
max_connections = 100
9+
shared_buffers = 128MB
10+
dynamic_shared_memory_type = posix
11+
max_wal_size = 1GB
12+
min_wal_size = 80MB

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ dateparser==1.1.1
2727
decorator==5.1.1
2828
defusedxml==0.7.1
2929
distro==1.7.0
30-
Django==4.2.16
30+
Django==4.2.17
3131
django-crispy-forms==2.3
3232
django-environ==0.11.2
3333
django-filter==24.3
@@ -53,7 +53,7 @@ ipython==8.10.0
5353
isort==5.10.1
5454
itypes==1.2.0
5555
jedi==0.18.1
56-
Jinja2==3.1.4
56+
Jinja2==3.1.5
5757
jsonschema==3.2.0
5858
license-expression==30.3.1
5959
lxml==4.9.1

vulnerabilities/api_v2.py

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

1010

11+
from django.db.models import Prefetch
1112
from django_filters import rest_framework as filters
1213
from drf_spectacular.utils import OpenApiParameter
1314
from drf_spectacular.utils import extend_schema
@@ -20,8 +21,7 @@
2021
from rest_framework.response import Response
2122
from rest_framework.reverse import reverse
2223

23-
from vulnerabilities.api import PackageFilterSet
24-
from vulnerabilities.api import VulnerabilitySeveritySerializer
24+
from vulnerabilities.models import CodeFix
2525
from vulnerabilities.models import Package
2626
from vulnerabilities.models import Vulnerability
2727
from vulnerabilities.models import VulnerabilityReference
@@ -195,7 +195,31 @@ class Meta:
195195
]
196196

197197
def get_affected_by_vulnerabilities(self, obj):
198-
return [vuln.vulnerability_id for vuln in obj.affected_by_vulnerabilities.all()]
198+
"""
199+
Return a dictionary with vulnerabilities as keys and their details, including fixed_by_packages.
200+
"""
201+
result = {}
202+
request = self.context.get("request")
203+
for vuln in getattr(obj, "prefetched_affected_vulnerabilities", []):
204+
fixed_by_package = vuln.fixed_by_packages.first()
205+
purl = None
206+
if fixed_by_package:
207+
purl = fixed_by_package.package_url
208+
# Get code fixed for a vulnerability
209+
code_fixes = CodeFix.objects.filter(
210+
affected_package_vulnerability__vulnerability=vuln
211+
).distinct()
212+
code_fix_urls = [
213+
reverse("codefix-detail", args=[code_fix.id], request=request)
214+
for code_fix in code_fixes
215+
]
216+
217+
result[vuln.vulnerability_id] = {
218+
"vulnerability_id": vuln.vulnerability_id,
219+
"fixed_by_packages": purl,
220+
"code_fixes": code_fix_urls,
221+
}
222+
return result
199223

200224
def get_fixing_vulnerabilities(self, obj):
201225
# Ghost package should not fix any vulnerability.
@@ -233,7 +257,13 @@ class PackageV2FilterSet(filters.FilterSet):
233257

234258

235259
class PackageV2ViewSet(viewsets.ReadOnlyModelViewSet):
236-
queryset = Package.objects.all()
260+
queryset = Package.objects.all().prefetch_related(
261+
Prefetch(
262+
"affected_by_vulnerabilities",
263+
queryset=Vulnerability.objects.prefetch_related("fixed_by_packages"),
264+
to_attr="prefetched_affected_vulnerabilities",
265+
)
266+
)
237267
serializer_class = PackageV2Serializer
238268
filter_backends = (filters.DjangoFilterBackend,)
239269
filterset_class = PackageV2FilterSet
@@ -503,3 +533,76 @@ def lookup(self, request):
503533

504534
qs = self.get_queryset().for_purls([purl]).with_is_vulnerable()
505535
return Response(PackageV2Serializer(qs, many=True, context={"request": request}).data)
536+
537+
538+
class CodeFixSerializer(serializers.ModelSerializer):
539+
"""
540+
Serializer for the CodeFix model.
541+
Provides detailed information about a code fix.
542+
"""
543+
544+
affected_vulnerability_id = serializers.CharField(
545+
source="affected_package_vulnerability.vulnerability.vulnerability_id",
546+
read_only=True,
547+
help_text="ID of the affected vulnerability.",
548+
)
549+
affected_package_purl = serializers.CharField(
550+
source="affected_package_vulnerability.package.package_url",
551+
read_only=True,
552+
help_text="PURL of the affected package.",
553+
)
554+
fixed_package_purl = serializers.CharField(
555+
source="fixed_package_vulnerability.package.package_url",
556+
read_only=True,
557+
help_text="PURL of the fixing package (if available).",
558+
)
559+
created_at = serializers.DateTimeField(
560+
format="%Y-%m-%dT%H:%M:%SZ",
561+
read_only=True,
562+
help_text="Timestamp when the code fix was created.",
563+
)
564+
updated_at = serializers.DateTimeField(
565+
format="%Y-%m-%dT%H:%M:%SZ",
566+
read_only=True,
567+
help_text="Timestamp when the code fix was last updated.",
568+
)
569+
570+
class Meta:
571+
model = CodeFix
572+
fields = [
573+
"id",
574+
"commits",
575+
"pulls",
576+
"downloads",
577+
"patch",
578+
"affected_vulnerability_id",
579+
"affected_package_purl",
580+
"fixed_package_purl",
581+
"notes",
582+
"references",
583+
"is_reviewed",
584+
"created_at",
585+
"updated_at",
586+
]
587+
read_only_fields = ["created_at", "updated_at"]
588+
589+
590+
class CodeFixViewSet(viewsets.ReadOnlyModelViewSet):
591+
"""
592+
API endpoint that allows viewing CodeFix entries.
593+
"""
594+
595+
queryset = CodeFix.objects.all()
596+
serializer_class = CodeFixSerializer
597+
598+
def get_queryset(self):
599+
"""
600+
Optionally filter by vulnerability ID.
601+
"""
602+
queryset = super().get_queryset()
603+
vulnerability_id = self.request.query_params.get("vulnerability_id")
604+
if vulnerability_id:
605+
queryset = queryset.filter(
606+
affected_package_vulnerability__vulnerability__vulnerability_id=vulnerability_id
607+
)
608+
return queryset

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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10-
from vulnerabilities.importers import alpine_linux
1110
from vulnerabilities.importers import apache_httpd
1211
from vulnerabilities.importers import apache_kafka
1312
from vulnerabilities.importers import apache_tomcat
@@ -36,6 +35,7 @@
3635
from vulnerabilities.importers import xen
3736
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipeline
3837
from vulnerabilities.pipelines import almalinux_importer
38+
from vulnerabilities.pipelines import alpine_linux_importer
3939
from vulnerabilities.pipelines import github_importer
4040
from vulnerabilities.pipelines import gitlab_importer
4141
from vulnerabilities.pipelines import nginx_importer
@@ -45,7 +45,6 @@
4545
from vulnerabilities.pipelines import pysec_importer
4646

4747
IMPORTERS_REGISTRY = [
48-
alpine_linux.AlpineImporter,
4948
openssl.OpensslImporter,
5049
redhat.RedhatImporter,
5150
debian.DebianImporter,
@@ -80,6 +79,7 @@
8079
nvd_importer.NVDImporterPipeline,
8180
pysec_importer.PyPIImporterPipeline,
8281
almalinux_importer.AlmalinuxImporterPipeline,
82+
alpine_linux_importer.AlpineLinuxImporterPipeline,
8383
]
8484

8585
IMPORTERS_REGISTRY = {

vulnerabilities/importers/apache_httpd.py

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

1010
import logging
11+
import re
1112
import urllib
1213

1314
import requests
@@ -23,6 +24,8 @@
2324
from vulnerabilities.importer import Reference
2425
from vulnerabilities.importer import VulnerabilitySeverity
2526
from vulnerabilities.severity_systems import APACHE_HTTPD
27+
from vulnerabilities.utils import create_weaknesses_list
28+
from vulnerabilities.utils import cwe_regex
2629
from vulnerabilities.utils import get_item
2730

2831
logger = logging.getLogger(__name__)
@@ -102,11 +105,14 @@ def to_advisory(self, data):
102105
)
103106
)
104107

108+
weaknesses = get_weaknesses(data)
109+
105110
return AdvisoryData(
106111
aliases=[alias],
107112
summary=description or "",
108113
affected_packages=affected_packages,
109114
references=[reference],
115+
weaknesses=weaknesses,
110116
url=reference.url,
111117
)
112118

@@ -152,3 +158,97 @@ def fetch_links(url):
152158
continue
153159
links.append(urllib.parse.urljoin(url, link))
154160
return links
161+
162+
163+
def get_weaknesses(cve_data):
164+
"""
165+
Extract CWE IDs from CVE data.
166+
167+
Args:
168+
cve_data (dict): The CVE data in a dictionary format.
169+
170+
Returns:
171+
List[int]: A list of unique CWE IDs.
172+
173+
Examples:
174+
>>> mock_cve_data1 = {
175+
... "containers": {
176+
... "cna": {
177+
... "providerMetadata": {
178+
... "orgId": "f0158376-9dc2-43b6-827c-5f631a4d8d09"
179+
... },
180+
... "title": "mod_macro buffer over-read",
181+
... "problemTypes": [
182+
... {
183+
... "descriptions": [
184+
... {
185+
... "description": "CWE-125 Out-of-bounds Read",
186+
... "lang": "en",
187+
... "cweId": "CWE-125",
188+
... "type": "CWE"
189+
... }
190+
... ]
191+
... }
192+
... ]
193+
... }
194+
... }
195+
... }
196+
>>> mock_cve_data2 = {
197+
... "data_type": "CVE",
198+
... "data_format": "MITRE",
199+
... "data_version": "4.0",
200+
... "generator": {
201+
... "engine": "Vulnogram 0.0.9"
202+
... },
203+
... "CVE_data_meta": {
204+
... "ID": "CVE-2022-28614",
205+
... "ASSIGNER": "security@apache.org",
206+
... "TITLE": "read beyond bounds via ap_rwrite() ",
207+
... "STATE": "PUBLIC"
208+
... },
209+
... "problemtype": {
210+
... "problemtype_data": [
211+
... {
212+
... "description": [
213+
... {
214+
... "lang": "eng",
215+
... "value": "CWE-190 Integer Overflow or Wraparound"
216+
... }
217+
... ]
218+
... },
219+
... {
220+
... "description": [
221+
... {
222+
... "lang": "eng",
223+
... "value": "CWE-200 Exposure of Sensitive Information to an Unauthorized Actor"
224+
... }
225+
... ]
226+
... }
227+
... ]
228+
... }
229+
... }
230+
231+
>>> get_weaknesses(mock_cve_data1)
232+
[125]
233+
234+
>>> get_weaknesses(mock_cve_data2)
235+
[190, 200]
236+
"""
237+
alias = get_item(cve_data, "CVE_data_meta", "ID")
238+
cwe_strings = []
239+
if alias:
240+
problemtype_data = get_item(cve_data, "problemtype", "problemtype_data") or []
241+
for problem in problemtype_data:
242+
for desc in problem.get("description", []):
243+
value = desc.get("value", "")
244+
cwe_id_string_list = re.findall(cwe_regex, value)
245+
cwe_strings.extend(cwe_id_string_list)
246+
else:
247+
problemTypes = cve_data.get("containers", {}).get("cna", {}).get("problemTypes", [])
248+
descriptions = problemTypes[0].get("descriptions", []) if len(problemTypes) > 0 else []
249+
for description in descriptions:
250+
cwe_id_string = description.get("cweId", "")
251+
cwe_strings.append(cwe_id_string)
252+
253+
weaknesses = create_weaknesses_list(cwe_strings)
254+
return weaknesses

0 commit comments

Comments
 (0)