В статье рассматриваются различные способы получения TGT Kerberos в Python, а также их сравнение.
Подготовка
Для тестирования развернем MIT Kerberos в Docker’е (можно развернуть FreeIPA, но это будет избыточно)
Простейший 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
Перед сборкой создадим файлы из инструкций COPY
:
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
Опции dns_*
нужны для того, чтобы не тратить время на DNS при получении билета
Собираем образ:
docker build . -t my_kerberos
Запускаем в терминале:
docker run -h kdc.example.org -it --rm my_kerberos bash
После запуска контейнера обязательно добавьте в файл /etc/hosts
запись с hostname контейнера:
# Можно найти IP командами ниже, либо найти самому и добавить в файл /etc/hosts
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
Попробуем получить билет с другого терминала:
echo admin123 | KRB5_CONFIG=krb5.conf kinit admin
Проверяем, что билет получен:
klist
Практика
После настройки тестового окружения можно приступить к проверке
Способ №1: Subprocess
Наиболее простой способ, единственный плюс перед gssapi
это встроенная поддержка многопоточности (в gevent точно, в других библиотеках не знаю)
Пишем Python скрипт:
#!/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")
Результат выполнения для for _ in range(1):
и for _ in range(100):
Получаем скорость получения билета в среднем 8-9 ms (~10 ms для 1 получения, 8-9 ms для 100 получений)
Способ №2: python-gssapi
Реккомендуемый способ, использует вызов функций из языка C, что делает код получения билета thread-blocking (в gevent можно обойти используя pool.spawn()
)
Для полной имитации поведения kinit
добавим сохранение билета в файл, но этот шаг можно пропустить, поскольку билет можно использовать из переменной
Пишем Python скрипт:
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")
Результат выполнения для for _ in range(1):
и for _ in range(100):
Получаем скорость получения билета в среднем 5-7 ms (~7 ms для 1 получения, 5-6 ms для 100 получений)
Способ №3: minikerberos
Малоизвестная библиотека, написанная на чистом Python, но имеющая большой набор функций и классов для работы с Kerberos, в том числе для того, чтобы проводить пентест
Имеет встроенную возможность работать с asyncio
, но может работать и с другими, поскольку написана на чистом Python
Из минусов: очевидно более низкая скорость, а также неудобный формат kerberos_url
(например, использовать admin/admin
в качестве пользователя нельзя из-за форматирования)
Пишем Python скрипт:
#!/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")
Результат выполнения для for _ in range(1):
и for _ in range(100):
Получаем скорость получения билета в среднем 27-33 ms (~30-33 ms для 1 получения, 27-28 ms для 100 получений)
Другие библиотеки
В известной библиотеке impacket
есть функции для работы с Kerberos, но у меня не получилось заставить их работать
Для работы с Kerberos в HTTP есть библиотека requests_gssapi