Skip to content

Instantly share code, notes, and snippets.

@rhymeswithmogul
Last active June 8, 2021 20:20
Show Gist options
  • Save rhymeswithmogul/d4ee2ddecd36c5af9dc15a4a6ace41ff to your computer and use it in GitHub Desktop.
Save rhymeswithmogul/d4ee2ddecd36c5af9dc15a4a6ace41ff to your computer and use it in GitHub Desktop.
A large smattering of random lookups for discovery purposes.
<#PSScriptInfo
Colin's untitled domain analyzer
Copyright (c) 2021 Colin Cogle.
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>.
.VERSION 1.6.1
.GUID 77300be0-8cd0-4ce8-838d-ea5a286e9119
.AUTHOR Colin Cogle
.COPYRIGHT (c) 2021 Colin Cogle. All Rights Reserved.
.TAGS discover, hack, infosec, security, security-txt, robots-txt, DNS, DNSSEC, SPF, DKIM, DMARC, email, ADSP, SRV, SIP, POP3, HIBP, robots, ads, SSL, TLS, SSL Labs, Tor, Domain Connect, nmap, ping, TCP, open ports, closed ports
.LICENSEURI https://www.gnu.org/licenses/agpl-3.0.html
.PROJECTURI https://gist.github.com/rhymeswithmogul/d4ee2ddecd36c5af9dc15a4a6ace41ff
.EXTERNALMODULEDEPENDENCIES DnsClient
.RELEASENOTES Cool Banner Edition! This version validates DNSSEC signatures, and provides bug fixes all around.
#> <#
CHANGELOG:
Version 1.6.1: Don't scan IPv6 addresses if only Teredo connectivity is available. Don't scan for IRC servers to save time. Detect Cloudflare DNS.
Version 1.6: It now pings, and scans some common service ports.
Version 1.5: Add cool banner (and -NoCoolBanner switch). Check DNSSEC signature validity. Bug fixes in the Domain Connect API and DNS wildcard tests. Support Oracle Dyn nameserver detection.
Version 1.4.1: Query more interesting subdomains (mail, webmail).
Version 1.4: The Domain Connect API can now be queried.
Version 1.3: DomainKeys/DKIM policy information (r=, n=) are now queried. Tor version detection fixed.
Version 1.2.1: Clean up output and help.
Version 1.2: Check for wildcard DNS records. Improve DKIM handling when wildcard records exist.
Version 1.1: Check for Have I Been Pwned verification records in DNS.
Version 1.0: Initial release.
#>
<#
.SYNOPSIS
Checks a domain for interesting things.
.DESCRIPTION
This script will check a domain's DNS and web site for anything of interest. This includes security problems, but can also be a good discovery tool.
.PARAMETER Domain
The domain to examine.
.PARAMETER AsPlainText
By default, this script will use formatted text. This is great for on-screen viewing, but bad for sending this data to another process, or out of the environment. Specify this switch to ignore all colors and return things in simple plain text.
.PARAMETER NoCoolBanner
Disable the startup banner. You might need this for printing, or for narrow screens.
.EXAMPLE
PS C:\> .\DomainAnalyzer.ps1 google.com
Tests the domain Google.com for interesting things.
.EXAMPLE
PS C:\> .\DomainAnalyzer.ps1 yahoo.com -AsPlainText | Out-Termbin
Tests the domain Yahoo.com. In this example, the text is sent to the terminal-capture site Termbin.
.INPUTS
The domain name to scan can be specified with the -DomainName parameter, or sent in via the pipeline.
.OUTPUTS
This cmdlet generates no output to the pipeline. However, you can pipe the output to the screen or to a cmdlet such as Out-Printer.
.NOTES
At this time, this script only runs on Microsoft Windows, due to a dependency on DnsClient's Resolve-DnsName cmdlet.
#>
#Requires -Version 7.1
#Requires -Module DnsClient
[CmdletBinding()]
Param(
[Alias('DomainName')]
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[String] $Domain,
[Alias('PlainText')]
[Switch] $AsPlainText,
[Switch] $NoCoolBanner
)
Set-StrictMode -Version 'Latest'
# I'm going to be lazy and change this variable's scope so that
# I can use it in my helper functions.
$script:AsPlainText = $AsPlainText
#region Helper functions
Function Bold {
[OutputType('String')]
Param(
[Parameter(Mandatory)]
[AllowNull()]
[String] $Message
)
Return ($AsPlainText ? $Message : "`e[1m${Message}`e[22m")
}
Function Underline {
[OutputType('String')]
Param(
[Parameter(Mandatory)]
[AllowNull()]
[String] $Message
)
Return ($AsPlainText ? $Message : "`e[4m${Message}`e[24m")
}
Function Invert {
[Alias('ReverseVideo')]
[OutputType('String')]
Param(
[Parameter(Mandatory)]
[AllowNull()]
[String] $Message
)
Return ($AsPlainText ? $Message : "`e[7m${Message}`e[27m")
}
Function Strikeout {
[Alias('Strikethrough')]
[OutputType('String')]
Param(
[Parameter(Mandatory)]
[AllowNull()]
[String] $Message
)
Return ($AsPlainText ? $Message : "`e[9m${Message}`e[29m")
}
Function ForegroundColor {
[Alias('Color', 'FGColor')]
[OutputType('String')]
Param(
[Parameter(Position=0)]
[ValidateSet('Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White')]
[ValidateNotNullOrEmpty()]
[String] $Color,
[Parameter(Position=1)]
[AllowNull()]
[String] $Message
)
If ($AsPlainText) {
Return $Message
}
Switch ($Color) {
'Black' {Return "`e[30m${Message}`e[37m"}
'Red' {Return "`e[31m${Message}`e[37m"}
'Green' {Return "`e[32m${Message}`e[37m"}
'Yellow' {Return "`e[33m${Message}`e[37m"}
'Blue' {Return "`e[34m${Message}`e[37m"}
'Magenta' {Return "`e[35m${Message}`e[37m"}
'Cyan' {Return "`e[36m${Message}`e[37m"}
'White' {Return "`e[37m${Message}"}
default {Return $Message}
}
}
Function BackgroundColor {
[Alias('Background', 'BGColor')]
[OutputType('String')]
Param(
[Parameter(Position=0)]
[ValidateSet('Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White')]
[ValidateNotNullOrEmpty()]
[String] $Color,
[Parameter(Position=1)]
[AllowNull()]
[String] $Message
)
If ($AsPlainText) {
Return $Message
}
Switch ($Color) {
'Black' {Return "`e[40m${Message}`e[40m"}
'Red' {Return "`e[41m${Message}`e[40m"}
'Green' {Return "`e[42m${Message}`e[40m"}
'Yellow' {Return "`e[43m${Message}`e[40m"}
'Blue' {Return "`e[44m${Message}`e[40m"}
'Magenta' {Return "`e[45m${Message}`e[40m"}
'Cyan' {Return "`e[46m${Message}`e[40m"}
'White' {Return "`e[47m${Message}"}
default {Return $Message}
}
}
#endregion
$Version = (Test-ScriptFileInfo $MyInvocation.InvocationName).Version
If (-Not $NoCoolBanner) {
# For compatibility with old or small terminals (looking at you, Windows default),
# don't exceed 132 characters. Don't forget to escape any dollar signs or backticks
# that PowerShell might try to execute. This has to be a double-quoted string because
# of where I decided to stick the version number.
Write-Output @"
____ _ _ _ _ _ _
/ ___|___ | (_)_ __ ( )___ __| | ___ _ __ ___ __ _(_)_ __ __ _ _ __ __ _| |_ _ _______ _ __
| | / _ \| | | '_ \|// __| / _`` |/ _ \| '_ `` _ \ / _`` | | '_ \ / _`` | '_ \ / _`` | | | | |_ / _ \ '__|
| |__| (_) | | | | | | \__ \ | (_| | (_) | | | | | | (_| | | | | | | (_| | | | | (_| | | |_| |/ / __/ |
\____\___/|_|_|_| |_| |___/ \__,_|\___/|_| |_| |_|\__,_|_|_| |_| \__,_|_| |_|\__,_|_|\__, /___\___|_|
|___/ VERSION $Version
"@
}
Else {
Write-Output (Underline "Colin's domain analyzer, version $Version")
}
Write-Output "Investigating: $Domain."
#######################################################################
### WEB SERVER CHECK
#######################################################################
Write-Output (Underline "`nChecking their web server`n")
Write-Information -Tag 'Verbose' -Message "Resolving the IP addresses for $Domain"
$IPAddresses = @()
$IPAddresses += Resolve-DnsName -Type 'A_AAAA' -Name $Domain -ErrorAction Ignore | Where-Object Type -in 'A','AAAA' | Select-Object -ExpandProperty IPAddress
If ($IPAddresses) {
$IPAddresses | ForEach-Object -Parallel {
$ptr = (Resolve-DnsName -Type 'PTR' -Name $_ -ErrorAction Ignore).NameHost ?? 'no reverse DNS record'
Write-Output "• Their web server has the $($_ -Match ':' ? 'IPv6' : 'IPv4') address $_ ($ptr)."
}
}
Else {
Write-Output "• The domain $Domain does not resolve to any IP addresses."
}
#endregion
#region Find an IP address to test.
If ($IPAddresses) {
# Find all of the IP addresses of the web server. We'll pick one to test,
# based on whether or not the user has IPv4 and IPv6 connectivity.
$IPv4Connectivity = Invoke-RestMethod -Method GET -Uri 'https://ip4only.me/api' -ErrorAction Ignore
$IPv6Connectivity = Invoke-RestMethod -Method GET -Uri 'https://ip6only.me/api' -ErrorAction Ignore
$IPv4Addresses = $IPAddresses | Select-String -Pattern ':' -NotMatch
$IPv6Addresses = $IPAddresses | Select-String -Pattern ':'
# If all we have is Teredo, don't use IPv6. Microsoft shut down their
# Teredo servers, leaving Windows 10 high and dry.
$UsingTeredo = (Get-NetRoute -DestinationPrefix '::/0' -ErrorAction Ignore)?.InterfaceAlias -Match 'Teredo'
If ($IPv6Addresses -and $IPv6Connectivity -and -Not $UsingTeredo) {
$IPAddresses = $IPv6Addresses
}
ElseIf ($IPv4Addresses -and $IPv4Connectivity) {
$IPAddresses = $IPv4Addresses
}
Else {
Write-Error 'Neither IPv4 nor IPv6 connectivity are available. Aborting the test!'
Return
}
}
#endregion
#region Ping test
If ($IPAddresses) {
# Hide the progress bar.
$OldProgressPreference = $global:ProgressPreference
$global:ProgressPreference = 'SilentlyContinue'
# A warning message is printed if a ping fails. Redirect that to null.
$Test = (Test-NetConnection -ComputerName $IPAddresses[0] 3> $null)
If ($Test.PingSucceeded) {
Write-Output "• $($IPAddresses[0]) responds to pings ($($Test.PingReplyDetails.RoundTripTime) ms)."
}
Else {
Write-Output "• $($IPAddresses[0]) does not respond to pings."
}
# Restore the user's progress preference.
$global:ProgressPreference = $OldProgressPreference
}
#endregion
#region Common port scan
If ($IPAddresses) {
# The Windows version of /etc/services may be incomplete compared to the *nix version,
# so let's add some definitions to it, for the user's benefit.
If ($IsWindows) {
$Services = Get-Content "${env:WinDir}\System32\drivers\etc\services"
$Services += @'
smtps 465/tcp
smtp-submission 587/tcp
http-alt 8080/tcp
https-alt 8443/tcp
https-alt 8843/tcp
hp-jetdirect 9100/tcp
'@
}
Else {
$Services = Get-Content '/etc/services'
}
# Since we're ending this sentence in a colon, wrap an IPv6 address in brackets.
If ($IPAddresses[0] -Match ':') {
Write-Output "• Scanning for common service ports on [$($IPAddresses[0])]:"
}
Else {
Write-Output "• Scanning for common service ports on $($IPAddresses[0]):"
}
# This is the list of ports that we're going to scan. Make sure they have
# a matching entry in the services file or the variable above.
$Ports = @(21, 22, 23, 25, 53, 70, 80, 110, 139, 143, 389, 443, 445, 465,
515, 587, 636, 666, 993, 995, 1723, 3268, 3269, 3389, 5355,
8080, 8443, 9100)
$Ports | ForEach-Object -ThrottleLimit 25 -Parallel {
$Port = $_
$ServiceName = (($using:Services | Select-String "\s$Port\/tcp") -Split "\s+" | Select-Object -First 1) ?? 'unknown'
Try {
Write-Progress -Activity "Scanning TCP port $Port"
# TODO: Figure out how to get a shorter timeout.
# This can really get painful when testing a lot of ports.
$null = [Net.Sockets.TcpClient]::new(${using:IPAddresses}[0], $Port)
Write-Output " • TCP port $Port ($ServiceName) is open."
}
Catch {
Write-Information " • TCP port $Port ($ServiceName) is closed or filtered."
}
}
# it's a lot of data to keep in memory.
Remove-Variable -Name 'Services'
}
#endregion
#region HTTPS/HTTP checks
If ($IPAddresses) {
Try {
# I'm using the HEAD(er) method just to make responses smaller.
# Some of these web pages can get pretty big.
# The $HTTPRequest variable will be used later in the script.
Write-Information -Tag 'Verbose' -Message "Attempting to connect to https://$Domain"
$script:HTTPRequest = Invoke-WebRequest -Method HEAD -Uri "https://$Domain/"
# If they have a working and valid HTTPS server, now we will test different versions of
# SSL/TLS. In each case, we'll "Try" to connect with a specific version, and assume that
# a failure means that version is not supported.
$SupportedTLSVersions = @()
# To attempt to use SSL 2.0 and SSL 3.0, we need to manually set the security protocols.
# It's considered good etiquette to save the user's preference, and restore it afterwards.
$OldTLSClientSettings = [Net.ServicePointManager]::SecurityProtocol
#region SSL 2.0 test
# Note that newer versions of Windows and PowerShell don't support SSL 2.0 at all (good!).
# Thus, we're wrapping this in two separate Try/Catch blocks. The outer one throws if the
# system doesn't support it. The inner one throws if the server doesn't support it.
Write-Information -Tag 'Verbose' -Message "Attempting to connect to https://$Domain with SSL 2.0."
Try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl2
Try {
# I don't know if DisableKeepAlive is required or not, so let's play it safe.
Invoke-WebRequest -Method HEAD -Uri "https://$Domain/" -DisableKeepAlive | Out-Null
If ($AsPlainText) {
Write-Output ' • SSL 2.0 is supported!'
} Else {
$SupportedTLSVersions += (Color Red 'SSL 2.0')
Write-Information -Tag 'Notice' -Message 'The web server supports SSL 2.0.'
}
}
Catch {
If ($AsPlainText) {
Write-Output ' • SSL 2.0 is not supported.'
} Else {
$SupportedTLSVersions += (Color Green (Strikeout 'SSL 2.0'))
Write-Information -Tag 'Notice' -Message 'The web server does not support SSL 2.0.'
}
}
}
Catch {
Write-Information -Tag 'Notice' -Message 'SSL 2.0 could not be tested because the operating system does not support it.'
}
#endregion
#region SSL 3.0 test
# Note that newer versions of Windows and PowerShell don't support SSL 3.0 at all (good!).
# See explanation above.
Try {
Write-Information -Tag 'Verbose' -Message "Attempting to connect to https://$Domain with SSL 3.0."
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3
Try {
Invoke-WebRequest -Method HEAD -Uri "https://$Domain/" -DisableKeepAlive | Out-Null
If ($AsPlainText) {
Write-Output ' • SSL 3.0 is supported!'
} Else {
$SupportedTLSVersions += (Color Red 'SSL 3.0')
Write-Information -Tag 'Notice' -Message 'The web server supports SSL 3.0.'
}
}
Catch {
If ($AsPlainText) {
Write-Output ' • SSL 3.0 is not supported.'
} Else {
$SupportedTLSVersions += (Color Green (Strikeout 'SSL 3.0'))
Write-Information -Tag 'Notice' -Message 'The web server does not support SSL 3.0.'
}
}
}
Catch {
Write-Information -Tag 'Notice' -Message 'SSL 3.0 could not be tested because the operating system does not support it.'
}
#endregion
# Restore the user's preferences, so they won't accidentally use SSL 2.0/3.0 to connect to things.
# We won't need to use this method when testing TLS 1.0 and up, as those protocols can be tested
# directly with the Invoke-WebRequest cmdlet.
[Net.ServicePointManager]::SecurityProtocol = $OldTlsClientSettings
#region TLS 1.0 test
Try {
Write-Information -Tag 'Verbose' -Message "Attempting to connect to https://$Domain with TLS 1.0."
Invoke-WebRequest -Method HEAD -Uri "https://$Domain/" -SslProtocol Tls | Out-Null
If ($AsPlainText) {
Write-Output ' • TLS 1.0 is supported'
} Else {
$SupportedTLSVersions += (Color Yellow 'TLS 1.0')
Write-Information -Tag 'Notice' -Message 'The web server supports TLS 1.0.'
}
}
Catch {
If ($AsPlainText) {
Write-Output ' • TLS 1.0 is not supported'
} Else {
$SupportedTLSVersions += (Color Green (Strikeout 'TLS 1.0'))
Write-Information -Tag 'Notice' -Message 'The web server supports TLS 1.0.'
}
}
#endregion
#region TLS 1.1 test
Try {
Write-Information -Tag 'Verbose' -Message "Attempting to connect to https://$Domain with TLS 1.1."
Invoke-WebRequest -Method HEAD -Uri "https://$Domain/" -SslProtocol Tls11 | Out-Null
If ($AsPlainText) {
Write-Output ' • TLS 1.1 is supported'
} Else {
$SupportedTLSVersions += (Color Yellow 'TLS 1.1')
Write-Information -Tag 'Notice' -Message 'The web server supports TLS 1.1.'
}
}
Catch {
If ($AsPlainText) {
Write-Output ' • TLS 1.1 is not supported'
} Else {
$SupportedTLSVersions += (Color Green (Strikeout 'TLS 1.1'))
Write-Information -Tag 'Notice' -Message 'The web server does not support TLS 1.1.'
}
}
#endregion
#region TLS 1.2 test
Try {
Write-Information -Tag 'Verbose' -Message "Attempting to connect to https://$Domain with TLS 1.2."
Invoke-WebRequest -Method HEAD -Uri "https://$Domain/" -SslProtocol Tls12 | Out-Null
If ($AsPlainText) {
Write-Output ' • TLS 1.2 is supported'
} Else {
$SupportedTLSVersions += (Color Green 'TLS 1.2')
Write-Information -Tag 'Notice' -Message 'The web server supports TLS 1.2.'
}
}
Catch {
If ($AsPlainText) {
Write-Output ' • TLS 1.2 is not supported!'
} Else {
$SupportedTLSVersions += (Color Red (Strikeout 'TLS 1.2'))
Write-Information -Tag 'Notice' -Message 'The web server does not support TLS 1.2.'
}
}
#endregion
#region TLS 1.3 test
Try {
Write-Information -Tag 'Verbose' -Message "Attempting to connect to https://$Domain with TLS 1.3."
Invoke-WebRequest -Method HEAD -Uri "https://$Domain/" -SslProtocol Tls13 | Out-Null
If ($AsPlainText) {
Write-Output ' • TLS 1.3 is supported'
} Else {
$SupportedTLSVersions += (Color Green 'TLS 1.3')
Write-Information -Tag 'Notice' -Message 'The web server supports TLS 1.3.'
}
}
Catch [Security.Authentication.AuthenticationException] {
# This is a weird bug that I found out. Windows 10 supports TLS 1.3 as of Version 1903, but the support
# is not complete. A TLS 1.3 connection is negotiated, but there are somehow no ciphers in common.
# This behavior is fixed in Version 21H1. Thus, we're going to Catch this specific exception.
Write-Information -Tag 'Notice' -Message 'TLS 1.3 could not be tested because this computer does not support it.'
}
Catch {
If ($AsPlainText) {
Write-Output ' • TLS 1.3 is not supported'
} Else {
$SupportedTLSVersions += (Color Yellow (Strikeout 'TLS 1.3'))
Write-Information -Tag 'Notice' -Message 'The web server does not support TLS 1.3.'
}
}
#endregion
# When running with PlainText, output is generated incrementally.
If (-Not $AsPlainText) {
Write-Output "• Tested SSL/TLS versions: $($SupportedTLSVersions -Join ', ')"
}
}
# If the above Try block threw an exception, that would be because HTTPS did not connect.
Catch {
# If it happened because of a certificate error, we'll find out here.
Try {
Write-Information -Tag 'Verbose' -Message "Attempting to connect to https://$Domain (ignoring certificate checks)."
$script:HTTPRequest = Invoke-WebRequest -Method HEAD -Uri "https://$Domain" -SkipCertificateCheck
Write-Output (Color Red "• Their web server supports HTTPS, but there's something wrong with their certificate!")
}
# If we get this far, then HTTPS is not supported at all.
Catch {
Write-Output (Color Red "• Their web server does not support HTTPS!")
}
}
}
#endregion
#region HTTP test
# It's somewhat rare, but maybe the web server is not listening on port 80 at all.
# Recent versions of Chrome and Firefox try HTTPS first, so this may not be a bad thing.
If ($IPAddresses) {
Try {
Write-Information -Tag 'Notice' -Message "Attempting a clear-text HTTP connection to http://$Domain"
$iwr = Invoke-WebRequest -Method HEAD -Uri "http://$Domain/"
If ($null -eq $script:HTTPRequest) {
$script:HTTPRequest = $iwr
}
}
Catch {
Write-Output (Color Yellow "• Their web server does not support non-secure HTTP.")
}
}
#endregion
#region HTTP header check
If ($IPAddresses) {
Write-Output '• Looking for interesting HTTP headers:'
Try {
$headers = Select-Object -InputObject $script:HTTPRequest -ExpandProperty 'Headers'
ForEach ($header in (Sort-Object -InputObject $headers.GetEnumerator())) {
Switch ($header.Key) {
'Alt-Svc' {
$header.Value -Split ',' | ForEach-Object {
$_ -Split ';\s*' | ForEach-Object {
$_ -Match "(.+)=(`"?.*`"?)" | Out-Null
$Scheme = $Matches[1]
$URL = $Matches[2]
If ($Matches[2][0] -eq ':') {
$URL = "$Domain$($Matches[2])"
}
Switch -WildCard ($Scheme) {
'h2*' {
Write-Output " • This web site is also available over HTTP/2 at: $URL"
}
'h3*' {
Write-Output " • This web site is also available over HTTP/3 at: $URL"
}
'ma' {
# do nothing with the max-age token.
}
'persist' {
# do nothing with the persist token.
}
default {
Write-Output " • This web site is also available over $Scheme at: $URL"
}
}
}
}
}
'Content-Security-Policy' {Write-Output (Color Green ' • A content security policy is set to prevent CSRF and XSS attacks.')}
'Cross-Origin-Embedder-Policy' {Write-Output (Color Green ' • A cross-origin embedder policy is set to prevent CSRF and XSS attacks.')}
'Cross-Origin-Opener-Policy' {Write-Output (Color Green ' • A cross-origin opener policy is set to prevent CSRF and XSS attacks.')}
'Cross-Origin-Resource-Policy' {Write-Output (Color Green ' • A cross-origin resource policy is set to prevent CSRF and XSS attacks.')}
'Expect-CT' {
If ($header.Value -NotMatch 'max-age=0') {
Write-Output (Color Green " • Certificate Transparency is enforced. A certificate and HTTPS connection without CT information $(Bold 'must') fail!")
} Else {
Write-Output (Color Yellow ' • Certificate Transparency is being explicitly disabled by the server administrator.')
}
}
'Expect-Staple' {
If ($header.Value -NotMatch 'max-age=0') {
Write-Output (Color Green " • A valid and fresh OCSP response $(Bold 'must') be stapled, or the HTTPS connection $(Bold 'must') fail.")
} Else {
Write-Output (Color Yellow ' • OCSP stapling enforcement is being explicitly disabled by the server administrator.')
}
}
'Last-Modified' {Write-Output " • This page was last modified on $(Get-Date $header.Value)."}
'Onion-Location' {
[String]($header.Value) -Match "https?\:\/\/([a-z2-7]+)\.onion" | Out-Null
If ($Matches[1].Length -eq 56) {
Write-Output " • This site is available as a Tor $(Color Green '(version 3)') hidden service."
} ElseIf ($Matches[1].Length -eq 16) {
Write-Output " • This site is available as a Tor $(Color Yellow 'version 2') hidden service."
} Else {
Write-Output ' • This site is available as a Tor hidden service.'
}
}
'Permissions-Policy' {Write-Output (Color Green ' • A permission policy is defined to limit browser features.')}
'Referrer-Policy' {Write-Output (Color Green ' • A referrer policy is defined for user privacy.')}
'Server' {Write-Output " $(Invert '•') Their web server is running $($header.Value)."}
'X-Powered-By' {Write-Output " $(Invert '•') Their web site is powered by $($header.Value)."}
'X-XSS-Protection' {Write-Output (Color Green " • A cross-site scripting policy is set.")}
'Strict-Transport-Security' {
If ($header.Value -Match 'max-age=0') {
Write-Output (Color Yellow ' • HSTS is being intentionally disabled by the server administrator.')
} Else {
Write-Output (Color Green " • HSTS is in use. All HTTP connections $(Bold 'must') be secure.")
If ($header.Value -Match 'preload') {
Write-Output (Color Green " • This domain name wants to be on the HSTS preload list. If it is, HSTS will $(Bold 'always') be enforced!")
}
}
}
default {
Write-Information -Tag 'Verbose' -Message " • We found the HTTP header $(Bold $header.Key) with the value: $($header.Value)"
}
}
}
}
Catch {
# If a customer doesn't have a web site, this will fail.
# That's okay.
If ($AsPlainText) {
Write-Output " • Nothing interesting was found."
}
Else {
# Erase the previous line.
Write-Output "`e[1A`e[K"
}
}
}
#endregion
#region Look for interesting files
# Web servers can be full of interesting things describing the site.
# Let's look for some well-known sources of information.
If ($IPAddresses) {
Try {
@(
'.well-known/host-meta', # machine-readable web site info
'.well-known/security.txt', # security contact info
'ads.txt', # authorized digital sellers
'have-i-been-pwned-verification.txt', # HIBP verification data
'humans.txt', # a place to write fun things about the people involved
'robots.txt', # instructions for bots
'README.txt', # if they use a template, this might exist
'sitemap.xml' # a list of all web pages on this site
) | `
ForEach-Object {
If ((Invoke-WebRequest -Uri "http://$Domain/$_" -Method HEAD -SkipHttpErrorCheck -ErrorAction Ignore).StatusCode -eq 200)
{
If ($_ -eq 'robots.txt') {
Write-Output "$(Invert '•') The file http://$Domain/$_ exists. Let's see where they don't want you to go."
(Invoke-WebRequest -Uri "http://$Domain/robots.txt").Content -Split "[`r`n]+" | ForEach-Object {
$Directive, $Value = $_ -Split ":\s+",2
If ($Directive -eq 'Disallow') {
Write-Output " $(Invert '•') The URL $(Bold http://$Domain$Value) is disallowed. It might be worth a peek."
}
}
}
# Early drafts of the security.txt specification placed this file in the root.
# It now should live in the .well-known folder.
ElseIf ($_ -Like '*security.txt') {
Write-Output (Color Green '• There is a security.txt file in the well-known location:')
# These files are supposed to be served only over HTTPS.
# I'm a bit more lenient, so I can show an error.
Try {
$securityTxt = Invoke-WebRequest -Uri "https://$Domain/.well-known/security.txt"
}
Catch {
$securityTxt = Invoke-WebRequest -Uri "http://$Domain/.well-known/security.txt"
Write-Output (Color Red " • This file could not be fetched over HTTPS.")
}
# Go through the file line by line.
$securityTxt.Content -Split "[`r`n]+" `
| Select-String -Pattern '^[A-Za-z-]+:\s+' ` # This regex matches all directive-value pairs.
| ForEach-Object {
$Directive, $Value = $_ -Split ":\s+",2
Switch ($Directive) {
'Acknowledgments' {Write-Output " • See who's reported security issues: $(Bold $Value)"}
'Contact' {Write-Output " • Report security issues to: $(Bold $Value)"}
'Encryption' {Write-Output " • Encrypt your security reports with: $(Bold $Value)"}
'Hiring' {Write-Output (Invert " • They're hiring security professionals! Visit: (Bold $Value)")}
'Policy' {Write-Output " • A security policy is available at: $(Bold $Value)"}
'Expires' {
Try {
$expires = Get-Date $Value
If ((Get-Date) -gt $expires) {
Write-Output (Color Red " • Their security.txt file is expired!")
} Else {
Write-Output " • This file expires at $expires."
}
}
Catch {
Write-Output (Color Red " • There's an expiration date, but it's not parseable.")
}
}
}
}
# We can't assume that the user will have the GnuPG tools installed, so we're just going
# to check for the existence of something that looks like a signature and call it a day.
# Validating the signature is an exercise left to the reader.
If ($securityTxt.Content -Match 'BEGIN PGP SIGNED MESSAGE') {
Write-Output (Color Green " • This file $(Underline 'appears') to have a PGP signature.")
}
}
# For all other interesting files, just show this generic message.
Else {
Write-Output "$(Invert '•') The file http://$Domain/$_ exists. There may be something interesting inside."
}
}
}
}
Catch {
# If a customer doesn't have a web site, this will fail. That's okay.
}
}
#endregion
#######################################################################
### DNS CHECKS
#######################################################################
#region DNSSEC check
Write-Output (Underline "`nChecking DNS zone and records`n")
# Check for properly-signed DNS records. Note that this may fail if the user's DNS server
# doesn't support DNSSEC at all. However, almost all of them will at least return the records,
# even if validation is disabled.
$DnssecCheckParams = @{
'Name' = $Domain
'Type' = 'RRSIG'
'DnssecOK' = $true
'ErrorAction' = 'Ignore'
}
If (Resolve-DnsName @DnssecCheckParams | Where-Object Type -eq 'RRSIG') {
Write-Output (Color Green "• The DNS zone $Domain is properly signed with DNSSEC.")
}
ElseIf (Resolve-DnsName @DnssecCheckParams -DnssecCD | Where-Object Type -eq 'RRSIG') {
Write-Output (Color Red "• The DNS zone $Domain is $(Bold 'improperly') signed with DNSSEC. DNS records may be being spoofed right now!")
}
Else {
Write-Output (Color Red "• The DNS zone $Domain is not signed with DNSSEC. DNS records can be spoofed.")
}
#endregion
#region Where is the main DNS server?
# Manually select the first SOA record. There is supposed to be only be one, but sometimes Resolve-DnsName hands us lemons.
$soa = (Resolve-DnsName -Name $Domain -Type 'SOA')[0]
Switch -WildCard ($soa.PrimaryServer) {
# Feel free to add to this list!
'*.domaincontrol.com' {Write-Output '• The primary DNS zone is hosted by GoDaddy.'}
'p*.domaincontrol.com' {Write-Output ' • And they have GoDaddy Premium DNS.'}
'*.worldnic.com' {Write-Output '• Their primary DNS zone is hosted by Network Solutions.'}
'*.dynect.net' {Write-Output '• Their primary DNS zone is hosted by Oracle Dyn.'}
'*.cloudflare.com' {Write-Output '• Their primary DNS zone is hosted by Cloudflare.'}
default {Write-Output "• Their primary DNS zone is hosted by the nameserver $($soa.PrimaryServer)"}
}
#endregion
#region Date of last DNS update
# The SOA record's serial number should be in the formation YYYYMMDDNN, where N are
# arbitrary numbers. Not all DNS servers support this (looking at you, Microsoft).
$serial = "$($soa.SerialNumber)"
Try {
$Date = "$($serial.Substring(0,4))-$($serial.Substring(4,2))-$($serial.Substring(6,2))"
Write-Output "• Their DNS zone was last updated on $(Get-Date -Format 'MM/dd/yyyy' -Date $Date)."
}
Catch {
Write-Output (Color Yellow "• Their DNS zone does not use the recommended date-based serial numbering. Their hostmaster may be dumb.")
}
#endregion
#region Check for Domain Connect API support
Resolve-DnsName -Type TXT -Name "_domainconnect.$Domain" -ErrorAction Ignore `
| Where-Object Type -eq 'TXT' `
| ForEach-Object {
$TxtRecord = $_ # so we can catch it
# Once most versions of PowerShell support TLS 1.3, the SslProtocol parameter value should be changed to:
# [Microsoft.PowerShell.Commands.WebSslProtocol]::Tls12 -bor [Microsoft.PowerShell.Commands.WebSslProtocol]::Tls13
Try {
$Results = Invoke-RestMethod -SslProtocol Tls12 -Uri "https://$($_.Strings)/v2/$Domain/settings" -ErrorAction Stop
If ($Results.providerName) {
Write-Output "• The Domain Connect API is supported, and managed by $($Results.providerName)."
}
Else {
Write-Output '• The Domain Connect API is supported, but the record could not be parsed.'
}
}
Catch {
Write-Output "$(Color Red '• An invalid Domain Connect API TXT record was found: ') $($TxtRecord.Strings)"
}
}
#endregion
#region DNS records that might indicate remote access
# You can call this section "Interesting DNS Records."
@('exchange','mail','remote','rds','vpn','webmail') | ForEach-Object -Parallel {
Resolve-DnsName -Type 'A_AAAA' -Name "$_.${using:Domain}" -ErrorAction Ignore `
| Where-Object Type -in 'A','AAAA' `
| ForEach-Object {
$ptr = (Resolve-DnsName -Type 'PTR' -Name $_.IPAddress -ErrorAction Ignore)?.NameHost ?? 'no reverse DNS record'
Write-Output "• $($_.Name) exists and has the $($_.DataLength -eq 4 ? 'IPv4' : 'IPv6') address $($_.IPAddress) ($ptr)."
}
}
#endregion
#region Wildcard DNS record
# Querying for a nonsensical DNS record will tell us if they have a wildcard
# DNS record in place. This is the default for Network Solutions' hosted zones.
Resolve-DnsName -Name "wildcard-dns-check-xyzzy.$Domain" -ErrorAction Ignore `
| Where-Object Type -ne 'SOA' `
| ForEach-Object {
$ptr = (Resolve-DnsName -Type 'PTR' -Name $_.IPAddress -ErrorAction Ignore)?.NameHost ?? 'no reverse DNS record'
Write-Output "• A wildcard DNS entry exists for *.$Domain, for the $($_.DataLength -eq 4 ? 'IPv4' : 'IPv6') address $($_.IPAddress) ($ptr)."
}
#endregion
#######################################################################
### EMAIL CHECKS
#######################################################################
Write-Output (Underline "`nChecking their email`n")
#region First MX record
$mx = Resolve-DnsName -Type 'MX' -Name $Domain | Where-Object Type -eq 'MX' | Sort-Object Preference
If ($mx) {
$Output = @()
Switch -WildCard ($mx.NameExchange) {
'*.mail.protection.outlook.com' {$Output += '• They have an MX record pointing to Microsoft 365.'}
'*aspmx.l.google.com' {$Output += '• They have an MX record pointing to Google Workspace.'}
'*.arsmtp.com' {$Output += '• They have an MX record pointing to AppRiver.'}
default {$Output += "• They have an MX record: $_"}
}
Get-Unique -InputObject $Output
}
Else {
Write-Output '• There are no MX records. They cannot receive email.'
}
#endregion
$autodiscover = Resolve-DnsName -Name "autodiscover.$Domain" -ErrorAction Ignore
$CNAME = $autodiscover | Where-Object Type -eq 'CNAME'
$IPs = $autodiscover | Where-Object Type -in @('A','AAAA')
If ($CNAME) {
If ($CNAME.NameHost -eq 'autodiscover.outlook.com') {
Write-Output '• They use Exchange Online for Autodiscover.'
}
Else {
Write-Output "• There is an Exchange Autodiscover server at $($($CNAME[-1]).NameHost)."
}
}
ElseIf ($IPs) {
$IPs | ForEach-Object {
$ptr = (Resolve-DnsName -Type 'PTR' -Name $_.IPAddress -ErrorAction Ignore)?.NameHost ?? 'no reverse DNS record'
Write-Output "• There is an Exchange Autodiscover server with the $($_.DataLength -eq 4 ? 'IPv4' : 'IPv6') address $($_.IPAddress) ($ptr)."
}
}
#region Interesting TXT records
# This is so we can print an error if there's no SPF record.
$foundSPF = $false
Resolve-DnsName -Name $Domain -Type 'TXT' -ErrorAction Ignore `
| Where-Object Type -eq 'TXT' `
| ForEach-Object {
#region SPF and Sender ID check.
# "SPF 2.0" was Microsoft's failed Sender ID initiative. I don't think it
# ever got off the ground, but if someone out there is using it, we may as
# well check it.
If ($_.Strings -Like 'v=spf1 *' -or $_.Strings -Like 'spf2.0/*') {
$script:foundSPF = $true
Write-Output "• According to SPF, valid email from $(Bold $Domain) will come from:"
$_.Strings -Split "\s+" | ForEach-Object {
$TokenName, $TokenValue = $_ -Split "[=:]",2
$TokenValue ??= $Domain # if there's no value, the base domain is used.
# I'm not bothering to check for anything prefixed with ? because that's basically meaningless.
# PTR lookups are in yellow because those are deprecated and should not be used.
Switch -RegEx ($TokenName) {
'redirect' { Write-Output " • anything listed in the SPF record for $(Bold $TokenValue)"; Continue}
'^\+?a$' { Write-Output " • the IP addresses of $(Bold $TokenValue)"}
'^\+?exists' { Write-Output " • anywhere, if there's a DNS A record for $(Bold $TokenValue)"}
'^\+?include' { Write-Output " • anything listed in $TokenValue's SPF record"}
'^\+?ip4' { Write-Output " • the IPv4 $($TokenValue -Match '/' ? 'subnet' : 'address') $(Bold $TokenValue)"}
'^\+?ip6' { Write-Output " • the IPv6 $($TokenValue -Match '/' ? 'subnet' : 'address') $(Bold $TokenValue)"}
'^\+?mx' { Write-Output " • the named MX records"}
'^\+?ptr' { Write-Output (Color Yellow " • anything with a reverse DNS record matching $(Bold $TokenValue)") }
'^\?a$' { Write-Output " • $(Bold 'maybe') the IP addresses of $(Bold $TokenValue)"}
'^\?exists' { Write-Output " • $(Bold 'maybe') anywhere, if there's a DNS A record for $(Bold $TokenValue)"}
'^\?include' { Write-Output " • $(Bold 'maybe') anything listed in $TokenValue's SPF record"}
'^\?ip4' { Write-Output " • $(Bold 'maybe') the IPv4 $($TokenValue -Match '/' ? 'subnet' : 'address') $(Bold $TokenValue)"}
'^\?ip6' { Write-Output " • $(Bold 'maybe') the IPv6 $($TokenValue -Match '/' ? 'subnet' : 'address') $(Bold $TokenValue)"}
'^\?mx' { Write-Output " • $(Bold 'maybe') the named MX records for $TokenValue"}
'^\?ptr' { Write-Output (Color Yellow " • anything with a reverse DNS record matching $(Bold $TokenValue)") }
'-a$' { Write-Output " • $(Underline 'not') the IP addresses of $(Bold $TokenValue)"}
'-exists' { Write-Output " • $(Underline 'not') if the host $(Bold $TokenValue) exists"}
'-include' { Write-Output " • $(Underline 'nothing') listed in $(Bold $TokenValue)'s SPF record"}
'-ip4' { Write-Output " • $(Underline 'not') the IPv4 $($TokenValue -Match '/' ? 'subnet' : 'address') $(Bold $TokenValue)"}
'-ip6' { Write-Output " • $(Underline 'not') the IPv6 $($TokenValue -Match '/' ? 'subnet' : 'address') $(Bold $TokenValue)"}
'-mx' { Write-Output " • $(Underline 'not') the named MX records for $TokenValue"}
'-ptr' { Write-Output (Color Yellow " • $(Underline 'nothing') with a reverse DNS record matching $(Bold $TokenValue)")}
'^\+?all' { Write-Output (Color Red " • anywhere!") }
'\?all' { Write-Output " • and $(Bold 'maybe') nowhere else" }
'~all' { Write-Output " • and $(Bold 'probably') nowhere else" }
'-all' { Write-Output (Color Green " • and definitely nowhere else") }
'exp' {
$Explanation = (Resolve-DnsName -Type TXT -Name $TokenValue).Strings
Write-Output " • Also, if a message fails SPF, the sender will see this error message: `"$Explanation`""
}
}
}
}
#endregion
ElseIf ($_.Strings -Like 'MS=ms*') {
Write-Output '• This domain may be registered with Microsoft 365 (and they forgot to delete the TXT challenge record).'
}
ElseIf ($_.Strings -Like 'amazonses*') {
Write-Output '• This domain is registered with Amazon Simple Email Service.'
}
ElseIf ($_.Strings -Like 'have-i-been-pwned-verification=*') {
Write-Output '• Someone has requested domain-wide data from Have I Been Pwned.'
}
}
If (-Not $foundSPF) {
Write-Output (Color Red '• No SPF TXT record was found!')
}
#region Amazon SES check.
# Yes, we checked for this already. It can be specified as a TXT record at the root, too.
If ($null -ne (Resolve-DnsName -Name "_amazonses.$Domain" -Type TXT -ErrorAction Ignore)) {
Write-Output '• This domain is registered with Amazon SES.'
}
Else {
Write-Information '• This domain may not be registered with Amazon SES.'
}
#endregion
#region Well-known DKIM records
$TxtParams = @{
'ErrorAction' = 'Ignore'
'Type' = 'TXT'
}
If ((Resolve-DnsName -Name "selector1._domainkey.$Domain" @TxtParams | Where-Object Type -eq 'TXT') ?? `
(Resolve-DnsName -Name "selector2._domainkey.$Domain" @TxtParams | Where-Object Type -eq 'TXT'))
{
Write-Output "• One or more DKIM records exists for Exchange Online. $(Bold 'They use Microsoft 365.')"
}
If (Resolve-DnsName -Name "autotask._domainkey.$Domain" @TxtParams | Where-Object Type -eq 'TXT') {
Write-Output "• A DKIM selector called `"autotask`" exists. $(Bold 'They use AutoTask.')"
}
If (Resolve-DnsName -Name "google._domainkey.$Domain" @TxtParams | Where-Object Type -eq 'TXT') {
Write-Output "• A DKIM selector called `"google`" exists. $(Bold 'They use Google Workspace.')"
}
If (Resolve-DnsName -Name "k1._domainkey.$Domain" @TxtParams | Where-Object Type -eq 'TXT') {
Write-Output "• A DKIM selector called `"k1`" exists. $(Bold 'They use MailChimp.')"
}
If (Resolve-DnsName -Name "default._domainkey.$Domain" @TxtParams | Where-Object Type -eq 'TXT') {
Write-Output '• A DKIM selector called "default" exists.'
}
#endregion
#region DKIM-related records
$DkimPolicyRecord = Resolve-DnsName -Name "_domainkey.$Domain" @TxtParams | Where-Object Type -eq 'TXT'
Switch -WildCard (${DkimPolicyRecord}?.Strings -Split "[;\s*]") {
'o=~' {Write-Output "• This domain asserts that $(Bold 'some') outgoing emails are signed with DomainKeys or DKIM."}
'o=-' {Write-Output (Color Green "• This domain asserts that $(Bold 'all') outgoing emails are signed with DomainKeys or DKIM.")}
'r=*' {Write-Output "• DomainKeys/DKIM policy violations will be sent to $($_.Substring(2))."}
'n=*' {Write-Output "• The DomainKeys/DKIM policy has a note: $($_.Substring(2))"}
}
# ADSP is considered historical. Yet, we may as well check it if it's there.
$AdspRecord = Resolve-DnsName -Name "_adsp._domainkey.$Domain" @TxtParams | Where-Object Type -eq 'TXT'
Switch (${AdspRecord}?.Strings) {
'dkim=unknown' {Write-Output "• This domain asserts that $(Bold 'some, most, or all') outgoing emails have an DKIM author domain signature."}
'dkim=all' {Write-Output (Color Green "• This domain asserts that $(Bold 'all') outgoing emails have an DKIM author domain signature.")}
'dkim=discarable' {Write-Output (Color Green "• This domain asserts that $(Bold 'all') outgoing emails have an DKIM author domain signature, $(Bold 'and') anything else can be discarded. Power move.")}
}
#region DMARC check
$DMARC = Resolve-DnsName -Name "_dmarc.$Domain" @TxtParams | Where-Object Type -eq 'TXT'
If ($DMARC) {
Write-Output "$(Color Green '• This domain publishes a DMARC record:') $($DMARC.Strings)"
}
Else {
Write-Output (Color Yellow '• This domain does not publish a DMARC record.')
}
#endregion
#######################################################################
### OTHER DEFINED SERVICES
#######################################################################
Write-Output (Underline "`nChecking for other services`n")
#region Interesting TXT records
Resolve-DnsName -Name $Domain -Type 'TXT' -ErrorAction Ignore `
| Where-Object Type -eq 'TXT'
| ForEach-Object {
If ($_.Strings -Like 'google-site-verification=*') {
Write-Output '• They use Google Webmaster Tools.'
}
ElseIf ($_.Strings -Like 'knowbe4-site-verification=*') {
Write-Output '• They receive simulated phishing emails from KnowBe4.'
}
}
#endregion
#region Other services
$lync = Resolve-DnsName -Name "lyncdiscover.$Domain" -ErrorAction Ignore
$CNAME = $lync | Where-Object Type -eq 'CNAME'
$IPs = $lync | Where-Object Type -in 'A','AAAA'
If ($CNAME) {
If ($CNAME.NameHost -eq 'webdir.online.lync.com') {
Write-Output '• They use Microsoft Teams and/or Skype for Business Online.'
}
Else {
Write-Output "• There is a Skype for Business or Lync server at $($($CNAME[-1]).NameHost)."
}
}
ElseIf ($IPs) {
$IPs | ForEach-Object {
$ptr = (Resolve-DnsName -Type 'PTR' -Name $_.IPAddress -ErrorAction Ignore).NameHost ?? 'no reverse DNS record'
Write-Output "• They have a Skype for Business/Lync server with the $($_.DataLength -eq 4 ? 'IPv4' : 'IPv6') address $($_.IPAddress) ($ptr)."
}
}
#endregion
#region Advertised services
# This is a comma-separated values list of human-readable names,
# and the service names they correspond to.
@"
Name,Service
CalDAV,caldav
CardDAV,carddav
IMAP,imap
IMAP over TLS,imaps
IMPS SAP,imps-server
Kerberos 5 KDC,kerberos
Kerberos 5 primary KDC,kerberos-adm
Kerberos 4 KDC,kerberos-iv
Kerberos 5 primary KDC,kerberos-master
Kerberos 5 password change,kpasswd
LDAP,ldap
Matrix,matrix
Minecraft,minecraft
Mumble,mumble
POP3,pop3
POP3 over TLS,pop3s
Puppet,x-puppet
SIP,sip
SIP federation,sipfederation
SMTP submission,submission
secure TURN,turns
STUN,stun
Teamspeak,ts3
TURN,turn
Web,http
XMPP client connection,xmpp-client
XMPP server connection,xmpp-server
"@ `
| ConvertFrom-CSV `
| ForEach-Object -Parallel {
#region Redefine functions due to ForEach Parallel running in a separate space.
Function Bold {
[OutputType('String')]
Param(
[Parameter(Mandatory)]
[AllowNull()]
[String] $Message
)
Return "`e[1m${Message}`e[22m"
}
Function ForegroundColor {
[Alias('Color', 'FGColor')]
[OutputType('String')]
Param(
[Parameter(Position=0)]
[ValidateSet('Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White')]
[ValidateNotNullOrEmpty()]
[String] $Color,
[Parameter(Position=1)]
[AllowNull()]
[String] $Message
)
Switch ($Color) {
'Black' {Return "`e[30m${Message}`e[37m"}
'Red' {Return "`e[31m${Message}`e[37m"}
'Green' {Return "`e[32m${Message}`e[37m"}
'Yellow' {Return "`e[33m${Message}`e[37m"}
'Blue' {Return "`e[34m${Message}`e[37m"}
'Magenta' {Return "`e[35m${Message}`e[37m"}
'Cyan' {Return "`e[36m${Message}`e[37m"}
'White' {Return "`e[37m${Message}"}
default {Return $Message}
}
}
#endregion
# This $_ will not be available in the nested ForEach, so we need to redefine them here.
$Name = $_.Name
$Service = $_.Service
# Not all of these will be valid combinations (e.g., _autodiscover._udp),
# but this makes the code easier to write. Anything that fails will be ignored.
$Results = @()
$Results += Resolve-DnsName -Name "_$Service._tcp.${using:Domain}" -Type 'SRV' -ErrorAction Ignore
$Results += Resolve-DnsName -Name "_$Service._udp.${using:Domain}" -Type 'SRV' -ErrorAction Ignore
$Results += Resolve-DnsName -Name "_$Service._tls.${using:Domain}" -Type 'SRV' -ErrorAction Ignore
$Results | Where-Object Type -eq 'SRV' | Sort-Object Priority,Weight | ForEach-Object {
$_.Name -Match "_$Service._([A-Za-z]+)\..*" | Out-Null
$Protocol = $Matches[1].ToUpper()
# For some services, a hostname that's only a dot means that the service is explicitly not available.
If ($_.NameTarget -eq '.') {
Write-Output "• $Name is not available for this domain."
}
Else {
If ($Protocol -eq 'TLS') {
Write-Output "• There is an encrypted $Name server at $(Bold ($_.NameTarget.ToString())) on TCP port $($_.Port)."
}
Else {
Write-Output "• There is a $Name server at $(Bold ($_.NameTarget.ToString())) on $Protocol port $($_.Port)."
}
}
}
}
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment