Socket exception when connecting to SQL Server when impersonating user in Golang

79 Views Asked by At

I am trying to connect to SQL Server DB with integrated auth while impersonating a user who has DB access. The logon and impersonation Windows API call works fine, but the database connection fails with error

unable to open tcp connection with host '10.199.25.69:1433': dial tcp 10.199.25.69:1433: socket: winapi error #10106

On looking deeper into the windows package, I could see the call failing on function internal\syscall\windows\zsyscall_windows.go.WSASocket on the below line

r0, _, e1 := syscall.Syscall6(procWSASocketW.Addr(), 6, uintptr(af), uintptr(typ), uintptr(protocol), uintptr(unsafe.Pointer(protinfo)), uintptr(group), uintptr(flags))

where procWSASocketW = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll")).NewProc("WSASocketW")

The call to procWSASocketW fails with error

golang.org/x/sys/windows.WSAEPROVIDERFAILEDINIT (10106) = 0x277a

I tried connecting to the DB with SQLServer Auth and surprisingly it fails as well with the same error. I am able to connect to database outside of impersonation block. It is only while impersonating the connection fails. Also, I did not see any difference in the parameters of procWSASocketW function in a failed vs a successful call, which makes me think that there is something in the procWSASocketW that does not like the process being impersonated.

Not sure what I am doing wrong. I did check the access token returned from the logon call and it looks valid. Below is a sample code that I have been playing with. Any help is appreciated.

package main

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "os"
    "os/signal"
    "runtime"
    "sync"
    "syscall"
    "unsafe"

    _ "github.com/microsoft/go-mssqldb"
    "golang.org/x/sys/windows"
)

const (
    user   = "username"
    pass   = "password"
    domain = "domain"
)

var (
    advapi32        = syscall.NewLazyDLL("advapi32.dll")
    logonProc       = advapi32.NewProc("LogonUserW")
    impersonateProc = advapi32.NewProc("ImpersonateLoggedOnUser")
    revertSelfProc  = advapi32.NewProc("RevertToSelf")
)

func main() {
    log.SetFlags(0)
    //connectToDB()
    var wg sync.WaitGroup
    wg.Add(1)
    go func(user, pass string) {
        defer wg.Done()
        runtime.LockOSThread()
        defer runtime.UnlockOSThread()
        
        log.Println("In a goroutine")
        token, err := logonUser(user, pass, domain)
        if err != nil {
            fmt.Println(err)
        }
        u, _ := token.GetTokenUser() //the sid returned matches with whoami /user 
        log.Println(u)
        r, _ := token.IsRestricted() // returns false
        log.Println(fmt.Printf("token is restricted: %t", r))
        e := token.IsElevated() // returns true
        log.Println(fmt.Printf("token is elevated: %t", e))
        
        defer closeHandle(token)
        err = impersonateUser(token)
        if err != nil {
            log.Println(fmt.Println(err))
        }
        connectToDB()

        defer mustRevertToSelf()
        log.Println("Impersonated")
    }(user, pass)
    wg.Wait()
    log.Println("OK")
}

func logonUser(user, pass, domain string) (windows.Token, error) {
    const (
        // Taken from WinBase.h (SDK 7.1):
        LOGON32_LOGON_NETWORK           = uintptr(3)
        LOGON32_PROVIDER_DEFAULT        = uintptr(0)
        LOGON32_LOGON_SERVICE           = uintptr(5)
        LOGON32_LOGON_NETWORK_CLEARTEXT = uintptr(8)
    )

    pUsername, _ := windows.UTF16PtrFromString(user)
    pDomain, _ := windows.UTF16PtrFromString(domain)
    pPassword, _ := windows.UTF16PtrFromString(pass)

    hToken := uintptr(0)
    res, _, err := logonProc.Call(
        uintptr(unsafe.Pointer(pUsername)),
        uintptr(unsafe.Pointer(pDomain)),
        uintptr(unsafe.Pointer(pPassword)),
        uintptr(LOGON32_LOGON_NETWORK_CLEARTEXT),
        uintptr(LOGON32_PROVIDER_DEFAULT),
        uintptr(unsafe.Pointer(&hToken)),
    )
    if res != 0 {
        return windows.Token(hToken), nil
    }

    return 0, err

}

func impersonateUser(token windows.Token) error {
    res, _, err := impersonateProc.Call(uintptr(token))
    if res == 0 {
        return error(err)
    }
    return nil
}

func revertToSelf() error {
    rc, _, ec := syscall.SyscallN(revertSelfProc.Addr(), 0, 0, 0, 0)
    if rc == 0 {
        return error(ec)
    }
    return nil
}

func mustRevertToSelf() {
    err := revertToSelf()
    if err != nil {
        panic(err)
    }
}

func closeHandle(token windows.Token) {
    err := token.Close()
    if err != nil {
        panic(err)
    }
}

func connectToDB() {
    connectionStr := "Data Source=<host>;Initial Catalog=<Database>;Integrated Security=true;User ID=<user>;Password=<pwd>;Encrypt=False;TrustServerCertificate=True"
    db, err := sql.Open("mssql", connectionStr)
    if err != nil {
        fmt.Println(err)
    }
    err = db.Ping()
    if err != nil {
        log.Println(err.Error())
    }

}
0

There are 0 best solutions below