Windows service impersonation with Golang with a sub-process - access denied

0

I am trying to get the UPN of a logged user while running as a service. I was able to get the Username of the logged in users in format DOMAIN\username, along with the SessionID of the user and a lot of other things.

To get the UPN, I tried to impersonate the user by getting an access token and running the whoami /upn command with this target token. It works pretty well when the service does this directly, but it doesn't work when a sub-process of this service do it.

Here's the go example that fetches the UPN of the logged in users. As a matter of fact, I can't simply get the current user's UPN, because the application is running as a service

import (
    "errors"
    "fmt"
    "os"
    "os/exec"
    "strings"
    "syscall"
    "unsafe"

    winapi "github.com/kumako/go-win64api"
    "golang.org/x/sys/windows"
    "qohash.com/qostodian-agent/internal/logging"
)

var (
    modwtsapi32 *windows.LazyDLL = windows.NewLazySystemDLL("wtsapi32.dll")
    modadvapi32 *windows.LazyDLL = windows.NewLazySystemDLL("advapi32.dll")

    procWTSQueryUserToken *windows.LazyProc = modwtsapi32.NewProc("WTSQueryUserToken")
    procDuplicateTokenEx  *windows.LazyProc = modadvapi32.NewProc("DuplicateTokenEx")
)

func GetUserSessions() ([]User, error) {
    var u []User
    users, err := winapi.ListLoggedInUsers()
    if err != nil {
        return u, err
    }

    for _, user := range users {
        var upn string
        var err error

            // UPN Translation doesn't work -> try to impersonate the session and run whoami command
            upn, err= getUpnForLoggedUser(user.SessionID)
            if err!= nil {
                logging.Debugf("could not get UPN for user %v: \n\t - First attempt: %v \n\t ", user.FullUser(), err)
            } else {
                logging.Debugf("User UPN: %v", upn)
            }
    

        u = append(u, User{
            Username: user.Username,
            Domain:   user.Domain,
            Upn:      upn,
        })
    }

    return u, nil
}

func getUpnForLoggedUser(sessionID uint32) (string, error) {

    userToken, err := duplicateUserTokenFromSessionID(windows.Handle(sessionID))
    if err != nil {
        return "", fmt.Errorf("get duplicate user token for current user session: %s", err)
    }

    winPath, ok := os.LookupEnv("SystemRoot")
    if !ok {
        return "", errors.New("Cannot obtain SystemRoot environment variable")
    }
    cmd := exec.Command(fmt.Sprintf("%s\\system32\\whoami.exe", winPath), "/upn")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        HideWindow: true,
        Token:      userToken,
    }
    out, err := cmd.CombinedOutput()
    if err != nil {
        return "", err
    }

    userToken.Close()
    return strings.TrimSpace(string(out)), nil
}

func duplicateUserTokenFromSessionID(sessionId windows.Handle) (syscall.Token, error) {
    var (
        impersonationToken windows.Handle = 0
        userToken          syscall.Token  = 0
    )

    if returnCode, _, err := procWTSQueryUserToken.Call(uintptr(sessionId), uintptr(unsafe.Pointer(&impersonationToken))); returnCode == 0 {
        return 0xFFFFFFFF, fmt.Errorf("call native WTSQueryUserToken: %s", err)
    }

    if returnCode, _, err := procDuplicateTokenEx.Call(uintptr(impersonationToken), 0, 0, uintptr(windows.SecurityImpersonation), uintptr(windows.TokenPrimary), uintptr(unsafe.Pointer(&userToken))); returnCode == 0 {
        return 0xFFFFFFFF, fmt.Errorf("call native DuplicateTokenEx: %s", err)
    }

    if err := windows.CloseHandle(impersonationToken); err != nil {
        return 0xFFFFFFFF, fmt.Errorf("close windows handle used for token duplication: %s", err)
    }

    return userToken, nil
}

When my main service calls GetUserSessions it works and return the users with the logged in UPNs.

If this main service spawn a subprocess, the UPN is always empty. When calling the whoami, the application gets fork/exec C:\WINDOWS\system32\whoami.exe: Access is denied.

My questions are:

  1. Is impersonating the user a good way to get the UPN of a user? I found no other way to do this after many different tests
  2. If there is no other way, what do I need to do to get the sub-process to have permissions to run the whoami /upn command as another service ?

Thank you!

windows
service
impersonation
upn
asked on Stack Overflow Nov 9, 2020 by Sunny Pelletier • edited Nov 10, 2020 by Sunny Pelletier

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0