Field case: an Intune host lists "MSTeams 1.0.0.0" (CN=Microsoft
Corporation). It drew CVE-2023-4863, -29328, -29330, CVE-2022-21965 and
CVE-2025-53783, plus the Teams classic EOL.
1.0.0.0 names no build. It is the new client's MSIX package (Intune's
"MSTeams", Wazuh's WindowsApps\MSTeams_1.0.0.0_...) with no version read;
no desktop Teams shipped as 1.0.0.0 and classic was never MSIX. Four parts
under 100, so it passed the scheme check and sat below every classic and
Android bound. teams_placeholder_version (1.0.0.0, 1.0, 1, 0.0.0.0) is now
skipped by the cvelistV5 and NVD scans and by _teams_classic. 0a73eafb had
read it as classic; "MSTeams" now counts as the new client for EOL, like
"Microsoft Teams". Real classic builds (1.5.00.31168) keep both.
CVE-2025-53783 came through teams_panels: the NVD filter matched the
vendor:product token as a substring, so microsoft:teams also took
teams_panels and teams_phones (1.0.9x device builds), and Teams for
Android (1.0.0.2025...) got their CVE and the Panel fix 1.0.97.2025102203.
Teams now takes its CPE exactly. Other products keep the prefix match;
Adobe relies on it (acrobat_dc). NVD cache prefix v5.
248 lines
11 KiB
Python
248 lines
11 KiB
Python
"""Classic Teams (1.x) — run: python tests/test_teams_classic.py
|
|
|
|
Field case: an Intune host lists "Microsoft Teams" 1.5.00.31168 and "Teams
|
|
Machine-Wide Installer" 1.5.0.31168. Neither CVE-2023-29328 nor -29330
|
|
(Teams for Desktop, fixed 1.6.00.18681) reached it from cvelistV5.
|
|
|
|
* cvelistV5: Teams is a strict-scheme key, so bounds in another build shape
|
|
(iOS 7.10.1 against new Teams' 26183.x) are not compared. The check ran
|
|
over the floor too, and Microsoft writes the desktop floor as "1.0.0":
|
|
three parts against 1.5.00.31168's four, so every classic Teams range was
|
|
refused. Only the upper bounds decide the scheme now.
|
|
* NVD: the same CVEs did match, but showed the macOS fix (1.6.00.17554,
|
|
the first entry) instead of the desktop's. A platform-neutral entry's fix
|
|
now wins. And CVE-2023-24881: NVD lost Microsoft's floor and says
|
|
"< 2.10.1" for any platform; the record says 2.0.0 .. < 2.10.1, MSRC's
|
|
only fix is 2.10.1. 1.x is not that product, so the NVD Teams entries
|
|
get the same scheme check (bound 2.10.1 has three parts).
|
|
|
|
The Machine-Wide Installer stays out: it stages the client into each profile
|
|
and keeps its own version while the client updates itself. The AppX
|
|
name "MSTeams" Intune reports and a bare "Teams" are the client and now match.
|
|
|
|
Fixtures are the real record fields.
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import zipfile
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from app.services import app_cve_scanner_service as cpe # noqa: E402
|
|
from app.services import cvelistv5_scan_service as c5 # noqa: E402
|
|
|
|
|
|
def _rng(product, lo, lt):
|
|
return {"vendor": "Microsoft", "product": product, "versions": [
|
|
{"version": lo, "lessThan": lt, "versionType": "custom", "status": "affected"}]}
|
|
|
|
|
|
_29328 = [_rng("Microsoft Teams for Android", "1.0.0", "1.0.0.2023070204"),
|
|
_rng("Microsoft Teams for Desktop", "1.0.0", "1.6.00.18681"),
|
|
_rng("Microsoft Teams for iOS", "2.0.0", "5.12.1"),
|
|
_rng("Microsoft Teams for Mac", "1.0.0.0", "1.6.00.17554")]
|
|
_24881 = [dict(_rng("Microsoft Teams", "2.0.0", "2.10.1"), platforms=["Unknown"])]
|
|
# CVE-2025-49731: new Teams, MSRC's 11-digit build and the mobile bounds.
|
|
_49731 = [_rng("Microsoft Teams for Android", "1.0.0", "1.0.0.2025112902"),
|
|
_rng("Microsoft Teams for iOS", "7.0.0", "7.10.1"),
|
|
_rng("Microsoft Teams", "0", "25060212643")]
|
|
CVE5 = {"CVE-2023-29328": _29328, "CVE-2023-29330": _29328,
|
|
"CVE-2023-24881": _24881, "CVE-2025-49731": _49731}
|
|
|
|
|
|
def _m(tsw, end, edition="*"):
|
|
return {"vulnerable": True, "versionEndExcluding": end,
|
|
"criteria": f"cpe:2.3:a:microsoft:teams:*:*:*:*:{edition}:{tsw}:*:*"}
|
|
|
|
|
|
_N29328 = [_m("android", "1.0.0.2023070204"), _m("macos", "1.6.00.17554"),
|
|
_m("-", "1.6.00.18681"), _m("iphone_os", "5.12.1")]
|
|
NVD = {"vulnerabilities": [
|
|
{"cve": {"id": cve, "configurations": [{"nodes": [{"cpeMatch": ms}]}]}}
|
|
for cve, ms in (
|
|
("CVE-2023-29328", _N29328), ("CVE-2023-29330", _N29328),
|
|
("CVE-2023-24881", [_m("*", "2.10.1")]),
|
|
# libwebp, on KEV: desktop edition, classic build bound.
|
|
("CVE-2023-4863", [_m("macos", "1.6.00.26463"),
|
|
_m("*", "1.6.00.26474", edition="desktop")]),
|
|
("CVE-2025-53783", [_m("android", "1.0.0.2025102802"), _m("iphone_os", "7.10.1"),
|
|
_m("macos", "25122.1207.3700.1444"),
|
|
_m("*", "25122.1415.3698.6812", edition="desktop"),
|
|
{"vulnerable": True, "versionEndExcluding": "1.0.97.2025102203",
|
|
"criteria": "cpe:2.3:a:microsoft:teams_panels:*:*:*:*:*:*:*:*"},
|
|
{"vulnerable": True, "versionEndExcluding": "1.0.94.2025168802",
|
|
"criteria": "cpe:2.3:a:microsoft:teams_phones:*:*:*:*:*:*:*:*"}]),
|
|
# No platform on the bounded entry: Android / Rooms numbering.
|
|
("CVE-2022-21965", [_m("*", "1.0.94.20000")]),
|
|
)]}
|
|
|
|
|
|
class FakeAsset:
|
|
id = 1
|
|
operating_system = "Windows 11 Enterprise"
|
|
|
|
|
|
def _build_index():
|
|
"""Run the REAL index build over a ZIP shaped like cvelistV5's."""
|
|
fd, path = tempfile.mkstemp(suffix=".zip")
|
|
os.close(fd)
|
|
with zipfile.ZipFile(path, "w") as zf:
|
|
for cve_id, aff in CVE5.items():
|
|
year, num = cve_id.split("-")[1:]
|
|
zf.writestr(f"cvelistV5-main/cves/{year}/{num[:-3]}xxx/{cve_id}.json",
|
|
json.dumps({"cveMetadata": {"cveId": cve_id},
|
|
"containers": {"cna": {"affected": aff}}}))
|
|
orig = (c5._ZIP_PATH, c5._ensure_zip, c5._store_index, c5._merge_mozilla_mfsa,
|
|
c5._merge_chrome_releases)
|
|
c5._ZIP_PATH = path
|
|
c5._ensure_zip = lambda force=False: True
|
|
c5._store_index = lambda db, index: None
|
|
c5._merge_mozilla_mfsa = lambda *a, **kw: None
|
|
c5._merge_chrome_releases = lambda *a, **kw: None
|
|
try:
|
|
return c5.build_product_index(None)
|
|
finally:
|
|
(c5._ZIP_PATH, c5._ensure_zip, c5._store_index, c5._merge_mozilla_mfsa,
|
|
c5._merge_chrome_releases) = orig
|
|
os.unlink(path)
|
|
|
|
|
|
def _scan5(index, name, version, os_name="Windows 11 Enterprise"):
|
|
class Host(FakeAsset):
|
|
operating_system = os_name
|
|
found = []
|
|
orig = cpe._upsert
|
|
cpe._upsert = lambda db, asset, n, v, c, new_ids, **kw: (
|
|
found.append((c["cve"], c["fixed"])), new_ids.append(len(new_ids)))
|
|
try:
|
|
c5.scan_asset(None, Host(), [{"name": name, "version": version}],
|
|
index, touched=set())
|
|
finally:
|
|
cpe._upsert = orig
|
|
return sorted(found)
|
|
|
|
|
|
def _nvd(version):
|
|
"""The REAL NVD decision, as a Windows host would keep it."""
|
|
return sorted((c["cve"], c["fixed"]) for c in _query(version)
|
|
if cpe._platform_ok(c.get("tsw"), "windows"))
|
|
|
|
|
|
def _query(version):
|
|
"""The REAL NVD query over the fixture response."""
|
|
class _R:
|
|
status_code = 200
|
|
|
|
def json(self):
|
|
return NVD
|
|
|
|
class _C:
|
|
def __init__(self, *a, **k):
|
|
pass
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *a):
|
|
return False
|
|
|
|
def get(self, *a, **k):
|
|
return _R()
|
|
|
|
orig = cpe.httpx.Client, cpe.time.sleep
|
|
cpe.httpx.Client, cpe.time.sleep = _C, (lambda s: None)
|
|
try:
|
|
return cpe._query_nvd_cpe("cpe:2.3:a:microsoft:teams", version)
|
|
finally:
|
|
cpe.httpx.Client, cpe.time.sleep = orig
|
|
|
|
|
|
def _scan_nvd(os_name, version):
|
|
"""scan_asset_packages over the fixture, as a host on `os_name`."""
|
|
class Host(FakeAsset):
|
|
operating_system = os_name
|
|
found = []
|
|
orig = cpe._upsert, cpe.lookup_cves
|
|
cpe._upsert = lambda db, asset, n, v, c, new_ids, **kw: (
|
|
found.append((c["cve"], c["fixed"])), new_ids.append(len(new_ids)))
|
|
cpe.lookup_cves = lambda db, entry, ver, **kw: _query(ver)
|
|
try:
|
|
cpe.scan_asset_packages(None, Host(), [{"name": "Microsoft Teams", "version": version}],
|
|
touched=set())
|
|
finally:
|
|
cpe._upsert, cpe.lookup_cves = orig
|
|
return sorted(found)
|
|
|
|
|
|
def demo():
|
|
# --- names ------------------------------------------------------------------
|
|
for name in ("Microsoft Teams", "Microsoft Teams classic", "MSTeams", "Teams"):
|
|
assert c5.resolve(name) == "teams", name
|
|
assert (cpe.resolve_product(name) or {}).get("key") == "cpe:microsoft:teams", name
|
|
for other in ("Teams Machine-Wide Installer", "Microsoft Teams Meeting Add-in for "
|
|
"Microsoft Office", "Veeam Explorer for Microsoft Teams", "Teams Rooms"):
|
|
assert c5.resolve(other) is None, other
|
|
assert cpe.resolve_product(other) is None, other
|
|
|
|
# --- cvelistV5 ----------------------------------------------------------------
|
|
index = _build_index()
|
|
want = [("CVE-2023-29328", "1.6.00.18681"), ("CVE-2023-29330", "1.6.00.18681")]
|
|
for name in ("Microsoft Teams", "MSTeams", "Teams"):
|
|
assert _scan5(index, name, "1.5.00.31168") == want, name
|
|
assert _scan5(index, "Microsoft Teams", "1.6.00.18681") == []
|
|
# A Mac on its own fix: the record's Mac range is not indexed, the
|
|
# desktop's is not the Mac's. NVD has the macOS entries.
|
|
assert _scan5(index, "Microsoft Teams", "1.6.00.17554", "macOS 13.6") == []
|
|
# New Teams: the 11-digit MSRC build and the mobile bounds stay uncompared.
|
|
assert _scan5(index, "MSTeams", "26183.1903.4892.4448") == []
|
|
|
|
# --- NVD --------------------------------------------------------------------
|
|
assert _nvd("1.5.00.31168") == [("CVE-2023-29328", "1.6.00.18681"),
|
|
("CVE-2023-29330", "1.6.00.18681"),
|
|
("CVE-2023-4863", "1.6.00.26474")]
|
|
assert _nvd("1.6.00.18681") == [("CVE-2023-4863", "1.6.00.26474")]
|
|
assert _nvd("1.6.00.26474") == []
|
|
# New Teams keeps its desktop CVE.
|
|
assert _nvd("25122.1405.3600.1111") == [("CVE-2025-53783", "25122.1415.3698.6812")]
|
|
assert _nvd("26183.1903.4892.4448") == []
|
|
# The fix shown is the host's own platform's: Windows the desktop build,
|
|
# a Mac its own (CVE-2023-4863: 1.6.00.26463 on macOS).
|
|
assert _scan_nvd("Windows 11 Enterprise", "1.5.00.31168") == _nvd("1.5.00.31168")
|
|
mac = dict(_scan_nvd("macOS 13.6", "1.5.00.31168"))
|
|
assert mac["CVE-2023-4863"] == "1.6.00.26463", mac
|
|
assert mac["CVE-2023-29328"] == "1.6.00.17554", mac
|
|
|
|
# --- placeholder ------------------------------------------------------------
|
|
# Intune: "MSTeams 1.0.0.0" — the new client's MSIX package without a
|
|
# build. Four parts and below every classic / Android bound, so it drew
|
|
# CVE-2023-29328/-29330 (cvelistV5 + NVD), -4863, CVE-2022-21965 and,
|
|
# via teams_panels' 1.0.97.x, CVE-2025-53783.
|
|
for version in ("1.0.0.0", "1.0.0", "1", "0.0.0.0"):
|
|
for name in ("MSTeams", "Microsoft Teams", "Teams"):
|
|
assert _scan5(index, name, version) == [], (name, version)
|
|
assert _scan_nvd("Windows 11 Enterprise", version) == [], version
|
|
# …while a real classic build still gets them, and the old bound's
|
|
# Android numbering (1.0.94.x) is not a placeholder.
|
|
assert _scan_nvd("Windows 11 Enterprise", "1.5.00.31168") == _nvd("1.5.00.31168")
|
|
assert not cpe.teams_placeholder_version("1.0.94.19999")
|
|
|
|
# --- Teams Panel / Phone --------------------------------------------------
|
|
# Their own CPEs (teams_panels, teams_phones), not Teams. Teams for
|
|
# Android (1.0.0.2025…) sits below their 1.0.9x bounds and picked them up,
|
|
# fix included. It gets the Android entry only.
|
|
c = next(c for c in _query("1.0.0.2025102801") if c["cve"] == "CVE-2025-53783")
|
|
assert c["fixes"] == {"android": "1.0.0.2025102802"}, c
|
|
|
|
print("ok classic Teams 1.5.00.31168 gets CVE-2023-29328/-29330 from both paths "
|
|
"with the desktop fix, CVE-2023-24881 (2.x only) stays off it")
|
|
|
|
|
|
def test_teams_classic():
|
|
demo()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
demo()
|