1 using Microsoft.Win32.SafeHandles; 2 using System; 3 using System.Collections; 4 using System.Collections.Generic; 5 using System.IO; 6 using System.Linq; 7 using System.Runtime.ConstrainedExecution; 8 using System.Runtime.InteropServices; 9 using System.Security.AccessControl; 10 using System.Security.Principal; 11 using System.Text; 12 using Ansible.AccessToken; 13 using Ansible.Process; 14 15 namespace Ansible.Become 16 { 17 internal class NativeHelpers 18 { 19 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 20 public struct KERB_S4U_LOGON 21 { 22 public UInt32 MessageType; 23 public UInt32 Flags; 24 public LSA_UNICODE_STRING ClientUpn; 25 public LSA_UNICODE_STRING ClientRealm; 26 } 27 28 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 29 public struct LSA_STRING 30 { 31 public UInt16 Length; 32 public UInt16 MaximumLength; 33 [MarshalAs(UnmanagedType.LPStr)] public string Buffer; 34 operator stringAnsible.Become.NativeHelpers.LSA_STRING35 public static implicit operator string(LSA_STRING s) 36 { 37 return s.Buffer; 38 } 39 operator LSA_STRINGAnsible.Become.NativeHelpers.LSA_STRING40 public static implicit operator LSA_STRING(string s) 41 { 42 if (s == null) 43 s = ""; 44 45 LSA_STRING lsaStr = new LSA_STRING 46 { 47 Buffer = s, 48 Length = (UInt16)s.Length, 49 MaximumLength = (UInt16)(s.Length + 1), 50 }; 51 return lsaStr; 52 } 53 } 54 55 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 56 public struct LSA_UNICODE_STRING 57 { 58 public UInt16 Length; 59 public UInt16 MaximumLength; 60 public IntPtr Buffer; 61 } 62 63 [StructLayout(LayoutKind.Sequential)] 64 public struct SECURITY_LOGON_SESSION_DATA 65 { 66 public UInt32 Size; 67 public Luid LogonId; 68 public LSA_UNICODE_STRING UserName; 69 public LSA_UNICODE_STRING LogonDomain; 70 public LSA_UNICODE_STRING AuthenticationPackage; 71 public SECURITY_LOGON_TYPE LogonType; 72 } 73 74 [StructLayout(LayoutKind.Sequential)] 75 public struct TOKEN_SOURCE 76 { 77 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public char[] SourceName; 78 public Luid SourceIdentifier; 79 } 80 81 public enum SECURITY_LOGON_TYPE 82 { 83 System = 0, // Used only by the Sytem account 84 Interactive = 2, 85 Network, 86 Batch, 87 Service, 88 Proxy, 89 Unlock, 90 NetworkCleartext, 91 NewCredentials, 92 RemoteInteractive, 93 CachedInteractive, 94 CachedRemoteInteractive, 95 CachedUnlock 96 } 97 } 98 99 internal class NativeMethods 100 { 101 [DllImport("advapi32.dll", SetLastError = true)] AllocateLocallyUniqueId( out Luid Luid)102 public static extern bool AllocateLocallyUniqueId( 103 out Luid Luid); 104 105 [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] CreateProcessWithTokenW( SafeNativeHandle hToken, LogonFlags dwLogonFlags, [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName, StringBuilder lpCommandLine, Process.NativeHelpers.ProcessCreationFlags dwCreationFlags, Process.SafeMemoryBuffer lpEnvironment, [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory, Process.NativeHelpers.STARTUPINFOEX lpStartupInfo, out Process.NativeHelpers.PROCESS_INFORMATION lpProcessInformation)106 public static extern bool CreateProcessWithTokenW( 107 SafeNativeHandle hToken, 108 LogonFlags dwLogonFlags, 109 [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName, 110 StringBuilder lpCommandLine, 111 Process.NativeHelpers.ProcessCreationFlags dwCreationFlags, 112 Process.SafeMemoryBuffer lpEnvironment, 113 [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory, 114 Process.NativeHelpers.STARTUPINFOEX lpStartupInfo, 115 out Process.NativeHelpers.PROCESS_INFORMATION lpProcessInformation); 116 117 [DllImport("kernel32.dll")] GetCurrentThreadId()118 public static extern UInt32 GetCurrentThreadId(); 119 120 [DllImport("user32.dll", SetLastError = true)] GetProcessWindowStation()121 public static extern NoopSafeHandle GetProcessWindowStation(); 122 123 [DllImport("user32.dll", SetLastError = true)] GetThreadDesktop( UInt32 dwThreadId)124 public static extern NoopSafeHandle GetThreadDesktop( 125 UInt32 dwThreadId); 126 127 [DllImport("secur32.dll", SetLastError = true)] LsaDeregisterLogonProcess( IntPtr LsaHandle)128 public static extern UInt32 LsaDeregisterLogonProcess( 129 IntPtr LsaHandle); 130 131 [DllImport("secur32.dll", SetLastError = true)] LsaFreeReturnBuffer( IntPtr Buffer)132 public static extern UInt32 LsaFreeReturnBuffer( 133 IntPtr Buffer); 134 135 [DllImport("secur32.dll", SetLastError = true)] LsaGetLogonSessionData( ref Luid LogonId, out SafeLsaMemoryBuffer ppLogonSessionData)136 public static extern UInt32 LsaGetLogonSessionData( 137 ref Luid LogonId, 138 out SafeLsaMemoryBuffer ppLogonSessionData); 139 140 [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)] LsaLogonUser( SafeLsaHandle LsaHandle, NativeHelpers.LSA_STRING OriginName, LogonType LogonType, UInt32 AuthenticationPackage, IntPtr AuthenticationInformation, UInt32 AuthenticationInformationLength, IntPtr LocalGroups, NativeHelpers.TOKEN_SOURCE SourceContext, out SafeLsaMemoryBuffer ProfileBuffer, out UInt32 ProfileBufferLength, out Luid LogonId, out SafeNativeHandle Token, out IntPtr Quotas, out UInt32 SubStatus)141 public static extern UInt32 LsaLogonUser( 142 SafeLsaHandle LsaHandle, 143 NativeHelpers.LSA_STRING OriginName, 144 LogonType LogonType, 145 UInt32 AuthenticationPackage, 146 IntPtr AuthenticationInformation, 147 UInt32 AuthenticationInformationLength, 148 IntPtr LocalGroups, 149 NativeHelpers.TOKEN_SOURCE SourceContext, 150 out SafeLsaMemoryBuffer ProfileBuffer, 151 out UInt32 ProfileBufferLength, 152 out Luid LogonId, 153 out SafeNativeHandle Token, 154 out IntPtr Quotas, 155 out UInt32 SubStatus); 156 157 [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)] LsaLookupAuthenticationPackage( SafeLsaHandle LsaHandle, NativeHelpers.LSA_STRING PackageName, out UInt32 AuthenticationPackage)158 public static extern UInt32 LsaLookupAuthenticationPackage( 159 SafeLsaHandle LsaHandle, 160 NativeHelpers.LSA_STRING PackageName, 161 out UInt32 AuthenticationPackage); 162 163 [DllImport("advapi32.dll")] LsaNtStatusToWinError( UInt32 Status)164 public static extern UInt32 LsaNtStatusToWinError( 165 UInt32 Status); 166 167 [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)] LsaRegisterLogonProcess( NativeHelpers.LSA_STRING LogonProcessName, out SafeLsaHandle LsaHandle, out IntPtr SecurityMode)168 public static extern UInt32 LsaRegisterLogonProcess( 169 NativeHelpers.LSA_STRING LogonProcessName, 170 out SafeLsaHandle LsaHandle, 171 out IntPtr SecurityMode); 172 } 173 174 internal class SafeLsaHandle : SafeHandleZeroOrMinusOneIsInvalid 175 { SafeLsaHandle()176 public SafeLsaHandle() : base(true) { } 177 178 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] ReleaseHandle()179 protected override bool ReleaseHandle() 180 { 181 UInt32 res = NativeMethods.LsaDeregisterLogonProcess(handle); 182 return res == 0; 183 } 184 } 185 186 internal class SafeLsaMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid 187 { SafeLsaMemoryBuffer()188 public SafeLsaMemoryBuffer() : base(true) { } 189 190 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] ReleaseHandle()191 protected override bool ReleaseHandle() 192 { 193 UInt32 res = NativeMethods.LsaFreeReturnBuffer(handle); 194 return res == 0; 195 } 196 } 197 198 internal class NoopSafeHandle : SafeHandle 199 { NoopSafeHandle()200 public NoopSafeHandle() : base(IntPtr.Zero, false) { } 201 public override bool IsInvalid { get { return false; } } 202 203 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] ReleaseHandle()204 protected override bool ReleaseHandle() { return true; } 205 } 206 207 [Flags] 208 public enum LogonFlags 209 { 210 WithProfile = 0x00000001, 211 NetcredentialsOnly = 0x00000002 212 } 213 214 public class BecomeUtil 215 { 216 private static List<string> SERVICE_SIDS = new List<string>() 217 { 218 "S-1-5-18", // NT AUTHORITY\SYSTEM 219 "S-1-5-19", // NT AUTHORITY\LocalService 220 "S-1-5-20" // NT AUTHORITY\NetworkService 221 }; 222 private static int WINDOWS_STATION_ALL_ACCESS = 0x000F037F; 223 private static int DESKTOP_RIGHTS_ALL_ACCESS = 0x000F01FF; 224 CreateProcessAsUser(string username, string password, string command)225 public static Result CreateProcessAsUser(string username, string password, string command) 226 { 227 return CreateProcessAsUser(username, password, LogonFlags.WithProfile, LogonType.Interactive, 228 null, command, null, null, ""); 229 } 230 CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType, string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment, string stdin)231 public static Result CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType, 232 string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment, 233 string stdin) 234 { 235 byte[] stdinBytes; 236 if (String.IsNullOrEmpty(stdin)) 237 stdinBytes = new byte[0]; 238 else 239 { 240 if (!stdin.EndsWith(Environment.NewLine)) 241 stdin += Environment.NewLine; 242 stdinBytes = new UTF8Encoding(false).GetBytes(stdin); 243 } 244 return CreateProcessAsUser(username, password, logonFlags, logonType, lpApplicationName, lpCommandLine, 245 lpCurrentDirectory, environment, stdinBytes); 246 } 247 248 /// <summary> 249 /// Creates a process as another user account. This method will attempt to run as another user with the 250 /// highest possible permissions available. The main privilege required is the SeDebugPrivilege, without 251 /// this privilege you can only run as a local or domain user if the username and password is specified. 252 /// </summary> 253 /// <param name="username">The username of the runas user</param> 254 /// <param name="password">The password of the runas user</param> 255 /// <param name="logonFlags">LogonFlags to control how to logon a user when the password is specified</param> 256 /// <param name="logonType">Controls what type of logon is used, this only applies when the password is specified</param> 257 /// <param name="lpApplicationName">The name of the executable or batch file to executable</param> 258 /// <param name="lpCommandLine">The command line to execute, typically this includes lpApplication as the first argument</param> 259 /// <param name="lpCurrentDirectory">The full path to the current directory for the process, null will have the same cwd as the calling process</param> 260 /// <param name="environment">A dictionary of key/value pairs to define the new process environment</param> 261 /// <param name="stdin">Bytes sent to the stdin pipe</param> 262 /// <returns>Ansible.Process.Result object that contains the command output and return code</returns> CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType, string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment, byte[] stdin)263 public static Result CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType, 264 string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment, byte[] stdin) 265 { 266 // While we use STARTUPINFOEX having EXTENDED_STARTUPINFO_PRESENT causes a parameter validation error 267 Process.NativeHelpers.ProcessCreationFlags creationFlags = Process.NativeHelpers.ProcessCreationFlags.CREATE_UNICODE_ENVIRONMENT; 268 Process.NativeHelpers.PROCESS_INFORMATION pi = new Process.NativeHelpers.PROCESS_INFORMATION(); 269 Process.NativeHelpers.STARTUPINFOEX si = new Process.NativeHelpers.STARTUPINFOEX(); 270 si.startupInfo.dwFlags = Process.NativeHelpers.StartupInfoFlags.USESTDHANDLES; 271 272 SafeFileHandle stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinRead, stdinWrite; 273 ProcessUtil.CreateStdioPipes(si, out stdoutRead, out stdoutWrite, out stderrRead, out stderrWrite, 274 out stdinRead, out stdinWrite); 275 FileStream stdinStream = new FileStream(stdinWrite, FileAccess.Write); 276 277 // $null from PowerShell ends up as an empty string, we need to convert back as an empty string doesn't 278 // make sense for these parameters 279 if (lpApplicationName == "") 280 lpApplicationName = null; 281 282 if (lpCurrentDirectory == "") 283 lpCurrentDirectory = null; 284 285 // A user may have 2 tokens, 1 limited and 1 elevated. GetUserTokens will return both token to ensure 286 // we don't close one of the pairs while the process is still running. If the process tries to retrieve 287 // one of the pairs and the token handle is closed then it will fail with ERROR_NO_SUCH_LOGON_SESSION. 288 List<SafeNativeHandle> userTokens = GetUserTokens(username, password, logonType); 289 try 290 { 291 using (Process.SafeMemoryBuffer lpEnvironment = ProcessUtil.CreateEnvironmentPointer(environment)) 292 { 293 bool launchSuccess = false; 294 StringBuilder commandLine = new StringBuilder(lpCommandLine); 295 foreach (SafeNativeHandle token in userTokens) 296 { 297 // GetUserTokens could return null if an elevated token could not be retrieved. 298 if (token == null) 299 continue; 300 301 if (NativeMethods.CreateProcessWithTokenW(token, logonFlags, lpApplicationName, 302 commandLine, creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi)) 303 { 304 launchSuccess = true; 305 break; 306 } 307 } 308 309 if (!launchSuccess) 310 throw new Process.Win32Exception("CreateProcessWithTokenW() failed"); 311 } 312 return ProcessUtil.WaitProcess(stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinStream, stdin, 313 pi.hProcess); 314 } 315 finally 316 { 317 userTokens.Where(t => t != null).ToList().ForEach(t => t.Dispose()); 318 } 319 } 320 GetUserTokens(string username, string password, LogonType logonType)321 private static List<SafeNativeHandle> GetUserTokens(string username, string password, LogonType logonType) 322 { 323 List<SafeNativeHandle> userTokens = new List<SafeNativeHandle>(); 324 325 SafeNativeHandle systemToken = null; 326 bool impersonated = false; 327 string becomeSid = username; 328 if (logonType != LogonType.NewCredentials) 329 { 330 // If prefixed with .\, we are becoming a local account, strip the prefix 331 if (username.StartsWith(".\\")) 332 username = username.Substring(2); 333 334 NTAccount account = new NTAccount(username); 335 becomeSid = ((SecurityIdentifier)account.Translate(typeof(SecurityIdentifier))).Value; 336 337 // Grant access to the current Windows Station and Desktop to the become user 338 GrantAccessToWindowStationAndDesktop(account); 339 340 // Try and impersonate a SYSTEM token, we need a SYSTEM token to either become a well known service 341 // account or have administrative rights on the become access token. 342 // If we ultimately are becoming the SYSTEM account we want the token with the most privileges available. 343 // https://github.com/ansible/ansible/issues/71453 344 bool mostPrivileges = becomeSid == "S-1-5-18"; 345 systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), 346 new List<string>() { "SeTcbPrivilege" }, mostPrivileges); 347 if (systemToken != null) 348 { 349 try 350 { 351 TokenUtil.ImpersonateToken(systemToken); 352 impersonated = true; 353 } 354 catch (Process.Win32Exception) { } // We tried, just rely on current user's permissions. 355 } 356 } 357 358 // We require impersonation if becoming a service sid or becoming a user without a password 359 if (!impersonated && (SERVICE_SIDS.Contains(becomeSid) || String.IsNullOrEmpty(password))) 360 throw new Exception("Failed to get token for NT AUTHORITY\\SYSTEM required for become as a service account or an account without a password"); 361 362 try 363 { 364 if (becomeSid == "S-1-5-18") 365 userTokens.Add(systemToken); 366 // Cannot use String.IsEmptyOrNull() as an empty string is an account that doesn't have a pass. 367 // We only use S4U if no password was defined or it was null 368 else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials) 369 { 370 // If no password was specified, try and duplicate an existing token for that user or use S4U to 371 // generate one without network credentials 372 SecurityIdentifier sid = new SecurityIdentifier(becomeSid); 373 SafeNativeHandle becomeToken = GetPrimaryTokenForUser(sid); 374 if (becomeToken != null) 375 { 376 userTokens.Add(GetElevatedToken(becomeToken)); 377 userTokens.Add(becomeToken); 378 } 379 else 380 { 381 becomeToken = GetS4UTokenForUser(sid, logonType); 382 userTokens.Add(null); 383 userTokens.Add(becomeToken); 384 } 385 } 386 else 387 { 388 string domain = null; 389 switch (becomeSid) 390 { 391 case "S-1-5-19": 392 logonType = LogonType.Service; 393 domain = "NT AUTHORITY"; 394 username = "LocalService"; 395 break; 396 case "S-1-5-20": 397 logonType = LogonType.Service; 398 domain = "NT AUTHORITY"; 399 username = "NetworkService"; 400 break; 401 default: 402 // Trying to become a local or domain account 403 if (username.Contains(@"\")) 404 { 405 string[] userSplit = username.Split(new char[1] { '\\' }, 2); 406 domain = userSplit[0]; 407 username = userSplit[1]; 408 } 409 else if (!username.Contains("@")) 410 domain = "."; 411 break; 412 } 413 414 SafeNativeHandle hToken = TokenUtil.LogonUser(username, domain, password, logonType, 415 LogonProvider.Default); 416 417 // Get the elevated token for a local/domain accounts only 418 if (!SERVICE_SIDS.Contains(becomeSid)) 419 userTokens.Add(GetElevatedToken(hToken)); 420 userTokens.Add(hToken); 421 } 422 } 423 finally 424 { 425 if (impersonated) 426 TokenUtil.RevertToSelf(); 427 } 428 429 return userTokens; 430 } 431 GetPrimaryTokenForUser(SecurityIdentifier sid, List<string> requiredPrivileges = null, bool mostPrivileges = false)432 private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid, 433 List<string> requiredPrivileges = null, bool mostPrivileges = false) 434 { 435 // According to CreateProcessWithTokenW we require a token with 436 // TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY 437 // Also add in TOKEN_IMPERSONATE so we can get an impersonated token 438 TokenAccessLevels dwAccess = TokenAccessLevels.Query | 439 TokenAccessLevels.Duplicate | 440 TokenAccessLevels.AssignPrimary | 441 TokenAccessLevels.Impersonate; 442 443 SafeNativeHandle userToken = null; 444 int privilegeCount = 0; 445 446 foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess)) 447 { 448 // Filter out any Network logon tokens, using become with that is useless when S4U 449 // can give us a Batch logon 450 NativeHelpers.SECURITY_LOGON_TYPE tokenLogonType = GetTokenLogonType(hToken); 451 if (tokenLogonType == NativeHelpers.SECURITY_LOGON_TYPE.Network) 452 continue; 453 454 List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList(); 455 456 // If the token has less or the same number of privileges than the current token, skip it. 457 if (mostPrivileges && privilegeCount >= actualPrivileges.Count) 458 continue; 459 460 // Check that the required privileges are on the token 461 if (requiredPrivileges != null) 462 { 463 int missing = requiredPrivileges.Where(x => !actualPrivileges.Contains(x)).Count(); 464 if (missing > 0) 465 continue; 466 } 467 468 // Duplicate the token to convert it to a primary token with the access level required. 469 try 470 { 471 userToken = TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed, 472 SecurityImpersonationLevel.Anonymous, TokenType.Primary); 473 privilegeCount = actualPrivileges.Count; 474 } 475 catch (Process.Win32Exception) 476 { 477 continue; 478 } 479 480 // If we don't care about getting the token with the most privileges, escape the loop as we already 481 // have a token. 482 if (!mostPrivileges) 483 break; 484 } 485 486 return userToken; 487 } 488 GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)489 private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType) 490 { 491 NTAccount becomeAccount = (NTAccount)sid.Translate(typeof(NTAccount)); 492 string[] userSplit = becomeAccount.Value.Split(new char[1] { '\\' }, 2); 493 string domainName = userSplit[0]; 494 string username = userSplit[1]; 495 bool domainUser = domainName.ToLowerInvariant() != Environment.MachineName.ToLowerInvariant(); 496 497 NativeHelpers.LSA_STRING logonProcessName = "ansible"; 498 SafeLsaHandle lsaHandle; 499 IntPtr securityMode; 500 UInt32 res = NativeMethods.LsaRegisterLogonProcess(logonProcessName, out lsaHandle, out securityMode); 501 if (res != 0) 502 throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaRegisterLogonProcess() failed"); 503 504 using (lsaHandle) 505 { 506 NativeHelpers.LSA_STRING packageName = domainUser ? "Kerberos" : "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"; 507 UInt32 authPackage; 508 res = NativeMethods.LsaLookupAuthenticationPackage(lsaHandle, packageName, out authPackage); 509 if (res != 0) 510 throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), 511 String.Format("LsaLookupAuthenticationPackage({0}) failed", (string)packageName)); 512 513 int usernameLength = username.Length * sizeof(char); 514 int domainLength = domainName.Length * sizeof(char); 515 int authInfoLength = (Marshal.SizeOf(typeof(NativeHelpers.KERB_S4U_LOGON)) + usernameLength + domainLength); 516 IntPtr authInfo = Marshal.AllocHGlobal((int)authInfoLength); 517 try 518 { 519 IntPtr usernamePtr = IntPtr.Add(authInfo, Marshal.SizeOf(typeof(NativeHelpers.KERB_S4U_LOGON))); 520 IntPtr domainPtr = IntPtr.Add(usernamePtr, usernameLength); 521 522 // KERB_S4U_LOGON has the same structure as MSV1_0_S4U_LOGON (local accounts) 523 NativeHelpers.KERB_S4U_LOGON s4uLogon = new NativeHelpers.KERB_S4U_LOGON 524 { 525 MessageType = 12, // KerbS4ULogon 526 Flags = 0, 527 ClientUpn = new NativeHelpers.LSA_UNICODE_STRING 528 { 529 Length = (UInt16)usernameLength, 530 MaximumLength = (UInt16)usernameLength, 531 Buffer = usernamePtr, 532 }, 533 ClientRealm = new NativeHelpers.LSA_UNICODE_STRING 534 { 535 Length = (UInt16)domainLength, 536 MaximumLength = (UInt16)domainLength, 537 Buffer = domainPtr, 538 }, 539 }; 540 Marshal.StructureToPtr(s4uLogon, authInfo, false); 541 Marshal.Copy(username.ToCharArray(), 0, usernamePtr, username.Length); 542 Marshal.Copy(domainName.ToCharArray(), 0, domainPtr, domainName.Length); 543 544 Luid sourceLuid; 545 if (!NativeMethods.AllocateLocallyUniqueId(out sourceLuid)) 546 throw new Process.Win32Exception("AllocateLocallyUniqueId() failed"); 547 548 NativeHelpers.TOKEN_SOURCE tokenSource = new NativeHelpers.TOKEN_SOURCE 549 { 550 SourceName = "ansible\0".ToCharArray(), 551 SourceIdentifier = sourceLuid, 552 }; 553 554 // Only Batch or Network will work with S4U, prefer Batch but use Network if asked 555 LogonType lsaLogonType = logonType == LogonType.Network 556 ? LogonType.Network 557 : LogonType.Batch; 558 SafeLsaMemoryBuffer profileBuffer; 559 UInt32 profileBufferLength; 560 Luid logonId; 561 SafeNativeHandle hToken; 562 IntPtr quotas; 563 UInt32 subStatus; 564 565 res = NativeMethods.LsaLogonUser(lsaHandle, logonProcessName, lsaLogonType, authPackage, 566 authInfo, (UInt32)authInfoLength, IntPtr.Zero, tokenSource, out profileBuffer, out profileBufferLength, 567 out logonId, out hToken, out quotas, out subStatus); 568 if (res != 0) 569 throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), 570 String.Format("LsaLogonUser() failed with substatus {0}", subStatus)); 571 572 profileBuffer.Dispose(); 573 return hToken; 574 } 575 finally 576 { 577 Marshal.FreeHGlobal(authInfo); 578 } 579 } 580 } 581 GetElevatedToken(SafeNativeHandle hToken)582 private static SafeNativeHandle GetElevatedToken(SafeNativeHandle hToken) 583 { 584 TokenElevationType tet = TokenUtil.GetTokenElevationType(hToken); 585 // We already have the best token we can get, no linked token is really available. 586 if (tet != TokenElevationType.Limited) 587 return null; 588 589 SafeNativeHandle linkedToken = TokenUtil.GetTokenLinkedToken(hToken); 590 TokenStatistics tokenStats = TokenUtil.GetTokenStatistics(linkedToken); 591 592 // We can only use a token if it's a primary one (we had the SeTcbPrivilege set) 593 if (tokenStats.TokenType == TokenType.Primary) 594 return linkedToken; 595 else 596 return null; 597 } 598 GetTokenLogonType(SafeNativeHandle hToken)599 private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken) 600 { 601 TokenStatistics stats = TokenUtil.GetTokenStatistics(hToken); 602 603 SafeLsaMemoryBuffer sessionDataPtr; 604 UInt32 res = NativeMethods.LsaGetLogonSessionData(ref stats.AuthenticationId, out sessionDataPtr); 605 if (res != 0) 606 // Default to Network, if we weren't able to get the actual type treat it as an error and assume 607 // we don't want to run a process with the token 608 return NativeHelpers.SECURITY_LOGON_TYPE.Network; 609 610 using (sessionDataPtr) 611 { 612 NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure( 613 sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA)); 614 return sessionData.LogonType; 615 } 616 } 617 GrantAccessToWindowStationAndDesktop(IdentityReference account)618 private static void GrantAccessToWindowStationAndDesktop(IdentityReference account) 619 { 620 GrantAccess(account, NativeMethods.GetProcessWindowStation(), WINDOWS_STATION_ALL_ACCESS); 621 GrantAccess(account, NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId()), DESKTOP_RIGHTS_ALL_ACCESS); 622 } 623 GrantAccess(IdentityReference account, NoopSafeHandle handle, int accessMask)624 private static void GrantAccess(IdentityReference account, NoopSafeHandle handle, int accessMask) 625 { 626 GenericSecurity security = new GenericSecurity(false, ResourceType.WindowObject, handle, AccessControlSections.Access); 627 security.AddAccessRule(new GenericAccessRule(account, accessMask, AccessControlType.Allow)); 628 security.Persist(handle, AccessControlSections.Access); 629 } 630 631 private class GenericSecurity : NativeObjectSecurity 632 { GenericSecurity(bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested)633 public GenericSecurity(bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested) 634 : base(isContainer, resType, objectHandle, sectionsRequested) { } Persist(SafeHandle handle, AccessControlSections includeSections)635 public new void Persist(SafeHandle handle, AccessControlSections includeSections) { base.Persist(handle, includeSections); } AddAccessRule(AccessRule rule)636 public new void AddAccessRule(AccessRule rule) { base.AddAccessRule(rule); } 637 public override Type AccessRightType { get { throw new NotImplementedException(); } } AccessRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)638 public override AccessRule AccessRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, 639 InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) 640 { throw new NotImplementedException(); } 641 public override Type AccessRuleType { get { return typeof(AccessRule); } } AuditRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags)642 public override AuditRule AuditRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, 643 InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) 644 { throw new NotImplementedException(); } 645 public override Type AuditRuleType { get { return typeof(AuditRule); } } 646 } 647 648 private class GenericAccessRule : AccessRule 649 { GenericAccessRule(IdentityReference identity, int accessMask, AccessControlType type)650 public GenericAccessRule(IdentityReference identity, int accessMask, AccessControlType type) : 651 base(identity, accessMask, false, InheritanceFlags.None, PropagationFlags.None, type) 652 { } 653 } 654 } 655 } 656