Vulnerability/Get-ExposedServices.ps1

function Get-ExposedServices {
    <#
    .SYNOPSIS
        Identifies internet-facing services and exposed network ports
    .DESCRIPTION
        Scans for services listening on public interfaces, checks firewall rules,
        identifies high-risk exposed services, and provides security recommendations.
    .PARAMETER IncludeAllPorts
        Scan all common ports (1-1024) instead of just high-risk ports
    .PARAMETER CustomPorts
        Additional custom ports to scan
    .PARAMETER CheckFirewall
        Analyze firewall rules for exposed services
    .PARAMETER OutputPath
        Path to save exposed services report
    .PARAMETER Format
        Report format: HTML, JSON, CSV, or All
    .EXAMPLE
        Get-ExposedServices
        Get-ExposedServices -IncludeAllPorts -CheckFirewall -OutputPath "C:\Reports"
    .OUTPUTS
        PSCustomObject with exposed services findings
    #>

    
    [CmdletBinding()]
    param(
        [Parameter()]
        [switch]$IncludeAllPorts,
        
        [Parameter()]
        [int[]]$CustomPorts,
        
        [Parameter()]
        [switch]$CheckFirewall,
        
        [Parameter()]
        [string]$OutputPath,
        
        [Parameter()]
        [ValidateSet('HTML', 'JSON', 'CSV', 'All')]
        [string]$Format = 'HTML'
    )
    
    begin {
        Write-Host "=== EXPOSED SERVICES SCAN ===" -ForegroundColor Cyan
        Write-Host "Start Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Yellow
        Write-Host "Scan Mode: $(if($IncludeAllPorts){'Full Port Scan'}else{'High-Risk Ports Only'})" -ForegroundColor Yellow
        Write-Host "Check Firewall: $(if($CheckFirewall){'Yes'}else{'No'})" -ForegroundColor Yellow
        Write-Host "Custom Ports: $(if($CustomPorts){$CustomPorts -join ', '}else{'None'})" -ForegroundColor Yellow
        
        $findings = [PSCustomObject]@{
            ScanDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
            ComputerName = $env:COMPUTERNAME
            PublicIP = $null
            LocalIPs = @()
            ListeningPorts = @()
            ExposedServices = @()
            FirewallRules = @()
            Summary = @{
                TotalListening = 0
                HighRisk = 0
                MediumRisk = 0
                LowRisk = 0
                FirewallAllowRules = 0
            }
            Recommendations = @()
        }
        
        # High-risk ports database
        $highRiskPorts = @{
            21 = @{Service='FTP'; Risk='High'; Description='Unencrypted file transfer'}
            22 = @{Service='SSH'; Risk='Medium'; Description='Secure shell - requires strong auth'}
            23 = @{Service='Telnet'; Risk='Critical'; Description='Unencrypted remote access'}
            25 = @{Service='SMTP'; Risk='Medium'; Description='Mail server - spam relay risk'}
            53 = @{Service='DNS'; Risk='Medium'; Description='DNS server - DDoS amplification'}
            80 = @{Service='HTTP'; Risk='Medium'; Description='Web server - unencrypted'}
            110 = @{Service='POP3'; Risk='Medium'; Description='Mail retrieval - unencrypted'}
            135 = @{Service='RPC'; Risk='High'; Description='Windows RPC - attack vector'}
            139 = @{Service='NetBIOS'; Risk='High'; Description='NetBIOS - information disclosure'}
            143 = @{Service='IMAP'; Risk='Medium'; Description='Mail access - unencrypted'}
            443 = @{Service='HTTPS'; Risk='Low'; Description='Secure web server'}
            445 = @{Service='SMB'; Risk='Critical'; Description='File sharing - ransomware vector'}
            1433 = @{Service='SQL Server'; Risk='High'; Description='Database - data breach risk'}
            1434 = @{Service='SQL Monitor'; Risk='High'; Description='SQL Server Discovery'}
            3306 = @{Service='MySQL'; Risk='High'; Description='Database - data breach risk'}
            3389 = @{Service='RDP'; Risk='Critical'; Description='Remote Desktop - brute-force target'}
            5432 = @{Service='PostgreSQL'; Risk='High'; Description='Database - data breach risk'}
            5900 = @{Service='VNC'; Risk='High'; Description='Remote desktop - weak auth'}
            8080 = @{Service='HTTP-Alt'; Risk='Medium'; Description='Alternative web server'}
            8443 = @{Service='HTTPS-Alt'; Risk='Low'; Description='Alternative secure web'}
        }
        
        # Merge user-specified custom ports into the assessed port database
        if ($CustomPorts) {
            foreach ($customPort in $CustomPorts) {
                if (-not $highRiskPorts.ContainsKey($customPort)) {
                    $highRiskPorts[$customPort] = @{Service='Custom'; Risk='Medium'; Description='User-specified custom port'}
                }
            }
        }
    }
    
    process {
        try {
            # 1. GET NETWORK INFORMATION
            Write-Host "`n[1/4] Gathering network information..." -ForegroundColor Cyan
            
            # Get local IP addresses
            try {
                $adapters = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | 
                    Where-Object { $_.InterfaceAlias -notlike '*Loopback*' }
                
                foreach ($adapter in $adapters) {
                    $findings.LocalIPs += [PSCustomObject]@{
                        IPAddress = $adapter.IPAddress
                        Interface = $adapter.InterfaceAlias
                        PrefixLength = $adapter.PrefixLength
                    }
                    Write-Host " Local IP: $($adapter.IPAddress) ($($adapter.InterfaceAlias))" -ForegroundColor Gray
                }
            }
            catch {
                Write-Warning "Could not retrieve network adapters"
            }
            
            # Ensure TLS 1.2 on Windows PowerShell 5.1 (idempotent, only if the type exists)
            try {
                if ($null -ne ([Net.SecurityProtocolType] -as [type])) {
                    [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
                }
            }
            catch {
                Write-Verbose "Could not enforce TLS 1.2: $_"
            }

            # Try to get public IP
            try {
                Write-Host " Detecting public IP..." -ForegroundColor Gray
                $publicIP = (Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing -TimeoutSec 5).Content
                $findings.PublicIP = $publicIP
                Write-Host " Public IP: $publicIP" -ForegroundColor Green
            }
            catch {
                Write-Host " Could not detect public IP (offline or blocked)" -ForegroundColor Gray
            }
            
            # 2. SCAN LISTENING PORTS
            Write-Host "`n[2/4] Scanning listening ports..." -ForegroundColor Cyan
            
            # Get listening TCP connections
            try {
                $connections = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue
                
                foreach ($conn in $connections) {
                    $localPort = $conn.LocalPort
                    
                    # Get process info
                    $process = Get-Process -Id $conn.OwningProcess -ErrorAction SilentlyContinue
                    $processName = if ($process) { $process.ProcessName } else { 'Unknown' }
                    $processPath = if ($process) { $process.Path } else { 'Unknown' }
                    
                    # Check if it's a high-risk port
                    $portInfo = $highRiskPorts[$localPort]
                    $serviceName = if ($portInfo) { $portInfo.Service } else { 'Unknown' }
                    $riskLevel = if ($portInfo) { $portInfo.Risk } else { 'Low' }
                    $description = if ($portInfo) { $portInfo.Description } else { 'Custom service' }
                    
                    # Determine if exposed to internet
                    $isExposed = ($conn.LocalAddress -eq '0.0.0.0' -or $conn.LocalAddress -eq '::')
                    
                    $portEntry = [PSCustomObject]@{
                        Port = $localPort
                        Service = $serviceName
                        Process = $processName
                        ProcessPath = $processPath
                        PID = $conn.OwningProcess
                        LocalAddress = $conn.LocalAddress
                        IsExposed = $isExposed
                        Risk = $riskLevel
                        Description = $description
                    }
                    
                    $findings.ListeningPorts += $portEntry
                    
                    # If exposed and high/critical risk, add to exposed services
                    if ($isExposed -and $riskLevel -in @('Critical', 'High', 'Medium')) {
                        $findings.ExposedServices += $portEntry
                        
                        $color = switch ($riskLevel) {
                            'Critical' { 'Red' }
                            'High' { 'Yellow' }
                            'Medium' { 'Gray' }
                            default { 'Gray' }
                        }
                        
                        Write-Host " [!] $riskLevel - Port $localPort ($serviceName) - $processName" -ForegroundColor $color
                    }
                }
                
                $findings.Summary.TotalListening = $findings.ListeningPorts.Count
                Write-Host " Found $($findings.Summary.TotalListening) listening ports" -ForegroundColor Green
            }
            catch {
                Write-Warning "Could not scan TCP connections - $_"
            }
            
            # FIXED: Count risk levels with safe array wrapping
            $findings.Summary.HighRisk = @($findings.ExposedServices | Where-Object { $_.Risk -in @('Critical', 'High') }).Count
            $findings.Summary.MediumRisk = @($findings.ExposedServices | Where-Object { $_.Risk -eq 'Medium' }).Count
            $findings.Summary.LowRisk = @($findings.ExposedServices | Where-Object { $_.Risk -eq 'Low' }).Count
            
            # 3. CHECK FIREWALL RULES
            if ($CheckFirewall) {
                Write-Host "`n[3/4] Analyzing firewall rules..." -ForegroundColor Cyan
                
                try {
                    $fwRules = Get-NetFirewallRule -Enabled True -Direction Inbound -ErrorAction SilentlyContinue | 
                        Where-Object { $_.Action -eq 'Allow' }
                    
                    foreach ($rule in $fwRules) {
                        # Get port filter
                        $portFilter = Get-NetFirewallPortFilter -AssociatedNetFirewallRule $rule -ErrorAction SilentlyContinue
                        $addressFilter = Get-NetFirewallAddressFilter -AssociatedNetFirewallRule $rule -ErrorAction SilentlyContinue
                        
                        if ($portFilter -and $portFilter.LocalPort) {
                            $localPorts = $portFilter.LocalPort
                            $protocol = $portFilter.Protocol
                            $remoteAddress = if ($addressFilter) { $addressFilter.RemoteAddress } else { 'Any' }
                            
                            # Check if allows from internet
                            $allowsInternet = ($remoteAddress -eq 'Any' -or $remoteAddress -contains 'Internet')
                            
                            if ($allowsInternet) {
                                $ruleEntry = [PSCustomObject]@{
                                    Name = $rule.Name
                                    DisplayName = $rule.DisplayName
                                    Port = $localPorts
                                    Protocol = $protocol
                                    RemoteAddress = $remoteAddress
                                    Profile = $rule.Profile
                                }
                                
                                $findings.FirewallRules += $ruleEntry
                                $findings.Summary.FirewallAllowRules++
                                
                                Write-Host " [*] Firewall allows: $($rule.DisplayName) - Port $localPorts" -ForegroundColor Gray
                            }
                        }
                    }
                    
                    Write-Host " Found $($findings.Summary.FirewallAllowRules) allow rules from internet" -ForegroundColor $(
                        if ($findings.Summary.FirewallAllowRules -gt 10) { 'Yellow' } else { 'Green' }
                    )
                }
                catch {
                    Write-Warning "Could not analyze firewall rules - $_"
                }
            }
            else {
                Write-Host "`n[3/4] Skipping firewall analysis (disabled)" -ForegroundColor Gray
            }
            
            # 4. GENERATE RECOMMENDATIONS
            Write-Host "`n[4/4] Generating recommendations..." -ForegroundColor Cyan
            
            # Check for critical services
            $criticalServices = @($findings.ExposedServices | Where-Object { $_.Risk -eq 'Critical' })
            if ($criticalServices.Count -gt 0) {
                foreach ($svc in $criticalServices) {
                    $findings.Recommendations += "CRITICAL: Port $($svc.Port) ($($svc.Service)) is exposed - block immediately or use VPN"
                }
            }
            
            # Check for RDP
            $rdp = $findings.ExposedServices | Where-Object { $_.Port -eq 3389 }
            if ($rdp) {
                $findings.Recommendations += "CRITICAL: Remote Desktop (3389) exposed - use VPN or change to non-standard port with strong auth"
            }
            
            # Check for SMB
            $smb = $findings.ExposedServices | Where-Object { $_.Port -eq 445 }
            if ($smb) {
                $findings.Recommendations += "CRITICAL: SMB (445) exposed - major ransomware risk, block immediately"
            }
            
            # Check for databases
            $databases = @($findings.ExposedServices | Where-Object { $_.Port -in @(1433, 3306, 5432) })
            if ($databases.Count -gt 0) {
                $findings.Recommendations += "HIGH: Database port(s) exposed - restrict to application servers only"
            }
            
            # Check for management ports
            $mgmt = @($findings.ExposedServices | Where-Object { $_.Port -in @(22, 23, 135, 5900) })
            if ($mgmt.Count -gt 0) {
                $findings.Recommendations += "HIGH: Management protocol exposed - use VPN or IP whitelisting"
            }
            
            # General recommendations
            if ($findings.ExposedServices.Count -eq 0) {
                $findings.Recommendations += "No high-risk services exposed - maintain current security posture"
            }
            
            $findings.Recommendations += "Use VPN for remote access instead of direct port exposure"
            $findings.Recommendations += "Implement IP whitelisting for necessary exposed services"
            $findings.Recommendations += "Enable Network Level Authentication for RDP if required"
            $findings.Recommendations += "Use reverse proxy (nginx/Cloudflare) for web services"
            $findings.Recommendations += "Monitor exposed services with IDS/IPS"
            $findings.Recommendations += "Conduct quarterly port scans from external perspective"
            $findings.Recommendations += "Use fail2ban or similar for brute-force protection"
            
            # 5. EXPORT REPORTS
            if ($OutputPath) {
                Write-Host "`n[*] Exporting exposed services reports..." -ForegroundColor Cyan
                
                if (-not (Test-Path $OutputPath)) {
                    New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
                }
                
                $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
                $reportName = "ExposedServices_$timestamp"
                
                # Export JSON
                if ($Format -in @('JSON', 'All')) {
                    $jsonPath = Join-Path $OutputPath "$reportName.json"
                    $findings | ConvertTo-Json -Depth 10 | Out-File $jsonPath -Encoding UTF8
                    Write-Host " JSON report saved: $jsonPath" -ForegroundColor Green
                }
                
                # Export CSV
                if ($Format -in @('CSV', 'All')) {
                    $csvPath = Join-Path $OutputPath "$reportName.csv"
                    $findings.ExposedServices | Export-Csv $csvPath -NoTypeInformation
                    Write-Host " CSV report saved: $csvPath" -ForegroundColor Green
                }
                
                # Export HTML
                if ($Format -in @('HTML', 'All')) {
                    $htmlPath = Join-Path $OutputPath "$reportName.html"
                    $html = Generate-ExposedServicesHTML -Findings $findings
                    $html | Out-File $htmlPath -Encoding UTF8
                    Write-Host " HTML report saved: $htmlPath" -ForegroundColor Green
                }
            }
            
        }
        catch {
            Write-Error "Error during exposed services scan - $_"
            throw
        }
    }
    
    end {
        Write-Host "`n=== EXPOSED SERVICES SCAN COMPLETE ===" -ForegroundColor $(
            if ($findings.Summary.HighRisk -gt 0) { 'Red' }
            elseif ($findings.Summary.MediumRisk -gt 0) { 'Yellow' }
            else { 'Green' }
        )
        
        Write-Host "`nSummary:" -ForegroundColor Cyan
        Write-Host " Total Listening Ports: $($findings.Summary.TotalListening)" -ForegroundColor White
        Write-Host " Exposed Services: $($findings.ExposedServices.Count)" -ForegroundColor $(
            if ($findings.ExposedServices.Count -gt 0) { 'Yellow' } else { 'Green' }
        )
        Write-Host " Critical/High Risk: $($findings.Summary.HighRisk)" -ForegroundColor $(
            if ($findings.Summary.HighRisk -gt 0) { 'Red' } else { 'Gray' }
        )
        Write-Host " Medium Risk: $($findings.Summary.MediumRisk)" -ForegroundColor Gray
        Write-Host " Low Risk: $($findings.Summary.LowRisk)" -ForegroundColor Gray
        
        if ($CheckFirewall) {
            Write-Host " Firewall Allow Rules: $($findings.Summary.FirewallAllowRules)" -ForegroundColor Gray
        }
        
        if ($findings.ExposedServices.Count -gt 0) {
            Write-Host "`nExposed High-Risk Services:" -ForegroundColor Cyan
            
            $highRiskExposed = @($findings.ExposedServices | Where-Object { $_.Risk -in @('Critical', 'High') })
            
            if ($highRiskExposed.Count -gt 0) {
                $highRiskExposed |
                    Sort-Object { 
                        switch ($_.Risk) {
                            'Critical' { 0 }
                            'High' { 1 }
                            default { 2 }
                        }
                    } |
                    Select-Object -First 10 |
                    ForEach-Object {
                        $color = switch ($_.Risk) {
                            'Critical' { 'Red' }
                            'High' { 'Yellow' }
                            default { 'Gray' }
                        }
                        Write-Host " [$($_.Risk)] Port $($_.Port) - $($_.Service) ($($_.Process))" -ForegroundColor $color
                    }
            }
        }
        
        if ($findings.Recommendations.Count -gt 0) {
            Write-Host "`nRecommendations:" -ForegroundColor Cyan
            $findings.Recommendations | Select-Object -First 10 | ForEach-Object {
                Write-Host " ! $_" -ForegroundColor Yellow
            }
        }
        
        return $findings
    }
}

# Helper function to generate HTML report
function Generate-ExposedServicesHTML {
    param($Findings)
    
    $riskColor = if($Findings.Summary.HighRisk -gt 0){'critical'}
                 elseif($Findings.Summary.MediumRisk -gt 0){'medium'}
                 else{'safe'}
    
    $html = @"
<!DOCTYPE html>
<html>
<head>
    <title>Exposed Services Report - $($Findings.ComputerName)</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
        .container { max-width: 1400px; margin: 0 auto; background: white; padding: 30px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        h1 { color: #2c3e50; border-bottom: 3px solid #e74c3c; padding-bottom: 10px; }
        h2 { color: #34495e; margin-top: 30px; }
        .summary { background: #ecf0f1; padding: 20px; border-radius: 5px; margin: 20px 0; }
        .metric { display: inline-block; margin: 10px 20px 10px 0; }
        .metric-label { font-weight: bold; color: #7f8c8d; }
        .metric-value { font-size: 24px; font-weight: bold; }
        .critical { color: #e74c3c; }
        .high { color: #e67e22; }
        .medium { color: #f39c12; }
        .low { color: #95a5a6; }
        .safe { color: #27ae60; }
        table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 14px; }
        th { background: #e74c3c; color: white; padding: 12px; text-align: left; }
        td { padding: 10px; border-bottom: 1px solid #ddd; vertical-align: top; }
        tr:hover { background: #f8f9fa; }
        .risk-badge { padding: 4px 8px; border-radius: 3px; color: white; font-weight: bold; font-size: 12px; }
        .risk-critical { background: #e74c3c; }
        .risk-high { background: #e67e22; }
        .risk-medium { background: #f39c12; }
        .risk-low { background: #95a5a6; }
        .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; color: #7f8c8d; font-size: 12px; }
        .no-exposure { color: #27ae60; font-weight: bold; font-size: 16px; padding: 20px; text-align: center; }
        .network-info { background: #e8f4f8; padding: 15px; border-radius: 5px; margin: 10px 0; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Exposed Services Security Report</h1>
        <p><strong>Computer:</strong> $($Findings.ComputerName) | <strong>Scan Date:</strong> $($Findings.ScanDate)</p>
         
        <div class="network-info">
            <h3>Network Information</h3>
            <p><strong>Public IP:</strong> $(if($Findings.PublicIP){$Findings.PublicIP}else{'Not detected'})</p>
            <p><strong>Local IPs:</strong> $(($Findings.LocalIPs | ForEach-Object { "$($_.IPAddress) ($($_.Interface))" }) -join ', ')</p>
        </div>
         
        <div class="summary">
            <h2>Summary</h2>
            <div class="metric">
                <div class="metric-label">Listening Ports</div>
                <div class="metric-value">$($Findings.Summary.TotalListening)</div>
            </div>
            <div class="metric">
                <div class="metric-label">Exposed Services</div>
                <div class="metric-value $(if($Findings.ExposedServices.Count -gt 0){'critical'}else{'safe'})">
                    $($Findings.ExposedServices.Count)
                </div>
            </div>
            <div class="metric">
                <div class="metric-label">Critical/High Risk</div>
                <div class="metric-value critical">$($Findings.Summary.HighRisk)</div>
            </div>
            <div class="metric">
                <div class="metric-label">Medium Risk</div>
                <div class="metric-value medium">$($Findings.Summary.MediumRisk)</div>
            </div>
        </div>
         
        <h2>Exposed Services</h2>
"@

    
    if ($Findings.ExposedServices.Count -gt 0) {
        $html += @"
        <table>
            <tr>
                <th style="width: 80px;">Risk</th>
                <th style="width: 80px;">Port</th>
                <th style="width: 120px;">Service</th>
                <th style="width: 150px;">Process</th>
                <th>Description</th>
                <th style="width: 150px;">Local Address</th>
            </tr>
"@

        
        foreach ($service in ($Findings.ExposedServices | Sort-Object { 
            switch ($_.Risk) {
                'Critical' { 0 }
                'High' { 1 }
                'Medium' { 2 }
                'Low' { 3 }
            }
        })) {
            $riskClass = "risk-$($service.Risk.ToLower())"
            $desc = $service.Description -replace '<', '&lt;' -replace '>', '&gt;'
            
            $html += @"
            <tr>
                <td><span class="risk-badge $riskClass">$($service.Risk)</span></td>
                <td><strong>$($service.Port)</strong></td>
                <td>$($service.Service)</td>
                <td>$($service.Process)</td>
                <td>$desc</td>
                <td>$($service.LocalAddress)</td>
            </tr>
"@

        }
        
        $html += " </table>`n"
    }
    else {
        $html += " <p class=`"no-exposure`">No high-risk services exposed to internet</p>`n"
    }
    
    $html += " `n <h2>Recommendations</h2>`n <ul>`n"
    
    foreach ($rec in $Findings.Recommendations) {
        $recEscaped = $rec -replace '<', '&lt;' -replace '>', '&gt;'
        $html += " <li>$recEscaped</li>`n"
    }
    
    $currentDate = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    
    $html += @"
        </ul>
         
        <div class="footer">
            Generated by WindowsSecurityAudit Module | Exposed Services Scan | $currentDate
        </div>
    </div>
</body>
</html>
"@

    
    return $html
}