· Research How-To

Kerberos Errata Submission - Research Process

Quick note here, this blog does not dive into the details of how kerberos operates. If that’s what you’re looking for, see this excellent post.

Background

When I was studying for the CISSP, I was chatting away in the Discord server recommended here, when I saw the gem “Kerberos encrypts the username to authenticate” fly past. Having played with Kerberos a little myself, I knew this was incorrect, and lively debate followed. The original messager pointed to the Sybex CISSP Official Study Guide as their source. Intruiged, I cracked open my own copy, and saw the Kerberos steps listed below.

(as an aside, I withdrew from the debate at this time. While I know technically how Kerberos works, if that’s how ISC2 reports Kerberos works, that’s probably how they test it too. I’d hate for someone to fail an exam because they learnt how the real world works instead of what ISC2 want you to know).

For reference, below are the steps listed in the study guide as how Kerberos performs authentication:

1. The user types a username and password into the client.
2. The client encrypts the username with AES for transmission to the KDC.
3. The KDC verifies the username against a database of known credentials.
4. The KDC generates a symmetric key that will be used by the client and the Kerberos server. It encrypts this with a hash of the user’s password. The KDC also generates an encrypted time-stamped TGT.
5. The KDC then transmits the encrypted symmetric key and the encrypted time-stamped TGT to the client.
6. The client installs the TGT for use until it expires.

Logically, steps two and three can’t co-exist. If the username is encrypted, either it needs to be encrypted with a Pre-shared key (PSK) that is NOT the users password, or a PSK that IS a hash of the users password.

If the username is encrypted with a hash of the users password, then when it gets to the KDC, the KDC needs to bruteforce this auth attempt with the password for EVERY user in it’s database, unless another piece of identifying information is passed with the encrypted username.

Alternatively, the PSK is NOT based on the users password, but in this case, the AES password needs to be sent to the requesting system across the network in either cleartext, or using something like Diffie Hellman Key Exchange. I’ve never read this being the case, nor observed it. Nontheless, we allow for this in our testing environment.

Process

On to the research! First, I like to start with a problem statement, which is effectively “What am I trying to prove / disprove”. In this example, my problem statement read:

The listed Kerberos logon process, step 2: 2. The client encrypts the username with AES for transmission to the KDC (as taken from CISSP Official study guide, Sybex, 8e, p. 604; Chapple, M. Stewart, JM. & Gibson, D.) is incorrect.

Now I’m generally looking for three investigative avenues I try and persue independently of each other, whenever possible:

  1. Does the assertion pass the sniff test?
  2. How does the documentation say it should work?
  3. How does it actually work?

We’ve already documented one above. It doesn’t pass the sniff test (ie, it can’t work that way) because of the intense burden it would place on kerberos servers with many users.

For two, we dive into the RFC, or request for comments. This is a document that is effectively where a bunch of very smart people get together and agree on how a given system / protocol / other should work by design. In this particular case, RFC4120 states:

In the request, the client sends (in cleartext) its own identity and the identity of the server for which it is requesting credentials, other information about the credentials it is requesting, and a randomly generated nonce, which can be used to detect replays and to associate replies with the matching requests.

Happy days! This reads to me that, at least by design, the username should not be encrypted when it’s shipped to the server. Better, this isn’t because there’s an omission of specification, it explicitly calls out “in cleartext”. No ambiguity here! Great, this aligns with our sniff test. There’s no guarantee that this is how it’s been implemented though, so let’s move on to point three.

Sounds like labbing time! When I’m doing this research, I’ll record and report how the lab is built and justification, so that people reviewing my work in the future can replicate it should they so desire.

Lab Setup

The Network

Here’s a high level view of the network I set up in two seperate testing instances (which is why the IP addresses overlap)

Kerberos lab network overview

Router

This machine was set up to emulate a stupid network tap. It hasn’t been added to any domain, and has no contextual awareness of the hosts on either side. IF this machine can see credentials passing over the network in plaintext, then we know that PSK passing for AES can’t be in place, because if it was, this machine wouldn’t know what it was, OR tcpdump wouldn’t apply it automatically.

Router was set up following this process.

iptables was configured with the below settings:

#!/bin/bash

# /etc/rc.local

# Default policy to drop all incoming packets.
iptables -P INPUT DROP
iptables -P FORWARD DROP

# Accept incoming packets from localhost and the LAN interface.
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -i ens34 -j ACCEPT

# Accept incoming packets from the WAN if the router initiated the connection.
iptables -A INPUT -i ens35 -m conntrack \
--ctstate ESTABLISHED,RELATED -j ACCEPT

# Forward LAN packets to the WAN.
iptables -A FORWARD -i ens34 -o ens35 -j ACCEPT

# Forward WAN packets to the LAN if the LAN initiated the connection.
iptables -A FORWARD -i ens35 -o ens34 -m conntrack \
--ctstate ESTABLISHED,RELATED -j ACCEPT

# NAT traffic going out the WAN interface.
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# rc.local needs to exit with 0
exit 0

Here’s a complete view of the host’s network interfaces post configuration:

deloril@ubuntu:~$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:28:f5:01 brd ff:ff:ff:ff:ff:ff
    inet 172.16.68.129/24 brd 172.16.68.255 scope global dynamic noprefixroute ens33
       valid_lft 1732sec preferred_lft 1732sec
    inet6 fe80::f34e:6be1:573c:7e38/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: ens34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:30:10:02 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.9/24 brd 10.0.0.255 scope global noprefixroute ens34
       valid_lft forever preferred_lft forever
    inet6 fe80::20e1:c419:da20:3547/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
