diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index c4f4acd..22252ce 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16179,6 +16179,8 @@ Function Get-SQLInstanceDomain Domain account to filter for. .PARAMETER CheckMgmt Performs UDP scan of servers with registered MSServerClusterMgmtAPI SPNs to help find additional SQL Server instances. + .PARAMETER CheckEncryption + Tests each instance to determine if encryption is enforced. Useful for identifying NTLM relay targets. .PARAMETER UDPTimeOut Timeout in seconds for UDP scans of management servers. Longer timeout = more accurate. .EXAMPLE @@ -16274,6 +16276,39 @@ Function Get-SQLInstanceDomain HelpMessage = 'Performs UDP scan of servers managing SQL Server clusters.')] [switch]$CheckMgmt, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Tests encryption enforcement (NTLM relay vulnerability check).')] + [switch]$CheckEncryption, + + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Always test default port 1433 on each hostname, even if not in SPN.')] + [switch]$CheckDefaultInstance, + + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Use UDP SQL Browser (port 1434) to discover all instances dynamically on each host.')] + [switch]$DiscoverDynamicPorts, + + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Perform quick audit: test login, get version, database, privileges, and sysadmin status.')] + [switch]$QuickAudit, + + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server login username for QuickAudit authentication.')] + [string]$SQLUsername, + + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server login password for QuickAudit authentication.')] + [string]$SQLPassword, + + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server credentials for QuickAudit authentication.')] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()]$SQLCredential = [System.Management.Automation.PSCredential]::Empty, + [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Preforms a DNS lookup on the instance.')] @@ -16303,6 +16338,23 @@ Function Get-SQLInstanceDomain { $null = $TblSQLServerSpns.Columns.Add('IPAddress') } + + if($CheckEncryption) + { + $null = $TblSQLServerSpns.Columns.Add('EncryptionEnforced') + } + + if($QuickAudit) + { + $null = $TblSQLServerSpns.Columns.Add('LoginSuccess') + $null = $TblSQLServerSpns.Columns.Add('Version') + $null = $TblSQLServerSpns.Columns.Add('CurrentLogin') + $null = $TblSQLServerSpns.Columns.Add('CurrentDatabase') + $null = $TblSQLServerSpns.Columns.Add('IsSysadmin') + $null = $TblSQLServerSpns.Columns.Add('HasXpDirtree') + $null = $TblSQLServerSpns.Columns.Add('HasXpFileexist') + $null = $TblSQLServerSpns.Columns.Add('HasXpCmdshell') + } # Table for UDP scan results of management servers } @@ -16362,11 +16414,858 @@ Function Get-SQLInstanceDomain } $TableRow += $IPAddress } + + # Check encryption enforcement if requested + if($CheckEncryption) + { + Write-Verbose -Message "Testing encryption on $SpnServerInstance..." + $EncryptionStatus = "Unknown" + + # Determine port + $TestPort = 1433 + if($SpnServerInstance -match ',') + { + $TestPort = $SpnServerInstance.Split(',')[1] + $TestComputer = $SpnServerInstance.Split(',')[0] + } + elseif($SpnServerInstance -match '\\') + { + # Named instance - try UDP scan to get port + $TestComputer = $SpnServerInstance.Split('\\')[0] + $TestInstanceName = $SpnServerInstance.Split('\\')[1] + try { + $UDPResult = Get-SQLInstanceScanUDP -ComputerName $TestComputer -SuppressVerbose | + Where-Object {$_.InstanceName -eq $TestInstanceName} | + Select-Object -First 1 + if($UDPResult -and $UDPResult.TCPPort) { + $TestPort = $UDPResult.TCPPort + } + } catch { + # UDP scan failed, skip encryption test + $EncryptionStatus = "Unknown (Port Resolution Failed)" + } + } + else + { + $TestComputer = $SpnServerInstance + } + + # Test encryption if we have a port + if($TestPort -and $EncryptionStatus -eq "Unknown") + { + $TcpClient = $null + try { + # Use TDS pre-login to test encryption enforcement + $TcpClient = New-Object System.Net.Sockets.TcpClient + + # Try to connect with timeout (10 seconds) + $ConnectResult = $TcpClient.BeginConnect($TestComputer, $TestPort, $null, $null) + $WaitHandle = $ConnectResult.AsyncWaitHandle + + if($WaitHandle.WaitOne(10000, $false)) { + try { + $TcpClient.EndConnect($ConnectResult) + } catch { + # Connection failed + $EncryptionStatus = "Unknown" + } + } else { + # Timeout + $EncryptionStatus = "Unknown" + } + + if($TcpClient.Connected) { + $Stream = $TcpClient.GetStream() + $Stream.ReadTimeout = 10000 + $Stream.WriteTimeout = 5000 + + # Build TDS pre-login packet (same as Get-SQLEncryptionStatus) + $Version = @(0x08, 0x00, 0x01, 0x55, 0x00, 0x00) + $EncryptionValue = @(0x02) + $InstanceBytes = [System.Text.Encoding]::ASCII.GetBytes("MSSQLServer`0") + $ThreadIDBytes = [BitConverter]::GetBytes([uint32](Get-Random -Maximum 65535)) + + $VersionOffset = 21 + $EncryptionOffset = $VersionOffset + $Version.Length + $InstanceOffset = $EncryptionOffset + $EncryptionValue.Length + $ThreadIDOffset = $InstanceOffset + $InstanceBytes.Length + + $PreLoginOptions = @( + 0x00, + ([byte]($VersionOffset -shr 8)), + ([byte]($VersionOffset -band 0xFF)), + ([byte]($Version.Length -shr 8)), + ([byte]($Version.Length -band 0xFF)), + 0x01, + ([byte]($EncryptionOffset -shr 8)), + ([byte]($EncryptionOffset -band 0xFF)), + 0x00, 0x01, + 0x02, + ([byte]($InstanceOffset -shr 8)), + ([byte]($InstanceOffset -band 0xFF)), + ([byte]($InstanceBytes.Length -shr 8)), + ([byte]($InstanceBytes.Length -band 0xFF)), + 0x03, + ([byte]($ThreadIDOffset -shr 8)), + ([byte]($ThreadIDOffset -band 0xFF)), + 0x00, 0x04, + 0xFF + ) + + $PreLoginData = [System.Collections.ArrayList]@() + $null = $PreLoginData.AddRange($Version) + $null = $PreLoginData.AddRange($EncryptionValue) + $null = $PreLoginData.AddRange($InstanceBytes) + $null = $PreLoginData.AddRange($ThreadIDBytes) + + $PreLoginPayload = [System.Collections.ArrayList]@() + $null = $PreLoginPayload.AddRange($PreLoginOptions) + $null = $PreLoginPayload.AddRange($PreLoginData) + + $PayloadLength = $PreLoginPayload.Count + $TotalLength = 8 + $PayloadLength + + $TdsHeader = @( + 0x12, 0x01, + ([byte]($TotalLength -shr 8)), + ([byte]($TotalLength -band 0xFF)), + 0x00, 0x00, 0x00, 0x00 + ) + + $TdsPacket = [System.Collections.ArrayList]@() + $null = $TdsPacket.AddRange($TdsHeader) + $null = $TdsPacket.AddRange($PreLoginPayload) + + $PacketBytes = [byte[]]$TdsPacket.ToArray() + $Stream.Write($PacketBytes, 0, $PacketBytes.Length) + $Stream.Flush() + + $ResponseHeader = New-Object byte[] 8 + $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + Write-Verbose " Read $BytesRead response header bytes" + + if($BytesRead -eq 8) { + $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] + Write-Verbose " Response length: $ResponseLength" + + if($ResponseLength -gt 8) { + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + Write-Verbose " Read $BytesRead payload bytes" + + # Find ENCRYPTION option + $i = 0 + $EncryptionOffset = -1 + + while($i -lt $ResponsePayload.Length) { + $OptionType = $ResponsePayload[$i] + if($OptionType -eq 0xFF) { break } + if($OptionType -eq 0x01) { + $EncryptionOffset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] + break + } + $i += 5 + } + + if($EncryptionOffset -ge 0) { + if($EncryptionOffset -lt $ResponsePayload.Length) { + $EncryptionValue = $ResponsePayload[$EncryptionOffset] + + # Only 0x02 (TDS_ENCRYPT_NOT_SUP) means NOT enforced + if($EncryptionValue -eq 0x02) { + $EncryptionStatus = "No" + } else { + $EncryptionStatus = "Yes" + } + } else { + Write-Verbose " Encryption offset $EncryptionOffset out of range (payload length: $($ResponsePayload.Length))" + } + } else { + Write-Verbose " Encryption option not found in response" + } + } else { + Write-Verbose " Response length too short: $ResponseLength" + } + } else { + Write-Verbose " Failed to read response header (read $BytesRead bytes)" + } + + $Stream.Close() + $TcpClient.Close() + } else { + $EncryptionStatus = "Unknown" + Write-Verbose " TcpClient not connected" + } + } catch { + $EncryptionStatus = "Unknown" + Write-Verbose " Encryption test error: $($_.Exception.Message)" + } finally { + if($TcpClient -ne $null) { + if($TcpClient.Connected) { + try { $TcpClient.Close() } catch { } + } + try { $TcpClient.Dispose() } catch { } + } + } + } + + $TableRow += $EncryptionStatus + Write-Verbose -Message " Encryption Enforced: $EncryptionStatus" + } + + # Perform quick audit if requested + if($QuickAudit) + { + Write-Verbose -Message "Performing quick audit on $SpnServerInstance..." + + # Initialize audit values + $LoginSuccess = "No" + $VersionInfo = "" + $CurrentLogin = "" + $CurrentDatabase = "" + $IsSysadminStatus = "No" + $HasXpDirtree = "No" + $HasXpFileexist = "No" + $HasXpCmdshell = "No" + + # Get version via unauthenticated TDS pre-login (before authentication attempt) + $TestPort = 1433 + $TestComputer = "" + if($SpnServerInstance -match ',') + { + $TestPort = $SpnServerInstance.Split(',')[1] + $TestComputer = $SpnServerInstance.Split(',')[0] + } + elseif($SpnServerInstance -match '\\') + { + $TestComputer = $SpnServerInstance.Split('\\')[0] + $TestInstanceName = $SpnServerInstance.Split('\\')[1] + try { + $UDPResult = Get-SQLInstanceScanUDP -ComputerName $TestComputer -SuppressVerbose | + Where-Object {$_.InstanceName -eq $TestInstanceName} | + Select-Object -First 1 + if($UDPResult -and $UDPResult.TCPPort) { + $TestPort = $UDPResult.TCPPort + } + } catch { } + } + else + { + $TestComputer = $SpnServerInstance + } + + # Try to get version via TDS pre-login + if($TestPort -and $TestComputer) + { + $TcpClient = $null + try { + $TcpClient = New-Object System.Net.Sockets.TcpClient + $ConnectResult = $TcpClient.BeginConnect($TestComputer, $TestPort, $null, $null) + $WaitHandle = $ConnectResult.AsyncWaitHandle + + if($WaitHandle.WaitOne(5000, $false)) { + try { $TcpClient.EndConnect($ConnectResult) } catch { } + } + + if($TcpClient.Connected) { + $Stream = $TcpClient.GetStream() + $Stream.ReadTimeout = 5000 + $Stream.WriteTimeout = 3000 + + # Build TDS pre-login packet + $Version = @(0x08, 0x00, 0x01, 0x55, 0x00, 0x00) + $EncryptionValue = @(0x02) + $InstanceBytes = [System.Text.Encoding]::ASCII.GetBytes("MSSQLServer`0") + $ThreadIDBytes = [BitConverter]::GetBytes([uint32](Get-Random -Maximum 65535)) + + $VersionOffset = 21 + $EncryptionOffset = $VersionOffset + $Version.Length + $InstanceOffset = $EncryptionOffset + $EncryptionValue.Length + $ThreadIDOffset = $InstanceOffset + $InstanceBytes.Length + + $PreLoginOptions = @( + 0x00, ([byte]($VersionOffset -shr 8)), ([byte]($VersionOffset -band 0xFF)), + ([byte]($Version.Length -shr 8)), ([byte]($Version.Length -band 0xFF)), + 0x01, ([byte]($EncryptionOffset -shr 8)), ([byte]($EncryptionOffset -band 0xFF)), 0x00, 0x01, + 0x02, ([byte]($InstanceOffset -shr 8)), ([byte]($InstanceOffset -band 0xFF)), + ([byte]($InstanceBytes.Length -shr 8)), ([byte]($InstanceBytes.Length -band 0xFF)), + 0x03, ([byte]($ThreadIDOffset -shr 8)), ([byte]($ThreadIDOffset -band 0xFF)), 0x00, 0x04, + 0xFF + ) + + $PreLoginData = [System.Collections.ArrayList]@() + $null = $PreLoginData.AddRange($Version) + $null = $PreLoginData.AddRange($EncryptionValue) + $null = $PreLoginData.AddRange($InstanceBytes) + $null = $PreLoginData.AddRange($ThreadIDBytes) + + $PreLoginPayload = [System.Collections.ArrayList]@() + $null = $PreLoginPayload.AddRange($PreLoginOptions) + $null = $PreLoginPayload.AddRange($PreLoginData) + + $PayloadLength = $PreLoginPayload.Count + $TotalLength = 8 + $PayloadLength + + $TdsHeader = @(0x12, 0x01, ([byte]($TotalLength -shr 8)), ([byte]($TotalLength -band 0xFF)), 0x00, 0x00, 0x00, 0x00) + + $TdsPacket = [System.Collections.ArrayList]@() + $null = $TdsPacket.AddRange($TdsHeader) + $null = $TdsPacket.AddRange($PreLoginPayload) + + $PacketBytes = [byte[]]$TdsPacket.ToArray() + $Stream.Write($PacketBytes, 0, $PacketBytes.Length) + $Stream.Flush() + + $ResponseHeader = New-Object byte[] 8 + $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + + if($BytesRead -eq 8) { + $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] + + if($ResponseLength -gt 8) { + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + + # Parse VERSION option (token 0x00) + $i = 0 + while($i -lt $ResponsePayload.Length) { + $OptionType = $ResponsePayload[$i] + if($OptionType -eq 0xFF) { break } + if($OptionType -eq 0x00) { + $VersionOffset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] + if($VersionOffset -lt $ResponsePayload.Length - 6) { + $Major = $ResponsePayload[$VersionOffset] + $Minor = $ResponsePayload[$VersionOffset + 1] + $Build = ([int]$ResponsePayload[$VersionOffset + 2] -shl 8) + [int]$ResponsePayload[$VersionOffset + 3] + + # Map version to SQL Server version name + $VersionName = switch($Major) { + 16 { "2022" } + 15 { "2019" } + 14 { "2017" } + 13 { "2016" } + 12 { "2014" } + 11 { "2012" } + 10 { "2008/2008R2" } + 9 { "2005" } + default { "Unknown" } + } + + $VersionInfo = "$VersionName ($Major.$Minor.$Build)" + Write-Verbose -Message " Version (TDS): $VersionInfo" + } + break + } + $i += 5 + } + } + } + + $Stream.Close() + $TcpClient.Close() + } + } catch { + Write-Verbose -Message " TDS version detection failed: $($_.Exception.Message)" + } finally { + if($TcpClient -ne $null) { + if($TcpClient.Connected) { + try { $TcpClient.Close() } catch { } + } + try { $TcpClient.Dispose() } catch { } + } + } + } + + # Test connection + $ConnTest = Get-SQLConnectionTest -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose + + if($ConnTest.Status -eq "Accessible") + { + $LoginSuccess = "Yes" + Write-Verbose -Message " Login successful" + + # Get server information (login and sysadmin status) + try { + $ServerInfo = Get-SQLServerInfo -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose + + if($ServerInfo) + { + # Only update version if we didn't get it from TDS + if(-not $VersionInfo) { + $VersionInfo = "$($ServerInfo.SQLServerMajorVersion) ($($ServerInfo.SQLServerVersionNumber))" + Write-Verbose -Message " Version (Authenticated): $VersionInfo" + } + $CurrentLogin = $ServerInfo.Currentlogin + $IsSysadminStatus = $ServerInfo.IsSysadmin + Write-Verbose -Message " Current Login: $CurrentLogin" + Write-Verbose -Message " SysAdmin: $IsSysadminStatus" + } + } catch { + Write-Verbose -Message " Error getting server info: $($_.Exception.Message)" + } + + # Get current database + try { + $DBQuery = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "SELECT DB_NAME() as CurrentDB" -SuppressVerbose + if($DBQuery) { + $CurrentDatabase = $DBQuery.CurrentDB + Write-Verbose -Message " Database: $CurrentDatabase" + } + } catch { + Write-Verbose -Message " Error getting current database: $($_.Exception.Message)" + } + + # Check xp_dirtree access (check permissions, don't execute) + try { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_dirtree', 'OBJECT', 'EXECUTE') as HasPermission" + $XpDirtreeTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpDirtreeTest.HasPermission -eq 1) { + $HasXpDirtree = "Yes" + Write-Verbose -Message " xp_dirtree: Available" + } else { + Write-Verbose -Message " xp_dirtree: No permission" + } + } catch { + Write-Verbose -Message " xp_dirtree: Check failed - $($_.Exception.Message)" + } + + # Check xp_fileexist access (check permissions, don't execute) + try { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_fileexist', 'OBJECT', 'EXECUTE') as HasPermission" + $XpFileexistTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpFileexistTest.HasPermission -eq 1) { + $HasXpFileexist = "Yes" + Write-Verbose -Message " xp_fileexist: Available" + } else { + Write-Verbose -Message " xp_fileexist: No permission" + } + } catch { + Write-Verbose -Message " xp_fileexist: Check failed - $($_.Exception.Message)" + } + + # Check xp_cmdshell access (check permissions, don't execute) + try { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_cmdshell', 'OBJECT', 'EXECUTE') as HasPermission" + $XpCmdshellTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpCmdshellTest.HasPermission -eq 1) { + $HasXpCmdshell = "Yes" + Write-Verbose -Message " xp_cmdshell: Available" + } else { + Write-Verbose -Message " xp_cmdshell: No permission" + } + } catch { + Write-Verbose -Message " xp_cmdshell: Check failed - $($_.Exception.Message)" + } + } + else + { + Write-Verbose -Message " Login failed or instance not accessible" + } + + # Add audit results to table row + $TableRow += $LoginSuccess + $TableRow += $VersionInfo + $TableRow += $CurrentLogin + $TableRow += $CurrentDatabase + $TableRow += $IsSysadminStatus + $TableRow += $HasXpDirtree + $TableRow += $HasXpFileexist + $TableRow += $HasXpCmdshell + } # Add SQL Server spn to table $null = $TblSQLServerSpns.Rows.Add($TableRow) } + # Additional discovery: check default port 1433 and/or use UDP discovery + if($CheckDefaultInstance -or $DiscoverDynamicPorts) + { + # Extract unique hostnames from SPN results + $UniqueHosts = @() + if($TblSQLServers) { + $UniqueHosts = $TblSQLServers | + Select-Object -Property ComputerName -Unique | + ForEach-Object { $_.ComputerName } + } + + Write-Verbose -Message "Found $($UniqueHosts.Count) unique hosts from SPNs for additional discovery..." + + # Track instances we've already tested to avoid duplicates + $AlreadyTested = @{} + foreach($Row in $TblSQLServerSpns.Rows) { + $AlreadyTested[$Row.Instance] = $true + } + + # Process each unique hostname + foreach($HostName in $UniqueHosts) + { + Write-Verbose -Message "Processing additional discovery for $HostName..." + + # List to store newly discovered instances for this host + $NewInstances = @() + + # Check default instance on port 1433 + if($CheckDefaultInstance) + { + $DefaultInstance = "$HostName,1433" + if(-not $AlreadyTested.ContainsKey($DefaultInstance)) { + Write-Verbose -Message " Adding default instance: $DefaultInstance" + $NewInstances += @{ + ComputerName = $HostName + Instance = $DefaultInstance + InstanceName = "MSSQLSERVER" + TCPPort = 1433 + Source = "DefaultPort" + } + $AlreadyTested[$DefaultInstance] = $true + } else { + Write-Verbose -Message " Default instance $DefaultInstance already discovered via SPN" + } + } + + # Use UDP SQL Browser to discover all instances + if($DiscoverDynamicPorts) + { + Write-Verbose -Message " Running UDP discovery on $HostName..." + try { + $UDPResults = Get-SQLInstanceScanUDP -ComputerName $HostName -UDPTimeOut $UDPTimeOut -SuppressVerbose + + foreach($UDPResult in $UDPResults) { + $UDPInstance = $UDPResult.Instance + if(-not $AlreadyTested.ContainsKey($UDPInstance)) { + Write-Verbose -Message " Discovered via UDP: $UDPInstance (Port $($UDPResult.TCPPort))" + $NewInstances += @{ + ComputerName = $UDPResult.ComputerName + Instance = $UDPInstance + InstanceName = $UDPResult.InstanceName + TCPPort = $UDPResult.TCPPort + Source = "UDP" + BaseVersion = $UDPResult.BaseVersion + IsClustered = $UDPResult.IsClustered + } + $AlreadyTested[$UDPInstance] = $true + } else { + Write-Verbose -Message " Instance $UDPInstance already discovered" + } + } + } catch { + Write-Verbose -Message " UDP discovery failed: $($_.Exception.Message)" + } + } + + # Process newly discovered instances + foreach($NewInstance in $NewInstances) + { + Write-Verbose -Message " Processing newly discovered instance: $($NewInstance.Instance)" + + # Build table row with available data + $TableRow = @( + [string]$NewInstance.ComputerName, + [string]$NewInstance.Instance, + '', # DomainAccountSid - not available for non-SPN discoveries + '', # DomainAccount - not available for non-SPN discoveries + '', # DomainAccountCn - not available for non-SPN discoveries + '', # Service - not available for non-SPN discoveries + "Discovered via $($NewInstance.Source)", # Spn - indicate discovery method + '', # LastLogon - not available for non-SPN discoveries + "Discovered via $($NewInstance.Source)" # Description + ) + + # Get IP address if requested + if($IncludeIP) + { + try { + $IPAddress = [Net.DNS]::GetHostAddresses([String]$NewInstance.ComputerName).IPAddressToString + if($IPAddress -is [Object[]]) { + $IPAddress = $IPAddress -join ", " + } + } catch { + $IPAddress = "0.0.0.0" + } + $TableRow += $IPAddress + } + + # Test encryption if requested + if($CheckEncryption) + { + Write-Verbose -Message " Testing encryption on $($NewInstance.Instance)..." + $EncryptionStatus = "Unknown" + + $TestComputer = $NewInstance.ComputerName + $TestPort = $NewInstance.TCPPort + + # Test encryption using TDS pre-login + $TcpClient = $null + try { + $TcpClient = New-Object System.Net.Sockets.TcpClient + + $ConnectResult = $TcpClient.BeginConnect($TestComputer, $TestPort, $null, $null) + $WaitHandle = $ConnectResult.AsyncWaitHandle + + if($WaitHandle.WaitOne(10000, $false)) { + try { + $TcpClient.EndConnect($ConnectResult) + } catch { + $EncryptionStatus = "Unknown" + } + } else { + $EncryptionStatus = "Unknown" + } + + if($TcpClient.Connected) { + $Stream = $TcpClient.GetStream() + $Stream.ReadTimeout = 10000 + $Stream.WriteTimeout = 5000 + + # Build TDS pre-login packet (same as in main loop) + $Version = @(0x08, 0x00, 0x01, 0x55, 0x00, 0x00) + $EncryptionValue = @(0x02) + $InstanceBytes = [System.Text.Encoding]::ASCII.GetBytes("MSSQLServer`0") + $ThreadIDBytes = [BitConverter]::GetBytes([uint32](Get-Random -Maximum 65535)) + + $VersionOffset = 21 + $EncryptionOffset = $VersionOffset + $Version.Length + $InstanceOffset = $EncryptionOffset + $EncryptionValue.Length + $ThreadIDOffset = $InstanceOffset + $InstanceBytes.Length + + $PreLoginOptions = @( + 0x00, + ([byte]($VersionOffset -shr 8)), + ([byte]($VersionOffset -band 0xFF)), + ([byte]($Version.Length -shr 8)), + ([byte]($Version.Length -band 0xFF)), + 0x01, + ([byte]($EncryptionOffset -shr 8)), + ([byte]($EncryptionOffset -band 0xFF)), + 0x00, 0x01, + 0x02, + ([byte]($InstanceOffset -shr 8)), + ([byte]($InstanceOffset -band 0xFF)), + ([byte]($InstanceBytes.Length -shr 8)), + ([byte]($InstanceBytes.Length -band 0xFF)), + 0x03, + ([byte]($ThreadIDOffset -shr 8)), + ([byte]($ThreadIDOffset -band 0xFF)), + 0x00, 0x04, + 0xFF + ) + + $PreLoginData = [System.Collections.ArrayList]@() + $null = $PreLoginData.AddRange($Version) + $null = $PreLoginData.AddRange($EncryptionValue) + $null = $PreLoginData.AddRange($InstanceBytes) + $null = $PreLoginData.AddRange($ThreadIDBytes) + + $PreLoginPayload = [System.Collections.ArrayList]@() + $null = $PreLoginPayload.AddRange($PreLoginOptions) + $null = $PreLoginPayload.AddRange($PreLoginData) + + $PayloadLength = $PreLoginPayload.Count + $TotalLength = 8 + $PayloadLength + + $TdsHeader = @( + 0x12, 0x01, + ([byte]($TotalLength -shr 8)), + ([byte]($TotalLength -band 0xFF)), + 0x00, 0x00, 0x00, 0x00 + ) + + $TdsPacket = [System.Collections.ArrayList]@() + $null = $TdsPacket.AddRange($TdsHeader) + $null = $TdsPacket.AddRange($PreLoginPayload) + + $PacketBytes = [byte[]]$TdsPacket.ToArray() + $Stream.Write($PacketBytes, 0, $PacketBytes.Length) + $Stream.Flush() + + $ResponseHeader = New-Object byte[] 8 + $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + Write-Verbose " Read $BytesRead response header bytes" + + if($BytesRead -eq 8) { + $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] + Write-Verbose " Response length: $ResponseLength" + + if($ResponseLength -gt 8) { + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + Write-Verbose " Read $BytesRead payload bytes" + + # Find ENCRYPTION option + $i = 0 + $EncryptionOffset = -1 + + while($i -lt $ResponsePayload.Length) { + $OptionType = $ResponsePayload[$i] + if($OptionType -eq 0xFF) { break } + if($OptionType -eq 0x01) { + $EncryptionOffset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] + break + } + $i += 5 + } + + if($EncryptionOffset -ge 0) { + if($EncryptionOffset -lt $ResponsePayload.Length) { + $EncryptionValue = $ResponsePayload[$EncryptionOffset] + + # Only 0x02 (TDS_ENCRYPT_NOT_SUP) means NOT enforced + if($EncryptionValue -eq 0x02) { + $EncryptionStatus = "No" + } else { + $EncryptionStatus = "Yes" + } + } else { + Write-Verbose " Encryption offset $EncryptionOffset out of range" + } + } else { + Write-Verbose " Encryption option not found in response" + } + } else { + Write-Verbose " Response length too short: $ResponseLength" + } + } else { + Write-Verbose " Failed to read response header" + } + + $Stream.Close() + $TcpClient.Close() + } + } catch { + $EncryptionStatus = "Unknown" + Write-Verbose " Encryption test error: $($_.Exception.Message)" + } finally { + if($TcpClient -ne $null) { + if($TcpClient.Connected) { + try { $TcpClient.Close() } catch { } + } + try { $TcpClient.Dispose() } catch { } + } + } + + $TableRow += $EncryptionStatus + Write-Verbose -Message " Encryption Enforced: $EncryptionStatus" + } + + # Perform quick audit if requested + if($QuickAudit) + { + Write-Verbose -Message " Performing quick audit on $($NewInstance.Instance)..." + + # Initialize audit values + $LoginSuccess = "No" + $VersionInfo = "" + $CurrentLogin = "" + $CurrentDatabase = "" + $IsSysadminStatus = "No" + $HasXpDirtree = "No" + $HasXpFileexist = "No" + $HasXpCmdshell = "No" + + # Test connection + $ConnTest = Get-SQLConnectionTest -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose + + if($ConnTest.Status -eq "Accessible") + { + $LoginSuccess = "Yes" + Write-Verbose -Message " Login successful" + + # Get server information (login and sysadmin status) + try { + $ServerInfo = Get-SQLServerInfo -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose + + if($ServerInfo) + { + # Only update version if we didn't get it already (from UDP or TDS) + if(-not $VersionInfo) { + $VersionInfo = "$($ServerInfo.SQLServerMajorVersion) ($($ServerInfo.SQLServerVersionNumber))" + Write-Verbose -Message " Version (Authenticated): $VersionInfo" + } + $CurrentLogin = $ServerInfo.Currentlogin + $IsSysadminStatus = $ServerInfo.IsSysadmin + Write-Verbose -Message " Current Login: $CurrentLogin" + Write-Verbose -Message " SysAdmin: $IsSysadminStatus" + } + } catch { + Write-Verbose -Message " Error getting server info: $($_.Exception.Message)" + } + + # Get current database + try { + $DBQuery = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "SELECT DB_NAME() as CurrentDB" -SuppressVerbose + if($DBQuery) { + $CurrentDatabase = $DBQuery.CurrentDB + Write-Verbose -Message " Database: $CurrentDatabase" + } + } catch { + Write-Verbose -Message " Error getting current database: $($_.Exception.Message)" + } + + # Check xp_dirtree access (check permissions, don't execute) + try { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_dirtree', 'OBJECT', 'EXECUTE') as HasPermission" + $XpDirtreeTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpDirtreeTest.HasPermission -eq 1) { + $HasXpDirtree = "Yes" + Write-Verbose -Message " xp_dirtree: Available" + } else { + Write-Verbose -Message " xp_dirtree: No permission" + } + } catch { + Write-Verbose -Message " xp_dirtree: Check failed - $($_.Exception.Message)" + } + + # Check xp_fileexist access (check permissions, don't execute) + try { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_fileexist', 'OBJECT', 'EXECUTE') as HasPermission" + $XpFileexistTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpFileexistTest.HasPermission -eq 1) { + $HasXpFileexist = "Yes" + Write-Verbose -Message " xp_fileexist: Available" + } else { + Write-Verbose -Message " xp_fileexist: No permission" + } + } catch { + Write-Verbose -Message " xp_fileexist: Check failed - $($_.Exception.Message)" + } + + # Check xp_cmdshell access (check permissions, don't execute) + try { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_cmdshell', 'OBJECT', 'EXECUTE') as HasPermission" + $XpCmdshellTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpCmdshellTest.HasPermission -eq 1) { + $HasXpCmdshell = "Yes" + Write-Verbose -Message " xp_cmdshell: Available" + } else { + Write-Verbose -Message " xp_cmdshell: No permission" + } + } catch { + Write-Verbose -Message " xp_cmdshell: Check failed - $($_.Exception.Message)" + } + } + else + { + Write-Verbose -Message " Login failed or instance not accessible" + } + + # Add audit results to table row + $TableRow += $LoginSuccess + $TableRow += $VersionInfo + $TableRow += $CurrentLogin + $TableRow += $CurrentDatabase + $TableRow += $IsSysadminStatus + $TableRow += $HasXpDirtree + $TableRow += $HasXpFileexist + $TableRow += $HasXpCmdshell + } + + # Add newly discovered instance to table + $null = $TblSQLServerSpns.Rows.Add($TableRow) + Write-Verbose -Message " Added to results" + } + } + } + # Enumerate SQL Server instances from management servers if($CheckMgmt) { @@ -16413,6 +17312,381 @@ Function Get-SQLInstanceDomain } +# ------------------------------------------- +# Function: Get-SQLEncryptionStatus +# ------------------------------------------- +# Author: Community Contribution +# Reference: https://blog.compass-security.com/2023/10/relaying-ntlm-to-mssql/ +Function Get-SQLEncryptionStatus +{ + <# + .SYNOPSIS + Tests whether encryption is enforced on a specific SQL Server instance. + Useful for identifying instances vulnerable to NTLM relay attacks. + + .PARAMETER Instance + SQL Server instance to test (e.g., "Server\Instance", "Server,1433", or "Server"). + + .PARAMETER TimeOut + Connection timeout in seconds. Default is 10. + + .EXAMPLE + PS C:\> Get-SQLEncryptionStatus -Instance "SQLServer1.domain.com" -Verbose + VERBOSE: Testing encryption on SQLServer1.domain.com:1433... + VERBOSE: Encryption NOT enforced - Vulnerable to NTLM relay + + Instance EncryptionEnforced + -------- ------------------ + SQLServer1.domain.com No + + .EXAMPLE + PS C:\> Get-SQLEncryptionStatus -Instance "SQLServer1\SQLEXPRESS" + + Tests a named instance (will attempt UDP scan to resolve port). + + .EXAMPLE + PS C:\> "Server1", "Server2\SQL01", "Server3,14330" | Get-SQLEncryptionStatus + + Test multiple instances via pipeline. + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'SQL Server instance to test.')] + [string]$Instance, + + [Parameter(Mandatory = $false, + HelpMessage = 'Connection timeout in seconds.')] + [int]$TimeOut = 10 + ) + + Begin + { + # Setup data table for results + $TblResults = New-Object -TypeName System.Data.DataTable + $null = $TblResults.Columns.Add('Instance') + $null = $TblResults.Columns.Add('EncryptionEnforced') + } + + Process + { + # Parse instance to get computer and port + $Computer = $Instance + $Port = 1433 + + if($Instance -match '\\') + { + # Named instance + $Computer = $Instance.Split('\')[0] + $InstanceName = $Instance.Split('\')[1] + + # Try UDP scan to get port + Write-Verbose "Resolving port for named instance $Instance..." + try { + $UDPResult = Get-SQLInstanceScanUDP -ComputerName $Computer -SuppressVerbose | + Where-Object {$_.InstanceName -eq $InstanceName} | + Select-Object -First 1 + + if($UDPResult -and $UDPResult.TCPPort) { + $Port = $UDPResult.TCPPort + Write-Verbose "Resolved to port $Port" + } else { + Write-Verbose "Could not resolve port via UDP scan" + $null = $TblResults.Rows.Add($Instance, "Unknown (Port Resolution Failed)") + return + } + } catch { + Write-Verbose "Error resolving port: $_" + $null = $TblResults.Rows.Add($Instance, "Unknown (Port Resolution Failed)") + return + } + } + elseif($Instance -match ',') + { + # Port specified + $Computer = $Instance.Split(',')[0] + $Port = $Instance.Split(',')[1] + } + + Write-Verbose "Testing encryption on ${Computer}:${Port} using TDS pre-login..." + + # Test using TDS pre-login packet (like mssqlrelay does) + $EncryptionStatus = "Unknown" + $TcpClient = $null + + try { + # Create TCP connection + Write-Verbose " Creating TCP client..." + $TcpClient = New-Object System.Net.Sockets.TcpClient + + Write-Verbose " Attempting connection to ${Computer}:${Port}..." + + # Try to connect with timeout + try { + $ConnectResult = $TcpClient.BeginConnect($Computer, $Port, $null, $null) + Write-Verbose " BeginConnect initiated..." + $WaitHandle = $ConnectResult.AsyncWaitHandle + + Write-Verbose " Waiting for connection (timeout: $($TimeOut)s)..." + if($WaitHandle.WaitOne($TimeOut * 1000, $false)) { + Write-Verbose " Connection wait completed, calling EndConnect..." + try { + $TcpClient.EndConnect($ConnectResult) + Write-Verbose " EndConnect successful" + } catch { + $ErrorMsg = $_.Exception.Message + Write-Verbose " EndConnect failed: $($_.Exception.GetType().FullName) - $ErrorMsg" + + # Check for specific errors + if($ErrorMsg -match "actively refused|connection refused") { + throw "Connection refused - SQL Server not listening on port $Port" + } elseif($ErrorMsg -match "timed out") { + throw "Connection timed out" + } else { + throw "Connection failed: $ErrorMsg" + } + } + } else { + Write-Verbose " Connection timed out after $($TimeOut) seconds" + throw "Connection timeout" + } + } catch { + Write-Verbose " Connection error: $($_.Exception.GetType().FullName)" + Write-Verbose " Error message: $($_.Exception.Message)" + if($_.Exception.InnerException) { + Write-Verbose " Inner exception: $($_.Exception.InnerException.GetType().FullName) - $($_.Exception.InnerException.Message)" + } + throw + } + + if($TcpClient.Connected) { + Write-Verbose " TCP connection established" + $Stream = $TcpClient.GetStream() + # Set timeouts in milliseconds (convert from seconds) + $Stream.ReadTimeout = $TimeOut * 1000 + $Stream.WriteTimeout = 1000 # Write timeout can be short + + # Build TDS pre-login packet (exactly as impacket does) + # TDS Header: Type=0x12 (Pre-Login), Status=0x01 (End of message), Length, SPID=0x0000, PacketID=0x00, Window=0x00 + + # Pre-login option tokens (21 bytes total before data section) + $Version = @(0x08, 0x00, 0x01, 0x55, 0x00, 0x00) # Version 8.0.0.0 + $EncryptionValue = @(0x02) # TDS_ENCRYPT_NOT_SUP - we say we don't support, server tells us what IT requires + $InstanceBytes = [System.Text.Encoding]::ASCII.GetBytes("MSSQLServer`0") + $ThreadIDBytes = [BitConverter]::GetBytes([uint32](Get-Random -Maximum 65535)) + + # Calculate offsets (start from byte 21 after TDS header, which is byte 29 from start of packet) + $VersionOffset = 21 + $EncryptionOffset = $VersionOffset + $Version.Length + $InstanceOffset = $EncryptionOffset + $EncryptionValue.Length + $ThreadIDOffset = $InstanceOffset + $InstanceBytes.Length + + # Build option token section (big-endian for offsets/lengths) + $PreLoginOptions = @( + 0x00, # VERSION token + ([byte]($VersionOffset -shr 8)), # VERSION offset high byte + ([byte]($VersionOffset -band 0xFF)), # VERSION offset low byte + ([byte]($Version.Length -shr 8)), # VERSION length high byte + ([byte]($Version.Length -band 0xFF)), # VERSION length low byte + + 0x01, # ENCRYPTION token + ([byte]($EncryptionOffset -shr 8)), # ENCRYPTION offset high byte + ([byte]($EncryptionOffset -band 0xFF)), # ENCRYPTION offset low byte + 0x00, 0x01, # ENCRYPTION length = 1 + + 0x02, # INSTANCE token + ([byte]($InstanceOffset -shr 8)), # INSTANCE offset high byte + ([byte]($InstanceOffset -band 0xFF)), # INSTANCE offset low byte + ([byte]($InstanceBytes.Length -shr 8)), # INSTANCE length high byte + ([byte]($InstanceBytes.Length -band 0xFF)), # INSTANCE length low byte + + 0x03, # THREADID token + ([byte]($ThreadIDOffset -shr 8)), # THREADID offset high byte + ([byte]($ThreadIDOffset -band 0xFF)), # THREADID offset low byte + 0x00, 0x04, # THREADID length = 4 + + 0xFF # TERMINATOR + ) + + # Build data section + $PreLoginData = [System.Collections.ArrayList]@() + $null = $PreLoginData.AddRange($Version) + $null = $PreLoginData.AddRange($EncryptionValue) + $null = $PreLoginData.AddRange($InstanceBytes) + $null = $PreLoginData.AddRange($ThreadIDBytes) + + # Build complete payload + $PreLoginPayload = [System.Collections.ArrayList]@() + $null = $PreLoginPayload.AddRange($PreLoginOptions) + $null = $PreLoginPayload.AddRange($PreLoginData) + + $PayloadLength = $PreLoginPayload.Count + $TotalLength = 8 + $PayloadLength + + # TDS Header + $TdsHeader = @( + 0x12, # Type: Pre-Login + 0x01, # Status: End of message + ([byte]($TotalLength -shr 8)), # Length high byte + ([byte]($TotalLength -band 0xFF)), # Length low byte + 0x00, 0x00, # SPID + 0x00, # PacketID + 0x00 # Window + ) + + $TdsPacket = [System.Collections.ArrayList]@() + $null = $TdsPacket.AddRange($TdsHeader) + $null = $TdsPacket.AddRange($PreLoginPayload) + + # Send pre-login packet + Write-Verbose " Sending TDS pre-login packet..." + try { + $PacketBytes = [byte[]]$TdsPacket.ToArray() + $PacketHex = ($PacketBytes | ForEach-Object { $_.ToString("X2") }) -join " " + Write-Verbose " Packet ($($PacketBytes.Length) bytes): $PacketHex" + $Stream.Write($PacketBytes, 0, $PacketBytes.Length) + $Stream.Flush() + } catch { + Write-Verbose " Error building/sending packet: $_" + throw + } + + # Read response (Stream.Read is blocking and will wait for data up to ReadTimeout) + Write-Verbose " Reading TDS pre-login response..." + $ResponseHeader = New-Object byte[] 8 + $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + Write-Verbose " Read $BytesRead header bytes" + + if($BytesRead -eq 8) { + $HeaderHex = ($ResponseHeader | ForEach-Object { $_.ToString("X2") }) -join " " + Write-Verbose " Response header: $HeaderHex" + + $ResponseType = $ResponseHeader[0] + $ResponseStatus = $ResponseHeader[1] + $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] + + Write-Verbose " Response type: 0x$($ResponseType.ToString('X2')), status: 0x$($ResponseStatus.ToString('X2')), length: $ResponseLength" + + if($ResponseLength -gt 8) { + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + Write-Verbose " Read $BytesRead payload bytes (expected $($ResponseLength - 8))" + + $PayloadHex = ($ResponsePayload[0..([Math]::Min(50, $ResponsePayload.Length-1))] | ForEach-Object { $_.ToString("X2") }) -join " " + Write-Verbose " Payload start: $PayloadHex..." + + # Parse pre-login response to find ENCRYPTION option + Write-Verbose " Parsing encryption flag from response..." + $i = 0 + $EncryptionOffset = -1 + + while($i -lt $ResponsePayload.Length) { + $OptionType = $ResponsePayload[$i] + Write-Verbose " Option at index $i : type 0x$($OptionType.ToString('X2'))" + + if($OptionType -eq 0xFF) { + # Terminator found + Write-Verbose " Found terminator at index $i" + break + } + + if($i + 4 -ge $ResponsePayload.Length) { + Write-Verbose " Not enough bytes for option header" + break + } + + $Offset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] + $Length = ([int]$ResponsePayload[$i+3] -shl 8) + [int]$ResponsePayload[$i+4] + Write-Verbose " Offset: $Offset, Length: $Length" + + if($OptionType -eq 0x01) { + # ENCRYPTION option found + $EncryptionOffset = $Offset + Write-Verbose " Found ENCRYPTION option! Offset in payload: $EncryptionOffset" + break + } + + $i += 5 # Move to next option (type + offset[2] + length[2]) + } + + if($EncryptionOffset -ge 0) { + # The offset is relative to the start of the PAYLOAD (not the packet) + # So we use it directly as an index + Write-Verbose " Encryption value at payload offset: $EncryptionOffset" + + if($EncryptionOffset -ge 0 -and $EncryptionOffset -lt $ResponsePayload.Length) { + $EncryptionValue = $ResponsePayload[$EncryptionOffset] + + Write-Verbose " Encryption flag value: 0x$($EncryptionValue.ToString('X2'))" + + # When we send TDS_ENCRYPT_NOT_SUP (0x02), server response interpretation: + # 0x02 = Server agrees, no encryption needed = NOT enforced (vulnerable to relay) + # Anything else = Server requires encryption = IS enforced (protected) + if($EncryptionValue -eq 0x02) { + # TDS_ENCRYPT_NOT_SUP - Server allows unencrypted connections + $EncryptionStatus = "No" + Write-Verbose "RESULT: Encryption NOT enforced - Vulnerable to NTLM relay" + } else { + # TDS_ENCRYPT_OFF (0x00), TDS_ENCRYPT_ON (0x01), TDS_ENCRYPT_REQ (0x03) + # All mean encryption is enforced when we say we don't support it + $EncryptionStatus = "Yes" + Write-Verbose "RESULT: Encryption IS enforced (response: 0x$($EncryptionValue.ToString('X2'))) - Protected from NTLM relay" + } + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Encryption offset out of range (offset: $EncryptionOffset, payload length: $($ResponsePayload.Length))" + } + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Could not find encryption option in response" + } + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Invalid response length: $ResponseLength" + } + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Invalid response header (read $BytesRead bytes instead of 8)" + } + + $Stream.Close() + $TcpClient.Close() + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Not connected" + } + } catch { + $EncryptionStatus = "Unknown" + $ErrorMsg = if($_.Exception.Message -match "Connection refused") { + "Port $Port not accepting connections (wrong port or SQL not running)" + } elseif($_.Exception.Message -match "timed out") { + "Connection timed out" + } else { + $_.Exception.Message + } + Write-Verbose "RESULT: Cannot test - $ErrorMsg" + } finally { + if($TcpClient -ne $null) { + if($TcpClient.Connected) { + try { $TcpClient.Close() } catch { } + } + try { $TcpClient.Dispose() } catch { } + } + } + + # Add to results + $null = $TblResults.Rows.Add($Instance, $EncryptionStatus) + } + + End + { + return $TblResults + } +} + + # ------------------------------------------- # Function: Get-SQLInstanceLocal # ------------------------------------------- diff --git a/PowerUpSQL.psd1 b/PowerUpSQL.psd1 index 6b6b2bb..007e2e6 100644 --- a/PowerUpSQL.psd1 +++ b/PowerUpSQL.psd1 @@ -39,6 +39,7 @@ 'Get-SQLDomainController', 'Get-SQLDomainExploitableSystem', 'Get-SQLDomainGroupMember', + 'Get-SQLEncryptionStatus', 'Get-SQLFuzzDatabaseName', 'Get-SQLFuzzDomainAccount', 'Get-SQLFuzzObjectName', @@ -119,4 +120,3 @@ ) FileList = 'PowerUpSQL.psm1', 'PowerUpSQL.ps1', 'README.md' } -