Skip to content

Commit 644be74

Browse files
committed
fix: validate aliases for alpine linux, mattermost, fireeye, istio advisories
Signed-off-by: ziad hany <ziadhany2016@gmail.com>
1 parent 80bf345 commit 644be74

File tree

7 files changed

+238
-15
lines changed

7 files changed

+238
-15
lines changed

vulnerabilities/pipelines/v2_importers/alpine_linux_importer.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import json
1111
import logging
12+
import re
1213
from pathlib import Path
1314
from typing import Any
1415
from typing import Iterable
@@ -28,6 +29,7 @@
2829
from vulnerabilities.references import XsaReferenceV2
2930
from vulnerabilities.references import ZbxReferenceV2
3031
from vulnerabilities.utils import get_advisory_url
32+
from vulnerabilities.utils import is_cve
3133
from vulnerabilities.utils import load_json
3234

3335

@@ -163,8 +165,10 @@ def load_advisories(
163165
# fixed_vulns is a list of strings and each string is a space-separated
164166
# list of aliases and CVES
165167
for vuln_ids in fixed_vulns:
166-
aliases = vuln_ids.strip().split(" ")
167-
vuln_id = aliases[0]
168+
vuln_id, aliases = parse_vuln_ids(vuln_ids)
169+
170+
if not vuln_id:
171+
continue
168172

169173
references = []
170174
if vuln_id.startswith("XSA"):
@@ -248,3 +252,34 @@ def load_advisories(
248252
url=url,
249253
original_advisory_text=json.dumps(pkg_infos, indent=2, ensure_ascii=False),
250254
)
255+
256+
257+
PARENTHESES_RE = re.compile(r"\(.*?\)")
258+
259+
260+
def parse_vuln_ids(vuln_ids_string):
261+
"""
262+
Parses a raw vulnerability ids, removes parentheses and returns the advisory_id and a list of all valid aliases.
263+
"""
264+
vuln_ids = PARENTHESES_RE.sub("", vuln_ids_string)
265+
if not vuln_ids_string:
266+
return None, []
267+
268+
cleaned_vuln_ids = []
269+
for alias in vuln_ids.split():
270+
clean_alias = alias.replace("_", "-").replace(".patch", "")
271+
cleaned_vuln_ids.append(clean_alias)
272+
273+
aliases = [
274+
alias
275+
for alias in cleaned_vuln_ids
276+
if alias
277+
and alias not in ["N/A", "CVE"]
278+
and not (alias.startswith("CVE") and not is_cve(alias))
279+
]
280+
281+
if not aliases:
282+
return None, []
283+
284+
vuln_id = aliases[0]
285+
return vuln_id, aliases

vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,16 @@ def parse_advisory_data(raw_data, file_path, base_path) -> AdvisoryDataV2:
104104
cve_refs = md_dict.get("## CVE Reference") or []
105105
cve_ids = md_dict.get("## CVE ID") or []
106106
cleaned_cve_ids = []
107-
for line in cve_ids:
107+
for line in cve_ids + cve_refs:
108108
found_cves = find_all_cve(line)
109109
cleaned_cve_ids.extend(found_cves)
110110

111111
references = md_dict.get("## References") or []
112112
cwe_data = md_dict.get("## Common Weakness Enumeration") or []
113113

114114
advisory_id = file_path.stem
115-
aliases = dedupe([cve.strip() for cve in cleaned_cve_ids + cve_refs])
115+
aliases = dedupe([cve.strip() for cve in cleaned_cve_ids])
116+
116117
aliases = [aliase for aliase in aliases if aliase != advisory_id]
117118
advisory_url = get_advisory_url(
118119
file=file_path,

vulnerabilities/pipelines/v2_importers/istio_importer.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from vulnerabilities.importer import ReferenceV2
2929
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2
3030
from vulnerabilities.utils import get_advisory_url
31+
from vulnerabilities.utils import is_cve
3132
from vulnerabilities.utils import split_markdown_front_matter
3233

3334
is_release = re.compile(r"^[\d.]+$", re.IGNORECASE).match
@@ -88,6 +89,7 @@ def collect_advisories(self) -> Iterable[AdvisoryDataV2]:
8889
data.get("releases", []), GolangVersion
8990
)
9091
cves = data.get("cves", [])
92+
aliases = [cve for cve in cves if is_cve(cve)]
9193

9294
affected_packages = []
9395
if semver_constraints:
@@ -116,10 +118,10 @@ def collect_advisories(self) -> Iterable[AdvisoryDataV2]:
116118
url=f"https://istio.io/latest/news/security/{title}/",
117119
)
118120
)
119-
121+
print(title, aliases)
120122
yield AdvisoryDataV2(
121123
advisory_id=title,
122-
aliases=cves,
124+
aliases=aliases,
123125
summary=summary,
124126
affected_packages=affected_packages,
125127
references=references,

vulnerabilities/pipelines/v2_importers/mattermost_importer.py

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from vulnerabilities.importer import VulnerabilitySeverity
2121
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2
2222
from vulnerabilities.utils import fetch_response
23+
from vulnerabilities.utils import is_cve
2324

2425
MM_REPO = {
2526
"Mattermost Mobile Apps": "mattermost-mobile",
@@ -62,11 +63,17 @@ def collect_advisories(self) -> Iterable[AdvisoryDataV2]:
6263
return
6364

6465
for advisory in data:
65-
vuln_id = advisory.get("issue_id")
66-
if not vuln_id or not vuln_id.startswith("MMSA-"):
67-
self.log(f"Skipping advisory with missing issue_id. {vuln_id}")
66+
issue_id = advisory.get("issue_id") or ""
67+
cve_id = advisory.get("cve_id") or ""
68+
69+
advisory_id, aliases = parse_vuln_ids(issue_id, cve_id)
70+
71+
if not advisory_id:
72+
self.log(
73+
f"Skipping advisory with missing advisory_id. issue_id:{issue_id} cve_id:{cve_id}"
74+
)
6875
continue
69-
cve_id = advisory.get("cve_id")
76+
7077
details = advisory.get("details")
7178

7279
platform = advisory.get("platform")
@@ -78,7 +85,7 @@ def collect_advisories(self) -> Iterable[AdvisoryDataV2]:
7885
affected_packages = []
7986
severity = advisory.get("severity")
8087
if not package_name:
81-
self.log(f"Unknown platform '{platform}' in advisory '{vuln_id}'.")
88+
self.log(f"Unknown platform '{platform}' in advisory '{advisory_id}'.")
8289

8390
else:
8491
package = PackageURL(
@@ -105,7 +112,7 @@ def collect_advisories(self) -> Iterable[AdvisoryDataV2]:
105112
)
106113
except Exception as e:
107114
self.log(
108-
f"Error processing fixed versions '{fixed_versions}' for advisory '{vuln_id}': {e}"
115+
f"Error processing fixed versions '{fixed_versions}' for advisory '{advisory_id}': {e}"
109116
)
110117

111118
severities = []
@@ -118,12 +125,36 @@ def collect_advisories(self) -> Iterable[AdvisoryDataV2]:
118125
)
119126

120127
yield AdvisoryDataV2(
121-
advisory_id=vuln_id,
122-
aliases=[cve_id],
128+
advisory_id=advisory_id,
129+
aliases=aliases,
123130
summary=details,
124131
references=[reference],
125132
affected_packages=affected_packages,
126133
severities=severities,
127134
url=self.url,
128135
original_advisory_text=json.dumps(advisory, indent=2, ensure_ascii=False),
129136
)
137+
138+
139+
def parse_vuln_ids(issue_id, cve_id):
140+
"""
141+
Parses a raw issue_id, cve_id, validate and returns the advisory id and a list of all valid aliases.
142+
"""
143+
advisory_id = None
144+
aliases = []
145+
146+
cve_id = cve_id.strip()
147+
issue_id = issue_id.strip()
148+
149+
for vuln_id in issue_id.split(","):
150+
vuln_id = vuln_id.strip()
151+
if vuln_id.startswith("MMSA-") or vuln_id.startswith("CVE-"):
152+
aliases.append(vuln_id)
153+
154+
if cve_id and is_cve(cve_id):
155+
aliases.append(cve_id)
156+
157+
if aliases:
158+
advisory_id = aliases.pop(0)
159+
160+
return advisory_id, aliases

vulnerabilities/tests/pipelines/v2_importers/test_alpine_linux_importer_pipeline.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import pytest
1414

1515
from vulnerabilities.pipelines.v2_importers.alpine_linux_importer import load_advisories
16+
from vulnerabilities.pipelines.v2_importers.alpine_linux_importer import parse_vuln_ids
1617
from vulnerabilities.pipelines.v2_importers.alpine_linux_importer import process_record
1718
from vulnerabilities.tests import util_tests
1819
from vulnerabilities.tests.pipelines import TestLogger
@@ -95,3 +96,82 @@ def test_load_advisories_package_with_invalid_alpine_version(test_case):
9596
}
9697
result = list(load_advisories(package, "v3.11", "main", archs=[], url="", logger=logger.write))
9798
assert result != []
99+
100+
101+
@pytest.mark.parametrize(
102+
"raw_input, expected_advisory_id, expected_aliases",
103+
[
104+
("CVE-2022-42332 XSA-427", "CVE-2022-42332", ["CVE-2022-42332", "XSA-427"]),
105+
(
106+
"CVE-2022-42333 CVE-2022-43334 XSA-428",
107+
"CVE-2022-42333",
108+
["CVE-2022-42333", "CVE-2022-43334", "XSA-428"],
109+
),
110+
(
111+
"CVE-2020-11501 GNUTLS-SA-2020-03-31 CVE-2020-11501",
112+
"CVE-2020-11501",
113+
["CVE-2020-11501", "GNUTLS-SA-2020-03-31", "CVE-2020-11501"],
114+
),
115+
("CVE_2019-2426", "CVE-2019-2426", ["CVE-2019-2426"]),
116+
(
117+
"CVE-2024-22195 GHSA-h5c8-rqwp-cp95",
118+
"CVE-2024-22195",
119+
["CVE-2024-22195", "GHSA-h5c8-rqwp-cp95"],
120+
),
121+
("CVE-2023-44441 ZDI-CAN-22093", "CVE-2023-44441", ["CVE-2023-44441", "ZDI-CAN-22093"]),
122+
("CVE-2022-45059 VSV00010", "CVE-2022-45059", ["CVE-2022-45059", "VSV00010"]),
123+
("CVE-2021-35940.patch", "CVE-2021-35940", ["CVE-2021-35940"]),
124+
("XSA-207", "XSA-207", ["XSA-207"]),
125+
("ALPINE-13661", "ALPINE-13661", ["ALPINE-13661"]),
126+
("GHSA-vv2x-vrpj-qqpq", "GHSA-vv2x-vrpj-qqpq", ["GHSA-vv2x-vrpj-qqpq"]),
127+
("CVE N/A ZBX-11023", "ZBX-11023", ["ZBX-11023"]),
128+
("CVE-2017-2616 (+ regression fix)", "CVE-2017-2616", ["CVE-2017-2616"]),
129+
(
130+
"CVE-2020-14342 (Not affected, requires --with-systemd)",
131+
"CVE-2020-14342",
132+
["CVE-2020-14342"],
133+
),
134+
("CVE-2017-16808 (AoE)", "CVE-2017-16808", ["CVE-2017-16808"]),
135+
("CVE-2018-14468 (FrameRelay)", "CVE-2018-14468", ["CVE-2018-14468"]),
136+
("CVE-2018-14469 (IKEv1)", "CVE-2018-14469", ["CVE-2018-14469"]),
137+
("CVE-2018-14470 (BABEL)", "CVE-2018-14470", ["CVE-2018-14470"]),
138+
("CVE-2018-14466 (AFS/RX)", "CVE-2018-14466", ["CVE-2018-14466"]),
139+
("CVE-2018-14461 (LDP)", "CVE-2018-14461", ["CVE-2018-14461"]),
140+
("CVE-2018-14462 (ICMP)", "CVE-2018-14462", ["CVE-2018-14462"]),
141+
("CVE-2018-14465 (RSVP)", "CVE-2018-14465", ["CVE-2018-14465"]),
142+
("CVE-2018-14881 (BGP)", "CVE-2018-14881", ["CVE-2018-14881"]),
143+
("CVE-2018-14464 (LMP)", "CVE-2018-14464", ["CVE-2018-14464"]),
144+
("CVE-2018-14463 (VRRP)", "CVE-2018-14463", ["CVE-2018-14463"]),
145+
("CVE-2018-14467 (BGP)", "CVE-2018-14467", ["CVE-2018-14467"]),
146+
(
147+
"CVE-2018-10103 (SMB - partially fixed, but SMB printing disabled)",
148+
"CVE-2018-10103",
149+
["CVE-2018-10103"],
150+
),
151+
(
152+
"CVE-2018-10105 (SMB - too unreliably reproduced, SMB printing disabled)",
153+
"CVE-2018-10105",
154+
["CVE-2018-10105"],
155+
),
156+
("CVE-2018-14880 (OSPF6)", "CVE-2018-14880", ["CVE-2018-14880"]),
157+
("CVE-2018-16451 (SMB)", "CVE-2018-16451", ["CVE-2018-16451"]),
158+
("CVE-2018-14882 (RPL)", "CVE-2018-14882", ["CVE-2018-14882"]),
159+
("CVE-2018-16227 (802.11)", "CVE-2018-16227", ["CVE-2018-16227"]),
160+
("CVE-2018-16229 (DCCP)", "CVE-2018-16229", ["CVE-2018-16229"]),
161+
("CVE-2018-16301 (was fixed in libpcap)", "CVE-2018-16301", ["CVE-2018-16301"]),
162+
("CVE-2018-16230 (BGP)", "CVE-2018-16230", ["CVE-2018-16230"]),
163+
("CVE-2018-16452 (SMB)", "CVE-2018-16452", ["CVE-2018-16452"]),
164+
("CVE-2018-16300 (BGP)", "CVE-2018-16300", ["CVE-2018-16300"]),
165+
("CVE-2018-16228 (HNCP)", "CVE-2018-16228", ["CVE-2018-16228"]),
166+
("CVE-2019-15166 (LMP)", "CVE-2019-15166", ["CVE-2019-15166"]),
167+
("CVE-2019-15167 (VRRP)", "CVE-2019-15167", ["CVE-2019-15167"]),
168+
("CVE-????-????? TS-2024-005", "TS-2024-005", ["TS-2024-005"]),
169+
("CVE-????-????? TS-2024-005", "TS-2024-005", ["TS-2024-005"]),
170+
("CVE-2018-14879 (tcpdump -V)", "CVE-2018-14879", ["CVE-2018-14879"]),
171+
("CVE-46838", None, []), # invalid CVE
172+
],
173+
)
174+
def test_parse_vuln_ids(raw_input, expected_advisory_id, expected_aliases):
175+
advisory_id, aliases = parse_vuln_ids(raw_input)
176+
assert advisory_id == expected_advisory_id
177+
assert aliases == expected_aliases

