Published: mar. 30 juin 2026
By Jivay
In infosec .
tags: secu
Sur un pentest on peut de temps en temps tomber sur des belles personnes qui utilisent un FreeIPA plutôt qu'un Microslop ActiveDirectory très largement exploité et pour lequel il existe déjà une chiée d'outils. Oui mais FreeIPA c'est prévu pour être un remplacement de AD avec les mêmes protocoles à savoir LDAP et surtout Kerberos, et aussi d'autres services funky comme un équivalent de ADCS. Et là quand on essaye de taper dessus comme ~~un script kiddie~~ un⋅e gros⋅se bourrin⋅ne, ça marche moyennement.
Admettons qu'on ait déjà monté un FreeIPA dans un lab avec le domaine qui va bien, qu'il répond, etc.
# nmap -sS -sV ipatoast.orbital-chainsaw.test
Starting Nmap 7.92 ( https://nmap.org ) at 2026-06-30 11:38 CEST
Stats: 0:01:37 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 87.50% done; ETC: 11:40 (0:00:13 remaining)
Nmap scan report for ipatoast.orbital-chainsaw.test (192.168.1.50)
Host is up (0.00012s latency).
Not shown: 982 filtered tcp ports (no-response), 10 filtered tcp ports (admin-prohibited)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 10.2 (protocol 2.0)
80/tcp open http Apache httpd 2.4.68 ((Fedora Linux) OpenSSL/3.5.7 mod_wsgi/5.0.2 Python/3.14 mod_auth_gssapi/1.6.5)
88/tcp open kerberos-sec MIT Kerberos (server time: 2026-06-30 09:39:10Z)
389/tcp open ldap (Anonymous bind OK)
443/tcp open ssl/http Apache httpd 2.4.68 ((Fedora Linux) OpenSSL/3.5.7 mod_wsgi/5.0.2 Python/3.14 mod_auth_gssapi/1.6.5)
464/tcp open kerberos-sec MIT Kerberos (server time: 2026-06-30 09:39:10Z)
636/tcp open ssl/ldap (Anonymous bind OK)
9090/tcp open ssl/zeus-admin?
MAC Address: 52:54:00:AB:C5:01 (QEMU virtual NIC)
Service Info: Host: ORBITAL-CHAINSAW.TEST
Jusque là rien de choquant, c'est un FreeIPA tout frais.
Et de là on a l'irrépressible envie de faire un getTGT :
$ examples/getTGT.py -dc-ip 192.168.1.50 orbital-chainsaw.test/admin:FooBarBaz
Impacket v0.13.1 - Copyright Fortra, LLC and its affiliated companies
<TagSet object, tags 0:32:16-64:32:26> not in asn1Spec: <EncASRepPart schema object, tagSet=<TagSet object, tags 0:32:16-64:32:25>, subtypeSpec=<ConstraintsIntersection object>, componentType=<NamedTypes object, types <NamedType object, type key=<EncryptionKey schema object, tagSet=<TagSet object, tags 0:32:16-128:32:0>, subtypeSpec=<ConstraintsIntersection object>, componentType=<NamedTypes object, types <NamedType object, type keytype=<Int32 schema object, tagSet <TagSet object, tags 0:0:2-128:32:0>, subtypeSpec <ConstraintsIntersection object, consts <ValueRangeConstraint object, consts -2147483648, 2147483647>>>>, <NamedType object, type keyvalue=<OctetString schema object, tagSet <TagSet object, tags 0:0:4-128:32:1>, encoding iso-8859-1>>>, sizeSpec=<ConstraintsIntersection object>>>, <NamedType object, type last-req=<LastReq schema object, tagSet=<TagSet object, tags 0:32:16-128:32:1>, subtypeSpec=<ConstraintsIntersection object>, componentType=<Sequence schema object, tagSet=<TagSet object, tags 0:32:16>, subtypeSpec=<ConstraintsIntersection object>,[...]
C'est déjà embêtant. Impacket est sympa il nous dit déjà où est le problème. C'est pas un problème de XML contrairement à ce qu'on pourrait croire en lisant rapidement (ne demandez pas pourquoi cette idée subitement), c'est bien un problème de décodage de ASN1.
On va quand même s'en sortir parce-qu'on a prévu d'installer krb5-client et qu'on peut faire ceci :
$ kinit admin@ORBITAL-CHAINSAW.TEST
Password for admin@ORBITAL-CHAINSAW.TEST:
$ klist -e
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: admin@ORBITAL-CHAINSAW.TEST
Valid starting Expires Service principal
06/30/2026 05:01:49 07/01/2026 04:56:03 krbtgt/ORBITAL-CHAINSAW.TEST@ORBITAL-CHAINSAW.TEST
Etype (skey, tkt): aes256-cts-hmac-sha1-96, camellia256-cts-cmac
Tadaaaa, et là y-a plus qu'à utiliser le token en spécifiant KRB5CCNAME , non ?
$ impacket-GetADUsers orbital-chainsaw.test/admin -no-pass -k -dc-ip 192.168.1.50 -dc-host ipatoast.orbital-chainsaw.test
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[-] 26 is not a valid EncryptionTypes
Bien sûr que non. D'ailleurs ça vient bien de Impacket.:
$ impacket-describeTicket /tmp/krb5cc_1000
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Number of credentials in cache: 1
[*] Parsing credential[0]:
[-] 26 is not a valid EncryptionTypes
Pour info la liste des EncryptionTypes est dans krb5/constants.py :
class EncryptionTypes(Enum):
des_cbc_crc = 1
des_cbc_md4 = 2
des_cbc_md5 = 3
_reserved_4 = 4
des3_cbc_md5 = 5
_reserved_6 = 6
des3_cbc_sha1 = 7
dsaWithSHA1_CmsOID = 9
md5WithRSAEncryption_CmsOID = 10
sha1WithRSAEncryption_CmsOID = 11
rc2CBC_EnvOID = 12
rsaEncryption_EnvOID = 13
rsaES_OAEP_ENV_OID = 14
des_ede3_cbc_Env_OID = 15
des3_cbc_sha1_kd = 16
aes128_cts_hmac_sha1_96 = 17
aes256_cts_hmac_sha1_96 = 18
rc4_hmac = 23
rc4_hmac_exp = 24
subkey_keymaterial = 65
rc4_hmac_old_exp = -135
Il nous manque ce 26 , qui d'après krb5.h correspond à KRB5_ENCTYPE_CAMELLIA256_CTS_CMAC . Ça peut s'expliquer assez facilement par le fait qu'encore en 2026 et malgré l'existence de RFC 6803 , AD DS ne supporte pas Camellia. La bonne nouvelle c'est que ça correspond bien à ce qui est présenté dans klist. Comme on a pas assez réfléchi la question, on va tenter de rajouter le support pour CAMELLIA256_CTS_CMAC dans Impacket, on sait jamais ça pourrait resservir. Au pire il restera toujours kinit , non ? (non).
Est-ce qu'on va se contenter de scripts un peu léchés pour énumérer tout l'annuaire ? Pour l'instant oui, ça va être important pour la suite. Pour ça on pourra aller faire un tour sur Hacktricks qui nous donne quelques idées de ce qu'on peut faire avec ldapsearch et PKINIT pour s'authentifier avec une clé privée.
On pourrait aussi modifier permitted_enctypes dans /etc/krb5.conf pour forcer l'utilisation de AES avec kinit, mais si ça ne marche toujours pas et que Camellia rescend quoi qu'il arrive, on continue.
En suivant le fil depuis describeTicket.py jusqu'à crypto.py on arrive aux méthodes string_to_key qui appartiennent aux classes attribuées en fonction de l'identifiant de l'algorithme utilisé. Jusque là y-a plus qu'à intégrer les primitives Camellia depuis la lib utilisée par Impacket qui n'est autre que PyCryptodome et-
Ah ben y-a pas. Et on remercie pas au passage le branleur qui a généré une page avec un LLM disant comment faire quand même et qui donne du code pour AES. On va pas non plus faire une implem de Camellia, pas maintenant, pas aujourd'hui. On va juste rajouter le package python-camelia et ça sera très bien pour la journée.
$ python -m pip install python-camellia
Collecting python-camellia
Using cached python-camellia-1.0.tar.gz (27 kB)
Installing build dependencies ... done
Getting requirements to build wheel ... error
error: subprocess-exited-with-error
× Getting requirements to build wheel did not run successfully.
│ exit code: 1
╰─> [27 lines of output]
[...]
File "/tmp/pip-install-ycu90j5e/python-camellia_22781b53b84542378385055701139581/src/camellia_build/_build_utils.py", line 8, in <module>
from cffi import FFI
ModuleNotFoundError: No module named 'cffi'
[end of output]
note: This error originates from a subprocess, and is likely not a problem with pip.
Ah ben merci pip de te décharger sur les autres mais ça ne résout ni tes problèmes ni les miens.
On va passer outre Camellia pour le moment, en espérant pas passer à côté de nouveautés de RFC6803.
On revient sur le décodage du AS_REP
pyasn1.error.PyAsn1Error: <TagSet object, tags 0:32:16-64:32:26> not in asn1Spec: <EncASRepPart schema object
Cette fonction est appelée dans kerberosv5.py
encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0]
Si on regarde le tag correspondant à EncASRepPart on a ceci :
class ApplicationTagNumbers(Enum):
Ticket = 1
Authenticator = 2
EncTicketPart = 3
AS_REQ = 10
AS_REP = 11
TGS_REQ = 12
TGS_REP = 13
AP_REQ = 14
AP_REP = 15
RESERVED16 = 16
RESERVED17 = 17
KRB_SAFE = 20
KRB_PRIV = 21
KRB_CRED = 22
EncASRepPart = 25
EncTGSRepPart = 26
Et si on intervertit avec EncTGSRepPart on avance un peu, mais guère plus loin. Est-ce qu'on aurait directement un TGS et il faudrait vérifier si on est dans les clous niveau protocole ? On va décortiquer getKerberosTGT dans kerberosv5.py et comparer avec ce qu'on peut lire chez Hackndo , voire comparer le trafic d'un kinit et d'un getTGT dans Wireshark tutututulu.
Oui j'ai pris le screen à la fin de tout ça, jugez pas.
Si on met côte à côte les réponses AS_REP , à part l'utilisation de UDP par kinit et de TCP par Impacket, rien n'a l'air de sauter aux yeux. Evidemment il faut se pencher sur enc_part . En rajoutant une ligne de debug dans kerberosv5.py on peut obtenir un plaintext plus simple à interpréter:
7a81f03081eda02b3029a003020112a1220420831647e1cb7280bbaebbf2f5b8e5a23c4a84ccf56ebd18b35a8988bc0c9dada4a11c301a3018a003020100a111180f31393730303130313030303030305aa20602045cac96f6a311180f32303236303932383038343730335aa40703050050e10000a511180f32303236303730323039313934365aa711180f32303236303730333038353434345aa811180f32303236303730333039313934365aa9171b154f52424954414c2d434841494e5341572e54455354aa2a3028a003020101a121301f1b066b72627467741b154f52424954414c2d434841494e5341572e54455354
Application 26 (1 elem)
SEQUENCE (10 elem)
[0] (1 elem)
SEQUENCE (2 elem)
Offset: 8
Length: 2+41
(constructed)
Value:
(2 elem)
[0] (1 elem)
INTEGER 18
[1] (1 elem)
https://pkitools.net/pages/ca/asn1.html#eoHwMIHtoCswKaADAgESoSIEIIMWR-HLcoC7rrvy9bjlojxKhMz1br0Ys1qJiLwMna2koRwwGjAYoAMCAQChERgPMTk3MDAxMDEwMDAwMDBaogYCBFyslvajERgPMjAyNjA5MjgwODQ3MDNapAcDBQBQ4QAApREYDzIwMjYwNzAyMDkxOTQ2WqcRGA8yMDI2MDcwMzA4NTQ0NFqoERgPMjAyNjA3MDMwOTE5NDZaqRcbFU9SQklUQUwtQ0hBSU5TQVcuVEVTVKoqMCigAwIBAaEhMB8bBmtyYnRndBsVT1JCSVRBTC1DSEFJTlNBVy5URVNU
On retrouve bien le Tag 26, mais est-ce qu'on le retrouve pour autant dans la réponse donnée à kinit?
En suivant une présentation de Pass the SALT 2023 on se rappelle que Wireshark sait déchiffrer les échanges Kerberos pour peu qu'on lui donne une clé dans un fichier keytab.txt . Cette clé est dérivée du mot de passe et du sel. L'un est connu dans notre cas, l'autre se trouve dans le AS_REP .
>>> from binascii import hexlify
>>> from impacket.krb5.crypto import generate_kerberos_keys
>>> hexlify(generate_kerberos_keys(password='FooBarBaz', salt='^@w30dw=P/ew?e$,')[18].contents)
b'9bd196d2bdc526ea6e891899d4e417fe98c8cf71c0fa5813e358a1980d34975d'
On rentre ça dans un keytab, on le donne à Wireshark, et on peut lire les trames générées avec Impacket, mais toujours pas celles de kinit. C'est plutôt une bonne nouvelle, la clé générée par Impacket a l'air de faire le boulot.
On tente quand même de déchiffrer directement avec la lib impacket
>>> from binascii import hexlify, unhexlify
>>> from binascii import dehexlify
>>> from impacket.krb5.crypto import generate_kerberos_keys
>>> key = generate_kerberos_keys(password='FooBarBaz', salt='^@w30dw=P/ew?e$,')[18]
>>> enc_part = unhexlify('c2942c00c7b027bb41844394a1f2bddb168f039c15285740b7bb369a91d42c0fc340eb5f09288af9945c7d4baa8d5195264a35e98f1a28f7989982dd480e2e9574540af72e419f3c4eee4449ac6f8a0d8974e832e87877284581b3870636773925b5013ce4edbc79e6f470fecef0e924470dbac4a98db70dff89072c2584aa2b555d3b964a251f5a6eb1c8417435aa9e0ce85d2c1d733f8e6eef864b5ec5dd298b372d85bb0ae089462b2ad182991e158020077cc4055d1d3c9758f34fc5e7396e0408ddf02c2d23f8d1a33416dc3623f5d34eb27c894385f56fb1832cbfef16a8f4cc08f7aa76a8ad2518d580e2224405bc1e24fda5a9c2314b75a1507f0e5acdb1e03d3a2d10ad103577beaa2d09543097c22a899471e7defcf75cfad5088fe89dce3fd5e99e435546e8316b984427c48d2f3aa4fdcdbe14b978029f608cb9d1673287')
>>> cipher = _enctype_table[18]
>>> cipher.decrypt(key, 3, enc_part)
Traceback (most recent call last):
File "<python-input-25>", line 1, in <module>
cipher.decrypt(key, 3, enc_part)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
File "/home/jivay/Documents/boulot/code/impacket/impacket/krb5/crypto.py", line 240, in decrypt
raise InvalidChecksum('ciphertext integrity failure')
Et évidemment la même chose avec le ciphertext reçu par Impacket et lu depuis Wireshark ça fonctionne. Il nous manque encore quelques infos.
Et si on arrêtait deux secondes d'essayer de déchiffrer ça et qu'on disait juste à Impacket d'accepter le tag 26 au-lieu de 25 ? Et moyennant aussi quelques modifications complémentaires pour que tout soit lu comme du 26
$ PYTHONPATH=~/impacket examples/getTGT.py orbital-chainsaw.test/admin:FooBarBaz -dc-ip 192.168.1.50 -debug
Impacket v0.14.0.dev0+20260619.174856.9a5621d4 - Copyright Fortra, LLC and its affiliated companies
[+] Impacket Library Installation Path: /home/jivay/impacket/impacket
[+] Trying to connect to KDC at 192.168.1.50:88
[+] Trying to connect to KDC at 192.168.1.50:88
[*] Saving ticket in admin.ccache
$ klist -e -c admin.ccache
Ticket cache: FILE:admin.ccache
Default principal: admin@ORBITAL-CHAINSAW.TEST
Valid starting Expires Service principal
On a créé un fichier, mais concrètement il ne servira à rien. Et en fait une fois qu'on a regardé les exemples de Impacket, la plupart tournent autour de SMB et de composants MS plus qu'autour de LDAP.
Ca aura permis de se faire une meilleure idée de comment fonctionne Kerberos.