143 lines
3.7 KiB
Go
143 lines
3.7 KiB
Go
//go:build windows
|
||
|
||
package localreset
|
||
|
||
import (
|
||
"context"
|
||
"net"
|
||
"strings"
|
||
|
||
"github.com/Microsoft/go-winio"
|
||
"golang.org/x/sys/windows"
|
||
)
|
||
|
||
const DefaultPath = `\\.\pipe\scert-admin-control`
|
||
const pipeSDDL = "D:P(D;;GA;;;NU)(A;;GA;;;SY)(A;;GA;;;BA)"
|
||
|
||
func pipePath(path string) (string, error) {
|
||
if path == "" {
|
||
path = DefaultPath
|
||
}
|
||
prefix := `\\.\pipe\`
|
||
if !strings.HasPrefix(path, prefix) || len(path) <= len(prefix) ||
|
||
strings.ContainsAny(strings.TrimPrefix(path, prefix), `\/:`) {
|
||
return "", ErrUnavailable
|
||
}
|
||
return path, nil
|
||
}
|
||
|
||
func Listen(path string) (*Server, error) {
|
||
return listenPipe(path, pipeSDDL)
|
||
}
|
||
|
||
func listenPipe(path, sddl string) (*Server, error) {
|
||
path, err := pipePath(path)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
listener, err := winio.ListenPipe(path, &winio.PipeConfig{
|
||
SecurityDescriptor: sddl,
|
||
InputBufferSize: MaxFrameSize,
|
||
OutputBufferSize: MaxFrameSize,
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return newServer(listener, func(conn net.Conn) (Peer, error) {
|
||
return pipePeer(conn, false)
|
||
}, nil), nil
|
||
}
|
||
|
||
// pipePeer 除管道 ACL 外再次验证进程令牌,拒绝非提升管理员及网络登录令牌。
|
||
func pipePeer(conn net.Conn, server bool) (Peer, error) {
|
||
provider, ok := conn.(interface{ Fd() uintptr })
|
||
if !ok || provider.Fd() == 0 {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
var pid uint32
|
||
var err error
|
||
if server {
|
||
err = windows.GetNamedPipeServerProcessId(windows.Handle(provider.Fd()), &pid)
|
||
} else {
|
||
err = windows.GetNamedPipeClientProcessId(windows.Handle(provider.Fd()), &pid)
|
||
}
|
||
if err != nil || pid == 0 {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
process, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
|
||
if err != nil {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
defer windows.CloseHandle(process)
|
||
var token windows.Token
|
||
if windows.OpenProcessToken(process, windows.TOKEN_QUERY, &token) != nil {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
defer token.Close()
|
||
return tokenPeer(token)
|
||
}
|
||
|
||
func tokenPeer(token windows.Token) (Peer, error) {
|
||
user, err := token.GetTokenUser()
|
||
if err != nil || user == nil || user.User.Sid == nil {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
groups, err := token.GetTokenGroups()
|
||
if err != nil {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
network, err := windows.CreateWellKnownSid(windows.WinNetworkSid)
|
||
if err != nil {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
admin, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
|
||
if err != nil {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
enabledAdmin := false
|
||
for _, group := range groups.AllGroups() {
|
||
if group.Sid.Equals(network) {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
if group.Sid.Equals(admin) && group.Attributes&windows.SE_GROUP_ENABLED != 0 &&
|
||
group.Attributes&windows.SE_GROUP_USE_FOR_DENY_ONLY == 0 {
|
||
enabledAdmin = true
|
||
}
|
||
}
|
||
system, err := windows.CreateWellKnownSid(windows.WinLocalSystemSid)
|
||
if err != nil {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
if !user.User.Sid.Equals(system) && (!enabledAdmin || !token.IsElevated()) {
|
||
return Peer{}, ErrUnauthorized
|
||
}
|
||
return Peer{Identity: "sid:" + user.User.Sid.String()}, nil
|
||
}
|
||
|
||
// expectedUID 仅用于 Unix;Windows 通过 SYSTEM 或提升管理员令牌识别服务。
|
||
func Dial(ctx context.Context, path string, expectedUID uint32) (net.Conn, error) {
|
||
path, err := pipePath(path)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
conn, err := winio.DialPipeContext(ctx, path)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if _, err := pipePeer(conn, true); err != nil {
|
||
_ = conn.Close()
|
||
return nil, err
|
||
}
|
||
return conn, nil
|
||
}
|
||
|
||
func AuthorizeCurrentProcess() error {
|
||
token, err := windows.OpenCurrentProcessToken()
|
||
if err != nil {
|
||
return ErrUnauthorized
|
||
}
|
||
defer token.Close()
|
||
_, err = tokenPeer(token)
|
||
return err
|
||
}
|