-
Notifications
You must be signed in to change notification settings - Fork 14.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Forging diamond and sapphire tickets #18560
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the most part things look good. I just left a few comments but the themes were updating docs, catching some additional exceptions in the module and fixing an error when VERBOSE was printing the ticket.
I was able to test both new actions are working as intended.
Thanks for your pull request! Before this can be merged, we need the following documentation for your module: |
@@ -288,7 +289,7 @@ def send_request_tgt(options = {}) | |||
initial_as_res = send_request_as(req: initial_as_req) | |||
|
|||
# If we receive an AS_REP response immediately, no-preauthentication was required and we can return immediately | |||
if initial_as_res.msg_type == Rex::Proto::Kerberos::Model::AS_REP | |||
if initial_as_res.msg_type == Rex::Proto::Kerberos::Model::AS_REP && stop_if_preauth_not_required |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in what situations do we want to stop_if_preauth_not_required
?
I'm also not sure I expect to continue as we did previously if stop_if_preauth_not_required
is true
to me that says if pre auth isn't required something has gone wrong and we should stop rather than return the TgtResponse but that might just be me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One module that wants to stop in this circumstance is kerberos_login
: that one is designed to return immediately, to avoid unnecessary requests: no point in brute forcing if you can just crack offline and avoid the risk of detection/locking out accounts.
But I think there is a better behaviour available to us in this situation: rather than re-sending the request, we could attempt to decrypt it using the password or key provided. Essentially, the behaviour should mirror what a real kerberos client would do:
- If we have been provided a password, just attempt to decrypt the ticket.
- If success, return as normal (the caller is none the wiser, unless they check
preauth_required
) - If fail, return with a nil
decrypted_part
as per before, withpreauth_required
set to false - If we have been provided a key, attempt to decrypt with that key
- If success, return as normal (the caller is none the wiser, unless they check
preauth_required
) - If fail, return with a nil
decrypted_part
as per before, withpreauth_required
set to false
I've implemented this in place of the stop_if_preauth_not_required
- now, instead of directing the function to try the password regardless (and dealing with the possibility of an error), the caller is now responsible for dealing with the possibility of a nil
decrypted_part
(and with preauth_required
set to false).
With pre-auth not required, wrong password:
msf6 auxiliary(admin/kerberos/get_ticket) > run rhosts=192.168.20.210 domain=pod8.lan password=Password123 username=no.preauth
[*] Running module against 192.168.20.210
[*] 192.168.20.210:88 - Getting TGT for [email protected]
[-] Auxiliary aborted due to failure: unknown: Kerberos ticket does not require preauthentication. It is not possible to decrypt the encrypted message to request further TGS tickets. Try cracking the password via AS-REP Roasting techniques.
[*] Auxiliary module execution completed
With pre-auth not required, correct password:
msf6 auxiliary(admin/kerberos/get_ticket) > run rhosts=192.168.20.210 domain=pod8.lan password=Password123! username=no.preauth
[*] Running module against 192.168.20.210
[*] 192.168.20.210:88 - Getting TGT for [email protected]
[+] 192.168.20.210:88 - Received a valid TGT-Response
[*] 192.168.20.210:88 - TGT MIT Credential Cache ticket saved to /home/smash/.msf4/loot/20231127125836_default_192.168.20.210_mit.kerberos.cca_103727.bin
[*] Auxiliary module execution completed
Other happy paths:
msf6 auxiliary(admin/kerberos/get_ticket) > run rhosts=192.168.20.210 domain=pod8.lan password=Password123 username=nonexistent
[*] Running module against 192.168.20.210
[*] 192.168.20.210:88 - Getting TGT for [email protected]
[-] Auxiliary aborted due to failure: unknown: Kerberos Error - KDC_ERR_C_PRINCIPAL_UNKNOWN (6) - Client not found in Kerberos database
[*] Auxiliary module execution completed
msf6 auxiliary(admin/kerberos/get_ticket) > run rhosts=192.168.20.210 domain=pod8.lan password=Password123 username=Administrator
[*] Running module against 192.168.20.210
[*] 192.168.20.210:88 - Getting TGT for [email protected]
[-] Auxiliary aborted due to failure: unknown: Kerberos Error - KDC_ERR_PREAUTH_FAILED (24) - Pre-authentication information was invalid
[*] Auxiliary module execution completed
msf6 auxiliary(admin/kerberos/get_ticket) > run rhosts=192.168.20.210 domain=pod8.lan password=Password123! username=Administrator
[*] Running module against 192.168.20.210
[*] 192.168.20.210:88 - Getting TGT for [email protected]
[+] 192.168.20.210:88 - Received a valid TGT-Response
[*] 192.168.20.210:88 - TGT MIT Credential Cache ticket saved to /home/smash/.msf4/loot/20231127130111_default_192.168.20.210_mit.kerberos.cca_787617.bin
[*] Auxiliary module execution completed
Because the stop_if_preauth_not_required
flag was added just for this module, I don't believe it should affect any other modules (except now providing better usability; possibly fixing some bugs where the module failed to take the possibility of pre-auth into account).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added tests for these cases in client_spec.rb
) | ||
|
||
tgs_ticket, tgs_auth = kerberos_authenticator.u2uself(credential, impersonate: datastore['USER']) | ||
ticket = modify_ticket(tgs_ticket, tgs_auth, datastore['USER'], datastore['USER_RID'], datastore['DOMAIN'], extra_sids, session_key.value, enc_type, enc_key, true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conditions above suggest that USER_RID
isn't used for the sapphire ticket.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks - I've passed in nil
for sapphire, since it should retrieve this from the PAC.
Added in the stability/IOC notes, since diamond/sapphire do make requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything looks good to me now. I tested the module in a bunch of scenarios and all of the issues I found have been fixed. I tested the module on Server 2012, Server 2019 and Server 2022. In all cases it worked as intended. I also retested the kerberos_login module because of the new changes in the kerberos client.
I used the changes #18565 to automate testing the TGT after it was forged.
Release NotesThis updates the existing Kerberos ticket-forging module with new actions for forging tickets with fields copied from ones issued by the legitimate KDC using the Diamond and Sapphire techniques. |
This implements Diamond and Sapphire tickets in Metasploit. These are both like golden tickets, but in some circumstances may be preferable for reasons of stealth. Both require the same preconditions as Golden tickets (knowledge of krbtgt), but base the ticket off real tickets from the environment.
Diamond ticket: A TGT is requested (low-priv is fine), and the krbtgt hash is used to update it to be golden ticket-like.
Sapphire ticket: S4U2 and U2U logic is used to request a PAC for another user; we then copy those parameters across to our fake ticket. In this case, the PAC should look very realistic; although in practice, it needs to be different because of the new PAC elements added in KB5008380, which need to change.
Verification
inspect_ticket
module for this. We would expect it to have a lot of fields filled in which might otherwise be empty in a golden ticket.Bugfixes
As part of this work, this PR also fixes two bugs.
There was an intermittent issue with Golden/Silver tickets. The PAC's logon_time value needs to be identical to the ticket's auth_time value; otherwise, the ticket is rejected with KRB_AP_ERR_MODIFIED. Both
logon_time
value and theauth_time
values are set with independent calls toTime.now
. This could be several milliseconds difference. Because the KerberosTime type has a resolution of 1 second, usually both calls toTime.now
would fall within the same second; but it was possible for the two calls to fall on either side of a second boundary, thus getting them out of sync, and causing the Kerberos error. You could replicate this issue in the existing code by adding a 0.5 second sleep prior to generating the ticket. Sometimes using the ticket would succeed, and other times it would fail. By accessing the current time only once, and using that same value in all locations, the bug is fixed.There was also a bug in
Krb5UpnDnsInfo
structure. The cause, as best as I understand it, is that aKrb5PacInfoBuffer
, which is adelayed_io
object, relies on asking another object withdelayed_io
(Krb5UpnDnsInfo
) how long it is (num_bytes
). This meant that the code calculated the size ofKrb5UpnDnsInfo
without its strings. I created to unit test to show the effect of this:Prior to this change, the test case fails with junk ending up in the fields:
This bug was probably never experienced in MSF, because the structure was never written. With sapphire tickets, I needed to add them (to make the PAC as identical as we could).