Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions vulnerabilities/importers/kde.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

import logging
import re
from typing import Iterable

import requests
from bs4 import BeautifulSoup

from vulnerabilities.importer import AdvisoryData
from vulnerabilities.importer import Importer
from vulnerabilities.importer import Reference

logger = logging.getLogger(__name__)


class KdeImporter(Importer):
spdx_license_expression = "LGPL-2.0-or-later"
license_url = "https://kde.org/community/whatiskde/licensing/"
importer_name = "KDE Importer"
url = "https://kde.org/info/security/"

def advisory_data(self) -> Iterable[AdvisoryData]:
advisory_urls = fetch_advisory_urls(self.url)
for advisory_url in advisory_urls:
try:
advisory_text = fetch_advisory_text(advisory_url)
advisory = parse_advisory(advisory_text, advisory_url)
if advisory:
yield advisory
except Exception as e:
logger.error(f"Error parsing advisory {advisory_url}: {e}")


def fetch_advisory_urls(index_url):
"""
Fetch all advisory URLs from the KDE security index page.

Returns:
List of full advisory URLs
"""
response = requests.get(index_url)
if response.status_code != 200:
logger.error(f"Failed to fetch {index_url}")
return []

soup = BeautifulSoup(response.content, "html.parser")
advisory_urls = []

# Find all links in the page
for link in soup.find_all("a"):
href = link.get("href", "")
# Advisory files end with .txt and start with advisory- or contain xpdf
if href.endswith(".txt") and ("advisory-" in href or "xpdf" in href):
# Convert relative URL to absolute
if href.startswith("./"):
href = href[2:]
full_url = f"https://kde.org/info/security/{href}"
advisory_urls.append(full_url)

return advisory_urls


def fetch_advisory_text(advisory_url):
"""
Fetch the text content of a KDE security advisory.

Returns:
Advisory text as string
"""
response = requests.get(advisory_url)
if response.status_code != 200:
logger.error(f"Failed to fetch {advisory_url}")
return None

return response.text


def parse_advisory(advisory_text, advisory_url):
"""
Parse a KDE security advisory and extract relevant information.

Args:
advisory_text: The full text content of the advisory
advisory_url: URL of the advisory

Returns:
AdvisoryData object or None
"""
if not advisory_text:
return None

# Extract CVE IDs (both CVE and old CAN format)
cve_pattern = r'C(?:VE|AN)-\d{4}-\d{4,7}'
cve_ids = re.findall(cve_pattern, advisory_text)

# Convert old CAN format to CVE format
aliases = []
for cve_id in cve_ids:
if cve_id.startswith("CAN-"):
# CAN is old format, convert to CVE
cve_id = cve_id.replace("CAN-", "CVE-")
if cve_id not in aliases:
aliases.append(cve_id)

# Extract summary/title
summary = extract_summary(advisory_text)

# Extract references
references = extract_references(advisory_text, advisory_url, aliases)

# Only create advisory if we have CVE IDs or meaningful content
if not aliases and not summary:
logger.warning(f"No CVE IDs or summary found in {advisory_url}")
return None

return AdvisoryData(
aliases=aliases,
summary=summary,
references=references,
url=advisory_url,
)


def extract_summary(advisory_text):
"""
Extract the summary/title from the advisory text.
"""
lines = advisory_text.split("\n")

# Try to find Title: field (new format)
for i, line in enumerate(lines):
if line.startswith("Title:"):
return line.replace("Title:", "").strip()

# Try to find KDE Security Advisory: line (old format)
for line in lines:
if "KDE Security Advisory:" in line:
return line.replace("KDE Security Advisory:", "").strip()

# Try to find first non-empty line after PGP header
skip_pgp = False
for line in lines:
if line.startswith("-----BEGIN PGP"):
skip_pgp = True
continue
if skip_pgp and line.strip() and not line.startswith("Hash:"):
skip_pgp = False
continue
if not skip_pgp and line.strip() and not line.startswith("---"):
# Return first meaningful line as summary
return line.strip()

return ""


def extract_references(advisory_text, advisory_url, aliases):
"""
Extract reference URLs from the advisory text.
"""
references = []

# Add the advisory itself as a reference
references.append(Reference(url=advisory_url))

# Add CVE references
for cve_id in aliases:
cve_url = f"https://cve.mitre.org/cgi-bin/cvename.cgi?name={cve_id}"
references.append(Reference(url=cve_url, reference_id=cve_id))

# Extract URLs from text
url_pattern = r'https?://[^\s<>"\')]+[^\s<>"\')\.]'
urls = re.findall(url_pattern, advisory_text)

