Created
February 7, 2025 00:18
-
-
Save TechByTom/ab58969beb7508f313095a31bfc0e13a to your computer and use it in GitHub Desktop.
WIP - nmap xml file interpreter that checks a variety of information about any web interfaces found.
This file contains 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
param( | |
[Parameter(Mandatory=$true)] | |
[string]$XmlPath, | |
[Parameter(Mandatory=$false)] | |
[int]$MaxHosts = 15, | |
[Parameter(Mandatory=$false)] | |
[int]$RequestTimeout = 10 | |
) | |
# Function definitions must come before usage | |
function Test-DNSResolution { | |
param( | |
[string]$Hostname, | |
[string]$IP | |
) | |
try { | |
$dnsResults = [System.Net.Dns]::GetHostEntry($Hostname) | |
$ipAddresses = $dnsResults.AddressList | ForEach-Object { $_.IPAddressToString } | |
# Check if the IP we have matches any of the DNS results | |
$ipMatch = $ipAddresses -contains $IP | |
return @{ | |
Resolves = $true | |
IPAddresses = $ipAddresses | |
IPMatch = $ipMatch | |
Error = $null | |
} | |
} | |
catch { | |
return @{ | |
Resolves = $false | |
IPAddresses = @() | |
IPMatch = $false | |
Error = $_.Exception.Message | |
} | |
} | |
} | |
function Get-TargetHostname { | |
param( | |
[string]$Hostname, | |
[string]$IP | |
) | |
if (-not $Hostname) { | |
Write-Host " No hostname provided, using IP: $IP" | |
return $IP | |
} | |
$dnsCheck = Test-DNSResolution -Hostname $Hostname -IP $IP | |
if (-not $dnsCheck.Resolves) { | |
Write-Host " Hostname '$Hostname' does not resolve in DNS, using IP: $IP" | |
return $IP | |
} | |
if (-not $dnsCheck.IPMatch) { | |
Write-Host " Warning: Hostname '$Hostname' resolves to different IPs: $($dnsCheck.IPAddresses -join ', ')" | |
Write-Host " Target IP '$IP' not in DNS results, using IP instead of hostname" | |
return $IP | |
} | |
Write-Host " Hostname '$Hostname' resolves correctly to IP: $IP" | |
return $Hostname | |
} | |
function Get-Favicon { | |
param( | |
[string]$BaseUrl, | |
[bool]$IsSSL, | |
[int]$Timeout = 10 | |
) | |
try { | |
# First try standard /favicon.ico | |
$faviconUrl = "{0}/favicon.ico" -f $BaseUrl | |
Write-Host " → Attempting: $faviconUrl" | |
if ($IsSSL) { | |
$response = Invoke-WebRequest -Uri $faviconUrl -MaximumRedirection 0 -SkipCertificateCheck -TimeoutSec $Timeout -ErrorAction SilentlyContinue | |
} else { | |
$response = Invoke-WebRequest -Uri $faviconUrl -MaximumRedirection 0 -TimeoutSec $Timeout -ErrorAction SilentlyContinue | |
} | |
if ($response.StatusCode -eq 200 -and $response.RawContentLength -gt 0) { | |
return [Convert]::ToBase64String($response.Content) | |
} | |
} catch { | |
# Don't output anything - the error is expected for sites without favicon.ico | |
} | |
return $null | |
} | |
function Fetch-Url { | |
param( | |
[string]$TargetHost, | |
[int]$Port, | |
[bool]$IsSSL, | |
[int]$Timeout = 10 | |
) | |
$MaxRedirects = 10 | |
$RedirectCount = 0 | |
$results = @() | |
$protocol = if ($IsSSL) { "https" } else { "http" } | |
$currentUrl = "{0}://{1}:{2}" -f $protocol, $TargetHost, $Port | |
while ($true) { | |
try { | |
$requestResult = [ordered]@{ | |
Url = $currentUrl | |
RedirectDepth = $RedirectCount | |
StatusCode = $null | |
Error = $null | |
Headers = @{} | |
Content = $null | |
Favicon = $null | |
RedirectedRequest = $null | |
} | |
Write-Host " → Attempting: $currentUrl" | |
if ($IsSSL) { | |
$response = Invoke-WebRequest -Uri $currentUrl -MaximumRedirection 0 -SkipCertificateCheck -TimeoutSec $Timeout -ErrorAction SilentlyContinue | |
} else { | |
$response = Invoke-WebRequest -Uri $currentUrl -MaximumRedirection 0 -TimeoutSec $Timeout -ErrorAction SilentlyContinue | |
} | |
$requestResult.StatusCode = $response.StatusCode | |
$requestResult.Headers = $response.Headers | |
$requestResult.Content = $response.Content | |
# Display content length | |
$contentLength = if ($response.RawContentLength -gt 0) { | |
$response.RawContentLength | |
} else { | |
[System.Text.Encoding]::UTF8.GetByteCount($response.Content) | |
} | |
Write-Host " → Received $contentLength bytes" | |
# Try to get favicon | |
$favicon = Get-Favicon -BaseUrl $currentUrl -IsSSL $IsSSL -Timeout $Timeout | |
if ($favicon) { | |
$requestResult.Favicon = $favicon | |
} | |
$results += $requestResult | |
break | |
} catch { | |
if ($_.Exception.Response) { | |
$statusCode = [int]$_.Exception.Response.StatusCode | |
$requestResult.StatusCode = $statusCode | |
$requestResult.Headers = $_.Exception.Response.Headers | |
$requestResult.Error = $_.Exception.Message | |
# Try to get response content even for error status codes | |
try { | |
$stream = $_.Exception.Response.GetResponseStream() | |
$reader = New-Object System.IO.StreamReader($stream) | |
$responseContent = $reader.ReadToEnd() | |
$contentLength = [System.Text.Encoding]::UTF8.GetByteCount($responseContent) | |
$requestResult.Content = $responseContent | |
Write-Host " → Received $contentLength bytes" | |
} catch { | |
# If we can't read the response content, continue with error handling | |
} | |
$results += $requestResult | |
if ($statusCode -ge 300 -and $statusCode -lt 400) { | |
if ($RedirectCount -ge $MaxRedirects) { | |
Write-Host " → Warning: Maximum redirects reached" -ForegroundColor Yellow | |
break | |
} | |
$location = $_.Exception.Response.Headers.GetValues("Location")[0] | |
# Update IsSSL based on redirect URL | |
$IsSSL = $location.StartsWith("https") | |
if (-not $location.StartsWith("http")) { | |
$baseUri = [System.Uri]::new($currentUrl) | |
$location = "{0}://{1}:{2}{3}" -f $baseUri.Scheme, $baseUri.Host, $baseUri.Port, $location | |
} | |
$RedirectCount++ | |
Write-Host " → Following redirect to: $location" | |
$currentUrl = $location | |
continue | |
} | |
Write-Host " → Warning: HTTP $statusCode - $($_.Exception.Message)" -ForegroundColor Yellow | |
} else { | |
$requestResult.Error = $_.Exception.Message | |
$results += $requestResult | |
Write-Host " → Warning: $($_.Exception.Message)" -ForegroundColor Yellow | |
} | |
break | |
} | |
} | |
return $results | |
} | |
# Convert relative path to absolute | |
$XmlPath = if ([System.IO.Path]::IsPathRooted($XmlPath)) { | |
$XmlPath | |
} else { | |
Join-Path -Path (Get-Location) -ChildPath $XmlPath | |
} | |
if (-not (Test-Path $XmlPath)) { | |
Write-Error "XML file not found at path: $XmlPath" | |
exit 1 | |
} | |
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" | |
$outputDir = Join-Path -Path (Get-Location) -ChildPath "nmap_results_$timestamp" | |
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null | |
try { | |
$settings = New-Object System.Xml.XmlReaderSettings | |
$settings.DtdProcessing = [System.Xml.DtdProcessing]::Parse | |
$reader = [System.Xml.XmlReader]::Create($XmlPath, $settings) | |
$processedHosts = 0 | |
while ($reader.Read() -and ($processedHosts -lt $MaxHosts)) { | |
if ($reader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader.Name -eq "host") { | |
$hostXml = [xml]$reader.ReadOuterXml() | |
$targetHost = $hostXml.host | |
$hostIP = $targetHost.address.addr | |
$hostname = $targetHost.hostnames.hostname.name | |
Write-Host "`nScanning host: $hostIP $(if ($hostname) { "($hostname)" })" | |
# Determine the appropriate hostname/IP to use based on DNS resolution | |
$targetHostname = Get-TargetHostname -Hostname $hostname -IP $hostIP | |
# Get all open ports from the Nmap XML | |
$openPorts = @($targetHost.ports.port | Where-Object { $_.state.state -eq "open" }) | |
if ($openPorts.Count -eq 0) { | |
Write-Host " No open ports found in Nmap results" | |
$ports = @() | |
} else { | |
$ports = $openPorts | ForEach-Object { | |
$isSSL = $false | |
if ($_.service.tunnel -eq "ssl" -or $_.service.name -match "ssl|tls|https" -or $_.service.script.output -match "ssl|tls") { | |
$isSSL = $true | |
} | |
Write-Host "Port $($_.portid) ($($_.service.name)$(if ($isSSL) { "/ssl" })):" | |
$webRequests = Fetch-Url -TargetHost $targetHostname -Port $_.portid -IsSSL $isSSL -Timeout $RequestTimeout | |
[PSCustomObject]@{ | |
PortNumber = $_.portid | |
Protocol = $_.protocol | |
Service = $_.service.name | |
IsSSL = $isSSL | |
WebRequest = $webRequests | |
} | |
} | |
} | |
$hostResult = [PSCustomObject]@{ | |
IP = $hostIP | |
Hostname = $hostname | |
OpenPorts = $ports | |
ScanTime = $targetHost.starttime | |
} | |
$outputFile = Join-Path -Path $outputDir -ChildPath "$hostIP.json" | |
$hostResult | ConvertTo-Json -Depth 10 | Out-File $outputFile | |
$processedHosts++ | |
Write-Host "Completed host: $hostIP ($processedHosts of $MaxHosts)`n" | |
} | |
} | |
} catch { | |
Write-Error "Error processing NMAP XML: $_" | |
exit 1 | |
} finally { | |
if ($reader) { | |
$reader.Close() | |
} | |
} | |
Write-Host "Processing complete. Results saved in: $outputDir" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment