Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/sys/windows: check for admin #28804

Closed
tobiaskohlbau opened this issue Nov 14, 2018 · 5 comments
Closed

x/sys/windows: check for admin #28804

tobiaskohlbau opened this issue Nov 14, 2018 · 5 comments
Milestone

Comments

@tobiaskohlbau
Copy link

@tobiaskohlbau tobiaskohlbau commented Nov 14, 2018

What version of Go are you using (go version)?

$ go version
go version go1.11.2 windows/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\tobia\AppData\Local\go-build
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=C:\Users\tobia\go
set GOPROXY=
set GORACE=
set GOROOT=C:\Go
set GOTMPDIR=
set GOTOOLDIR=C:\Go\pkg\tool\windows_amd64
set GCCGO=gccgo
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\tobia\AppData\Local\Temp\go-build422145038=/tmp/go-build -gno-record-gcc-switches

What did you do?

Microsoft provides an example to check if the current process has admin rights here. I've transformed this example to go:

package main

import (
	"log"

	"golang.org/x/sys/windows"
)

func main() {
	var sid *windows.SID
	err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, 2, windows.SECURITY_BUILTIN_DOMAIN_RID, windows.DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &sid)
	if err != nil {
		panic(err)
	}

	token, err := windows.OpenCurrentProcessToken()
	if err != nil {
		panic(err)
	}

	member, err := token.IsMember(sid)
	if err != nil {
		panic(err)
	}
	log.Println(member)
}

What did you expect to see?

Showing if the user has admin rights.

What did you see instead?

panic: An attempt has been made to operate on an impersonation token by a thread that is not currently impersonating a client.

goroutine 1 [running]:
main.main()
        C:/Users/tobia/Desktop/bad.go:23 +0x135
exit status 2

Solution

package main

import (
	"log"
	"syscall"
	"unsafe"

	"golang.org/x/sys/windows"
)

const (
	errnoERROR_IO_PENDING = 997
)

var (
	errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)

// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
	switch e {
	case 0:
		return nil
	case errnoERROR_IO_PENDING:
		return errERROR_IO_PENDING
	}
	// TODO: add more here, after collecting data on the common
	// error values see on Windows. (perhaps when running
	// all.bat?)
	return e
}

var (
	modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
	modkernel32 = windows.NewLazySystemDLL("kernel32.dll")

	procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
	procOpenThreadToken  = modadvapi32.NewProc("OpenThreadToken")
	procImpersonateSelf  = modadvapi32.NewProc("ImpersonateSelf")
	procRevertToSelf     = modadvapi32.NewProc("RevertToSelf")
)

