diff --git a/winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs b/winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs index 5672235..420be2c 100644 --- a/winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs +++ b/winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs @@ -275,7 +275,7 @@ namespace winPEAS.Tests } [TestMethod] - public void PackageVulnerabilityFormatter_SortsByCriticalityBeforeCapping() + public void PackageVulnerabilityFormatter_SortsHackTricksCriticalityBeforeCapping() { var responseJson = "{\"package_vulnerabilities\":{\"checked\":4,\"affected\":4,\"vulnerable_packages\":[" + @@ -295,20 +295,20 @@ namespace winPEAS.Tests } [TestMethod] - public void PackageVulnerabilityFormatter_UsesCvssCriticalityWhenSeverityMissing() + public void PackageVulnerabilityFormatter_UsesHackTricksCvssWhenSeverityMissing() { var responseJson = "{\"package_vulnerabilities\":{\"checked\":3,\"affected\":3,\"vulnerable_packages\":[" + "{\"name\":\"mediumcvss\",\"version\":\"1.0\",\"manager\":\"windows-registry\",\"cvss_score\":5.0,\"vulns\":[\"CVE-2026-10001\"]}," + "{\"name\":\"criticalcvss\",\"version\":\"2.0\",\"manager\":\"windows-registry\",\"cvss_score\":9.8,\"vulns\":[\"CVE-2026-10002\"]}," + - "{\"name\":\"highobject\",\"version\":\"3.0\",\"manager\":\"windows-registry\",\"vulns\":[{\"id\":\"CVE-2026-10003\",\"severity\":\"high\",\"cvss_score\":8.1}]}" + + "{\"name\":\"highcvss\",\"version\":\"3.0\",\"manager\":\"windows-registry\",\"cvss_score\":8.1,\"vulns\":[\"CVE-2026-10003\"]}" + "]}}"; var summary = winPEAS.Info.NetworkInfo.HackTricksHostChecker.ParsePackageVulnerabilities(responseJson, 50); Assert.AreEqual(3, summary.Lines.Count); Assert.IsTrue(summary.Lines[0].StartsWith("- criticalcvss 2.0 [windows-registry] [Critical]: CVE-2026-10002")); - Assert.IsTrue(summary.Lines[1].StartsWith("- highobject 3.0 [windows-registry] [High]: CVE-2026-10003")); + Assert.IsTrue(summary.Lines[1].StartsWith("- highcvss 3.0 [windows-registry] [High]: CVE-2026-10003")); Assert.IsTrue(summary.Lines[2].StartsWith("- mediumcvss 1.0 [windows-registry] [Medium]: CVE-2026-10001")); } } diff --git a/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HackTricksHostChecker.cs b/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HackTricksHostChecker.cs index dd48bfc..7e13cbc 100644 --- a/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HackTricksHostChecker.cs +++ b/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HackTricksHostChecker.cs @@ -35,25 +35,13 @@ namespace winPEAS.Info.NetworkInfo public int OriginalIndex { get; set; } public int SeverityPriority { get; set; } public double CvssScore { get; set; } - public string Severity { get; set; } public string Line { get; set; } - public List VulnerabilityIds { get; } = new List(); - } - - internal sealed class VulnerabilityCriticality - { - public int SeverityPriority { get; set; } - public double CvssScore { get; set; } - public string Severity { get; set; } } public static class HackTricksHostChecker { private const int TimeoutSeconds = 15; - private const int OsvTimeoutSeconds = 12; - private const int MaxOsvVulnerabilityLookups = 100; private const string DefaultUrl = "https://tools.hacktricks.wiki/api/host-checker"; - private const string OsvVulnerabilityUrl = "https://api.osv.dev/v1/vulns/"; private static readonly object LockObj = new object(); private static Task lookupTask; private static bool startedWithPackages; @@ -107,21 +95,10 @@ namespace winPEAS.Info.NetworkInfo return new PackageVulnerabilitySummary { Error = result.Error }; } - return ParsePackageVulnerabilities( - result.RawResponse, - maxLines, - ResolveVulnerabilityCriticalitiesFromOsv); + return ParsePackageVulnerabilities(result.RawResponse, maxLines); } public static PackageVulnerabilitySummary ParsePackageVulnerabilities(string responseJson, int maxLines) - { - return ParsePackageVulnerabilities(responseJson, maxLines, null); - } - - private static PackageVulnerabilitySummary ParsePackageVulnerabilities( - string responseJson, - int maxLines, - Func, Dictionary> vulnerabilityCriticalityResolver) { var summary = new PackageVulnerabilitySummary(); if (string.IsNullOrWhiteSpace(responseJson)) @@ -173,21 +150,17 @@ namespace winPEAS.Info.NetworkInfo var sourcePart = !string.IsNullOrWhiteSpace(source) ? $" [{source}]" : string.Empty; var severity = GetPackageSeverity(package); var severityPart = !string.IsNullOrWhiteSpace(severity) ? $" [{severity}]" : string.Empty; - var entry = new PackageVulnerabilityEntry + var cvssScore = GetPackageCvssScore(package); + entries.Add(new PackageVulnerabilityEntry { OriginalIndex = index, - Severity = severity, SeverityPriority = GetSeverityPriority(severity), - CvssScore = GetPackageCvssScore(package), + CvssScore = cvssScore, Line = $"- {name} {version}{sourcePart}{severityPart}: {string.Join(", ", vulns)}".Trim() - }; - entry.VulnerabilityIds.AddRange(vulns); - entries.Add(entry); + }); index++; } - ApplyResolvedCriticalities(entries, vulnerabilityCriticalityResolver); - summary.Lines.AddRange(entries .OrderByDescending(entry => entry.SeverityPriority) .ThenByDescending(entry => entry.CvssScore) @@ -206,165 +179,6 @@ namespace winPEAS.Info.NetworkInfo return summary; } - private static void ApplyResolvedCriticalities( - List entries, - Func, Dictionary> vulnerabilityCriticalityResolver) - { - if (vulnerabilityCriticalityResolver == null || entries.Count == 0) - { - return; - } - - var vulnerabilityIds = entries - .SelectMany(entry => entry.VulnerabilityIds) - .Where(value => !string.IsNullOrWhiteSpace(value)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - if (vulnerabilityIds.Count == 0) - { - return; - } - - Dictionary criticalities; - try - { - criticalities = vulnerabilityCriticalityResolver(vulnerabilityIds); - } - catch - { - return; - } - - if (criticalities == null || criticalities.Count == 0) - { - return; - } - - foreach (var entry in entries) - { - var resolved = entry.VulnerabilityIds - .Where(id => criticalities.ContainsKey(id)) - .Select(id => criticalities[id]) - .OrderByDescending(criticality => criticality.SeverityPriority) - .ThenByDescending(criticality => criticality.CvssScore) - .FirstOrDefault(); - if (resolved == null || resolved.SeverityPriority <= entry.SeverityPriority) - { - continue; - } - - entry.Severity = resolved.Severity; - entry.SeverityPriority = resolved.SeverityPriority; - entry.CvssScore = resolved.CvssScore; - entry.Line = AddSeverityToLine(entry.Line, resolved.Severity); - } - } - - private static string AddSeverityToLine(string line, string severity) - { - severity = FormatSeverity(severity); - if (string.IsNullOrWhiteSpace(severity) || string.IsNullOrWhiteSpace(line)) - { - return line; - } - - var separator = line.IndexOf(": ", StringComparison.Ordinal); - if (separator < 0) - { - return line; - } - - return line.Substring(0, separator) + $" [{severity}]" + line.Substring(separator); - } - - private static Dictionary ResolveVulnerabilityCriticalitiesFromOsv(IEnumerable vulnerabilityIds) - { - var ids = vulnerabilityIds - .Where(value => !string.IsNullOrWhiteSpace(value)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .Take(MaxOsvVulnerabilityLookups) - .ToList(); - var results = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (ids.Count == 0) - { - return results; - } - - using (var httpClient = new HttpClient()) - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(OsvTimeoutSeconds))) - { - httpClient.Timeout = TimeSpan.FromSeconds(OsvTimeoutSeconds); - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("winpeas"); - httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - var tasks = ids.Select(id => GetOsvCriticality(httpClient, cts.Token, id)).ToArray(); - Task.WaitAll(tasks, TimeSpan.FromSeconds(OsvTimeoutSeconds)); - foreach (var task in tasks) - { - if (task.Status == TaskStatus.RanToCompletion && task.Result.Value != null) - { - results[task.Result.Key] = task.Result.Value; - } - } - } - - return results; - } - - private static async Task> GetOsvCriticality( - HttpClient httpClient, - CancellationToken cancellationToken, - string vulnerabilityId) - { - try - { - var url = OsvVulnerabilityUrl + Uri.EscapeDataString(vulnerabilityId); - var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); - if (!response.IsSuccessStatusCode) - { - return new KeyValuePair(vulnerabilityId, null); - } - - var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - using (var doc = JsonDocument.Parse(body)) - { - return new KeyValuePair( - vulnerabilityId, - GetOsvCriticality(doc.RootElement)); - } - } - catch - { - return new KeyValuePair(vulnerabilityId, null); - } - } - - private static VulnerabilityCriticality GetOsvCriticality(JsonElement root) - { - var best = new VulnerabilityCriticality(); - if (!root.TryGetProperty("severity", out var severities) || severities.ValueKind != JsonValueKind.Array) - { - return best; - } - - foreach (var severity in severities.EnumerateArray()) - { - var scoreText = GetString(severity, "score"); - var cvssScore = GetCvssScore(scoreText); - var severityName = GetSeverityFromCvss(cvssScore); - var priority = GetSeverityPriority(severityName); - if (priority > best.SeverityPriority || - (priority == best.SeverityPriority && cvssScore > best.CvssScore)) - { - best.Severity = severityName; - best.SeverityPriority = priority; - best.CvssScore = cvssScore; - } - } - - return best; - } - private static void EnsureStarted(bool includePackages) { Start( @@ -530,51 +344,9 @@ namespace winPEAS.Info.NetworkInfo } } - if (package.TryGetProperty("severities", out var severities) && severities.ValueKind == JsonValueKind.Array) - { - foreach (var item in severities.EnumerateArray()) - { - if (item.ValueKind != JsonValueKind.String) - { - continue; - } - - var candidate = item.GetString(); - var candidatePriority = GetSeverityPriority(candidate); - if (candidatePriority > priority) - { - severity = candidate; - priority = candidatePriority; - } - } - } - - if (package.TryGetProperty("vulns", out var vulns) && vulns.ValueKind == JsonValueKind.Array) - { - foreach (var vuln in vulns.EnumerateArray()) - { - if (vuln.ValueKind != JsonValueKind.Object) - { - continue; - } - - foreach (var propertyName in new[] { "severity", "max_severity", "criticality", "risk", "level" }) - { - var candidate = GetString(vuln, propertyName); - var candidatePriority = GetSeverityPriority(candidate); - if (candidatePriority > priority) - { - severity = candidate; - priority = candidatePriority; - } - } - } - } - if (priority == 0) { - var cvssScore = GetPackageCvssScore(package); - severity = GetSeverityFromCvss(cvssScore); + severity = GetSeverityFromCvss(GetPackageCvssScore(package)); } return FormatSeverity(severity); @@ -587,23 +359,6 @@ namespace winPEAS.Info.NetworkInfo { score = Math.Max(score, GetScore(package, propertyName)); } - - if (package.TryGetProperty("vulns", out var vulns) && vulns.ValueKind == JsonValueKind.Array) - { - foreach (var vuln in vulns.EnumerateArray()) - { - if (vuln.ValueKind != JsonValueKind.Object) - { - continue; - } - - foreach (var propertyName in new[] { "cvss", "cvss_score", "cvssScore", "max_cvss", "max_cvss_score", "score" }) - { - score = Math.Max(score, GetScore(vuln, propertyName)); - } - } - } - return score; } @@ -648,7 +403,6 @@ namespace winPEAS.Info.NetworkInfo { return "Low"; } - return string.Empty; } @@ -659,7 +413,6 @@ namespace winPEAS.Info.NetworkInfo { return string.Empty; } - return char.ToUpperInvariant(severity[0]) + severity.Substring(1).ToLowerInvariant(); } @@ -669,160 +422,21 @@ namespace winPEAS.Info.NetworkInfo { return 0.0; } - if (property.ValueKind == JsonValueKind.Number && property.TryGetDouble(out var value)) { return value; } - - if (property.ValueKind == JsonValueKind.String) - { - return GetCvssScore(property.GetString()); - } - - return 0.0; - } - - private static double GetCvssScore(string score) - { - score = (score ?? "").Trim(); - if (double.TryParse( - score, + if (property.ValueKind == JsonValueKind.String && double.TryParse( + property.GetString(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, - out var numericScore)) + out value)) { - return numericScore; + return value; } - - if (score.StartsWith("CVSS:3.", StringComparison.OrdinalIgnoreCase)) - { - return GetCvssV3Score(score); - } - return 0.0; } - private static double GetCvssV3Score(string vector) - { - var metrics = vector - .Split('/') - .Skip(1) - .Select(part => part.Split(':')) - .Where(parts => parts.Length == 2) - .ToDictionary(parts => parts[0], parts => parts[1], StringComparer.OrdinalIgnoreCase); - - if (!metrics.TryGetValue("AV", out var av) || - !metrics.TryGetValue("AC", out var ac) || - !metrics.TryGetValue("PR", out var pr) || - !metrics.TryGetValue("UI", out var ui) || - !metrics.TryGetValue("S", out var scope) || - !metrics.TryGetValue("C", out var c) || - !metrics.TryGetValue("I", out var i) || - !metrics.TryGetValue("A", out var a)) - { - return 0.0; - } - - var scopeChanged = string.Equals(scope, "C", StringComparison.OrdinalIgnoreCase); - var impact = 1 - ((1 - GetCvssImpactValue(c)) * (1 - GetCvssImpactValue(i)) * (1 - GetCvssImpactValue(a))); - var impactSubScore = scopeChanged - ? 7.52 * (impact - 0.029) - 3.25 * Math.Pow(impact - 0.02, 15) - : 6.42 * impact; - if (impactSubScore <= 0) - { - return 0.0; - } - - var exploitability = 8.22 * - GetCvssAttackVectorValue(av) * - GetCvssAttackComplexityValue(ac) * - GetCvssPrivilegesRequiredValue(pr, scopeChanged) * - GetCvssUserInteractionValue(ui); - var rawScore = scopeChanged - ? Math.Min(1.08 * (impactSubScore + exploitability), 10) - : Math.Min(impactSubScore + exploitability, 10); - return RoundUpCvss(rawScore); - } - - private static double RoundUpCvss(double value) - { - return Math.Ceiling((value - 0.000001) * 10.0) / 10.0; - } - - private static double GetCvssAttackVectorValue(string value) - { - switch ((value ?? "").ToUpperInvariant()) - { - case "N": - return 0.85; - case "A": - return 0.62; - case "L": - return 0.55; - case "P": - return 0.20; - default: - return 0.0; - } - } - - private static double GetCvssAttackComplexityValue(string value) - { - switch ((value ?? "").ToUpperInvariant()) - { - case "L": - return 0.77; - case "H": - return 0.44; - default: - return 0.0; - } - } - - private static double GetCvssPrivilegesRequiredValue(string value, bool scopeChanged) - { - switch ((value ?? "").ToUpperInvariant()) - { - case "N": - return 0.85; - case "L": - return scopeChanged ? 0.68 : 0.62; - case "H": - return scopeChanged ? 0.50 : 0.27; - default: - return 0.0; - } - } - - private static double GetCvssUserInteractionValue(string value) - { - switch ((value ?? "").ToUpperInvariant()) - { - case "N": - return 0.85; - case "R": - return 0.62; - default: - return 0.0; - } - } - - private static double GetCvssImpactValue(string value) - { - switch ((value ?? "").ToUpperInvariant()) - { - case "H": - return 0.56; - case "L": - return 0.22; - case "N": - return 0.0; - default: - return 0.0; - } - } - private static List GetStringArray(JsonElement element, string propertyName) { if (!element.TryGetProperty(propertyName, out var property) || property.ValueKind != JsonValueKind.Array) @@ -832,27 +446,8 @@ namespace winPEAS.Info.NetworkInfo return property .EnumerateArray() - .Select(item => - { - if (item.ValueKind == JsonValueKind.String) - { - return item.GetString(); - } - - if (item.ValueKind == JsonValueKind.Object) - { - foreach (var propertyNameCandidate in new[] { "id", "cve", "name", "vulnerability_id" }) - { - var value = GetString(item, propertyNameCandidate); - if (!string.IsNullOrWhiteSpace(value)) - { - return value; - } - } - } - - return string.Empty; - }) + .Where(item => item.ValueKind == JsonValueKind.String) + .Select(item => item.GetString()) .Where(value => !string.IsNullOrWhiteSpace(value)) .ToList(); }