diff --git a/PROJECTS/foundations/hash-identifier/hash_identifier.py b/PROJECTS/foundations/hash-identifier/hash_identifier.py index 959cbd00..e09ce3ca 100644 --- a/PROJECTS/foundations/hash-identifier/hash_identifier.py +++ b/PROJECTS/foundations/hash-identifier/hash_identifier.py @@ -177,10 +177,26 @@ PREFIX_RULES: list[tuple[str, str, str]] = [ ("{SHA}", "LDAP SHA", "LDAP SHA-1 (base64 payload)"), ("{SMD5}", "LDAP SMD5", "LDAP salted MD5 (base64 payload)"), ("{MD5}", "LDAP MD5", "LDAP MD5 (base64 payload)"), + ("{CRYPT}", "LDAP CRYPT", "LDAP wrapping a crypt(3) hash"), + + # Kerberos — Active Directory attack hashes. Both are emitted by + # Impacket's GetUserSPNs.py / GetNPUsers.py and Rubeus in + # "hashcat" format. The digit right after the second `$` is the + # Kerberos encryption type (23 = RC4, 17 = AES128, 18 = AES256) + # and decides which hashcat -m mode to crack it with, but it does + # not change WHAT the hash is, so we match on the prefix alone + ("$krb5tgs$", "Kerberos TGS-REP (Kerberoasting)", + "service ticket hash from Kerberoasting; etype digit after $krb5tgs$ " + "(23=RC4, 17/18=AES) picks the hashcat mode"), + ("$krb5asrep$", "Kerberos AS-REP (AS-REP Roasting)", + "pre-auth-disabled account hash from AS-REP roasting; etype digit " + "after $krb5asrep$ (23=RC4, 17/18=AES) picks the hashcat mode"), + ] + # ============================================================================= # Length-and-hex rules — fallback when no prefix matched # ============================================================================= diff --git a/PROJECTS/foundations/hash-identifier/test_hash_identifier.py b/PROJECTS/foundations/hash-identifier/test_hash_identifier.py index 0157471e..45020104 100644 --- a/PROJECTS/foundations/hash-identifier/test_hash_identifier.py +++ b/PROJECTS/foundations/hash-identifier/test_hash_identifier.py @@ -141,6 +141,41 @@ def test_django_pbkdf2_prefix_is_recognized() -> None: assert candidates[0].algorithm == "Django PBKDF2-SHA256" +def test_kerberos_tgs_prefix_is_recognized() -> None: + """ + Kerberoasting hashes start with `$krb5tgs$` and should be + reported as a Kerberos TGS-REP with HIGH confidence + + Sample is a real-shaped RC4 (`etype 23`) service ticket hash, + the format Impacket's GetUserSPNs.py and Rubeus both emit. + identify() never decodes the ticket payload, so a short + fake tail after the two known `$`-delimited fields is enough + """ + # $krb5tgs$$*$$*$$ + sample = "$krb5tgs$23$*user$REALM.COM$cifs/host.realm.com*$checksumhere$edata2here" + candidates = identify(sample) + assert candidates + assert candidates[0].algorithm == "Kerberos TGS-REP (Kerberoasting)" + assert candidates[0].confidence == "high" + + +def test_kerberos_asrep_prefix_is_recognized() -> None: + """ + AS-REP roasting hashes start with `$krb5asrep$` and should be + reported as a Kerberos AS-REP with HIGH confidence + + Sample mirrors what Impacket's GetNPUsers.py and Rubeus's + `asreproast` output for an account with Kerberos pre-auth + disabled. As with TGS-REP, identify() only inspects the prefix + """ + # $krb5asrep$$@:$ + sample = "$krb5asrep$23$user@REALM.COM:checksumhere$edata2here" + candidates = identify(sample) + assert candidates + assert candidates[0].algorithm == "Kerberos AS-REP (AS-REP Roasting)" + assert candidates[0].confidence == "high" + + def test_apr1_prefix_is_recognized() -> None: """ Apache `.htpasswd` MD5 hashes start with $apr1$