Skip to content

Commit 8b4bea6

Browse files
authored
Merge branch 'main' into linux-kernel
2 parents 7ece964 + 71408a5 commit 8b4bea6

11 files changed

Lines changed: 723 additions & 0 deletions

File tree

vulnerabilities/improvers/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
from vulnerabilities.pipelines import flag_ghost_packages
1919
from vulnerabilities.pipelines import populate_vulnerability_summary_pipeline
2020
from vulnerabilities.pipelines import remove_duplicate_advisories
21+
from vulnerabilities.pipelines.v2_improvers import archive_urls
2122
from vulnerabilities.pipelines.v2_improvers import collect_ssvc_trees
2223
from vulnerabilities.pipelines.v2_improvers import compute_advisory_todo as compute_advisory_todo_v2
2324
from vulnerabilities.pipelines.v2_improvers import compute_package_risk as compute_package_risk_v2
2425
from vulnerabilities.pipelines.v2_improvers import (
2526
computer_package_version_rank as compute_version_rank_v2,
2627
)
2728
from vulnerabilities.pipelines.v2_improvers import enhance_with_exploitdb as exploitdb_v2
29+
from vulnerabilities.pipelines.v2_improvers import enhance_with_github_poc
2830
from vulnerabilities.pipelines.v2_improvers import enhance_with_kev as enhance_with_kev_v2
2931
from vulnerabilities.pipelines.v2_improvers import (
3032
enhance_with_metasploit as enhance_with_metasploit_v2,
@@ -72,8 +74,10 @@
7274
unfurl_version_range_v2.UnfurlVersionRangePipeline,
7375
collect_ssvc_trees.CollectSSVCPipeline,
7476
relate_severities.RelateSeveritiesPipeline,
77+
archive_urls.ArchiveImproverPipeline,
7578
group_advisories_for_packages.GroupAdvisoriesForPackages,
7679
compute_advisory_todo_v2.ComputeToDo,
7780
reference_collect_commits.CollectReferencesFixCommitsPipeline,
81+
enhance_with_github_poc.GithubPocsImproverPipeline,
7882
]
7983
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 5.2.11 on 2026-05-15 00:41
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("vulnerabilities", "0127_advisorytodov2_todorelatedadvisoryv2_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="advisoryreference",
15+
name="archive_url",
16+
field=models.URLField(
17+
help_text="URL to the backup vulnerability reference", max_length=1024, null=True
18+
),
19+
),
20+
]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Generated by Django 5.2.11 on 2026-05-15 01:38
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("vulnerabilities", "0128_advisoryreference_archive_url"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="AdvisoryPOC",
16+
fields=[
17+
(
18+
"id",
19+
models.AutoField(
20+
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
21+
),
22+
),
23+
(
24+
"created_at",
25+
models.DateTimeField(
26+
blank=True,
27+
help_text="The date and time when this POC was created.",
28+
null=True,
29+
),
30+
),
31+
(
32+
"updated_at",
33+
models.DateTimeField(
34+
blank=True,
35+
help_text="The date and time when this POC was last updated.",
36+
null=True,
37+
),
38+
),
39+
(
40+
"url",
41+
models.URLField(
42+
help_text="The URL of the PoC, such as a repository or resource link."
43+
),
44+
),
45+
(
46+
"is_confirmed",
47+
models.BooleanField(
48+
default=False,
49+
help_text="Indicates whether this POC has been verified or confirmed.",
50+
),
51+
),
52+
(
53+
"advisory",
54+
models.ForeignKey(
55+
on_delete=django.db.models.deletion.CASCADE,
56+
related_name="pocs",
57+
to="vulnerabilities.advisoryv2",
58+
),
59+
),
60+
],
61+
),
62+
]

vulnerabilities/models.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2738,6 +2738,12 @@ class AdvisoryReference(models.Model):
27382738
help_text="URL to the vulnerability reference",
27392739
)
27402740