4: ens35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:27:db:03 brd ff:ff:ff:ff:ff:ff
    inet 10.1.1.4/24 brd 10.1.1.255 scope global noprefixroute ens35
       valid_lft forever preferred_lft forever
    inet6 fe80::f57b:5f31:8c5e:87e3/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

Finally, for each test, tcpdump was executed with this command: tcpdump -i ens35 -w ~/<relevant pcap name>

Windows

Server Setup

Use Server Manager to add the Role “Active Directory Domain Services” and dependencies only. Promote server to domain controller, creating a new forest called mydomain.hidden Accept forest and domain functional lecels of Windows Server 2016 Password: Accept netbios name of mydomain

Once installed and rebooted, create a user called plaintextusername. Add this user to domain administrators, because we’re lazy and don’t want hiccups.

Client Setup

Join to the domain, prior to logging packets. This is because when I didn’t, NTLM authentication was used, and I hadn’t the time to work out how to force kerberos.

Restart machine.

Windows Test

On the client, attempt to authenticate as mydomain\plaintextusername and mydomain\unknownuser.

For the known user test, we’re attempting to authenticate with a valid credential. If you want to follow along, the pcap is here. To generate this traffic, we had the client booted up, and attempted to sign in to the machine with the correct credentials for mydomain\plaintextusername. As shown below the CNameString plaintextusername and the realm mydomain are being passed across the network in clear text in packet 4.

Packet 4 of windows-knownuser.pcap

For the unknown user test, we’re attempting to authenticate with an invalid username (this user doesn’t exist in the domain). If you want to follow along, the pcap is here. To generate this traffic, we had the client booted up, and attempted to sign in to the machine with the invalid username mydomain\unknownuser. As shown below the CNameString unknownuser and the realm mydomain are being passed across the network in clear text in packet 4.

Packet 4 of windows-knownuser.pcap

This looks pretty great, right? Our sniff test is appears correct, and it looks like the windows configuration I’ve tested is RFC compliant! But what about Linux?! I hate being adamantly wrong, so best to cover our bases.

Linux

Server Setup

I followed this guide, running the below commands (relevant output is recorded too, because I’m fancy like that)

sudo apt install krb5-kdc krb5-admin-server
Realm: myrealm
Kerberos Server: 127.0.0.1
AS: 127.0.0.1

sudo krb5_newrealm
Password: <redacted>

sudo dpkg-reconfigure krb5-kdc

sudo kadmin.local
kadmin.local: addprinc plaintextusername
Password for user: <redacted>
quit

sudo vim /etc/krb5kdc/kadm5.acl
add line 
    plaintextusername@myrealm        *
:wq

sudo systemctl restart krb5-admin-server.service

kinit plaintextusername

deloril@ubuntu:~$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: plaintextusername@myrealm

Valid starting       Expires              Service principal
03/13/2020 15:39:57  03/14/2020 01:39:57  krbtgt/myrealm@myrealm
	renew until 03/14/2020 15:39:55
Client Setup

For the client, I followed this guide, running the below commands:

sudo apt install krb5-user libpam-krb5
Realm: myrealm
Server: 10.1.1.5
AS: 10.1.1.5
Linux Test

On the client, attempt to authenticate as mydomain\plaintextusername and mydomain\unknownuser.

For the known user test, we’re attempting to authenticate with a valid credential. If you want to follow along, the pcap is here. To generate this traffic, we ran the below commands:

kinit plaintextusername@myrealm
password: <redacted>

deloril@ubuntu:~$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: plaintextusername@myrealm

Valid starting       Expires              Service principal
03/13/2020 16:00:04  03/14/2020 02:00:04  krbtgt/myrealm@myrealm
	renew until 03/14/2020 16:00:01

As shown below the CNameString plaintextusername and the realm mydomain are being passed across the network in clear text in packet 4.

Packet 4 of linux-knownuser.pcap

For the known user test, we’re attempting to authenticate with a valid credential. If you want to follow along, the pcap is here. To generate this traffic, we ran the below commands:

kinit plaintextusername@myrealm
kinit: Client 'unknownuser@myrealm' not found in Kerberos database while getting initial credentials

As shown below the CNameString unknownuser and the realm mydomain are being passed across the network in clear text in packet 4.

Packet 4 of linux-unknownuser.pcap

Interestingly, ubuntu doesn’t ask for a password when you run kinit until AFTER it receives the response from the server indicating the username was valid or not. This makes sense as far as our research goes, but was interesting to me, and it could be used to enumerate valid usernames.

Conclusion

This doesn’t need to be fluffy, it should be concise and report your findings and conclusion. In this case “As the RFC quoted and testing undertaken have shown, neither in theory nor practice is the Authors assertion that at step two of kerberos authentication “The client encrypts the username with AES for transmission to the KDC” acccurate.” is sufficient.

Conclusive Conclusion

Different publishers have different formats for their errata, so format it how they want it. Unfortunately, in this case, I submitted and had my submission acknowledged, but the publisher and authors never provided closure on why the errata wasn’t implemented. So it goes, I guess.

I did provide my findings to the server for the benefit of people who wanted to understand how the real world works, however.