Refactoring

This commit is contained in:
Erik Geiser
2025-04-11 16:29:18 +02:00
parent 129849fe4e
commit 1fdd48fdb2
7 changed files with 178 additions and 242 deletions
+7 -5
View File
@@ -53,9 +53,9 @@ Kerberos authentication is possible even when specifying the target via IP
address if reverse lookups are possible. Similarly, the domain can be omitted
when the target hostname contains the domain.
The library also contains helper packages for LDAP and DCERPC, a Kerebros PKINIT
implementation as well as helpers for creating and writing CCache files (see
examples).
The library also contains helper packages for LDAP, SMB and DCERPC, a Kerebros
PKINIT implementation as well as helpers for creating and writing CCache files
(see examples).
## Features
@@ -70,12 +70,14 @@ examples).
* Kerberos, NTLM, Simple Bind
* mTLS Authentication / Pass-the-Certificate (LDAPS or LDAP+StartTLS)
* Channel Binding (Kerberos and NTLM)
* SMB
* Kerberos, NTLM
* Signing and Sealing
* DCERPC:
* Kerberos, NTLM
* Raw endpoits (with port mapping)
* Named pipes (SMB)
* Signing
* Sealing
* Signing and Sealing
## Caveats
+3 -3
View File
@@ -21,9 +21,9 @@ import (
// function.
type Options struct {
// SMBOptions holds options for the SMB dialer. This dialer is only used
// with the named pipe transport. If SMBOptions is nil, sealing will be
// enabled for the smb dialer, specify an empty slice to disable this
// default.
// with the named pipe transport. If SMBOptions is nil, encryption/sealing
// will be enabled for the SMB dialer, specify an empty slice to disable
// this default.
SMBOptions []smb2.DialerOption
// PKINITOptions can be used to modify the Kerberos PKINIT behavior.
PKINITOptions []pkinit.Option
+1 -2
View File
@@ -13,7 +13,6 @@ import (
"github.com/oiweiwei/go-msrpc/msrpc/dtyp"
"github.com/oiweiwei/go-msrpc/msrpc/epm/epm/v3"
"github.com/oiweiwei/go-msrpc/msrpc/samr/samr/v1"
"github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/spf13/pflag"
)
@@ -43,7 +42,7 @@ func run() error {
return err
}
ctx := gssapi.NewSecurityContext(context.Background())
ctx := context.Background()
dcerpcOpts, err := dcerpcauth.AuthenticationOptions(ctx, creds, target, dcerpcauthOpts)
if err != nil {
+79 -71
View File
@@ -1,97 +1,105 @@
package main
import (
"context"
"fmt"
"github.com/RedTeamPentesting/adauth/smbauth"
"github.com/oiweiwei/go-msrpc/smb2"
"github.com/oiweiwei/go-msrpc/ssp"
"net"
"os"
"path/filepath"
"context"
"fmt"
"net"
"os"
"path/filepath"
"github.com/RedTeamPentesting/adauth"
"github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/spf13/pflag"
"github.com/RedTeamPentesting/adauth/smbauth"
"github.com/RedTeamPentesting/adauth"
"github.com/spf13/pflag"
)
var (
debug bool
authOpts = &adauth.Options{
Debug: adauth.NewDebugFunc(&debug, os.Stderr, true),
}
)
func init() {
pflag.CommandLine.BoolVar(&debug, "debug", false, "Enable debug output")
authOpts.RegisterFlags(pflag.CommandLine)
gssapi.AddMechanism(ssp.SPNEGO)
gssapi.AddMechanism(ssp.NTLM)
}
func run() error {
pflag.Parse()
var (
debug bool
authOpts = &adauth.Options{
Debug: adauth.NewDebugFunc(&debug, os.Stderr, true),
}
smbauthOpts = &smbauth.Options{
Debug: authOpts.Debug,
}
)
if len(pflag.Args()) != 1 {
return fmt.Errorf("usage: %s <target> [--debug]", binaryName())
}
pflag.CommandLine.BoolVar(&debug, "debug", false, "Enable debug output")
authOpts.RegisterFlags(pflag.CommandLine)
pflag.Parse()
creds, target, err := authOpts.WithTarget(context.Background(), "host", pflag.Arg(0))
if err != nil {
return err
}
if len(pflag.Args()) != 1 {
return fmt.Errorf("usage: %s [options] <target>", binaryName())
}
ctx := gssapi.NewSecurityContext(context.Background())
creds, target, err := authOpts.WithTarget(context.Background(), "host", pflag.Arg(0))
if err != nil {
return err
}
smbOpts, secOpts, err := smbauth.AuthenticationOptions(ctx, creds, target, &smbauth.Options{})
if err != nil {
return err
}
if target.Port == "" {
target.Port = "445"
}
// Create go-smb2 Dialer
dialer := smb2.NewDialer(append(smbOpts, smb2.WithSecurity(secOpts...))...)
ctx := context.Background()
conn, err := net.Dial("tcp", net.JoinHostPort(target.AddressWithoutPort(), "445"))
if err != nil {
return err
}
defer conn.Close()
smbDialer, err := smbauth.Dialer(ctx, creds, target, smbauthOpts)
if err != nil {
return fmt.Errorf("setup SMB authentication: %w", err)
}
sess, err := dialer.Dial(conn)
if err != nil {
return err
}
defer sess.Logoff()
conn, err := net.Dial("tcp", target.Address())
if err != nil {
return fmt.Errorf("dial: %w", err)
}
names, err := sess.ListSharenames()
if err != nil {
return err
}
defer conn.Close()
for _, name := range names {
fmt.Println(name)
}
return nil
sess, err := smbDialer.DialContext(ctx, conn)
if err != nil {
return fmt.Errorf("create session: %w", err)
}
defer sess.Logoff()
shares, err := sess.ListSharenames()
if err != nil {
return fmt.Errorf("list share names: %w", err)
}
if len(shares) == 0 {
fmt.Println("No shares available")
return nil
}
fmt.Println("Shares:")
for _, share := range shares {
fmt.Printf(" - %s\n", share)
}
return nil
}
func binaryName() string {
executable, err := os.Executable()
if err == nil {
return filepath.Base(executable)
}
executable, err := os.Executable()
if err == nil {
return filepath.Base(executable)
}
if len(os.Args) > 0 {
return filepath.Base(os.Args[0])
}
if len(os.Args) > 0 {
return filepath.Base(os.Args[0])
}
return "list-shares"
return "smb"
}
func main() {
err := run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
err := run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
os.Exit(1)
}
}
+6 -6
View File
@@ -7,6 +7,7 @@ require (
github.com/go-ldap/ldap/v3 v3.4.11-0.20250110131057-5d1b644709df
github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/oiweiwei/go-msrpc v1.2.5
github.com/oiweiwei/go-smb2.fork v1.0.0
github.com/oiweiwei/gokrb5.fork/v9 v9.0.2
github.com/spf13/pflag v1.0.6
github.com/vadimi/go-ntlm v1.2.1
@@ -26,10 +27,9 @@ require (
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/oiweiwei/go-smb2.fork v1.0.0 // indirect
github.com/rs/zerolog v1.33.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
github.com/rs/zerolog v1.34.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
)
+2 -11
View File
@@ -55,10 +55,7 @@ github.com/oiweiwei/gokrb5.fork/v9 v9.0.2/go.mod h1:KEnkAYUYqZ5VwzxLFbv3JHlRhCvd
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
@@ -82,8 +79,6 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -102,8 +97,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -124,8 +119,6 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
@@ -146,8 +139,6 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+80 -144
View File
@@ -1,167 +1,103 @@
package smbauth
import (
"context"
"fmt"
"strings"
"context"
"fmt"
"github.com/RedTeamPentesting/adauth"
"github.com/RedTeamPentesting/adauth/pkinit"
"github.com/RedTeamPentesting/adauth"
"github.com/RedTeamPentesting/adauth/dcerpcauth"
"github.com/RedTeamPentesting/adauth/pkinit"
"github.com/jcmturner/gokrb5/v8/keytab"
"github.com/oiweiwei/go-msrpc/smb2"
"github.com/oiweiwei/go-msrpc/ssp"
"github.com/oiweiwei/go-msrpc/ssp/credential"
"github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/oiweiwei/go-msrpc/ssp/krb5"
"github.com/oiweiwei/go-smb2.fork"
msrpcSMB2 "github.com/oiweiwei/go-msrpc/smb2"
"github.com/oiweiwei/go-msrpc/ssp"
"github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/oiweiwei/go-msrpc/ssp/krb5"
)
// Options holds options that modify the behavior of the AuthenticationOptions
// function.
// Options holds options that modify the behavior of the Dialer function.
type Options struct {
// SMBOptions holds options for the SMB dialer. If SMBOptions is nil,
// sealing will be enabled for the smb dialer, specify an empty slice
// to disable this default.
SMBOptions []smb2.DialerOption
// SMBOptions holds options for the SMB dialer. If SMBOptions is nil,
// encryption/sealing will be enabled. Specify an empty slice to disable
// this default.
SMBOptions []msrpcSMB2.DialerOption
// PKINITOptions can be used to modify the Kerberos PKINIT behavior.
PKINITOptions []pkinit.Option
// PKINITOptions can be used to modify the Kerberos PKINIT behavior.
PKINITOptions []pkinit.Option
// Debug can be set to enable debug output, for example with
// adauth.NewDebugFunc(...).
Debug func(string, ...any)
// Debug can be set to enable debug output, for example with
// adauth.NewDebugFunc(...).
Debug func(string, ...any)
}
func (opts *Options) debug(format string, a ...any) {
if opts == nil || opts.Debug == nil {
return
}
if opts == nil || opts.Debug == nil {
return
}
opts.Debug(format, a...)
opts.Debug(format, a...)
}
// AuthenticationOptions returns the security context options for
// smb2.WithSecurity. It is possible to configure the SMB, PKINIT
// and debug behavior using the optional upstreamOptions argument.
func AuthenticationOptions(
ctx context.Context, creds *adauth.Credential, target *adauth.Target,
upstreamOptions *Options,
) (dialOptions []smb2.DialerOption, secOptions []gssapi.ContextOption, err error) {
// Dialer returns an SMB dialer which is prepared for authentication with the
// given credentials. The dialer can be further customized with
// options.SMBDialerOptions.
func Dialer(
ctx context.Context, creds *adauth.Credential, target *adauth.Target, options *Options,
) (*smb2.Dialer, error) {
smbCreds, err := dcerpcauth.DCERPCCredentials(ctx, creds, &dcerpcauth.Options{
PKINITOptions: options.PKINITOptions,
Debug: options.debug,
})
if err != nil {
return nil, err
}
if dialOptions = upstreamOptions.SMBOptions; dialOptions == nil {
dialOptions = []smb2.DialerOption{smb2.WithSeal()}
}
dialerOptions := options.SMBOptions
if dialerOptions == nil {
dialerOptions = append(dialerOptions, msrpcSMB2.WithSeal())
}
smbCredentials, err := SMBCredentials(ctx, creds, upstreamOptions)
if err != nil {
return nil, nil, err
}
switch {
case target.UseKerberos || creds.ClientCert != nil:
spn, err := target.SPN(ctx)
if err != nil {
return nil, fmt.Errorf("build SPN: %w", err)
}
switch {
case target.UseKerberos || creds.ClientCert != nil:
spn, err := target.SPN(ctx)
if err != nil {
return nil, nil, fmt.Errorf("build SPN: %w", err)
}
upstreamOptions.debug("Using Kerberos with SPN %q", spn)
options.debug("Using Kerberos with SPN %q", spn)
krbConf, err := creds.KerberosConfig(ctx)
if err != nil {
return nil, nil, fmt.Errorf("generate Kerberos config: %w", err)
}
krbConf, err := creds.KerberosConfig(ctx)
if err != nil {
return nil, fmt.Errorf("generate Kerberos config: %w", err)
}
secOptions = append(secOptions,
gssapi.WithTargetName(spn),
gssapi.WithCredential(smbCredentials),
gssapi.WithMechanismFactory(ssp.KRB5, &krb5.Config{
KRB5Config: krbConf,
CCachePath: creds.CCache,
DisablePAFXFAST: true,
DCEStyle: true,
}),
)
default:
upstreamOptions.debug("Using NTLM")
dialerOptions = append(dialerOptions, msrpcSMB2.WithSecurity(
gssapi.WithTargetName(spn),
gssapi.WithCredential(smbCreds),
gssapi.WithMechanismFactory(ssp.KRB5, &krb5.Config{
KRB5Config: krbConf,
CCachePath: creds.CCache,
DisablePAFXFAST: true,
}),
))
secOptions = append(secOptions,
gssapi.WithCredential(smbCredentials),
gssapi.WithMechanismFactory(ssp.NTLM),
)
// Try fetching SPN
if spn, err := target.SPN(ctx); err == nil {
secOptions = append(secOptions, gssapi.WithTargetName(spn))
}
}
return
}
func SMBCredentials(ctx context.Context, creds *adauth.Credential, options *Options) (credential.Credential, error) {
switch {
case creds.Password != "":
options.debug("Authenticating with password")
return credential.NewFromPassword(creds.LogonNameWithUpperCaseDomain(), creds.Password), nil
case creds.AESKey != "":
options.debug("Authenticating with AES key")
keyTab, err := creds.Keytab()
if err != nil {
return nil, fmt.Errorf("create keytab: %w", err)
}
return &keytabCredentials{username: creds.Username, domain: creds.Domain, keytab: keyTab}, nil
case creds.NTHash != "":
options.debug("Authenticating with NT hash")
return credential.NewFromNTHash(creds.LogonNameWithUpperCaseDomain(), creds.NTHash), nil
case creds.PasswordIsEmtpyString:
options.debug("Authenticating with empty password")
return credential.NewFromPassword(strings.ToUpper(creds.Domain)+`\`+creds.Username, ""), nil
case creds.ClientCert != nil:
options.debug("Authenticating with client certificate (PKINIT)")
krbConf, err := creds.KerberosConfig(ctx)
if err != nil {
return nil, fmt.Errorf("generate kerberos config: %w", err)
}
ccache, err := pkinit.Authenticate(ctx, creds.Username, strings.ToUpper(creds.Domain),
creds.ClientCert, creds.ClientCertKey, krbConf, options.PKINITOptions...)
if err != nil {
return nil, fmt.Errorf("PKINIT: %w", err)
}
return credential.NewFromCCache(creds.LogonNameWithUpperCaseDomain(), ccache), nil
case creds.CCache != "":
options.debug("Authenticating with ccache")
return credential.NewFromPassword(creds.LogonNameWithUpperCaseDomain(), ""), nil
default:
return nil, fmt.Errorf("no credentials available")
}
}
type keytabCredentials struct {
keytab *keytab.Keytab
username string
domain string
}
var _ credential.KeytabV8 = &keytabCredentials{}
func (ktc *keytabCredentials) DomainName() string {
return strings.ToUpper(ktc.domain)
}
func (ktc *keytabCredentials) Workstation() string {
return ""
}
func (ktc *keytabCredentials) UserName() string {
return ktc.username
}
func (ktc *keytabCredentials) Keytab() *keytab.Keytab {
return ktc.keytab
return msrpcSMB2.NewDialer(dialerOptions...), nil
default:
options.debug("Using NTLM")
secOptions := []gssapi.ContextOption{
gssapi.WithCredential(smbCreds),
gssapi.WithMechanismFactory(ssp.NTLM),
}
spn, err := target.SPN(ctx)
if err == nil {
secOptions = append(secOptions, gssapi.WithTargetName(spn))
}
dialerOptions = append(dialerOptions, msrpcSMB2.WithSecurity(secOptions...))
return msrpcSMB2.NewDialer(dialerOptions...), nil
}
}