from __future__ import annotations import gzip import hashlib import http.client import json import sys import tempfile import unittest from datetime import datetime, timezone from pathlib import Path from types import SimpleNamespace from unittest.mock import patch from urllib.error import HTTPError sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) import build_site import sync_reference_pocs as sync import update_cves CVE = "CVE-2020-18305" URL = "https://gist.github.com/researcher/1fe3fe58dd275edb77dcbe890fce2f2c" TEXT = "id: CVE-2020-18305\nrequests:\n - raw: GET /app/filelist/ HTTP/1.1\n" DIGEST = hashlib.sha256(TEXT.encode()).hexdigest() REVIEW = {"cve": CVE, "url": URL, "artifact": "file:poc.yaml", "sha256": DIGEST, "evidence": "Raw HTTP reproduction of the privileged filelist request"} def feed(references): return {"format": "NVD_CVE", "version": "2.0", "totalResults": 1, "vulnerabilities": [{"cve": {"id": CVE, "references": references}}]} class ReferencePoCTests(unittest.TestCase): def test_truncated_http_response_preserves_approval_and_checks_sibling(self): sibling = URL.replace("researcher/", "second/") for failure in (http.client.IncompleteRead(b"partial"), http.client.BadStatusLine("bad")): state = {"candidates": {CVE: [URL, sibling]}, "checks": {}} with patch.object(sync, "fetch_artifacts", side_effect=[failure, {"file:poc.yaml": TEXT}]) as fetch: failures = sync.refresh_checks(state, [REVIEW], limit=2, days=7, cache=Path("unused"), dry_run=True) self.assertEqual(failures, 1) self.assertEqual(fetch.call_count, 2) self.assertIn("error", state["checks"][URL]) self.assertIn("digests", state["checks"][sibling]) self.assertEqual(sync.approvals([REVIEW], state["candidates"], state["checks"], {CVE: [URL]}), {CVE: [URL]}) def test_nvd_tag_cannot_restore_withdrawn_reviewed_approval(self): metadata = {CVE: {"advisories": [[URL, ["Exploit"]]]}} previous = {CVE: [URL]} revoked = sync.approvals([REVIEW], previous, {URL: {"digests": {}}}, previous) self.assertEqual(revoked, {}) with patch.object(build_site, "load_managed_references", return_value={(CVE, build_site.link_key(URL))}): self.assertFalse(build_site.reference_is_verified_poc(CVE, URL, metadata, set())) self.assertTrue(build_site.reference_is_verified_poc(CVE, URL, metadata, {(CVE, build_site.link_key(URL))})) with patch.object(build_site, "load_managed_references", return_value=set()): self.assertTrue(build_site.reference_is_verified_poc(CVE, URL, metadata, set())) other = "https://example.org/exploit.py" self.assertTrue(build_site.reference_is_verified_poc(CVE, other, {CVE: {"advisories": [[other, ["Exploit"]]]}}, set())) def test_reviewed_wrong_cve_exclusion_overrides_all_approval_paths(self): url = "http://seclists.org/fulldisclosure/2024/Feb/14" wrong, correct = "CVE-2024-23749", "CVE-2024-25004" self.assertIn((wrong, url), sync.load_exclusions()) self.assertFalse(build_site.reference_is_verified_poc(wrong, url, {wrong: {"advisories": [[url, ["Exploit"]]]}}, {(wrong, url)})) self.assertTrue(build_site.reference_is_verified_poc(correct, url, {}, {(correct, url)})) self.assertIn((correct, url), {(row["cve"], row["url"]) for row in sync.load_reviews()}) def test_malformed_exclusion_registry_fails_closed(self): with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "reviews.json" for payload in ([], {}, {"version": 1, "exclusions": [{}]}): path.write_text(json.dumps(payload)) with self.assertRaises(ValueError): sync.load_exclusions(path) def test_published_cve_omits_wrong_cve_legacy_reference(self): cve = "CVE-2024-23749" wrong = "http://seclists.org/fulldisclosure/2024/Feb/14" right = "http://seclists.org/fulldisclosure/2024/Feb/13" with tempfile.TemporaryDirectory() as directory: root = Path(directory) path = root / "2024" / f"{cve}.md" path.parent.mkdir() path.write_text(f"### Description\nKiTTY command injection.\n\n#### Reference\n- {wrong}\n- {right}\n") with patch.object(build_site, "CVES", root), \ patch.object(build_site, "load_dates", return_value={}), \ patch.object(build_site, "load_metadata", return_value={cve: {"advisories": [[wrong, ["Exploit"]], [right, ["Exploit"]]]}}), \ patch.object(build_site, "load_verified_references", return_value={(cve, wrong), (cve, right)}): entries, _ = build_site.build_cve_list(set()) self.assertEqual(entries[0]["poc"], [right]) def test_exact_nvd_association_and_reviewed_hash_are_required(self): candidates = sync.feed_candidates(feed([ {"url": URL, "tags": ["Exploit"]}, {"url": URL + "/other", "tags": ["Exploit"]}, {"url": "https://packetstormsecurity.com/files/123/poc", "tags": ["Exploit"]}, ])) self.assertEqual(candidates, {CVE: [URL]}) checks = {URL: {"digests": {"file:poc.yaml": DIGEST}}} self.assertEqual(sync.approvals([], candidates, checks, {}), {}) self.assertEqual(sync.approvals([REVIEW], candidates, checks, {}), {CVE: [URL]}) self.assertEqual(sync.approvals([{**REVIEW, "cve": "CVE-2015-0235"}], candidates, checks, {}), {}) self.assertEqual(sync.feed_candidates(feed([{"url": URL, "tags": ["Third Party Advisory"]}])), {CVE: []}) def test_header_only_or_unrelated_page_poc_is_never_auto_approved(self): for cve, text in [ ("CVE-2009-3621", "Reproducer:\nhttps://example.org/external.c\n#include \n"), ("CVE-2015-0235", "CVE-2020-28010\nProof of concept:\n/usr/sbin/exim4 -R payload\n"), ]: digest = hashlib.sha256(text.encode()).hexdigest() self.assertEqual(sync.approvals([], {cve: [URL]}, {URL: {"digests": {"pre:0": digest}}}, {}), {}) def test_changed_content_withdraws_only_current_source_approval(self): previous = {CVE: [URL]} checks = {URL: {"digests": {"file:poc.yaml": "changed"}}} self.assertEqual(sync.approvals([REVIEW], {CVE: [URL]}, checks, previous), {}) checks[URL]["error"] = "HTTP 503" self.assertEqual(sync.approvals([REVIEW], {CVE: [URL]}, checks, previous), previous) self.assertEqual(sync.approvals([REVIEW], {CVE: []}, checks, previous), {}) self.assertEqual(sync.approvals([], {CVE: [URL]}, checks, previous), {}) def test_partial_gist_response_is_an_error(self): for payload in [{}, {"files": {"poc.py": {"truncated": True, "content": "partial"}}}, {"truncated": True, "files": {}}]: with self.subTest(payload=payload), self.assertRaises(ValueError): sync.gist_artifacts(payload) self.assertEqual(sync.gist_artifacts({"files": {"poc.yaml": {"content": TEXT}}}), {"file:poc.yaml": TEXT}) def test_html_identity_ignores_page_chrome_but_preserves_payload(self): before = b'
old
GET /?x=<script>\r\n
' after = b'
new
GET /?x=<script>\r\n
' self.assertEqual(sync.html_artifacts(before), {"pre:0": "GET /?x=