fix: verify Windows token identity and integrity

This commit is contained in:
2026-09-06 14:01:06 +00:00
parent 6c85c8e039
commit 09554a9b8c
@@ -25,10 +25,13 @@ import (
) )
const ( const (
logon32LogonService = 5 logon32LogonService = 5
logon32ProviderDefault = 0 logon32ProviderDefault = 0
securitySystemRID = "S-1-5-18" securitySystemRID = "S-1-5-18"
disableMaxPrivilege = 0x1 securityLocalServiceRID = "S-1-5-19"
disableMaxPrivilege = 0x1
mediumIntegrityRID = 0x2000
highIntegrityRID = 0x3000
) )
var ( var (
@@ -596,6 +599,10 @@ func createRestrictedMediumToken(source winapi.Token) (winapi.Token, error) {
_ = restricted.Close() _ = restricted.Close()
return 0, errors.New("restricted active-user token remained elevated") return 0, errors.New("restricted active-user token remained elevated")
} }
if err := verifyTokenIntegrity(restricted, mediumIntegrityRID); err != nil {
_ = restricted.Close()
return 0, err
}
return restricted, nil return restricted, nil
} }
@@ -615,27 +622,24 @@ func setMediumIntegrity(token winapi.Token) error {
} }
func verifyUserToken(token winapi.Token, candidate *SessionCandidate, normal bool) error { func verifyUserToken(token winapi.Token, candidate *SessionCandidate, normal bool) error {
user, err := token.GetTokenUser() if err := verifyTokenIdentity(token, candidate.UserSID, candidate.SessionID); err != nil {
if err != nil || user.User.Sid == nil { return fmt.Errorf("verify active token identity: %w", err)
if err != nil {
return err
}
return errors.New("active token has no user SID")
}
if user.User.Sid.String() != candidate.UserSID {
return errors.New("active token user SID changed during launch selection")
}
var sessionID uint32
var returned uint32
if err := winapi.GetTokenInformation(token, winapi.TokenSessionId, (*byte)(unsafe.Pointer(&sessionID)), uint32(unsafe.Sizeof(sessionID)), &returned); err != nil {
return err
}
if returned != uint32(unsafe.Sizeof(sessionID)) || sessionID != candidate.SessionID {
return errors.New("active token session changed during launch selection")
} }
if normal && token.IsElevated() { if normal && token.IsElevated() {
return errors.New("normal active-user token is elevated") return errors.New("normal active-user token is elevated")
} }
if normal {
if err := verifyTokenIntegrity(token, mediumIntegrityRID); err != nil {
return fmt.Errorf("normal active-user token is not medium integrity: %w", err)
}
} else {
if !token.IsElevated() {
return errors.New("elevated active-user token is not elevated")
}
if err := verifyTokenIntegrity(token, highIntegrityRID); err != nil {
return fmt.Errorf("elevated active-user token is not high integrity: %w", err)
}
}
return nil return nil
} }
@@ -678,15 +682,11 @@ func duplicateServiceTokenForSession(sessionID uint32) (winapi.Token, supervisor
return 0, supervisor.EffectiveIdentity{}, errors.New("active SYSTEM token session did not read back as requested") return 0, supervisor.EffectiveIdentity{}, errors.New("active SYSTEM token session did not read back as requested")
} }
} }
user, err := target.GetTokenUser() if err := verifyTokenIdentity(target, securitySystemRID, sessionID); err != nil {
if err != nil || user.User.Sid == nil || user.User.Sid.String() != securitySystemRID {
_ = target.Close() _ = target.Close()
if err != nil { return 0, supervisor.EffectiveIdentity{}, fmt.Errorf("duplicated service token is invalid: %w", err)
return 0, supervisor.EffectiveIdentity{}, err
}
return 0, supervisor.EffectiveIdentity{}, errors.New("duplicated service token is not LocalSystem")
} }
return target, supervisor.EffectiveIdentity{Context: string(ContextLocalSystem), SessionID: sessionID, UserSID: user.User.Sid.String(), Elevated: true, Integrity: "system"}, nil return target, supervisor.EffectiveIdentity{Context: string(ContextLocalSystem), SessionID: sessionID, UserSID: securitySystemRID, Elevated: true, Integrity: "system"}, nil
} }
// enableTokenPrivilege enables one privilege only for the short operation // enableTokenPrivilege enables one privilege only for the short operation
@@ -733,9 +733,90 @@ func logonLocalService() (winapi.Token, error) {
} }
return 0, syscall.GetLastError() return 0, syscall.GetLastError()
} }
if err := verifyTokenIdentity(token, securityLocalServiceRID, 0); err != nil {
_ = token.Close()
return 0, fmt.Errorf("LocalService token failed identity verification: %w", err)
}
return token, nil return token, nil
} }
func verifyTokenIdentity(token winapi.Token, expectedSID string, expectedSession uint32) error {
user, err := token.GetTokenUser()
if err != nil || user.User.Sid == nil {
if err != nil {
return err
}
return errors.New("token has no user SID")
}
if user.User.Sid.String() != expectedSID {
return fmt.Errorf("token user SID %q does not match %q", user.User.Sid.String(), expectedSID)
}
actualSession, err := tokenInformationUint32(token, winapi.TokenSessionId)
if err != nil {
return err
}
if actualSession != expectedSession {
return fmt.Errorf("token session %d does not match %d", actualSession, expectedSession)
}
tokenType, err := tokenInformationUint32(token, winapi.TokenType)
if err != nil {
return err
}
if tokenType != winapi.TokenPrimary {
return fmt.Errorf("token type %d is not primary", tokenType)
}
return nil
}
func tokenInformationUint32(token winapi.Token, class uint32) (uint32, error) {
buffer, err := tokenInformation(token, class)
if err != nil {
return 0, err
}
if len(buffer) != int(unsafe.Sizeof(uint32(0))) {
return 0, errors.New("token information has unexpected size")
}
return *(*uint32)(unsafe.Pointer(&buffer[0])), nil
}
func tokenInformation(token winapi.Token, class uint32) ([]byte, error) {
var returned uint32
err := winapi.GetTokenInformation(token, class, nil, 0, &returned)
if returned == 0 && err != nil && !errors.Is(err, winapi.ERROR_INSUFFICIENT_BUFFER) {
return nil, err
}
if returned == 0 {
return nil, errors.New("token information returned an empty buffer")
}
buffer := make([]byte, returned)
if err := winapi.GetTokenInformation(token, class, &buffer[0], returned, &returned); err != nil {
return nil, err
}
if returned > uint32(len(buffer)) {
return nil, errors.New("token information length changed during query")
}
return buffer[:returned], nil
}
func verifyTokenIntegrity(token winapi.Token, minimumRID uint32) error {
buffer, err := tokenInformation(token, winapi.TokenIntegrityLevel)
if err != nil {
return err
}
if len(buffer) < int(unsafe.Sizeof(winapi.Tokenmandatorylabel{})) {
return errors.New("token integrity information is truncated")
}
label := (*winapi.Tokenmandatorylabel)(unsafe.Pointer(&buffer[0]))
if label.Label.Sid == nil || label.Label.Sid.SubAuthorityCount() == 0 {
return errors.New("token integrity SID is missing")
}
level := label.Label.Sid.SubAuthority(uint32(label.Label.Sid.SubAuthorityCount()) - 1)
if level < minimumRID {
return fmt.Errorf("token integrity level 0x%x is below 0x%x", level, minimumRID)
}
return nil
}
func (manager *execSupervisor) Signal(ctx context.Context, process supervisor.Process, signal supervisor.SignalKind) (supervisor.SignalOutcome, error) { func (manager *execSupervisor) Signal(ctx context.Context, process supervisor.Process, signal supervisor.SignalKind) (supervisor.SignalOutcome, error) {
if process == nil { if process == nil {
return supervisor.SignalOutcome{}, ErrProcessNotFound return supervisor.SignalOutcome{}, ErrProcessNotFound