github.com/crowdsecurity/crowdsec@v1.6.1/pkg/csplugin/utils_windows.go (about) 1 //go:build windows 2 3 package csplugin 4 5 import ( 6 "fmt" 7 "os" 8 "os/exec" 9 "os/user" 10 "path/filepath" 11 "reflect" 12 "strings" 13 "syscall" 14 "unsafe" 15 16 log "github.com/sirupsen/logrus" 17 "golang.org/x/sys/windows" 18 ) 19 20 var ( 21 advapi32 = syscall.NewLazyDLL("advapi32.dll") 22 23 procGetAce = advapi32.NewProc("GetAce") 24 ) 25 26 type AclSizeInformation struct { 27 AceCount uint32 28 AclBytesInUse uint32 29 AclBytesFree uint32 30 } 31 32 type Acl struct { 33 AclRevision uint8 34 Sbz1 uint8 35 AclSize uint16 36 AceCount uint16 37 Sbz2 uint16 38 } 39 40 type AccessAllowedAce struct { 41 AceType uint8 42 AceFlags uint8 43 AceSize uint16 44 AccessMask uint32 45 SidStart uint32 46 } 47 48 const ACCESS_ALLOWED_ACE_TYPE = 0 49 const ACCESS_DENIED_ACE_TYPE = 1 50 51 func CheckPerms(path string) error { 52 log.Debugf("checking permissions of %s\n", path) 53 54 systemSid, err := windows.CreateWellKnownSid(windows.WELL_KNOWN_SID_TYPE(windows.WinLocalSystemSid)) 55 if err != nil { 56 return fmt.Errorf("while creating SYSTEM well known sid: %w", err) 57 } 58 59 adminSid, err := windows.CreateWellKnownSid(windows.WELL_KNOWN_SID_TYPE(windows.WinBuiltinAdministratorsSid)) 60 if err != nil { 61 return fmt.Errorf("while creating built-in Administrators well known sid: %w", err) 62 } 63 64 currentUser, err := user.Current() 65 if err != nil { 66 return fmt.Errorf("while getting current user: %w", err) 67 } 68 69 currentUserSid, _, _, err := windows.LookupSID("", currentUser.Username) 70 71 if err != nil { 72 return fmt.Errorf("while looking up current user sid: %w", err) 73 } 74 75 sd, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION) 76 if err != nil { 77 return fmt.Errorf("while getting owner security info: %w", err) 78 } 79 if !sd.IsValid() { 80 return fmt.Errorf("security descriptor is invalid") 81 } 82 owner, _, err := sd.Owner() 83 if err != nil { 84 return fmt.Errorf("while getting owner: %w", err) 85 } 86 if !owner.IsValid() { 87 return fmt.Errorf("owner is invalid") 88 } 89 90 if !owner.Equals(systemSid) && !owner.Equals(currentUserSid) && !owner.Equals(adminSid) { 91 return fmt.Errorf("plugin at %s is not owned by SYSTEM, Administrators or by current user, but by %s", path, owner.String()) 92 } 93 94 dacl, _, err := sd.DACL() 95 if err != nil { 96 return fmt.Errorf("while getting DACL: %w", err) 97 } 98 99 if dacl == nil { 100 return fmt.Errorf("no DACL found on plugin, meaning fully permissive access on plugin %s", path) 101 } 102 103 rs := reflect.ValueOf(dacl).Elem() 104 105 /* 106 For reference, the structure of the ACL type is: 107 type ACL struct { 108 aclRevision byte 109 sbz1 byte 110 aclSize uint16 111 aceCount uint16 112 sbz2 uint16 113 } 114 As the field are not exported, we have to use reflection to access them, this should not be an issue as the structure won't (probably) change any time soon. 115 */ 116 aceCount := rs.Field(3).Uint() 117 118 for i := uint64(0); i < aceCount; i++ { 119 ace := &AccessAllowedAce{} 120 ret, _, _ := procGetAce.Call(uintptr(unsafe.Pointer(dacl)), uintptr(i), uintptr(unsafe.Pointer(&ace))) 121 if ret == 0 { 122 return fmt.Errorf("while getting ACE: %w", windows.GetLastError()) 123 } 124 log.Debugf("ACE %d: %+v\n", i, ace) 125 126 if ace.AceType == ACCESS_DENIED_ACE_TYPE { 127 continue 128 } 129 aceSid := (*windows.SID)(unsafe.Pointer(&ace.SidStart)) 130 131 if aceSid.Equals(systemSid) || aceSid.Equals(adminSid) { 132 log.Debugf("Not checking permission for well-known SID %s", aceSid.String()) 133 continue 134 } 135 136 if aceSid.Equals(currentUserSid) { 137 log.Debugf("Not checking permission for current user %s", currentUser.Username) 138 continue 139 } 140 141 log.Debugf("Checking permission for SID %s", aceSid.String()) 142 denyMask := ^(windows.FILE_GENERIC_READ | windows.FILE_GENERIC_EXECUTE) 143 if ace.AccessMask&uint32(denyMask) != 0 { 144 return fmt.Errorf("only SYSTEM, Administrators or the user currently running crowdsec can have more than read/execute on plugin %s", path) 145 } 146 } 147 148 return nil 149 } 150 151 func getProcessAtr() (*syscall.SysProcAttr, error) { 152 var procToken, token windows.Token 153 154 proc := windows.CurrentProcess() 155 defer windows.CloseHandle(proc) 156 157 err := windows.OpenProcessToken(proc, windows.TOKEN_DUPLICATE|windows.TOKEN_ADJUST_DEFAULT| 158 windows.TOKEN_QUERY|windows.TOKEN_ASSIGN_PRIMARY|windows.TOKEN_ADJUST_GROUPS|windows.TOKEN_ADJUST_PRIVILEGES, &procToken) 159 if err != nil { 160 return nil, fmt.Errorf("while opening process token: %w", err) 161 } 162 defer procToken.Close() 163 164 err = windows.DuplicateTokenEx(procToken, 0, nil, windows.SecurityImpersonation, 165 windows.TokenPrimary, &token) 166 if err != nil { 167 return nil, fmt.Errorf("while duplicating token: %w", err) 168 } 169 170 //Remove all privileges from the token 171 172 err = windows.AdjustTokenPrivileges(token, true, nil, 0, nil, nil) 173 174 if err != nil { 175 return nil, fmt.Errorf("while adjusting token privileges: %w", err) 176 } 177 178 //Run the plugin as a medium integrity level process 179 //For some reasons, low level integrity don't work, the plugin and crowdsec cannot communicate over the TCP socket 180 sid, err := windows.CreateWellKnownSid(windows.WELL_KNOWN_SID_TYPE(windows.WinMediumLabelSid)) 181 if err != nil { 182 return nil, err 183 } 184 185 tml := &windows.Tokenmandatorylabel{} 186 tml.Label.Attributes = windows.SE_GROUP_INTEGRITY 187 tml.Label.Sid = sid 188 189 err = windows.SetTokenInformation(token, windows.TokenIntegrityLevel, 190 (*byte)(unsafe.Pointer(tml)), tml.Size()) 191 if err != nil { 192 token.Close() 193 return nil, fmt.Errorf("while setting token information: %w", err) 194 } 195 196 return &windows.SysProcAttr{ 197 CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, 198 Token: syscall.Token(token), 199 }, nil 200 } 201 202 func (pb *PluginBroker) CreateCmd(binaryPath string) (*exec.Cmd, error) { 203 var err error 204 cmd := exec.Command(binaryPath) 205 cmd.SysProcAttr, err = getProcessAtr() 206 if err != nil { 207 return nil, fmt.Errorf("while getting process attributes: %w", err) 208 } 209 return cmd, err 210 } 211 212 func getPluginTypeAndSubtypeFromPath(path string) (string, string, error) { 213 pluginFileName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) 214 215 parts := strings.Split(pluginFileName, "-") 216 if len(parts) < 2 { 217 return "", "", fmt.Errorf("plugin name %s is invalid. Name should be like {type-name}", path) 218 } 219 return strings.Join(parts[:len(parts)-1], "-"), parts[len(parts)-1], nil 220 } 221 222 func pluginIsValid(path string) error { 223 var err error 224 225 // check if it exists 226 if _, err = os.Stat(path); err != nil { 227 return fmt.Errorf("plugin at %s does not exist", path) 228 } 229 230 // check if it is owned by root 231 err = CheckPerms(path) 232 if err != nil { 233 return err 234 } 235 236 return nil 237 }