В статье рассматриваются различные способы получения 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): Python Subporcess TGT

Получаем скорость получения билета в среднем 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): Python gssapi TGT

Получаем скорость получения билета в среднем 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): Python minikerberos TGT

Получаем скорость получения билета в среднем 27-33 ms (~30-33 ms для 1 получения, 27-28 ms для 100 получений)

Другие библиотеки

В известной библиотеке impacket есть функции для работы с Kerberos, но у меня не получилось заставить их работать

Для работы с Kerberos в HTTP есть библиотека requests_gssapi