mirror of
https://github.com/peass-ng/PEASS-ng.git
synced 2026-07-28 14:47:18 -07:00
Use HackTricks package criticality only
This commit is contained in:
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> VulnerabilityIds { get; } = new List<string>();
|
||||
}
|
||||
|
||||
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<HostCheckerResult> 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<IEnumerable<string>, Dictionary<string, VulnerabilityCriticality>> 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<PackageVulnerabilityEntry> entries,
|
||||
Func<IEnumerable<string>, Dictionary<string, VulnerabilityCriticality>> 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<string, VulnerabilityCriticality> 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<string, VulnerabilityCriticality> ResolveVulnerabilityCriticalitiesFromOsv(IEnumerable<string> vulnerabilityIds)
|
||||
{
|
||||
var ids = vulnerabilityIds
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Take(MaxOsvVulnerabilityLookups)
|
||||
.ToList();
|
||||
var results = new Dictionary<string, VulnerabilityCriticality>(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<KeyValuePair<string, VulnerabilityCriticality>> 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<string, VulnerabilityCriticality>(vulnerabilityId, null);
|
||||
}
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
using (var doc = JsonDocument.Parse(body))
|
||||
{
|
||||
return new KeyValuePair<string, VulnerabilityCriticality>(
|
||||
vulnerabilityId,
|
||||
GetOsvCriticality(doc.RootElement));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new KeyValuePair<string, VulnerabilityCriticality>(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<string> 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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user