Files
PEASS-ng/.github/workflows/PR-tests.yml
T

404 lines
15 KiB
YAML

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 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