Definition
This article discusses the different ways to obtain a Kerberos TGT in Python and compares them.
Preparation
For testing, we will deploy MIT Kerberos in Docker (you can deploy FreeIPA, but it will be redundant)
The simplest Dockerfile:
FROM debian
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y krb5-kdc krb5-admin-server
RUN kdb5_util create -s -P admin123 -r EXAMPLE.ORG
COPY krb5.conf /etc/krb5.conf
COPY kadm5.acl /etc/krb5kdc/kadm5.acl
RUN service krb5-kdc start || true
RUN service krb5-admin-server start || true
RUN kadmin.local -q "add_principal -pw admin123 admin/admin@EXAMPLE.ORG"
RUN kadmin.local -q "add_principal -pw admin123 admin@EXAMPLE.ORG"
EXPOSE 88
ENTRYPOINT service krb5-kdc start && service krb5-admin-server start && /bin/bash
Before building, let’s create files from COPY
instructions:
kadm5.acl
:
# This file Is the access control list for krb5 administration.
# When this file is edited run service krb5-admin-server restart to activate
# One common way to set up Kerberos administration is to allow any principal
# ending in /admin is given full administrative rights.
# To enable this, uncomment the following line:
*/admin *
krb5.conf
[libdefaults]
default_realm = EXAMPLE.ORG
dns_lookup_realm = false
dns_lookup_kdc = false
dns_canonicalize_hostname = false
[realms]
EXAMPLE.ORG = {
kdc = kdc.example.org
admin_server = kdc.example.org
}
[domain_realm]
.example.org = EXAMPLE.ORG
example.org = EXAMPLE.ORG
The dns_*
options are needed to avoid wasting time on DNS when getting a ticket
Building the image:
docker build . -t my_kerberos
Run in terminal:
docker run -h kdc.example.org -it --rm my_kerberos bash
After starting the container, be sure to add an entry to the /etc/hosts
file with the container hostname:
# You can find the IP using the commands below, or find it yourself and add it to the /etc/hosts file
id=$(docker container ls --all --filter=ancestor="my_kerberos" --format "{{.ID}}")
ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $id)
echo "$ip kdc.example.org" >> /etc/hosts
Let’s try to get a ticket from another terminal:
echo admin123 | KRB5_CONFIG=krb5.conf kinit admin
We check that the ticket has been received:
klist
Practice
After setting up the test environment, you can start checking
Method #1: Subprocess
The simplest method, the only advantage over gssapi
is the built-in support for multithreading (in gevent for sure, I don’t know about other libraries)
Let’s write a Python script:
#!/usr/bin/python3
import time
import subprocess
import os
os.environ["KRB5_CONFIG"] = "./krb5.conf"
start = time.perf_counter()
for _ in range(1):
subprocess.run(['kinit', "admin"], input=b'admin123', stdout=subprocess.DEVNULL)
end = (time.perf_counter() - start) * 1000
print("The time of execution of above program is :", end, "ms")
Result of execution for for _ in range(1):
and for _ in range(100):
We get the speed of receiving a ticket on average 8-9 ms (~10 ms for 1 receipt, 8-9 ms for 100 receipts)
Способ №2: python-gssapi
The recommended method uses a call to functions from the C language, which makes the code for obtaining a ticket thread-blocking (in gevent you can bypass it using pool.spawn()
)
To fully imitate the behavior of kinit
, we will add saving the ticket to a file, but this step can be skipped, since the ticket can be used from a variable
Writing a Python script:
import time
import gssapi
import os
os.environ["KRB5_CONFIG"] = "./krb5.conf"
start = time.perf_counter()
name = gssapi.Name("admin@EXAMPLE.ORG", gssapi.NameType.kerberos_principal)
for _ in range(1):
creds = gssapi.raw.acquire_cred_with_password(name, b"admin123")
res = gssapi.Credentials(creds.creds)
res.store(store={"ccache": "FILE:/tmp/test_krb5cc"}, overwrite=True)
end = (time.perf_counter() - start) * 1000
print("The time of execution of above program is :", end, "ms")
Result of execution for for _ in range(1):
and for _ in range(100):
We get the speed of receiving a ticket on average 5-7 ms (~7 ms for 1 receipt, 5-6 ms for 100 receipts)
Method #3: minikerberos
A little-known library written in pure Python, but has a large set of functions and classes for working with Kerberos, including for pentesting
Has a built-in ability to work with asyncio
, but can work with others, since it is written in pure Python
Cons: obviously lower speed, as well as inconvenient kerberos_url
format (for example, you can’t use admin/admin
as a user because of formatting)
Writing a Python script:
#!/usr/bin/python3
import os
import time
from minikerberos.common.factory import KerberosClientFactory
os.environ["KRB5_CONFIG"] = "./krb5.conf"
start = time.perf_counter()
kerberos_url = "kerberos+password://EXAMPLE.ORG\\admin:admin123@172.20.0.2"
for _ in range(1):
cu = KerberosClientFactory.from_url(kerberos_url)
client = cu.get_client_blocking()
client.get_TGT(with_pac=None)
client.ccache.to_file('/tmp/test_krb5cc')
end = (time.perf_counter() - start) * 1000
print("The time of execution of above program is :", end, "ms")
Result of execution for for _ in range(1):
and for _ in range(100):
We get the speed of receiving a ticket on average 27-33 ms (~30-33 ms for 1 receipt, 27-28 ms for 100 receipts)
Other libraries
The well-known impacket
library has functions for working with Kerberos, but I couldn’t get them to work
For working with Kerberos in HTTP, there is a requests_gssapi
library