vulnerabilities/tests/pipelines/v2_importers/test_istio_importer_v2.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020

2121
@pytest.mark.django_db
22-
def test_istio_advisory_parsing():
22+
def test_istio_advisory_parsing1():
2323
sample_md = dedent(
2424
"""\
2525
---
@@ -79,3 +79,55 @@ def test_istio_advisory_parsing():
7979
str(advisory.affected_packages[1].affected_version_range)
8080
== "vers:golang/>=1.0.0|<=1.0.8|>=1.1.0|<=1.1.9|>=1.2.0|<=1.2.1"
8181
)
82+
83+
84+
@pytest.mark.django_db
85+
def test_istio_advisory_parsing2():
86+
sample_md = dedent(
87+
"""\
88+
---
89+
title: ISTIO-SECURITY-2021-004
90+
subtitle: Security Bulletin
91+
description: Potential misuse of mTLS-only fields in AuthorizationPolicy with plain text traffic.
92+
cves: [N/A]
93+
cvss: "N/A"
94+
vector: ""
95+
releases: ["All releases 1.5 and later"]
96+
publishdate: 2021-04-15
97+
keywords: [CVE]
98+
skip_seealso: true
99+
---
100+
101+
{{< security_bulletin >}}
102+
103+
This is a security advisory for customers to check the authorization policy to make sure [mTLS (STRICT mode) is enabled](/docs/tasks/security/authentication/authn-policy/#globally-enabling-istio-mutual-tls-in-strict-mode)
104+
when using [mTLS-only fields](/docs/concepts/security/#dependency-on-mutual-tls) in the authorization policy.
105+
...
106+
"""
107+
)
108+
109+
with tempfile.TemporaryDirectory() as tmp_dir:
110+
base_path = Path(tmp_dir)
111+
advisory_dir = base_path / "content/en/news/security"
112+
advisory_dir.mkdir(parents=True)
113+
advisory_file = advisory_dir / "ISTIO-SECURITY-2021-004.md"
114+
advisory_file.write_text(sample_md, encoding="utf-8")
115+
116+
importer = IstioImporterPipeline()
117+
importer.vcs_response = type(
118+
"FakeVCS", (), {"dest_dir": tmp_dir, "delete": lambda x: None}
119+
)()
120+
121+
advisories = list(importer.collect_advisories())
122+
123+
assert len(advisories) == 1
124+
advisory = advisories[0]
125+
126+
assert isinstance(advisory, AdvisoryDataV2)
127+
assert advisory.advisory_id == "ISTIO-SECURITY-2021-004"
128+
assert advisory.aliases == []
129+
assert advisory.summary.startswith(
130+
"Potential misuse of mTLS-only fields in AuthorizationPolicy with plain text traffic"
131+
)
132+
assert advisory.date_published.isoformat() == "2021-04-15T00:00:00+00:00"
133+
assert advisory.url.endswith("ISTIO-SECURITY-2021-004.md")

vulnerabilities/tests/pipelines/v2_importers/test_mattermost_importer_v2.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from vulnerabilities.importer import AdvisoryData
1515
from vulnerabilities.importer import AdvisoryDataV2
1616
from vulnerabilities.pipelines.v2_importers.mattermost_importer import MattermostImporterPipeline
17+
from vulnerabilities.pipelines.v2_importers.mattermost_importer import parse_vuln_ids
1718

1819

1920
@pytest.fixture
@@ -212,3 +213,24 @@ def json(self):
212213
importer.collect_advisories()
213214

214215
assert call_count["count"] == 1
216+
217+
218+
@pytest.mark.parametrize(
219+
"issue_id, cve_id, expected_advisory_id, expected_aliases",
220+
[
221+
("MMSA-2026-00624", "CVE-2026-3590", "MMSA-2026-00624", ["CVE-2026-3590"]),
222+
("MMSA-2026-00605", "", "MMSA-2026-00605", []),
223+
("MMSA-2021-0055,CVE-2021-37859", "", "MMSA-2021-0055", ["CVE-2021-37859"]),
224+
("MMSA-2021-0055, CVE-2021-37859", "", "MMSA-2021-0055", ["CVE-2021-37859"]),
225+
("MMSA-2024-00344", "n/a", "MMSA-2024-00344", []),
226+
("5.17.2.4", "", None, []),
227+
("na", "", None, []),
228+
("N/A", "", None, []),
229+
("Issue Identifier", "", None, []),
230+
("", "", None, []),
231+
],
232+
)
233+
def test_parse_vuln_ids(issue_id, cve_id, expected_advisory_id, expected_aliases):
234+
advisory_id, aliases = parse_vuln_ids(issue_id, cve_id)
235+
assert advisory_id == expected_advisory_id
236+
assert aliases == expected_aliases

0 commit comments

Comments
 (0)