Public/Reconnaissance/Test-DomainRegistration.ps1

function Test-DomainRegistration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Domain,
        
        [Parameter(Mandatory = $false)]
        [ValidateSet("rdap", "whois", "dns", "all")]
        [string]$Method = "all"
    )
    
    # Add some verification to ensure the domain is a valid format
    if (-not $Domain -or -not ($Domain -match '^[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}$')) {
        Write-Warning " Invalid domain format: $Domain"
        return [PSCustomObject]@{
            Status  = "Invalid"
            Domain  = $Domain
            Message = " Invalid domain format"
        }
    }
    
    # Helper function to check via DNS
    function Test-DomainViaDNS {
        param([string]$DomainName)
        
        try {
            $result = Resolve-DnsName -Name $DomainName -ErrorAction Stop
            if ($result) {
                return [PSCustomObject]@{
                    Status    = "Registered"
                    Domain    = $DomainName
                    Registrar = "Unknown (DNS lookup only)"
                    Message   = " Domain resolves in DNS"
                }
            }
        }
        catch {
            return [PSCustomObject]@{
                Status  = "Available"
                Domain  = $DomainName
                Message = "🆓 Domain does not resolve in DNS"
            }
        }
    }
    
    # If method is "dns" or we're rate limited and trying alternative methods
    if ($Method -eq "dns" -or $Method -eq "all") {
        $dnsResult = Test-DomainViaDNS -DomainName $Domain
        # If we're only using DNS or we get a successful DNS result when using "all" method
        if ($Method -eq "dns" -or ($Method -eq "all" -and $dnsResult.Status -eq "Registered")) {
            return $dnsResult
        }
    }
    
    # If we're not using RDAP, skip this section
    if ($Method -eq "dns") {
        # We've already returned the DNS result if it was the requested method
        return $dnsResult
    }
    
    try {
        # Add retry logic for rate limiting
        $maxRetries = 2
        $retryCount = 0
        $retryDelayMs = 1500  # Start with 1.5 second delay
        $success = $false
        $response = $null
        
        # Try multiple RDAP services if the primary fails
        $rdapServices = @(
            "https://rdap.org/domain/$Domain",
            "https://www.rdap.net/domain/$Domain"
        )
        
        foreach ($rdapService in $rdapServices) {
            $retryCount = 0
            while (-not $success -and $retryCount -lt $maxRetries) {
                try {
                    Write-Verbose "Trying RDAP service: $rdapService"
                    $response = Invoke-RestMethod -Uri $rdapService -ErrorAction Stop -TimeoutSec 10
                    $success = $true
                    break  # Exit the retry loop if successful
                }
                catch {
                    if ($_.Exception.Response.StatusCode.value__ -eq 429) {
                        # Rate limited - implement exponential backoff
                        $retryCount++
                        if ($retryCount -lt $maxRetries) {
                            # Calculate retry delay with exponential backoff and jitter
                            $jitter = Get-Random -Minimum 100 -Maximum 500
                            $retryDelayMs = $retryDelayMs * 2 + $jitter
                            Write-Verbose " Rate limited for domain $Domain, retry $retryCount after $($retryDelayMs)ms"
                            Start-Sleep -Milliseconds $retryDelayMs
                        }
                        else {
                            # Try the next service or fail if this was the last one
                            Write-Verbose " Rate limit exceeded for $rdapService, trying next service"
                            break
                        }
                    }
                    else {
                        # For other errors, try the next service
                        Write-Verbose " Error with $rdapService $($_.Exception.Message)"
                        break
                    }
                }
            }
            
            # If we got a successful response, no need to try additional services
            if ($success) {
                break
            }
        }
        
        # If we've exhausted all RDAP options, try DNS as last resort
        if (-not $success -and $Method -eq "all") {
            Write-Verbose " All RDAP services failed, falling back to DNS check for $Domain"
            return Test-DomainViaDNS -DomainName $Domain
        }
        
        # If we still haven't succeeded and we're not in "all" mode, throw to be caught by the catch block
        if (-not $success -and $Method -ne "all") {
            throw [System.Net.WebException]::new("All RDAP services failed for domain $Domain")
        }
        
        # If no valid response, stop here
        if (-not $response) {
            throw [System.Net.WebException]::new("No valid response from RDAP services for $Domain")
        }
        
        # Domain is registered
        if ($response.objectClassName -eq "domain") {
            try {
                # Extract important dates
                $creationDate = ($response.events | Where-Object { $_.eventAction -eq "registration" }).eventDate
                $expiryDate = ($response.events | Where-Object { $_.eventAction -eq "expiration" }).eventDate
                $lastUpdateDate = ($response.events | Where-Object { $_.eventAction -like "*last update*" }).eventDate
                
                # Extract name servers
                $nameServers = $response.nameservers | ForEach-Object { $_.ldhName }
                
                # Extract registrar information
                $registrarName = "Unknown Registrar"
                $registrarEntity = $response.entities | Where-Object { $_.roles -contains "registrar" }
                
                if ($registrarEntity) {
                    $vcardFn = $registrarEntity.vcardArray[1] | Where-Object { $_[0] -eq "fn" }
                    if ($vcardFn) {
                        $registrarName = $vcardFn[3]
                    }
                }
                
                # Create user-friendly output object
                return [PSCustomObject]@{
                    Status      = "Registered"
                    Domain      = $Domain
                    Registrar   = $registrarName
                    Created     = $creationDate
                    Expires     = $expiryDate
                    LastUpdated = $lastUpdateDate
                    NameServers = $nameServers -join ", "
                    Message     = " Domain is registered"
                }
            }
            catch {
                # If we got a response, but couldn't parse it correctly
                return [PSCustomObject]@{
                    Status      = "Registered"
                    Domain      = $Domain
                    Registrar   = "Unknown (Parser Error)"
                    Message     = " Error parsing domain data: $($_.Exception.Message)"
                }
            }
        }
        else {
            # Got a response, but it wasn't a domain
            return [PSCustomObject]@{
                Status   = "Unknown"
                Domain   = $Domain
                Message  = " Unexpected response format"
            }
        }
    }
    catch {
        # Handle specific error cases
        if ($_.Exception.Response -and $_.Exception.Response.StatusCode.value__ -eq 404) {
            # 404 means domain is available
            return [PSCustomObject]@{
                Status  = "Available"
                Domain  = $Domain
                Message = "🆓 Domain appears to be available for registration"
            }
        }
        elseif ($_.Exception.Response -and $_.Exception.Response.StatusCode.value__ -eq 429) {
            # Rate limiting
            return [PSCustomObject]@{
                Status  = "RateLimited"
                Domain  = $Domain
                Message = " Rate limited by API service when checking domain"
            }
        }
        elseif ($_.Exception.Message -match "timed out|timeout") {
            # Timeout errors
            return [PSCustomObject]@{
                Status  = "Timeout"
                Domain  = $Domain
                Message = " Request timed out when checking domain registration"
            }
        }
        else {
            # General errors
            return [PSCustomObject]@{
                Status  = "Error"
                Domain  = $Domain
                Message = " Error checking domain: $($_.Exception.Message)"
            }
        }
    }

    <#
    .SYNOPSIS
        Tests whether a domain is registered or available for registration.
 
    .DESCRIPTION
    Tests whether a domain is registered or available for registration using RDAP, WHOIS, or DNS. Helps identify potential domain takeover opportunities and infrastructure control points.
    .PARAMETER Domain
        The domain name to check for registration status.
 
    .PARAMETER Method
        The method to use for checking domain registration. Valid values are:
        - rdap: Use RDAP protocol
        - whois: Use WHOIS lookup
        - dns: Use DNS resolution
        - all: Try all methods (default)
 
    .EXAMPLE
        Test-DomainRegistration -Domain "example.com"
 
        Checks if example.com is registered using all available methods.
 
    .EXAMPLE
        Test-DomainRegistration -Domain "potential-subdomain.azurewebsites.net" -Method dns
 
        Checks if the subdomain resolves in DNS (useful for subdomain takeover checks).
 
    .NOTES
        Author: Rogier Dijkman
        This function is useful for identifying dangling DNS records and subdomain takeover opportunities.
 
    .LINK
        MITRE ATT&CK Tactic: TA0043 - Reconnaissance
        https://attack.mitre.org/tactics/TA0043/
 
    .LINK
        MITRE ATT&CK Technique: T1593.002 - Search Open Websites/Domains: Search Engines
        https://attack.mitre.org/techniques/T1593/002/
    #>

}