new-check(linpeas): add high-impact vulnerability detection #295
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR-tests | |
| on: | |
| pull_request: | |
| branches: | |
| - master | |
| - main | |
| paths-ignore: | |
| - '.github/**' | |
| workflow_dispatch: | |
| jobs: | |
| Build_and_test_winpeas_pr: | |
| runs-on: windows-latest | |
| # environment variables | |
| env: | |
| Solution_Path: 'winPEAS\winPEASexe\winPEAS.sln' | |
| Configuration: 'Release' | |
| steps: | |
| # checkout | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - name: Download regexes | |
| run: | | |
| powershell.exe -ExecutionPolicy Bypass -File build_lists/download_regexes.ps1 | |
| # Add MSBuild to the PATH | |
| - name: Setup MSBuild.exe | |
| uses: microsoft/setup-msbuild@v2 | |
| # Setup NuGet | |
| - name: Setup NuGet.exe | |
| uses: nuget/setup-nuget@v2 | |
| # Restore the packages for testing | |
| - name: Restore the application | |
| run: nuget restore $env:Solution_Path | |
| # build | |
| - name: run MSBuild | |
| run: msbuild $env:Solution_Path /p:Configuration=$env:Configuration /p:UseSharedCompilation=false | |
| # Execute unit tests in the solution | |
| - name: Execute unit tests | |
| run: dotnet test $env:Solution_Path --configuration $env:Configuration | |
| # Build all versions | |
| - name: Build all versions | |
| run: | | |
| echo "build x64" | |
| msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x64" /p:UseSharedCompilation=false | |
| echo "build x86" | |
| msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="x86" /p:UseSharedCompilation=false | |
| echo "build Any CPU" | |
| msbuild -m $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="Any CPU" /p:UseSharedCompilation=false | |
| - name: Execute winPEAS -h | |
| shell: pwsh | |
| run: | | |
| $Configuration = "Release" | |
| $exePath = "winPEAS/winPEASexe/winPEAS/bin/$Configuration/winPEAS.exe" | |
| if (Test-Path $exePath) { | |
| & $exePath -h | |
| } else { | |
| Write-Error "winPEAS.exe not found at $exePath" | |
| } | |
| - name: Execute winPEAS cloudinfo | |
| shell: pwsh | |
| run: | | |
| $Configuration = "Release" | |
| $exePath = "winPEAS/winPEASexe/winPEAS/bin/$Configuration/winPEAS.exe" | |
| if (Test-Path $exePath) { | |
| & $exePath cloudinfo | |
| } else { | |
| Write-Error "winPEAS.exe not found at $exePath" | |
| } | |
| - name: Execute winPEAS systeminfo | |
| shell: pwsh | |
| run: | | |
| $Configuration = "Release" | |
| $exePath = "winPEAS/winPEASexe/winPEAS/bin/$Configuration/winPEAS.exe" | |
| if (Test-Path $exePath) { | |
| & $exePath systeminfo | |
| } 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 | |
| import threading | |
| 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) | |
| try: | |
| with open(count_path, "r", encoding="utf-8") as f: | |
| count = int((f.read() or "0").strip()) | |
| except FileNotFoundError: | |
| count = 0 | |
| with open(count_path, "w", encoding="utf-8") as f: | |
| f.write(str(count + 1)) | |
| vulns = [] | |
| for i in range(1, 56): | |
| severity = "low" | |
| if i % 10 == 0: | |
| severity = "critical" | |
| elif i % 10 == 1: | |
| severity = "high" | |
| elif i % 10 == 2: | |
| severity = "medium" | |
| vulns.append({ | |
| "name": f"pkg{i}", | |
| "version": f"1.{i}.0", | |
| "manager": "windows-registry", | |
| "severity": severity, | |
| "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) | |
| threading.Thread(target=self.server.shutdown, daemon=True).start() | |
| def do_GET(self): | |
| if self.path == "/ready": | |
| data = b"ready" | |
| self.send_response(200) | |
| self.send_header("Content-Type", "text/plain") | |
| self.send_header("Content-Length", str(len(data))) | |
| self.end_headers() | |
| self.wfile.write(data) | |
| return | |
| self.send_response(404) | |
| self.end_headers() | |
| def log_message(self, fmt, *args): | |
| pass | |
| HTTPServer(("127.0.0.1", 18768), Handler).serve_forever() | |
| "@ | Set-Content -Path $serverPath -Encoding utf8 | |
| $pythonCandidates = @( | |
| @{ FilePath = "python"; VersionArgs = @("--version"); ServerArgs = @($serverPath, $bodyPath, $countPath) }, | |
| @{ FilePath = "python3"; VersionArgs = @("--version"); ServerArgs = @($serverPath, $bodyPath, $countPath) }, | |
| @{ FilePath = "py"; VersionArgs = @("-3", "--version"); ServerArgs = @("-3", $serverPath, $bodyPath, $countPath) } | |
| ) | |
| $pythonCommand = $null | |
| foreach ($candidate in $pythonCandidates) { | |
| if (!(Get-Command $candidate.FilePath -ErrorAction SilentlyContinue)) { | |
| continue | |
| } | |
| & $candidate.FilePath @($candidate.VersionArgs) | Out-Null | |
| if ($LASTEXITCODE -eq 0) { | |
| $pythonCommand = $candidate | |
| break | |
| } | |
| } | |
| if (!$pythonCommand) { | |
| Write-Error "No Python interpreter found to launch the mock host-checker server." | |
| } | |
| $startInfo = [System.Diagnostics.ProcessStartInfo]::new() | |
| $startInfo.FileName = $pythonCommand.FilePath | |
| $startInfo.UseShellExecute = $false | |
| $startInfo.RedirectStandardOutput = $true | |
| $startInfo.RedirectStandardError = $true | |
| foreach ($arg in $pythonCommand.ServerArgs) { | |
| [void]$startInfo.ArgumentList.Add($arg) | |
| } | |
| $server = [System.Diagnostics.Process]::Start($startInfo) | |
| try { | |
| $serverReady = $false | |
| $deadline = (Get-Date).AddSeconds(15) | |
| while ((Get-Date) -lt $deadline) { | |
| if ($server.HasExited) { | |
| break | |
| } | |
| try { | |
| $readyResponse = Invoke-WebRequest -UseBasicParsing -TimeoutSec 1 -Uri "http://127.0.0.1:18768/ready" | |
| if ($readyResponse.StatusCode -eq 200) { | |
| $serverReady = $true | |
| break | |
| } | |
| } catch { | |
| } | |
| Start-Sleep -Milliseconds 250 | |
| } | |
| if (!$serverReady) { | |
| if (!$server.HasExited) { | |
| $server.Kill() | |
| [void]$server.WaitForExit(5000) | |
| } | |
| $stdout = $server.StandardOutput.ReadToEnd() | |
| $stderr = $server.StandardError.ReadToEnd() | |
| Write-Error "Mock host-checker server did not become ready before winPEAS execution. ExitCode=$($server.ExitCode)`nSTDOUT:`n$stdout`nSTDERR:`n$stderr" | |
| } | |
| $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" | |
| } | |
| $firstVulnLine = [regex]::Match($output, "(?m)^\s+- pkg\d+ .*$").Value | |
| if ($firstVulnLine -notmatch "pkg10 .* \[Critical\]: CVE-2026-10010") { | |
| Write-Error "Expected critical package vulnerabilities to be printed first, got: $firstVulnLine`n$output" | |
| } | |
| $criticalIndex = $output.IndexOf("pkg10") | |
| $lowIndex = $output.IndexOf("- pkg3 ") | |
| if ($criticalIndex -lt 0 -or $lowIndex -lt 0 -or $criticalIndex -gt $lowIndex) { | |
| Write-Error "Expected critical packages to appear before low packages.`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 -and !$server.HasExited) { | |
| $server.Kill() | |
| [void]$server.WaitForExit(5000) | |
| } | |
| } | |
| - name: Execute winPEAS networkinfo | |
| shell: pwsh | |
| run: | | |
| $Configuration = "Release" | |
| $exePath = "winPEAS/winPEASexe/winPEAS/bin/$Configuration/winPEAS.exe" | |
| if (Test-Path $exePath) { | |
| & $exePath networkinfo | |
| } else { | |
| Write-Error "winPEAS.exe not found at $exePath" | |
| } | |
| Build_and_test_linpeas_pr: | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Download repo | |
| - uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| # Setup go | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: '1.23' | |
| cache: false | |
| - run: go version | |
| # Build linpeas | |
| - name: Build linpeas | |
| run: | | |
| python3 -m pip install PyYAML | |
| cd linPEAS | |
| python3 -m builder.linpeas_builder --all --output linpeas_fat.sh | |
| python3 -m builder.linpeas_builder --all-no-fat --output linpeas.sh | |
| python3 -m builder.linpeas_builder --small --output linpeas_small.sh | |
| - name: Run linPEAS module metadata tests | |
| run: python3 -m unittest linPEAS.tests.test_modules_metadata | |
| - name: Run PEASS parsers tests | |
| run: python3 -m unittest discover -s parsers/tests -p "test_*.py" | |
| - name: Run linPEAS builder tests | |
| run: python3 -m unittest discover -s linPEAS/tests -p "test_*.py" | |
| # Run linpeas help as quick test | |
| - name: Run linpeas help | |
| run: linPEAS/linpeas_fat.sh -h && linPEAS/linpeas.sh -h && linPEAS/linpeas_small.sh -h | |
| # Run linpeas as a test | |
| - name: Run linpeas system_information | |
| run: linPEAS/linpeas_fat.sh -o system_information -a | |
| - name: Run linpeas container | |
| run: linPEAS/linpeas_fat.sh -o container -a | |
| - name: Run linpeas cloud | |
| run: linPEAS/linpeas_fat.sh -o cloud -a | |
| - name: Run linpeas procs_crons_timers_srvcs_sockets | |
| run: linPEAS/linpeas_fat.sh -o procs_crons_timers_srvcs_sockets -a | |
| - name: Run linpeas network_information | |
| run: linPEAS/linpeas_fat.sh -o network_information -t -a | |
| - name: Run linpeas users_information | |
| run: linPEAS/linpeas_fat.sh -o users_information -a | |
| - name: Run linpeas software_information | |
| run: linPEAS/linpeas_fat.sh -o software_information -a | |
| - name: Run linpeas interesting_perms_files | |
| run: linPEAS/linpeas_fat.sh -o interesting_perms_files -a | |
| - name: Run linpeas interesting_files | |
| run: linPEAS/linpeas_fat.sh -o interesting_files -a | |
| Build_and_test_macpeas_pr: | |
| runs-on: macos-latest | |
| steps: | |
| # Download repo | |
| - uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| # Build linpeas (macpeas) | |
| - name: Build macpeas | |
| run: | | |
| python3 -m pip install PyYAML --break-system-packages | |
| python3 -m pip install requests --break-system-packages | |
| cd linPEAS | |
| python3 -m builder.linpeas_builder --all --output linpeas_fat.sh | |
| # Run linpeas help as quick test | |
| - name: Run macpeas help | |
| run: linPEAS/linpeas_fat.sh -h | |
| # Run macpeas parts to test it | |
| - name: Run macpeas system_information | |
| run: linPEAS/linpeas_fat.sh -o system_information -a | |
| # - name: Run macpeas container | |
| # run: linPEAS/linpeas_fat.sh -o container -a | |
| # - name: Run macpeas cloud | |
| # run: linPEAS/linpeas_fat.sh -o cloud -a | |
| # - name: Run macpeas procs_crons_timers_srvcs_sockets | |
| # run: linPEAS/linpeas_fat.sh -o procs_crons_timers_srvcs_sockets -a | |
| # - name: Run macpeas network_information | |
| # run: linPEAS/linpeas_fat.sh -o network_information -t -a | |
| # - name: Run macpeas users_information | |
| # run: linPEAS/linpeas_fat.sh -o users_information -a | |
| # - name: Run macpeas software_information | |
| # run: linPEAS/linpeas_fat.sh -o software_information -a |