2741+
archive_url = models.URLField(
2742+
max_length=1024,
2743+
null=True,
2744+
help_text="URL to the backup vulnerability reference",
2745+
)
2746+
27412747
ADVISORY = "advisory"
27422748
EXPLOIT = "exploit"
27432749
COMMIT = "commit"
@@ -3858,3 +3864,29 @@ class GroupedAdvisory(NamedTuple):
38583864
weighted_severity: Optional[float]
38593865
exploitability: Optional[float]
38603866
risk_score: Optional[float]
3867+
3868+
3869+
class AdvisoryPOC(models.Model):
3870+
"""
3871+
An AdvisoryPOC (Proof of Concept) demonstrating how a vulnerability related to an advisory can be exploited.
3872+
"""
3873+
3874+
advisory = models.ForeignKey(
3875+
"AdvisoryV2",
3876+
related_name="pocs",
3877+
on_delete=models.CASCADE,
3878+
)
3879+
3880+
created_at = models.DateTimeField(
3881+
null=True, blank=True, help_text="The date and time when this POC was created."
3882+
)
3883+
3884+
updated_at = models.DateTimeField(
3885+
null=True, blank=True, help_text="The date and time when this POC was last updated."
3886+
)
3887+
3888+
url = models.URLField(help_text="The URL of the PoC, such as a repository or resource link.")
3889+
3890+
is_confirmed = models.BooleanField(
3891+
default=False, help_text="Indicates whether this POC has been verified or confirmed."
3892+
)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright (c) nexB Inc. and others. All rights reserved.
2+
# VulnerableCode is a trademark of nexB Inc.
3+
# SPDX-License-Identifier: Apache-2.0
4+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
5+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
6+
# See https://aboutcode.org for more information about nexB OSS projects.
7+
#
8+
9+
import time
10+
11+
import requests
12+
13+
from vulnerabilities.models import AdvisoryReference
14+
from vulnerabilities.pipelines import VulnerableCodePipeline
15+
16+
17+
class ArchiveImproverPipeline(VulnerableCodePipeline):
18+
"""
19+
Archive Improver Pipeline
20+
"""
21+
22+
pipeline_id = "archive_improver_pipeline"
23+
24+
@classmethod
25+
def steps(cls):
26+
return (cls.archive_urls,)
27+
28+
def archive_urls(self):
29+
"""Get and stores archive URLs for AdvisoryReferences, flagging missing ones as NO_ARCHIVE"""
30+
advisory_refs = (
31+
AdvisoryReference.objects.filter(archive_url__isnull=True)
32+
.exclude(archive_url="NO_ARCHIVE")
33+
.only("id", "url")
34+
)
35+
36+
for advisory_ref in advisory_refs:
37+
url = advisory_ref.url
38+
if not url or not url.startswith("http"):
39+
continue
40+
41+
archive_url = self.get_archival(url)
42+
if not archive_url:
43+
AdvisoryReference.objects.filter(id=advisory_ref.id).update(
44+
archive_url="NO_ARCHIVE"
45+
)
46+
self.log(f"URL unreachable or returned no archive url: {url}")
47+
continue
48+
self.log(f"Found Archived Reference URL: {archive_url}")
49+
AdvisoryReference.objects.filter(id=advisory_ref.id).update(archive_url=archive_url)
50+
51+
def get_archival(self, url):
52+
self.log(f"Searching for archive URL for this Reference URL: {url}")
53+
try:
54+
archive_response = requests.get(
55+
url=f"https://web.archive.org/web/{url}", allow_redirects=True
56+
)
57+
time.sleep(30)
58+
if archive_response.status_code == 200:
59+
return archive_response.url
60+
elif archive_response.status_code == 403:
61+
self.log(f"Wayback Machine permission denied for '{url}'.")
62+
except requests.RequestException as e:
63+
self.log(f"Error checking existing archival: {e}")
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+
10+
import json
11+
from pathlib import Path
12+
13+
from aboutcode.pipeline import LoopProgress
14+
from fetchcode.vcs import fetch_via_vcs
15+
16+
from vulnerabilities.models import AdvisoryAlias
17+
from vulnerabilities.models import AdvisoryPOC
18+
from vulnerabilities.models import AdvisoryV2
19+
from vulnerabilities.pipelines import VulnerableCodePipeline
20+
21+
22+
class GithubPocsImproverPipeline(VulnerableCodePipeline):
23+
"""
24+
Pipeline to Collect an exploit-PoCs repository, parse exploit JSON files,
25+
match them to advisories via aliases, and update/create POC records.
26+
"""
27+
28+
pipeline_id = "enhance_with_github_poc"
29+
repo_url = "git+https://github.com/nomi-sec/PoC-in-GitHub"
30+
31+
@classmethod
32+
def steps(cls):
33+
return (
34+
cls.clone_repo,
35+
cls.collect_and_store_exploits,
36+
cls.clean_downloads,
37+
)
38+
39+
def clone_repo(self):
40+
self.log(f"Cloning `{self.repo_url}`")
41+
self.vcs_response = fetch_via_vcs(self.repo_url)
42+
43+
def collect_and_store_exploits(self):
44+
"""
45+
Parse PoC JSON files, match them to advisories via aliases,
46+
and create or update related exploit records.
47+
"""
48+
49+
base_directory = Path(self.vcs_response.dest_dir)
50+
json_files = list(base_directory.rglob("**/*.json"))
51+
exploits_count = len(json_files)
52+
self.log(f"Enhancing the vulnerability with {exploits_count:,d} exploit records")
53+
progress = LoopProgress(total_iterations=exploits_count, logger=self.log)
54+
for file_path in progress.iter(json_files):
55+
with open(file_path, "r") as f:
56+
try:
57+
exploits_data = json.load(f)
58+
except json.JSONDecodeError:
59+
self.log(f"Invalid JSON in {file_path}, skipping.")
60+
continue
61+
62+
filename = file_path.stem.strip()
63+
64+
advisories = set()
65+
try:
66+
if alias := AdvisoryAlias.objects.get(alias=filename):
67+
for adv in alias.advisories.all():
68+
advisories.add(adv)
69+
else:
70+
advs = AdvisoryV2.objects.filter(advisory_id=filename).latest_per_avid()
71+
for adv in advs:
72+
advisories.add(adv)
73+
except AdvisoryAlias.DoesNotExist:
74+
self.log(f"Advisory {filename} not found.")
75+
continue
76+
77+
for advisory in advisories:
78+
for exploit_data in exploits_data:
79+
exploit_repo_url = exploit_data.get("html_url")
80+
if not exploit_repo_url:
81+
continue
82+
83+
AdvisoryPOC.objects.update_or_create(
84+
advisory=advisory,
85+
url=exploit_repo_url,
86+
defaults={
87+
"created_at": exploit_data.get("created_at"),
88+
"updated_at": exploit_data.get("updated_at"),
89+
},
90+
)
91+
92+
self.log(f"Successfully added {exploits_count:,d} poc exploit advisory")
93+
94+
def clean_downloads(self):
95+
if self.vcs_response:
96+
self.log(f"Removing cloned repository")
97+
self.vcs_response.delete()
98+
99+
def on_failure(self):
100+
self.clean_downloads()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (c) nexB Inc. and others. All rights reserved.
2+
# VulnerableCode is a trademark of nexB Inc.
3+
# SPDX-License-Identifier: Apache-2.0
4+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
5+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
6+
# See https://aboutcode.org for more information about nexB OSS projects.
7+
#
8+
9+
from unittest.mock import MagicMock
10+
11+
import pytest
12+
13+
from vulnerabilities.models import AdvisoryReference
14+
from vulnerabilities.pipelines.v2_improvers.archive_urls import ArchiveImproverPipeline
15+
16+
17+
@pytest.mark.django_db
18+
def test_archive_urls_pipeline(monkeypatch):
19+
advisory = AdvisoryReference.objects.create(url="https://example.com", archive_url=None)
20+
21+
mock_response = MagicMock()
22+
mock_response.status_code = 200
23+
mock_response.url = "https://web.archive.org/web/20250519082420/https://example.com"
24+
25+
monkeypatch.setattr(
26+
f"vulnerabilities.pipelines.v2_improvers.archive_urls.time.sleep", MagicMock()
27+
)
28+
monkeypatch.setattr(
29+
f"vulnerabilities.pipelines.v2_improvers.archive_urls.requests.get",
30+
MagicMock(return_value=mock_response),
31+
)
32+
33+
pipeline = ArchiveImproverPipeline()
34+
pipeline.archive_urls()
35+
36+
advisory.refresh_from_db()
37+
assert advisory.archive_url == "https://web.archive.org/web/20250519082420/https://example.com"

0 commit comments

Comments
 (0)