mirror of
https://github.com/RedTeamPentesting/adauth
synced 2026-06-08 12:20:46 +00:00
330 lines
7.3 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|