mirror of
https://github.com/peass-ng/PEASS-ng.git
synced 2026-07-30 23:50:27 -07:00
winpeas: use KB supersedence graph for windows version vuln filtering
This commit is contained in:
@@ -34,18 +34,27 @@ def build_definitions(definitions_zip: Path) -> dict:
|
|||||||
rows.extend(_read_csv_from_zip(zf, "Custom_"))
|
rows.extend(_read_csv_from_zip(zf, "Custom_"))
|
||||||
|
|
||||||
products: dict[str, dict[str, dict[str, str]]] = defaultdict(dict)
|
products: dict[str, dict[str, dict[str, str]]] = defaultdict(dict)
|
||||||
|
kb_supersedes: dict[str, set[str]] = defaultdict(set)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if not (row.get("Exploits") or "").strip():
|
|
||||||
continue
|
|
||||||
|
|
||||||
product = (row.get("AffectedProduct") or "").strip()
|
product = (row.get("AffectedProduct") or "").strip()
|
||||||
if not product:
|
|
||||||
continue
|
|
||||||
if "windows" not in product.lower():
|
if "windows" not in product.lower():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cve = (row.get("CVE") or "").strip()
|
|
||||||
kb = (row.get("BulletinKB") or "").strip()
|
kb = (row.get("BulletinKB") or "").strip()
|
||||||
|
supersedes = (row.get("Supersedes") or "").strip()
|
||||||
|
if kb and supersedes:
|
||||||
|
for item in supersedes.split(";"):
|
||||||
|
item = item.strip()
|
||||||
|
if item:
|
||||||
|
kb_supersedes[kb].add(item)
|
||||||
|
|
||||||
|
if not (row.get("Exploits") or "").strip():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not product:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cve = (row.get("CVE") or "").strip()
|
||||||
vuln_key = cve or f"KB{kb}"
|
vuln_key = cve or f"KB{kb}"
|
||||||
if not vuln_key:
|
if not vuln_key:
|
||||||
continue
|
continue
|
||||||
@@ -58,13 +67,14 @@ def build_definitions(definitions_zip: Path) -> dict:
|
|||||||
"kb": kb,
|
"kb": kb,
|
||||||
"severity": (row.get("Severity") or "").strip(),
|
"severity": (row.get("Severity") or "").strip(),
|
||||||
"impact": (row.get("Impact") or "").strip(),
|
"impact": (row.get("Impact") or "").strip(),
|
||||||
"supersedes": (row.get("Supersedes") or "").strip(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data = {"generated": generated, "products": {}}
|
data = {"generated": generated, "products": {}, "kb_supersedes": {}}
|
||||||
for product in sorted(products):
|
for product in sorted(products):
|
||||||
entries = [products[product][key] for key in sorted(products[product])]
|
entries = [products[product][key] for key in sorted(products[product])]
|
||||||
data["products"][product] = entries
|
data["products"][product] = entries
|
||||||
|
for kb in sorted(kb_supersedes):
|
||||||
|
data["kb_supersedes"][kb] = sorted(kb_supersedes[kb])
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -62,7 +62,7 @@ namespace winPEAS.Info.SystemInfo
|
|||||||
report.MatchedProducts = matchedProducts.OrderBy(p => p).ToList();
|
report.MatchedProducts = matchedProducts.OrderBy(p => p).ToList();
|
||||||
report.TotalMatchedBeforeFiltering = matchedEntries.Count;
|
report.TotalMatchedBeforeFiltering = matchedEntries.Count;
|
||||||
|
|
||||||
var filteredVulns = FilterPatchedVulnerabilities(matchedEntries, installedHotfixes);
|
var filteredVulns = FilterPatchedVulnerabilities(matchedEntries, installedHotfixes, definitions.kb_supersedes);
|
||||||
var vulnById = new Dictionary<string, WindowsVersionVulnEntry>(StringComparer.OrdinalIgnoreCase);
|
var vulnById = new Dictionary<string, WindowsVersionVulnEntry>(StringComparer.OrdinalIgnoreCase);
|
||||||
AddEntries(filteredVulns, vulnById);
|
AddEntries(filteredVulns, vulnById);
|
||||||
|
|
||||||
@@ -168,89 +168,56 @@ namespace winPEAS.Info.SystemInfo
|
|||||||
return hotfixes;
|
return hotfixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<WindowsVersionVulnEntry> FilterPatchedVulnerabilities(List<WindowsVersionVulnEntry> entries, HashSet<string> installedHotfixes)
|
private static List<WindowsVersionVulnEntry> FilterPatchedVulnerabilities(
|
||||||
|
List<WindowsVersionVulnEntry> entries,
|
||||||
|
HashSet<string> installedHotfixes,
|
||||||
|
Dictionary<string, List<string>> kbSupersedes)
|
||||||
{
|
{
|
||||||
if (entries.Count == 0)
|
if (entries.Count == 0)
|
||||||
{
|
{
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
var relevant = entries.Select(e => new RelevantVuln
|
var suppressed = ExpandSupersededHotfixes(installedHotfixes, kbSupersedes);
|
||||||
{
|
return entries
|
||||||
Entry = e,
|
.Where(entry =>
|
||||||
Relevant = true
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var initialHotfixes = new HashSet<string>(installedHotfixes, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
// This mirrors WES behavior to allow recursive supersedence pruning.
|
|
||||||
foreach (var rv in relevant)
|
|
||||||
{
|
|
||||||
foreach (var ss in ParseSupersedes(rv.Entry.supersedes))
|
|
||||||
{
|
{
|
||||||
initialHotfixes.Add(ss);
|
if (string.IsNullOrWhiteSpace(entry.kb))
|
||||||
}
|
{
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
MarkSupersededHotfixes(relevant, initialHotfixes, new HashSet<string>(StringComparer.OrdinalIgnoreCase));
|
return !suppressed.Contains(entry.kb);
|
||||||
|
})
|
||||||
var supersedes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
.ToList();
|
||||||
foreach (var rv in relevant.Where(r => r.Relevant))
|
|
||||||
{
|
|
||||||
foreach (var ss in ParseSupersedes(rv.Entry.supersedes))
|
|
||||||
{
|
|
||||||
supersedes.Add(ss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var rv in relevant.Where(r => r.Relevant))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(rv.Entry.kb) && supersedes.Contains(rv.Entry.kb))
|
|
||||||
{
|
|
||||||
rv.Relevant = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return relevant.Where(r => r.Relevant).Select(r => r.Entry).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MarkSupersededHotfixes(List<RelevantVuln> relevant, HashSet<string> hotfixes, HashSet<string> visited)
|
private static HashSet<string> ExpandSupersededHotfixes(HashSet<string> installedHotfixes, Dictionary<string, List<string>> kbSupersedes)
|
||||||
{
|
{
|
||||||
foreach (var hotfix in hotfixes)
|
var suppressed = new HashSet<string>(installedHotfixes, StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (kbSupersedes == null || kbSupersedes.Count == 0)
|
||||||
{
|
{
|
||||||
MarkHotfixAndChildren(relevant, hotfix, visited);
|
return suppressed;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void MarkHotfixAndChildren(List<RelevantVuln> relevant, string hotfix, HashSet<string> visited)
|
var queue = new Queue<string>(installedHotfixes);
|
||||||
{
|
while (queue.Count > 0)
|
||||||
if (string.IsNullOrWhiteSpace(hotfix) || visited.Contains(hotfix))
|
|
||||||
{
|
{
|
||||||
return;
|
var current = queue.Dequeue();
|
||||||
}
|
if (!kbSupersedes.TryGetValue(current, out var children) || children == null)
|
||||||
visited.Add(hotfix);
|
|
||||||
|
|
||||||
foreach (var rv in relevant.Where(r => r.Relevant && string.Equals(r.Entry.kb, hotfix, StringComparison.OrdinalIgnoreCase)))
|
|
||||||
{
|
|
||||||
rv.Relevant = false;
|
|
||||||
foreach (var child in ParseSupersedes(rv.Entry.supersedes))
|
|
||||||
{
|
{
|
||||||
MarkHotfixAndChildren(relevant, child, visited);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in children)
|
||||||
|
{
|
||||||
|
if (suppressed.Add(child))
|
||||||
|
{
|
||||||
|
queue.Enqueue(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<string> ParseSupersedes(string supersedes)
|
return suppressed;
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(supersedes))
|
|
||||||
{
|
|
||||||
return Enumerable.Empty<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return supersedes
|
|
||||||
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(s => s.Trim())
|
|
||||||
.Where(s => !string.IsNullOrWhiteSpace(s));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> BuildCandidateProducts(Dictionary<string, string> basicInfo)
|
private static List<string> BuildCandidateProducts(Dictionary<string, string> basicInfo)
|
||||||
@@ -457,6 +424,7 @@ namespace winPEAS.Info.SystemInfo
|
|||||||
{
|
{
|
||||||
public string generated { get; set; }
|
public string generated { get; set; }
|
||||||
public Dictionary<string, List<WindowsVersionVulnEntry>> products { get; set; }
|
public Dictionary<string, List<WindowsVersionVulnEntry>> products { get; set; }
|
||||||
|
public Dictionary<string, List<string>> kb_supersedes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class WindowsVersionVulnEntry
|
internal class WindowsVersionVulnEntry
|
||||||
@@ -465,12 +433,5 @@ namespace winPEAS.Info.SystemInfo
|
|||||||
public string kb { get; set; }
|
public string kb { get; set; }
|
||||||
public string severity { get; set; }
|
public string severity { get; set; }
|
||||||
public string impact { get; set; }
|
public string impact { get; set; }
|
||||||
public string supersedes { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class RelevantVuln
|
|
||||||
{
|
|
||||||
public WindowsVersionVulnEntry Entry { get; set; }
|
|
||||||
public bool Relevant { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user