Remove keytab because KVNO may not match

This commit is contained in:
Erik Geiser
2025-05-05 15:57:51 +02:00
parent eb7dfe0b78
commit df6496cfa4
4 changed files with 47 additions and 260 deletions
-26
View File
@@ -5,10 +5,8 @@ package compat
import (
"github.com/jcmturner/gokrb5/v8/config"
"github.com/jcmturner/gokrb5/v8/credentials"
"github.com/jcmturner/gokrb5/v8/keytab"
gokrb5ForkConfig "github.com/oiweiwei/gokrb5.fork/v9/config"
gokrb5ForkCredentials "github.com/oiweiwei/gokrb5.fork/v9/credentials"
gokrb5ForkKeytab "github.com/oiweiwei/gokrb5.fork/v9/keytab"
gokrb5ForkTypes "github.com/oiweiwei/gokrb5.fork/v9/types"
"github.com/jcmturner/gokrb5/v8/types"
@@ -75,27 +73,3 @@ func Gokrb5ForkV9Principal(realm string, principalName types.PrincipalName) gokr
PrincipalName: gokrb5ForkTypes.PrincipalName(principalName),
}
}
func Gokrb5ForkV9Keytab(keytab *keytab.Keytab) *gokrb5ForkKeytab.Keytab {
entries := make([]gokrb5ForkKeytab.Entry, 0, len(keytab.Entries))
for _, entry := range keytab.Entries {
entries = append(entries, gokrb5ForkKeytab.Entry{
Principal: gokrb5ForkKeytab.Principal{
NumComponents: entry.Principal.NumComponents,
Realm: entry.Principal.Realm,
Components: entry.Principal.Components,
NameType: entry.Principal.NameType,
},
Timestamp: entry.Timestamp,
KVNO8: entry.KVNO8,
Key: gokrb5ForkTypes.EncryptionKey(entry.Key),
KVNO: entry.KVNO,
})
}
return &gokrb5ForkKeytab.Keytab{
Version: 2,
Entries: entries,
}
}
-104
View File
@@ -4,7 +4,6 @@ import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"fmt"
"net"
"os"
@@ -13,9 +12,7 @@ import (
"github.com/RedTeamPentesting/adauth/othername"
"github.com/jcmturner/gokrb5/v8/config"
"github.com/jcmturner/gokrb5/v8/credentials"
"github.com/jcmturner/gokrb5/v8/iana/etypeID"
"github.com/jcmturner/gokrb5/v8/keytab"
"software.sslmate.com/src/go-pkcs12"
)
@@ -156,30 +153,6 @@ func (c *Credential) mustUseKerberos() bool {
return c.Password == "" && c.NTHash == "" && (c.CCache != "" || c.AESKey != "")
}
// Keytab returns the Kerberos keytab containing the AES key and/or NT hash if
// they were supplied. If a password is supplied, the keys/hashes are not
// derived and the keytab will be empty. For compatibility with other Kerberos
// libraries, see the `compat` package.
func (c *Credential) Keytab() (*keytab.Keytab, error) {
kt := newKeytab()
if c.AESKey != "" {
err := addKeyToKeytab(kt, c.Username, c.Domain, c.AESKey, true, 1)
if err != nil {
return nil, fmt.Errorf("add AES key: %w", err)
}
}
if c.NTHash != "" {
err := addKeyToKeytab(kt, c.Username, c.Domain, c.NTHash, false, 1)
if err != nil {
return nil, fmt.Errorf("add RC4 key: %w", err)
}
}
return kt, nil
}
// KerberosConfig returns the Kerberos configuration for the credential's
// domain. For compatibility with other Kerberos libraries, see the `compat`
// package.
@@ -272,80 +245,3 @@ func splitUserIntoDomainAndUsername(user string) (domain string, username string
return "", user
}
}
func newKeytab() *keytab.Keytab {
kt := &keytab.Keytab{}
err := kt.Unmarshal([]byte{
// header.
0x05, // first-byte
0x02, // version
0x00, 0x00, 0x00, 0x00, // entry-length
})
if err != nil {
panic(err.Error())
}
return kt
}
func addKeyToKeytab(kt *keytab.Keytab, username string, domain string, key string, aes bool, kvno uint32) error {
keyBytes, err := hex.DecodeString(key)
if err != nil {
return fmt.Errorf("decode AES key: %w", err)
}
var keyType int32
switch len(keyBytes) {
case 32:
keyType = etypeID.AES256_CTS_HMAC_SHA1_96
case 16:
if aes {
keyType = etypeID.AES128_CTS_HMAC_SHA1_96
} else {
keyType = etypeID.RC4_HMAC
}
default:
return fmt.Errorf("invalid AES128/AES256 key")
}
tmp := &keytab.Keytab{}
err = tmp.Unmarshal([]byte{
// header
0x05, // first-byte
0x02, // version
0x00, 0x00, 0x00, 0x11, // entry-length
// principal
0x00, 0x00, // num components
0x00, 0x00, // realm length
0x00, 0x00, 0x00, 0x00, // name type
// key
0x00, 0x00, 0x00, 0x00, // timestamp
0x00, // kvno8
0x00, 0x00, // key type
0x00, 0x00, // key length
})
if err != nil {
return fmt.Errorf("invalid dummy data: %w", err)
}
e := tmp.Entries[0]
krbCreds := credentials.New(username, domain)
e.Principal.NumComponents = int16(len(krbCreds.CName().NameString))
e.Principal.Components = krbCreds.CName().NameString
e.Principal.Realm = strings.ToUpper(krbCreds.Realm())
e.Principal.NameType = krbCreds.CName().NameType
e.Timestamp = time.Now()
e.KVNO8 = 0
e.Key.KeyType = keyType
e.Key.KeyValue = keyBytes
e.KVNO = kvno
kt.Entries = append(kt.Entries, e)
return nil
}
-124
View File
@@ -2,136 +2,12 @@ package adauth_test
import (
"context"
"encoding/hex"
"net"
"strconv"
"strings"
"testing"
"github.com/RedTeamPentesting/adauth"
"github.com/jcmturner/gokrb5/v8/iana/etypeID"
"github.com/jcmturner/gokrb5/v8/iana/nametype"
"github.com/jcmturner/gokrb5/v8/types"
)
func TestKeytab(t *testing.T) {
expectedNTHash := hex.EncodeToString(make([]byte, 16))
expectedAES256Key := hex.EncodeToString(make([]byte, 32))
expectedAES128Key := hex.EncodeToString(make([]byte, 16))
principal := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, testUser)
testCases := []struct {
Cred adauth.Credential
ShouldHaveRC4Key bool
ShouldHaveAES128Key bool
ShouldHaveAES256Key bool
}{
{
Cred: adauth.Credential{},
ShouldHaveRC4Key: false,
ShouldHaveAES128Key: false,
ShouldHaveAES256Key: false,
},
{
Cred: adauth.Credential{
NTHash: expectedNTHash,
},
ShouldHaveRC4Key: true,
ShouldHaveAES128Key: false,
ShouldHaveAES256Key: false,
},
{
Cred: adauth.Credential{
NTHash: expectedNTHash,
AESKey: expectedAES128Key,
},
ShouldHaveRC4Key: true,
ShouldHaveAES128Key: true,
ShouldHaveAES256Key: false,
},
{
Cred: adauth.Credential{
NTHash: expectedNTHash,
AESKey: expectedAES256Key,
},
ShouldHaveRC4Key: true,
ShouldHaveAES128Key: false,
ShouldHaveAES256Key: true,
},
{
Cred: adauth.Credential{
AESKey: expectedAES128Key,
},
ShouldHaveRC4Key: false,
ShouldHaveAES128Key: true,
ShouldHaveAES256Key: false,
},
{
Cred: adauth.Credential{
AESKey: expectedAES256Key,
},
ShouldHaveRC4Key: false,
ShouldHaveAES128Key: false,
ShouldHaveAES256Key: true,
},
}
for i, testCase := range testCases {
testCase := testCase
t.Run(strconv.Itoa(i), func(t *testing.T) {
testCase.Cred.Username = testUser
testCase.Cred.Domain = testDomain
keyTab, err := testCase.Cred.Keytab()
if err != nil {
t.Fatalf("create keytab: %v", err)
}
rc4Key, _, rc4Err := keyTab.GetEncryptionKey(principal, strings.ToUpper(testDomain), 0, etypeID.RC4_HMAC)
switch {
case testCase.ShouldHaveRC4Key && rc4Err != nil:
t.Errorf("expected RC4 key but got error: %v", rc4Err)
case testCase.ShouldHaveRC4Key && len(rc4Key.KeyValue) != 16:
t.Errorf("RC4 key has %d bytes instead of %d", len(rc4Key.KeyValue), 16)
case testCase.ShouldHaveRC4Key && rc4Key.KeyType != etypeID.RC4_HMAC:
t.Errorf("RC4 key type is %d instead of %d", rc4Key.KeyType, etypeID.RC4_HMAC)
case !testCase.ShouldHaveRC4Key && (rc4Err == nil || len(rc4Key.KeyValue) > 0):
t.Errorf("RC4 key should not exist")
}
aes128Key, _, aes128Err := keyTab.GetEncryptionKey(
principal, strings.ToUpper(testDomain), 0, etypeID.AES128_CTS_HMAC_SHA1_96)
switch {
case testCase.ShouldHaveAES128Key && aes128Err != nil:
t.Errorf("expected AES128 key but got error: %v:\n%#v\n", aes128Err, keyTab.Entries)
case testCase.ShouldHaveAES128Key && len(aes128Key.KeyValue) != 16:
t.Errorf("AES128 key has %d bytes instead of %d", len(aes128Key.KeyValue), 16)
case testCase.ShouldHaveAES128Key && aes128Key.KeyType != etypeID.AES128_CTS_HMAC_SHA1_96:
t.Errorf("AES128 key type is %d instead of %d", aes128Key.KeyType, etypeID.AES128_CTS_HMAC_SHA1_96)
case !testCase.ShouldHaveAES128Key && (aes128Err == nil || len(aes128Key.KeyValue) > 0):
t.Errorf("AES128 key should not exist")
}
aes256Key, _, aes256Err := keyTab.GetEncryptionKey(
principal, strings.ToUpper(testDomain), 0, etypeID.AES256_CTS_HMAC_SHA1_96)
switch {
case testCase.ShouldHaveAES256Key && aes256Err != nil:
t.Errorf("expected AES256 key but got error: %v", aes256Err)
case testCase.ShouldHaveAES256Key && len(aes256Key.KeyValue) != 32:
t.Errorf("AES256 key has %d bytes instead of %d", len(aes256Key.KeyValue), 32)
case testCase.ShouldHaveAES256Key && aes256Key.KeyType != etypeID.AES256_CTS_HMAC_SHA1_96:
t.Errorf("AES256 key type is %d instead of %d", aes256Key.KeyType, etypeID.AES256_CTS_HMAC_SHA1_96)
case !testCase.ShouldHaveAES256Key && (aes256Err == nil || len(aes256Key.KeyValue) > 0):
t.Errorf("AES256 key should not exist")
}
})
}
}
func TestSetDC(t *testing.T) {
creds := adauth.Credential{
Username: testUser,
+47 -6
View File
@@ -11,6 +11,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/binary"
"encoding/hex"
"encoding/pem"
"fmt"
"net"
@@ -26,7 +27,9 @@ import (
"github.com/go-ldap/ldap/v3"
"github.com/oiweiwei/gokrb5.fork/v9/client"
"github.com/oiweiwei/gokrb5.fork/v9/iana/etypeID"
"github.com/oiweiwei/gokrb5.fork/v9/iana/flags"
"github.com/oiweiwei/gokrb5.fork/v9/types"
"github.com/spf13/pflag"
)
@@ -353,19 +356,57 @@ func kerberosClient(
}
authClient.BindCertificate = cert
case creds.NTHash != "" || creds.AESKey != "":
opts.Debug("authenticating using GSSAPI bind (key)")
case creds.NTHash != "":
opts.Debug("authenticating using GSSAPI bind (NT hash)")
keyTab, err := creds.Keytab()
ntHash, err := hex.DecodeString(creds.NTHash)
if err != nil {
return nil, fmt.Errorf("create keytab: %w", err)
return nil, fmt.Errorf("decode NT hash: %w", err)
}
authClient = &gssapiClient{
Client: client.NewWithKeytab(
Client: client.NewWithEncryptionKey(
creds.Username,
strings.ToUpper(creds.Domain),
compat.Gokrb5ForkV9Keytab(keyTab),
types.EncryptionKey{
KeyType: etypeID.RC4_HMAC,
KeyValue: ntHash,
},
compat.Gokrb5ForkV9KerberosConfig(krbConf),
client.DisablePAFXFAST(true),
client.Dialer(opts.KerberosDialer),
),
BindCertificate: cert,
}
authClient.BindCertificate = cert
case creds.AESKey != "":
opts.Debug("authenticating using GSSAPI bind (AES key)")
aesKey, err := hex.DecodeString(creds.AESKey)
if err != nil {
return nil, fmt.Errorf("decode AES key: %w", err)
}
var keyType int32
switch len(aesKey) {
case 32:
keyType = etypeID.AES256_CTS_HMAC_SHA1_96
case 16:
keyType = etypeID.AES128_CTS_HMAC_SHA1_96
default:
return nil, fmt.Errorf("invalid AES128/AES256 key: key size is %d bytes", len(aesKey))
}
authClient = &gssapiClient{
Client: client.NewWithEncryptionKey(
creds.Username,
strings.ToUpper(creds.Domain),
types.EncryptionKey{
KeyType: keyType,
KeyValue: aesKey,
},
compat.Gokrb5ForkV9KerberosConfig(krbConf),
client.DisablePAFXFAST(true),
client.Dialer(opts.KerberosDialer),