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?
Buy me a coffee
(you can learn about the bigger picture I'm working towards on my patreon page )