fix: verify Windows token identity and integrity
This commit is contained in:
@@ -25,10 +25,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
logon32LogonService = 5
|
||||
logon32ProviderDefault = 0
|
||||
securitySystemRID = "S-1-5-18"
|
||||
disableMaxPrivilege = 0x1
|
||||
logon32LogonService = 5
|
||||
logon32ProviderDefault = 0
|
||||
securitySystemRID = "S-1-5-18"
|
||||
securityLocalServiceRID = "S-1-5-19"
|
||||
disableMaxPrivilege = 0x1
|
||||
mediumIntegrityRID = 0x2000
|
||||
highIntegrityRID = 0x3000
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -596,6 +599,10 @@ func createRestrictedMediumToken(source winapi.Token) (winapi.Token, error) {
|
||||
_ = restricted.Close()
|
||||
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
|
||||
}
|
||||
|
||||
@@ -615,27 +622,24 @@ func setMediumIntegrity(token winapi.Token) error {
|
||||
}
|
||||
|
||||
func verifyUserToken(token winapi.Token, candidate *SessionCandidate, normal bool) error {
|
||||
user, err := token.GetTokenUser()
|
||||
if err != nil || user.User.Sid == nil {
|
||||
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 err := verifyTokenIdentity(token, candidate.UserSID, candidate.SessionID); err != nil {
|
||||
return fmt.Errorf("verify active token identity: %w", err)
|
||||
}
|
||||
if normal && token.IsElevated() {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
user, err := target.GetTokenUser()
|
||||
if err != nil || user.User.Sid == nil || user.User.Sid.String() != securitySystemRID {
|
||||
if err := verifyTokenIdentity(target, securitySystemRID, sessionID); err != nil {
|
||||
_ = target.Close()
|
||||
if err != nil {
|
||||
return 0, supervisor.EffectiveIdentity{}, err
|
||||
}
|
||||
return 0, supervisor.EffectiveIdentity{}, errors.New("duplicated service token is not LocalSystem")
|
||||
return 0, supervisor.EffectiveIdentity{}, fmt.Errorf("duplicated service token is invalid: %w", err)
|
||||
}
|
||||
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
|
||||
@@ -733,9 +733,90 @@ func logonLocalService() (winapi.Token, error) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
if process == nil {
|
||||
return supervisor.SignalOutcome{}, ErrProcessNotFound
|
||||
|
||||
Reference in New Issue
Block a user