diff --git a/.github/workflows/PR-tests.yml b/.github/workflows/PR-tests.yml index 39f5d72..f2a74ae 100644 --- a/.github/workflows/PR-tests.yml +++ b/.github/workflows/PR-tests.yml @@ -94,6 +94,101 @@ jobs: } else { Write-Error "winPEAS.exe not found at $exePath" } + + - name: Execute winPEAS online package vulnerability lookup with mock + shell: pwsh + run: | + $Configuration = "Release" + $exePath = "winPEAS/winPEASexe/winPEAS/bin/$Configuration/winPEAS.exe" + if (!(Test-Path $exePath)) { + Write-Error "winPEAS.exe not found at $exePath" + } + + $serverPath = Join-Path $env:RUNNER_TEMP "mock_host_checker.py" + $bodyPath = Join-Path $env:RUNNER_TEMP "host_checker_body.json" + $countPath = Join-Path $env:RUNNER_TEMP "host_checker_count.txt" + @" + from http.server import BaseHTTPRequestHandler, HTTPServer + import json + import sys + + body_path = sys.argv[1] + count_path = sys.argv[2] + + class Handler(BaseHTTPRequestHandler): + def do_POST(self): + length = int(self.headers.get("content-length", "0")) + body = self.rfile.read(length).decode("utf-8", "replace") + with open(body_path, "w", encoding="utf-8") as f: + f.write(body) + with open(count_path, "w", encoding="utf-8") as f: + f.write("1") + vulns = [] + for i in range(1, 56): + vulns.append({ + "name": f"pkg{i}", + "version": f"1.{i}.0", + "manager": "windows-registry", + "vulns": [f"CVE-2026-{10000 + i}"], + }) + response = { + "malicious": False, + "results": [], + "package_vulnerabilities": { + "checked": 300, + "affected": 55, + "vulnerable_packages": vulns, + }, + } + data = json.dumps(response).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + def log_message(self, fmt, *args): + pass + + HTTPServer(("127.0.0.1", 18768), Handler).handle_request() + "@ | Set-Content -Path $serverPath -Encoding utf8 + + $server = Start-Process python -ArgumentList "`"$serverPath`" `"$bodyPath`" `"$countPath`"" -PassThru -NoNewWindow + try { + Start-Sleep -Seconds 2 + $env:HACKTRICKS_HOST_CHECKER_URL = "http://127.0.0.1:18768/api/host-checker" + $output = & $exePath applicationsinfo -vulnpackages notcolor 2>&1 | Out-String + + if ($output -notmatch "Online package vulnerabilities found: 55 vulnerable package\(s\), checked 300\.") { + Write-Error "The package vulnerability summary was not printed as expected.`n$output" + } + $vulnLines = ([regex]::Matches($output, "(?m)^\s+- pkg\d+ ")).Count + if ($vulnLines -ne 50) { + Write-Error "Expected 50 vulnerable package lines, got $vulnLines.`n$output" + } + if ($output -notmatch "\.\.\. 5 more vulnerable package\(s\) not shown\.") { + Write-Error "The hidden vulnerable package count was not printed.`n$output" + } + if ($output -match "package_vulnerabilities") { + Write-Error "Raw package_vulnerabilities JSON leaked into the rendered output.`n$output" + } + + $requestCount = Get-Content $countPath -Raw + if ([int]$requestCount.Trim() -ne 1) { + Write-Error "Expected exactly one host-checker request, got $requestCount" + } + $request = Get-Content $bodyPath -Raw | ConvertFrom-Json + if (!$request.hostname) { + Write-Error "The host-checker payload did not include hostname." + } + if (!$request.packages -or $request.packages.Count -lt 1) { + Write-Error "The host-checker payload did not include installed packages." + } + } finally { + if (!$server.HasExited) { + Stop-Process -Id $server.Id -Force + } + } - name: Execute winPEAS networkinfo shell: pwsh diff --git a/winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs b/winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs index 6c6213a..47602a9 100644 --- a/winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs +++ b/winPEAS/winPEASexe/Tests/ArgumentParsingTests.cs @@ -49,6 +49,7 @@ namespace winPEAS.Tests winPEAS.Checks.Checks.IsLinpeas = false; winPEAS.Checks.Checks.IsLolbas = false; winPEAS.Checks.Checks.IsNetworkScan = false; + winPEAS.Checks.Checks.CheckOnlineVulnPackages = false; winPEAS.Checks.Checks.SearchProgramFiles = false; winPEAS.Checks.Checks.NetworkScanOptions = string.Empty; winPEAS.Checks.Checks.PortScannerPorts = null; @@ -240,5 +241,37 @@ namespace winPEAS.Tests Assert.AreEqual(500000, winPEAS.Checks.Checks.MaxRegexFileSize, "max-regex-file-size=500000 should set MaxRegexFileSize to 500000."); } + + [TestMethod] + public void VulnPackages_ArgParsed_Correctly() + { + ParseOnly("-vulnpackages"); + Assert.IsTrue(winPEAS.Checks.Checks.CheckOnlineVulnPackages, + "-vulnpackages should enable the online package vulnerability lookup."); + } + + [TestMethod] + public void All_EnablesOnlinePackageLookup() + { + ParseOnly("all"); + Assert.IsTrue(winPEAS.Checks.Checks.CheckOnlineVulnPackages, + "all should enable the online package vulnerability lookup."); + } + + [TestMethod] + public void PackageVulnerabilityFormatter_CapsAtFiftyLines() + { + var vulnerablePackages = string.Join(",", Enumerable.Range(1, 55).Select(i => + $"{{\"name\":\"pkg{i}\",\"version\":\"1.{i}.0\",\"manager\":\"windows-registry\",\"vulns\":[\"CVE-2026-{10000 + i}\"]}}")); + var responseJson = "{\"malicious\":false,\"results\":[],\"package_vulnerabilities\":{\"checked\":300,\"affected\":55,\"vulnerable_packages\":[" + vulnerablePackages + "]}}"; + + var summary = winPEAS.Info.NetworkInfo.HackTricksHostChecker.ParsePackageVulnerabilities(responseJson, 50); + + Assert.AreEqual(300, summary.Checked); + Assert.AreEqual(55, summary.Affected); + Assert.AreEqual(50, summary.Lines.Count); + Assert.AreEqual(5, summary.NotShown); + Assert.IsTrue(summary.Lines[0].StartsWith("- pkg1 1.1.0 [windows-registry]: CVE-2026-10001")); + } } } diff --git a/winPEAS/winPEASexe/winPEAS/Checks/ApplicationsInfo.cs b/winPEAS/winPEASexe/winPEAS/Checks/ApplicationsInfo.cs index b95b653..8078c8b 100644 --- a/winPEAS/winPEASexe/winPEAS/Checks/ApplicationsInfo.cs +++ b/winPEAS/winPEASexe/winPEAS/Checks/ApplicationsInfo.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using winPEAS.Helpers; using winPEAS.Info.ApplicationInfo; +using winPEAS.Info.NetworkInfo; namespace winPEAS.Checks { @@ -17,12 +18,55 @@ namespace winPEAS.Checks { PrintActiveWindow, PrintInstalledApps, + PrintOnlinePackageVulnerabilities, PrintAutoRuns, PrintScheduled, PrintDeviceDrivers, }.ForEach(action => CheckRunner.Run(action, isDebug)); } + void PrintOnlinePackageVulnerabilities() + { + if (!Checks.CheckOnlineVulnPackages) + { + return; + } + + try + { + Beaprint.MainPrint("Online package vulnerabilities", "T1518"); + Beaprint.LinkPrint("", "Optional HackTricks online lookup enabled with -vulnpackages or all. Output is capped at 50 vulnerable packages."); + + var summary = HackTricksHostChecker.GetPackageVulnerabilities(50); + if (!string.IsNullOrEmpty(summary.Error)) + { + Beaprint.PrintException(" " + summary.Error); + return; + } + + Beaprint.NoColorPrint($" Online package vulnerabilities found: {summary.Affected} vulnerable package(s), checked {summary.Checked}."); + if (summary.Lines.Count == 0) + { + Beaprint.GoodPrint(" No vulnerable packages found by the online lookup."); + return; + } + + foreach (var line in summary.Lines) + { + Beaprint.BadPrint(" " + line); + } + + if (summary.NotShown > 0) + { + Beaprint.NoColorPrint($" ... {summary.NotShown} more vulnerable package(s) not shown."); + } + } + catch (Exception ex) + { + Beaprint.PrintException(ex.Message); + } + } + void PrintActiveWindow() { try diff --git a/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs b/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs index 3fc60b7..43be764 100644 --- a/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs +++ b/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs @@ -24,6 +24,7 @@ namespace winPEAS.Checks public static bool IsLinpeas = false; public static bool IsLolbas = false; public static bool IsNetworkScan = false; + public static bool CheckOnlineVulnPackages = false; public static bool SearchProgramFiles = false; public static IEnumerable PortScannerPorts = null; @@ -141,6 +142,7 @@ namespace winPEAS.Checks if (string.Equals(arg, "all", StringComparison.CurrentCultureIgnoreCase)) { print_fileanalysis_warn = false; + CheckOnlineVulnPackages = true; } if (arg.StartsWith("log", StringComparison.CurrentCultureIgnoreCase)) @@ -184,6 +186,12 @@ namespace winPEAS.Checks DontCheckHostname = true; } + if (string.Equals(arg, "-vulnpackages", StringComparison.CurrentCultureIgnoreCase) || + string.Equals(arg, "vulnpackages", StringComparison.CurrentCultureIgnoreCase)) + { + CheckOnlineVulnPackages = true; + } + if (string.Equals(arg, "quiet", StringComparison.CurrentCultureIgnoreCase)) { Banner = false; @@ -321,6 +329,11 @@ namespace winPEAS.Checks _systemCheckSelectedKeysHashSet.Add("networkscan"); } + if (CheckOnlineVulnPackages && !isAllChecks) + { + _systemCheckSelectedKeysHashSet.Add("applicationsinfo"); + } + if (isAllChecks) { isFileSearchEnabled = true; @@ -346,6 +359,10 @@ namespace winPEAS.Checks CheckRunner.Run(() => CreateDynamicLists(isFileSearchEnabled), IsDebug); + Info.NetworkInfo.HackTricksHostChecker.Start( + includeHostname: !DontCheckHostname, + includePackages: CheckOnlineVulnPackages); + RunChecks(isAllChecks, wait); SearchHelper.CleanLists(); diff --git a/winPEAS/winPEASexe/winPEAS/Checks/NetworkInfo.cs b/winPEAS/winPEASexe/winPEAS/Checks/NetworkInfo.cs index 813907a..f567a3c 100644 --- a/winPEAS/winPEASexe/winPEAS/Checks/NetworkInfo.cs +++ b/winPEAS/winPEASexe/winPEAS/Checks/NetworkInfo.cs @@ -509,7 +509,7 @@ namespace winPEAS.Checks if (!string.IsNullOrEmpty(resolutionInfo.ExternalCheckResult)) { Beaprint.GoodPrint($" External Check Result:"); - Beaprint.NoColorPrint(resolutionInfo.ExternalCheckResult); + Beaprint.NoColorPrint(" " + resolutionInfo.ExternalCheckResult.Replace("\n", "\n ")); } else if (!string.IsNullOrEmpty(resolutionInfo.Error)) { diff --git a/winPEAS/winPEASexe/winPEAS/Helpers/Beaprint.cs b/winPEAS/winPEASexe/winPEAS/Helpers/Beaprint.cs index 21809ad..c2083a7 100644 --- a/winPEAS/winPEASexe/winPEAS/Helpers/Beaprint.cs +++ b/winPEAS/winPEASexe/winPEAS/Helpers/Beaprint.cs @@ -149,6 +149,7 @@ namespace winPEAS.Helpers Console.WriteLine(LCYAN + " wait" + GRAY + " Wait for user input between checks" + NOCOLOR); Console.WriteLine(LCYAN + " debug" + GRAY + " Display debugging information - memory usage, method execution time" + NOCOLOR); Console.WriteLine(LCYAN + " dont-check-hostname" + GRAY + " Don't check the hostname externally" + NOCOLOR); + Console.WriteLine(LCYAN + " -vulnpackages" + GRAY + " Send installed application names/versions to HackTricks for an online vulnerability check. Also enabled by 'all'." + NOCOLOR); Console.WriteLine(LCYAN + " log[=logfile]" + GRAY + $" Log all output to file defined as logfile, or to \"{Checks.Checks.DefaultLogFile}\" if not specified" + NOCOLOR); Console.WriteLine(LCYAN + " max-regex-file-size=1000000" + GRAY + $" Max file size (in Bytes) to search regex in. Default: {Checks.Checks.MaxRegexFileSize}B" + NOCOLOR); diff --git a/winPEAS/winPEASexe/winPEAS/Info/ApplicationInfo/InstalledPackageInventory.cs b/winPEAS/winPEASexe/winPEAS/Info/ApplicationInfo/InstalledPackageInventory.cs new file mode 100644 index 0000000..d357645 --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/Info/ApplicationInfo/InstalledPackageInventory.cs @@ -0,0 +1,102 @@ +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace winPEAS.Info.ApplicationInfo +{ + public static class InstalledPackageInventory + { + public const int MaxPackages = 300; + + public static List> GetInstalledPackages(int maxPackages = MaxPackages) + { + var packages = new List>(); + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + AddRegistryPackages(packages, seen, RegistryHive.LocalMachine, RegistryView.Registry64, maxPackages); + AddRegistryPackages(packages, seen, RegistryHive.LocalMachine, RegistryView.Registry32, maxPackages); + AddRegistryPackages(packages, seen, RegistryHive.CurrentUser, RegistryView.Default, maxPackages); + + return packages; + } + + private static void AddRegistryPackages( + List> packages, + HashSet seen, + RegistryHive hive, + RegistryView view, + int maxPackages) + { + if (packages.Count >= maxPackages) + { + return; + } + + try + { + using (var baseKey = RegistryKey.OpenBaseKey(hive, view)) + using (var uninstallKey = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall")) + { + if (uninstallKey == null) + { + return; + } + + foreach (var subkeyName in uninstallKey.GetSubKeyNames()) + { + if (packages.Count >= maxPackages) + { + return; + } + + using (var appKey = uninstallKey.OpenSubKey(subkeyName)) + { + if (appKey == null) + { + continue; + } + + var name = CleanValue(appKey.GetValue("DisplayName")); + var version = CleanValue(appKey.GetValue("DisplayVersion")); + if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(version)) + { + continue; + } + + var key = $"{name}\0{version}"; + if (!seen.Add(key)) + { + continue; + } + + var package = new Dictionary + { + { "name", name }, + { "version", version }, + { "manager", "windows-registry" } + }; + + var publisher = CleanValue(appKey.GetValue("Publisher")); + if (!string.IsNullOrWhiteSpace(publisher)) + { + package["publisher"] = publisher; + } + + packages.Add(package); + } + } + } + } + catch + { + // Registry inventory is best-effort; unreadable views should not break winPEAS. + } + } + + private static string CleanValue(object value) + { + return Convert.ToString(value)?.Trim().Replace("\r", " ").Replace("\n", " "); + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HackTricksHostChecker.cs b/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HackTricksHostChecker.cs new file mode 100644 index 0000000..b9bbd4d --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HackTricksHostChecker.cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using winPEAS.Info.ApplicationInfo; + +namespace winPEAS.Info.NetworkInfo +{ + public sealed class HostCheckerResult + { + public string Hostname { get; set; } + public string RawResponse { get; set; } + public string HostOnlyResponse { get; set; } + public string Error { get; set; } + } + + public sealed class PackageVulnerabilitySummary + { + public int Checked { get; set; } + public int Affected { get; set; } + public int NotShown { get; set; } + public string Error { get; set; } + public List Lines { get; } = new List(); + } + + public static class HackTricksHostChecker + { + private const int TimeoutSeconds = 15; + private const string DefaultUrl = "https://tools.hacktricks.wiki/api/host-checker"; + private static readonly object LockObj = new object(); + private static Task lookupTask; + private static bool startedWithPackages; + private static bool startedWithHostname; + + public static void Start(bool includeHostname, bool includePackages) + { + if (!includeHostname && !includePackages) + { + return; + } + + lock (LockObj) + { + if (lookupTask != null) + { + if ((includePackages && !startedWithPackages) || (includeHostname && !startedWithHostname)) + { + lookupTask = Task.Run(() => ExecuteLookup(includeHostname || startedWithHostname, includePackages || startedWithPackages)); + startedWithHostname = includeHostname || startedWithHostname; + startedWithPackages = includePackages || startedWithPackages; + } + return; + } + + startedWithHostname = includeHostname; + startedWithPackages = includePackages; + lookupTask = Task.Run(() => ExecuteLookup(includeHostname, includePackages)); + } + } + + public static HostCheckerResult GetResult() + { + Start(includeHostname: !Checks.Checks.DontCheckHostname, includePackages: Checks.Checks.CheckOnlineVulnPackages); + try + { + return lookupTask.GetAwaiter().GetResult(); + } + catch (Exception ex) + { + return new HostCheckerResult { Error = $"Error during online HackTricks check: {ex.Message}" }; + } + } + + public static PackageVulnerabilitySummary GetPackageVulnerabilities(int maxLines) + { + Start(includeHostname: !Checks.Checks.DontCheckHostname, includePackages: true); + var result = GetResult(); + if (!string.IsNullOrEmpty(result.Error)) + { + return new PackageVulnerabilitySummary { Error = result.Error }; + } + + return ParsePackageVulnerabilities(result.RawResponse, maxLines); + } + + public static PackageVulnerabilitySummary ParsePackageVulnerabilities(string responseJson, int maxLines) + { + var summary = new PackageVulnerabilitySummary(); + if (string.IsNullOrWhiteSpace(responseJson)) + { + summary.Error = "No response received from HackTricks online lookup."; + return summary; + } + + try + { + using (var doc = JsonDocument.Parse(responseJson)) + { + var root = doc.RootElement; + if (!root.TryGetProperty("package_vulnerabilities", out var packageVulns)) + { + return summary; + } + + summary.Checked = GetInt(packageVulns, "checked"); + summary.Affected = GetInt(packageVulns, "affected"); + + if (packageVulns.TryGetProperty("error", out var error) && error.ValueKind == JsonValueKind.String) + { + summary.Error = "Package vulnerability lookup failed: " + error.GetString(); + return summary; + } + + if (!packageVulns.TryGetProperty("vulnerable_packages", out var packages) || + packages.ValueKind != JsonValueKind.Array) + { + return summary; + } + + foreach (var package in packages.EnumerateArray()) + { + if (summary.Lines.Count >= maxLines) + { + break; + } + + var name = GetString(package, "name"); + var version = GetString(package, "version"); + var ecosystem = GetString(package, "ecosystem"); + var manager = GetString(package, "manager"); + var vulns = GetStringArray(package, "vulns"); + if (string.IsNullOrWhiteSpace(name) || vulns.Count == 0) + { + continue; + } + + var source = !string.IsNullOrWhiteSpace(ecosystem) ? ecosystem : manager; + var sourcePart = !string.IsNullOrWhiteSpace(source) ? $" [{source}]" : string.Empty; + summary.Lines.Add($"- {name} {version}{sourcePart}: {string.Join(", ", vulns)}".Trim()); + } + + summary.NotShown = Math.Max(0, summary.Affected - summary.Lines.Count); + } + } + catch (Exception ex) + { + summary.Error = "Could not parse package vulnerability lookup response: " + ex.Message; + } + + return summary; + } + + private static HostCheckerResult ExecuteLookup(bool includeHostname, bool includePackages) + { + var result = new HostCheckerResult(); + try + { + var hostname = GetHostname(); + result.Hostname = hostname; + + var payload = new Dictionary + { + { "source", "winpeas" }, + { "os", GetOsInfo() } + }; + + if (includeHostname && !string.IsNullOrWhiteSpace(hostname)) + { + payload["hostname"] = hostname; + } + + if (includePackages) + { + payload["packages"] = InstalledPackageInventory.GetInstalledPackages(); + } + + var url = Environment.GetEnvironmentVariable("HACKTRICKS_HOST_CHECKER_URL"); + if (string.IsNullOrWhiteSpace(url)) + { + url = DefaultUrl; + } + + using (var httpClient = new HttpClient()) + using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(TimeoutSeconds))) + { + httpClient.Timeout = TimeSpan.FromSeconds(TimeoutSeconds); + var body = JsonSerializer.Serialize(payload); + var req = new HttpRequestMessage(HttpMethod.Post, url) + { + Content = new StringContent(body, Encoding.UTF8, "application/json") + }; + req.Headers.UserAgent.ParseAdd("winpeas"); + req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + var resp = httpClient.SendAsync(req, cts.Token).GetAwaiter().GetResult(); + if (!resp.IsSuccessStatusCode) + { + result.Error = $"External check failed (HTTP {(int)resp.StatusCode})"; + return result; + } + + result.RawResponse = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + result.HostOnlyResponse = RemovePackageVulnerabilities(result.RawResponse); + } + } + catch (Exception ex) + { + result.Error = $"Error during online HackTricks check: {ex.Message}"; + } + + return result; + } + + private static string RemovePackageVulnerabilities(string responseJson) + { + if (string.IsNullOrWhiteSpace(responseJson)) + { + return responseJson; + } + + try + { + using (var doc = JsonDocument.Parse(responseJson)) + using (var stream = new MemoryStream()) + { + using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true })) + { + writer.WriteStartObject(); + foreach (var property in doc.RootElement.EnumerateObject()) + { + if (!string.Equals(property.Name, "package_vulnerabilities", StringComparison.OrdinalIgnoreCase)) + { + property.WriteTo(writer); + } + } + writer.WriteEndObject(); + } + + return Encoding.UTF8.GetString(stream.ToArray()); + } + } + catch + { + return responseJson; + } + } + + private static Dictionary GetOsInfo() + { + return new Dictionary + { + { "id", "windows" }, + { "name", "Windows" }, + { "version_id", Environment.OSVersion.VersionString }, + { "kernel", new Dictionary + { + { "release", Environment.OSVersion.Version.ToString() }, + { "arch", Environment.Is64BitOperatingSystem ? "x64" : "x86" } + } + } + }; + } + + private static string GetHostname() + { + try + { + var hostname = Dns.GetHostName(); + return string.IsNullOrWhiteSpace(hostname) ? Environment.MachineName : hostname; + } + catch + { + return Environment.MachineName; + } + } + + private static int GetInt(JsonElement element, string propertyName) + { + if (element.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.Number && property.TryGetInt32(out var value)) + { + return value; + } + return 0; + } + + private static string GetString(JsonElement element, string propertyName) + { + if (element.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String) + { + return property.GetString(); + } + return string.Empty; + } + + private static List GetStringArray(JsonElement element, string propertyName) + { + if (!element.TryGetProperty(propertyName, out var property) || property.ValueKind != JsonValueKind.Array) + { + return new List(); + } + + return property + .EnumerateArray() + .Where(item => item.ValueKind == JsonValueKind.String) + .Select(item => item.GetString()) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .ToList(); + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HostnameResolution.cs b/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HostnameResolution.cs index 784463b..2eaddfa 100644 --- a/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HostnameResolution.cs +++ b/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/HostnameResolution.cs @@ -1,9 +1,4 @@ using System; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Text.Json; - namespace winPEAS.Info.NetworkInfo { public class HostnameResolutionInfo @@ -15,9 +10,6 @@ namespace winPEAS.Info.NetworkInfo public static class HostnameResolution { - private const int INTERNET_SEARCH_TIMEOUT = 15; - private static readonly HttpClient httpClient = new HttpClient(); - /// /// Attempts to resolve the local hostname via the external lambda. /// Always returns a populated object. @@ -28,36 +20,10 @@ namespace winPEAS.Info.NetworkInfo try { - // 1. Determine hostname - info.Hostname = Dns.GetHostName(); - if (string.IsNullOrEmpty(info.Hostname)) - info.Hostname = Environment.MachineName; - - // 2. Prepare JSON body - var payload = new StringContent( - JsonSerializer.Serialize(new { hostname = info.Hostname }), - Encoding.UTF8, - "application/json"); - - // 3. Configure HttpClient (header added once) - if (!httpClient.DefaultRequestHeaders.Contains("User-Agent")) - httpClient.DefaultRequestHeaders.Add("User-Agent", "winpeas"); - httpClient.Timeout = TimeSpan.FromSeconds(INTERNET_SEARCH_TIMEOUT); - - // 4. Call external checker - var resp = httpClient - .PostAsync("https://tools.hacktricks.wiki/api/host-checker", payload) - .GetAwaiter().GetResult(); - - if (resp.IsSuccessStatusCode) - { - info.ExternalCheckResult = resp.Content.ReadAsStringAsync() - .GetAwaiter().GetResult(); - } - else - { - info.Error = $"External check failed (HTTP {(int)resp.StatusCode})"; - } + var result = HackTricksHostChecker.GetResult(); + info.Hostname = result.Hostname; + info.ExternalCheckResult = result.HostOnlyResponse; + info.Error = result.Error; } catch (Exception ex) { diff --git a/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/InternetConnectivity.cs b/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/InternetConnectivity.cs index b878aba..ff81bca 100644 --- a/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/InternetConnectivity.cs +++ b/winPEAS/winPEASexe/winPEAS/Info/NetworkInfo/InternetConnectivity.cs @@ -1,11 +1,8 @@ using System; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Net.NetworkInformation; using System.Net.Sockets; -using System.Text; -using System.Text.Json; using System.Threading; namespace winPEAS.Info.NetworkInfo @@ -49,9 +46,6 @@ namespace winPEAS.Info.NetworkInfo private static readonly string[] DNS_ICMP_IPS = { "1.1.1.1", "8.8.8.8" }; - private const string LAMBDA_URL = - "https://tools.hacktricks.wiki/api/host-checker"; - // Shared HttpClient (kept for HTTP & Lambda checks) private static readonly HttpClient http = new HttpClient { @@ -115,32 +109,7 @@ namespace winPEAS.Info.NetworkInfo private static bool TryLambdaAccess(out string error) { - try - { - using var cts = - new CancellationTokenSource(TimeSpan.FromMilliseconds(HTTP_TIMEOUT_MS)); - - var payload = new StringContent( - JsonSerializer.Serialize(new { hostname = Environment.MachineName }), - Encoding.UTF8, - "application/json"); - var req = new HttpRequestMessage(HttpMethod.Post, LAMBDA_URL); - req.Content = payload; - req.Headers.UserAgent.ParseAdd("winpeas"); - req.Headers.Accept.Add( - new MediaTypeWithQualityHeaderValue("application/json")); - - var resp = http.SendAsync(req, cts.Token).GetAwaiter().GetResult(); - - error = resp.IsSuccessStatusCode ? null : - $"HTTP {(int)resp.StatusCode}"; - return error == null; - } - catch (Exception ex) - { - error = ex.Message; - return false; - } + return TryWebRequest("https://example.com", out error); } private static bool TryDnsAccess(string ip, out string error) diff --git a/winPEAS/winPEASexe/winPEAS/winPEAS.csproj b/winPEAS/winPEASexe/winPEAS/winPEAS.csproj index 7fbfb80..81e3126 100644 --- a/winPEAS/winPEASexe/winPEAS/winPEAS.csproj +++ b/winPEAS/winPEASexe/winPEAS/winPEAS.csproj @@ -1226,6 +1226,7 @@ + @@ -1264,6 +1265,7 @@ +