Skip to content

Instantly share code, notes, and snippets.

@TechByTom
Created February 7, 2025 00:18
Show Gist options
  • Save TechByTom/ab58969beb7508f313095a31bfc0e13a to your computer and use it in GitHub Desktop.
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.
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