func GetCurrentThread() (pseudoHandle windows.Handle, err error) {
	r0, _, e1 := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
	pseudoHandle = windows.Handle(r0)
	if pseudoHandle == 0 {
		if e1 != 0 {
			err = errnoErr(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}

func OpenThreadToken(h windows.Handle, access uint32, self bool, token *windows.Token) (err error) {
	var _p0 uint32
	if self {
		_p0 = 1
	} else {
		_p0 = 0
	}
	r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(h), uintptr(access), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
	if r1 == 0 {
		if e1 != 0 {
			err = errnoErr(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}

func ImpersonateSelf() (err error) {
	r0, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(2), 0, 0)
	if r0 == 0 {
		if e1 != 0 {
			err = errnoErr(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}

func RevertToSelf() (err error) {
	r0, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
	if r0 == 0 {
		if e1 != 0 {
			err = errnoErr(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}

func OpenCurrentThreadToken() (windows.Token, error) {
	if e := ImpersonateSelf(); e != nil {
		return 0, e
	}
	defer RevertToSelf()
	t, e := GetCurrentThread()
	if e != nil {
		return 0, e
	}
	var tok windows.Token
	e = OpenThreadToken(t, windows.TOKEN_QUERY, true, &tok)
	if e != nil {
		return 0, e
	}
	return tok, nil
}

func main() {
	var sid *windows.SID
	err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, 2, windows.SECURITY_BUILTIN_DOMAIN_RID, windows.DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &sid)
	if err != nil {
		panic(err)
	}

	token, err := OpenCurrentThreadToken()
	if err != nil {
		panic(err)
	}

	member, err := token.IsMember(sid)
	if err != nil {
		panic(err)
	}
	log.Println(member)
}

Output:

2018/11/14 22:58:13 false

For what I found CurrentProcessToken does not have the rights to check against this "admin" SID. I've tried to use ImpersonateSelf() in combination with OpenCurrentProcessToken() which results in the same error. ImpersonateSelf() in combination with OpenCurrentThreadToken() allows to execute the membership check. Should this feature be supported within x/sys/windows or is is intended to be solved by the user in it's program? If this is accepted to be in x/sys/windows I'm happy to prepare a CL.

@gopherbot gopherbot added this to the Unreleased milestone Nov 14, 2018
@tobiaskohlbau
Copy link
Author

@tobiaskohlbau tobiaskohlbau commented Nov 14, 2018

Just after writing this issue I've got the idea what about using token := windows.Token(0) and let windows handle the impersonate. Certainly this works totally fine (sigh).

Closing this, sorry for the inconvenience.

@coolaj86
Copy link

@coolaj86 coolaj86 commented Jun 25, 2019

Would you mind sharing the updated code sample that works?

I don't quite know enough about windows yet to understand which part of the code you're implying that you changed.

@coolaj86
Copy link

@coolaj86 coolaj86 commented Jun 25, 2019

Solution

// +build windows
package main

import (
        "fmt"
        "log"

        "golang.org/x/sys/windows"
)

func main() {
        var sid *windows.SID

        // Although this looks scary, it is directly copied from the
        // official windows documentation. The Go API for this is a
        // direct wrap around the official C++ API.
        // See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-checktokenmembership
        err := windows.AllocateAndInitializeSid(
                &windows.SECURITY_NT_AUTHORITY,
                2,
                windows.SECURITY_BUILTIN_DOMAIN_RID,
                windows.DOMAIN_ALIAS_RID_ADMINS,
                0, 0, 0, 0, 0, 0,
                &sid)
        if err != nil {
                log.Fatalf("SID Error: %s", err)
                return
        }
        defer windows.FreeSid(sid)

        // This appears to cast a null pointer so I'm not sure why this
        // works, but this guy says it does and it Works for Me™:
        // https://github.com/golang/go/issues/28804#issuecomment-438838144
        token := windows.Token(0)

        member, err := token.IsMember(sid)
        if err != nil {
                log.Fatalf("Token Membership Error: %s", err)
                return
        }

        // Also note that an admin is _not_ necessarily considered
        // elevated.
        // For elevation see https://github.com/mozey/run-as-admin
        fmt.Println("Elevated?", token.IsElevated())

        fmt.Println("Admin?", member)
}

See Also

cmaster11 added a commit to cmaster11/kubefwd that referenced this issue Nov 16, 2019
…or windows shell.

Why: the old way of accessing "\\\\.\\PHYSICALDRIVE0" may not work on
every system, because.. what if I don't have that folder? (clearly my case :D )

Ref for the new way: golang/go#28804
@ianatha
Copy link

@ianatha ianatha commented Apr 11, 2020

    err := windows.AllocateAndInitializeSid(
            &windows.SECURITY_NT_AUTHORITY,
            2,
            windows.SECURITY_BUILTIN_DOMAIN_RID,
            windows.DOMAIN_ALIAS_RID_ADMINS,
            0, 0, 0, 0, 0, 0,
            &sid)

You're forgetting a defer windows.FreeSid(sid) -- being careful to not leak these security tokens is important

@coolaj86
Copy link

@coolaj86 coolaj86 commented Apr 21, 2020

Seeing as how this issue is google's 2nd hit for "windows FreeSid golang"... I'm guessing I'm not the only one.

Thanks for the tip. Not used to having to free() in Go... :)

181192 added a commit to 181192/ops-cli that referenced this issue Aug 12, 2020
…ows 2004

Checking if opening \\\\.\\PHYSICALDRIVE0 does not work on Windows 2004.
See golang/go#28804 and https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership for more details.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants
You can’t perform that action at this time.