mirror of
https://github.com/RedTeamPentesting/adauth
synced 2026-06-08 12:20:46 +00:00
Remove keytab because KVNO may not match
This commit is contained in:
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user