for url in urls:
# Skip if already added
if any(ref.url == url for ref in references):
continue
# Add unique URLs
references.append(Reference(url=url))

return references
50 changes: 50 additions & 0 deletions vulnerabilities/tests/test_data/kde/advisory-20030916-1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1



KDE Security Advisory: KDM vulnerabilities
Original Release Date: 2003-09-16
URL: http://www.kde.org/info/security/advisory-20030916-1.txt

0. References
http://cert.uni-stuttgart.de/archive/suse/security/2002/12/msg00101.html
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2003-0690
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2003-0692


1. Systems affected:

All versions of KDM as distributed with KDE up to and including
KDE 3.1.3.


2. Overview:

Two issues have been discovered in KDM:

a) CAN-2003-0690:
Privilege escalation with specific PAM modules
b) CAN-2003-0692:
Session cookies generated by KDM are potentially insecure


3. Impact:

If KDM is used in combination with the MIT pam_krb5 module and given
a valid username and password of an existing user, the login attempt
succeeds and establishes a session with excessive privileges.


4. Solution:

Users of KDE 3.1.x are advised to upgrade to KDE 3.1.4.


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2-rc1-SuSE (GNU/Linux)

iD8DBQE/Z1fBvsXr+iuy1UoRAi02AJ90TqHxCeqzqGJrN3jS7mRSd9u5xQCg6/Do
LB3tubiwfy8TUy5rL7B8UFY=
=2tbX
-----END PGP SIGNATURE-----
31 changes: 31 additions & 0 deletions vulnerabilities/tests/test_data/kde/advisory-20260109-1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
KDE Project Security Advisory
=============================

Title: Smb4K: Major security issues in KAuth mount helper
Risk rating: Major
CVE: CVE-2025-66002, CVE-2025-66003
Versions: Smb4K < 4.0.5
Date: 9 January 2026

Overview
========

The privileged KAuth mount helper of Smb4K runs with full root privileges and implements two KAuth actions accessible via D-Bus: mounting and unmounting a network share.


Impact
======

An attacker can exploit the shortcomings in the KAuth mount helper and perform arbitrary unmounts.


Solution
========

Update Smb4K to version 4.0.5 or later.


Credits
=======

Thanks to Matthias Gerstner and the SUSE security team for reporting this issue.
78 changes: 78 additions & 0 deletions vulnerabilities/tests/test_kde.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

import os
from unittest import TestCase

from vulnerabilities.importers.kde import extract_summary
from vulnerabilities.importers.kde import parse_advisory

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEST_DATA = os.path.join(BASE_DIR, "test_data/kde")


class TestKdeImporter(TestCase):
def test_parse_old_format_advisory(self):
"""Test parsing old format PGP-signed advisory"""
with open(os.path.join(TEST_DATA, "advisory-20030916-1.txt"), "r") as f:
advisory_text = f.read()

advisory_url = "https://kde.org/info/security/advisory-20030916-1.txt"
result = parse_advisory(advisory_text, advisory_url)

# Check that CVE IDs were extracted and converted from CAN format
assert "CVE-2003-0690" in result.aliases
assert "CVE-2003-0692" in result.aliases
assert len(result.aliases) == 2

# Check summary was extracted
assert "KDM vulnerabilities" in result.summary

# Check references include CVE URLs
cve_urls = [ref.url for ref in result.references if "cve.mitre.org" in ref.url]
assert len(cve_urls) == 2

def test_parse_new_format_advisory(self):
"""Test parsing new format advisory"""
with open(os.path.join(TEST_DATA, "advisory-20260109-1.txt"), "r") as f:
advisory_text = f.read()

advisory_url = "https://kde.org/info/security/advisory-20260109-1.txt"
result = parse_advisory(advisory_text, advisory_url)

# Check CVE IDs were extracted
assert "CVE-2025-66002" in result.aliases
assert "CVE-2025-66003" in result.aliases

# Check title was extracted
assert "Smb4K" in result.summary

def test_extract_summary_old_format(self):
"""Test summary extraction from old format"""
advisory_text = """-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1



KDE Security Advisory: KDM vulnerabilities
Original Release Date: 2003-09-16"""

summary = extract_summary(advisory_text)
assert "KDM vulnerabilities" in summary

def test_extract_summary_new_format(self):
"""Test summary extraction from new format"""
advisory_text = """KDE Project Security Advisory
=============================

Title: Smb4K: Major security issues
Risk rating: Major"""

summary = extract_summary(advisory_text)
assert "Smb4K" in summary