Files
RedTeamPentesting-adauth/options_test.go
T
Erik Geiser bb02617c3d Fix tests
2025-11-07 10:20:42 +01:00

330 lines
7.3 KiB
Go

package adauth_test
import (
"context"
"encoding/hex"
"net"
"strconv"
"testing"
"github.com/RedTeamPentesting/adauth"
)
const (
testUser = "someuser"
testDomain = "domain.tld"
)
func TestUsernameAndDomainParsing(t *testing.T) {
expectedUPN := testUser + "@" + testDomain
expectedLogonName := testDomain + `\` + testUser
expectedImpacketLogonName := testDomain + "/" + testUser
testCases := []struct {
Name string
Opts adauth.Options
}{
{
Name: "UPN",
Opts: adauth.Options{User: testUser + "@" + testDomain},
},
{
Name: "logon-name",
Opts: adauth.Options{User: testDomain + `\` + testUser},
},
{
Name: "impacket-style",
Opts: adauth.Options{User: testDomain + `/` + testUser},
},
{
Name: "pfx",
Opts: adauth.Options{PFXFileName: "testdata/someuser@domain.tld.pfx"},
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.Name, func(t *testing.T) {
creds, err := testCase.Opts.NoTarget()
if err != nil {
t.Fatalf("get credentials: %v", err)
}
if creds.Username != testUser {
t.Errorf("username %q is not %q", creds.Username, testUser)
}
if creds.Domain != testDomain {
t.Errorf("domain %q is not %q", creds.Domain, testDomain)
}
upn := creds.UPN()
if upn != expectedUPN {
t.Errorf("UPN %q is not %q", upn, expectedUPN)
}
logonName := creds.LogonName()
if logonName != expectedLogonName {
t.Errorf("logon name %q is not %q", logonName, expectedLogonName)
}
impacketLogonName := creds.ImpacketLogonName()
if impacketLogonName != expectedImpacketLogonName {
t.Errorf("impacket-style logon name %q is not %q", impacketLogonName, expectedImpacketLogonName)
}
})
}
}
func TestDomainFromTargetHostname(t *testing.T) {
opts := adauth.Options{
User: testUser,
}
creds, _, err := opts.WithTarget(context.Background(), "cifs", "host."+testDomain)
if err != nil {
t.Fatalf("get credentials: %v", err)
}
if creds.Domain != testDomain {
t.Errorf("domain is %q instead of %q", creds.Domain, testDomain)
}
// ignore domain in hostname for local authentication
opts = adauth.Options{
User: "./" + testUser,
}
creds, _, err = opts.WithTarget(context.Background(), "cifs", "host."+testDomain)
if err != nil {
t.Fatalf("get credentials: %v", err)
}
if creds.Domain != "." {
t.Errorf("domain is %q instead of %q", creds.Domain, ".")
}
}
func TestDCTarget(t *testing.T) {
dcHost := "dc." + testDomain
expectedSPN := "ldap/" + dcHost
opts := adauth.Options{
User: testUser + `@` + testDomain,
Resolver: &testResolver{
SRV: map[string]map[string]map[string]struct {
Name string
SRV []*net.SRV
}{
"ldap": {
"tcp": {
testDomain: {
Name: dcHost,
SRV: []*net.SRV{
{Target: dcHost, Port: 389},
},
},
},
},
},
},
}
_, dc, err := opts.WithDCTarget(context.Background(), "ldap")
if err != nil {
t.Fatalf("get DC target: %v", err)
}
if dc.Address() != net.JoinHostPort(dcHost, "389") {
t.Fatalf("DC address is %q instead of %q", dc.Address(), net.JoinHostPort(dcHost, "389"))
}
if dc.AddressWithoutPort() != dcHost {
t.Fatalf("DC address without port is %q instead of %q", dc.Address(), dcHost)
}
if dc.Port != "389" {
t.Errorf("DC port is %q instead of %q", dc.Port, "389")
}
spn, err := dc.SPN(context.Background())
if err != nil {
t.Fatalf("get DC SPN: %v", err)
}
if spn != expectedSPN {
t.Errorf("DC SPN is %q instead of %q", spn, expectedSPN)
}
}
func TestDCTargetWithoutSRVRecord(t *testing.T) {
dcHost := "dc." + testDomain
dcIP := net.ParseIP("10.0.0.1")
expectedSPN := "ldap/" + dcHost
opts := adauth.Options{
User: testUser + `@` + testDomain,
Resolver: &testResolver{
HostToAddr: map[string][]net.IP{
testDomain: {dcIP},
},
AddrToHost: map[string][]string{
dcIP.String(): {dcHost},
},
},
}
_, dc, err := opts.WithDCTarget(context.Background(), "ldap")
if err != nil {
t.Fatalf("get DC target: %v", err)
}
if dc.Address() != net.JoinHostPort(dcHost, "389") {
t.Fatalf("DC address is %q instead of %q", dc.Address(), net.JoinHostPort(dcHost, "389"))
}
if dc.AddressWithoutPort() != dcHost {
t.Fatalf("DC address without port is %q instead of %q", dc.Address(), dcHost)
}
if dc.Port != "389" {
t.Errorf("DC port is %q instead of %q", dc.Port, "389")
}
spn, err := dc.SPN(context.Background())
if err != nil {
t.Fatalf("get DC SPN: %v", err)
}
if spn != expectedSPN {
t.Errorf("DC SPN is %q instead of %q", spn, expectedSPN)
}
}
func TestDCTargetNoReverseLookup(t *testing.T) {
dcIP := net.ParseIP("10.0.0.1")
opts := adauth.Options{
User: testUser + `@` + testDomain,
Resolver: &testResolver{
HostToAddr: map[string][]net.IP{
testDomain: {dcIP},
},
},
}
_, dc, err := opts.WithDCTarget(context.Background(), "ldap")
if err != nil {
t.Fatalf("get DC target: %v", err)
}
if dc.Address() != net.JoinHostPort(dcIP.String(), "389") {
t.Fatalf("DC address is %q instead of %q", dc.Address(), net.JoinHostPort(dcIP.String(), "389"))
}
}
func TestKerberos(t *testing.T) {
upn := testUser + `@` + testDomain
testCases := []struct {
Opts adauth.Options
ShouldUseKerberos bool
}{
{
Opts: adauth.Options{
User: upn,
Password: "pass",
},
ShouldUseKerberos: false,
},
{
Opts: adauth.Options{
User: upn,
AESKey: hex.EncodeToString(make([]byte, 16)),
},
ShouldUseKerberos: true,
},
{
Opts: adauth.Options{
User: upn,
Password: "test",
AESKey: hex.EncodeToString(make([]byte, 16)),
},
ShouldUseKerberos: false,
},
{
Opts: adauth.Options{
User: upn,
Password: "pass",
ForceKerberos: true,
},
ShouldUseKerberos: true,
},
{
Opts: adauth.Options{
User: upn,
PFXFileName: "testdata/someuser@domain.tld.pfx",
},
ShouldUseKerberos: false,
},
{
Opts: adauth.Options{
User: upn,
// CCache indicates kerberos usage, but only if file is present
// (even though it might be empty)
CCache: "testdata/empty.ccache",
},
ShouldUseKerberos: true,
},
}
for i, testCase := range testCases {
testCase := testCase
t.Run(strconv.Itoa(i), func(t *testing.T) {
testCase.Opts.DomainController = "dc.domain.tld"
_, target, err := testCase.Opts.WithDCTarget(context.Background(), "ldap")
if err != nil {
t.Fatalf("get target: %v", err)
}
switch {
case testCase.ShouldUseKerberos && !target.UseKerberos:
t.Errorf("target would not use Kerberos even though it should: %#v", testCase.Opts)
case !testCase.ShouldUseKerberos && target.UseKerberos:
t.Errorf("target would use Kerberos even though it should not: %#v", testCase.Opts)
}
})
}
}
func TestCleanNTHash(t *testing.T) {
ntHash := "31d6cfe0d16ae931b73c59d7e0c089c0"
testCases := []string{
ntHash,
":" + ntHash,
"aad3b435b51404eeaad3b435b51404ee:" + ntHash,
}
for i, testCase := range testCases {
testCase := testCase
t.Run(strconv.Itoa(i), func(t *testing.T) {
creds, err := (&adauth.Options{
User: testUser + "@" + testDomain,
NTHash: testCase,
}).NoTarget()
if err != nil {
t.Fatalf("get credentials: %v", err)
}
if creds.NTHash != ntHash {
t.Errorf("NT hash is %q instead of %q", creds.NTHash, ntHash)
}
})
}
}