Intune lists published apps as "Microsoft Access 2010 / 1.0 / Delivered by Citrix". The CVE scanner drops those rows because the version is a placeholder, and the EOL sweep reused that same filter — so two products years past end-of-support produced no finding at all. The version is the untrustworthy half, not the name. A stub is now kept and restricted to the name-only sources (the Microsoft lifecycle export and the hardcoded exotics); the endoflife.date path, which derives the release from the installed version, is skipped for it. A stub named just "Firefox" still matches nothing, which is what the filter was written for.
115 lines
4.7 KiB
Python
115 lines
4.7 KiB
Python
"""A Citrix published-app stub still carries a real product NAME.
|
|
|
|
The CVE scanner drops the stub outright, and rightly: its version is a
|
|
placeholder ("Firefox 1.0"), so version-based matching reports decade-old CVEs
|
|
on software that is not installed. The EOL sweep reused that same filter and
|
|
inherited a false NEGATIVE — the tester's Intune inventory lists
|
|
|
|
Microsoft Access 2010 1.0 Delivered by Citrix
|
|
Microsoft Visio 2016 1.0 Delivered by Citrix
|
|
|
|
and both are long past end-of-support. The release is in the NAME there, not in
|
|
the version, so the Microsoft-lifecycle listing can date them without ever
|
|
looking at the "1.0".
|
|
|
|
Run: python tests/test_eol_citrix_stub.py
|
|
"""
|
|
import os
|
|
import sys
|
|
import types
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
_fake = types.ModuleType("app.routers.vulnerabilities")
|
|
_fake.log_vulnerability_change = (
|
|
lambda db, uid, vid, old, new, reason=None, cve_id=None, source=None,
|
|
hostname=None: None)
|
|
sys.modules["app.routers.vulnerabilities"] = _fake
|
|
|
|
from sqlalchemy import create_engine # noqa: E402
|
|
from sqlalchemy.orm import sessionmaker # noqa: E402
|
|
|
|
from app.models.base import Base # noqa: E402
|
|
import app.models.user, app.models.group # noqa: E402,F401
|
|
import app.models.audit_log, app.models.setting # noqa: E402,F401
|
|
from app.models.asset import Asset # noqa: E402
|
|
from app.models.vulnerability import Vulnerability # noqa: E402
|
|
from app.services import eol_service as E # noqa: E402
|
|
from app.services import ms_lifecycle_service as MSL # noqa: E402
|
|
|
|
# Trimmed from the real export (product-lifecycle-data-new.xlsx). Pinned: the
|
|
# assertion is about which packages reach the matcher, not about the dates.
|
|
ROWS = [
|
|
{"name": "Microsoft Access 2010", "release": "Service Pack 2",
|
|
"end_date": "2020-10-13"},
|
|
{"name": "Microsoft Visio 2016", "release": "Original Release",
|
|
"end_date": "2025-10-14"},
|
|
]
|
|
|
|
INVENTORY = [
|
|
# The two findings that went missing.
|
|
{"name": "Microsoft Access 2010", "version": "1.0",
|
|
"vendor": "Delivered by Citrix"},
|
|
{"name": "Microsoft Visio 2016", "version": "1.0",
|
|
"vendor": "Delivered by Citrix"},
|
|
# The stub the filter was written for: no release in the name, so the only
|
|
# thing left to match on is the placeholder version. Must stay unmatched.
|
|
{"name": "Firefox", "version": "1.0", "vendor": "Delivered by Citrix"},
|
|
# A real local install — the version path must be untouched.
|
|
{"name": "Mozilla Firefox", "version": "141.0", "vendor": "Mozilla"},
|
|
]
|
|
|
|
|
|
def demo():
|
|
MSL.fetch_lifecycle_data = lambda db, force_refresh=False: ROWS
|
|
|
|
# endoflife.date stands in for "the version says EOL": if a stub ever
|
|
# reaches this path, it produces a finding and the assertions below catch
|
|
# it. Recording the names is what tells a false negative (no row, nothing
|
|
# asked) apart from a correctly skipped one.
|
|
asked = []
|
|
|
|
def fake_check_eol(db, name, version):
|
|
asked.append(name)
|
|
return E.EOLStatus(
|
|
is_eol=True, is_eoas=False, is_maintained=False, days_to_eol=-4000,
|
|
release_label=version, release_name=version, eol_date="2014-01-01",
|
|
product_slug="firefox")
|
|
|
|
E.resolve_product_slug = lambda name: "firefox"
|
|
E.check_eol = fake_check_eol
|
|
|
|
engine = create_engine("sqlite://")
|
|
Base.metadata.create_all(engine)
|
|
db = sessionmaker(bind=engine)()
|
|
asset = Asset(hostname="ise-citrix-vdi", ip_address="10.0.0.11")
|
|
db.add(asset)
|
|
db.commit()
|
|
|
|
E.run_eol_for_packages(db, asset, INVENTORY)
|
|
db.commit()
|
|
|
|
rows = {v.cve_id: v for v in db.query(Vulnerability)
|
|
.filter(Vulnerability.asset_id == asset.id).all()}
|
|
|
|
# A stub is only dated by its name. The version never gets a vote.
|
|
assert asked == ["Mozilla Firefox"], asked
|
|
|
|
for cve_id, pkg in (("EOL-MS-LIFECYCLE-MicrosoftAccess2010", "Microsoft Access 2010"),
|
|
("EOL-MS-LIFECYCLE-MicrosoftVisio2016", "Microsoft Visio 2016")):
|
|
row = rows.get(cve_id)
|
|
assert row is not None, f"{pkg} lost its EOL finding: {list(rows)}"
|
|
assert row.source_list == ["ms-lifecycle"], row.source_list
|
|
|
|
# The stub with no release in its name stays where it was: nowhere. The
|
|
# real install next to it keeps its row.
|
|
assert "EOL-FIREFOX-1.0" not in rows, list(rows)
|
|
assert "EOL-FIREFOX-141.0" in rows, list(rows)
|
|
assert len(rows) == 3, list(rows) # 2 stubs + the real Firefox
|
|
|
|
print("ok a Citrix stub is EOL-checked by name, never by its placeholder version")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
demo()
|