Windows and Admins and Golang, Oh My!
Published 2019-6-25The goal is to determine if, on Windows, we have "root privileges", more or less:
go run am-i-admin-yet.go Elevated? false Admin? true
Whereas on Linux we have the concept of root and sudo,
on Windows we have the concept of the Administrators group and Elevated privileges.
TL;DR: Use dark magics
There's no simple way to do this.
You can't use user.Current() like you might expect (see below).
Instead you have to use very Windows-centric, low-level syscall APIs,
which are available through the golang.org/x/sys/windows as windows.AllocateAndInitializeSid.
- https://godoc.org/golang.org/x/sys/windows#AllocateAndInitializeSid
- https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-checktokenmembership
What it's actually checking isn't the user's memberships, but rather the process's memberships.
// +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
}
// 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)
}
Running in cmd.exe as a normal user:
go run as-admin.go
Elevated? false
Admin? false
Running in cmd.exe "As Administrator":
go run as-admin.go
Elevated? false
Admin? true
Note how Elevated is distinctly different from
the current process being an Admin.
Why you can't use user.Current()
On Unixes your UID changes when you "run as administrator" through sudo,
but on Windows that's not the case.
In order to "Run as Administrator" you need to belong to
the "Administrators" group (S-1-5-32-544),
but your UID and GID don't change when running as administrator.
This sample demonstrates that pretty well:
// +build windows
package main
import (
"fmt"
"os/user"
)
func main() {
fmt.Println(IsAdmin())
}
func IsAdmin() bool {
u, err := user.Current()
if nil != err {
return false
}
// These seem to be unique
fmt.Println(u.Uid)
fmt.Println(u.Gid)
ids, err := u.GroupIds()
if nil != err {
return false
}
for i := range ids {
fmt.Println(ids[i])
// not quite, but close enough for now
// BUILTIN\ADMINISTRATORS
if "S-1-5-32-544" == ids[i] {
return true
}
}
return false
}
As a normal user:
go run is-admin.go
S-1-5-21-1268909207-131461034-1794619431-1000
S-1-5-21-1268909207-131461034-1794619431-513
S-1-5-32-544
true
Right-Click Run As Admin:
S-1-5-21-1268909207-131461034-1794619431-1000
S-1-5-21-1268909207-131461034-1794619431-513
S-1-5-32-544
true
As you can see, there is no change in the UID or GID identifiers.
To learn more about the specific identifiers, take a (long) scroll through the Microsoft documentation:
Getting Elevated
If you want to get elevated privileges, that's a completely different story.
You need to create a special manifest file and use rsrc
to embed it into your application as a binary .syso.
See https://github.com/mozey/run-as-admin for more info.
By AJ ONeal
Did I make your day?
(you can learn about the bigger picture I'm working towards on my patreon page )
