private SafeProcThreadAttributeList AllocateAttributeList() { using (var localDisposalEscrow = new DisposalEscrow()) { SECURITY_CAPABILITIES securityCapabilities = new SECURITY_CAPABILITIES(); this.SetSecurityCapabilities( ref securityCapabilities, this.securityIdentifierHandle, new WELL_KNOWN_SID_TYPE[] { WELL_KNOWN_SID_TYPE.WinCapabilityInternetClientSid }); var attributeListHandle = localDisposalEscrow.Add(new SafeProcThreadAttributeList(1)); var securityCapabilitiesMemory = localDisposalEscrow.Add(new SafeHGlobalBuffer(Marshal.SizeOf(securityCapabilities))); Marshal.StructureToPtr(securityCapabilities, securityCapabilitiesMemory.DangerousGetHandle(), fDeleteOld: false); if (!Methods.UpdateProcThreadAttribute( attributeListHandle.DangerousGetHandle(), dwFlags: 0, attribute: PROC_THREAD_ATTRIBUTES.PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, securityCapabilitiesMemory.DangerousGetHandle(), securityCapabilitiesMemory.Size, lpPreviousValue: IntPtr.Zero, lpReturnSize: IntPtr.Zero)) { throw new SandboxException( $"Failed to update proc thread attribute list (0x{Marshal.GetLastWin32Error():X08})", new Win32Exception());; } this.disposalEscrow.Subsume(localDisposalEscrow); return(attributeListHandle); } }
private void SetSecurityCapabilities( ref SECURITY_CAPABILITIES securityCapabilities, SafeSecurityIdentifier appContainerSid, WELL_KNOWN_SID_TYPE[] appCapabilities) { using (var localDisposalEscrow = new DisposalEscrow()) { securityCapabilities.AppContainerSid = appContainerSid.DangerousGetHandle(); securityCapabilities.Capabilities = IntPtr.Zero; securityCapabilities.CapabilityCount = 0; securityCapabilities.Reserved = 0; if (appCapabilities != null && appCapabilities.Length > 0) { var attributesMemory = localDisposalEscrow.Add(new SafeHGlobalBuffer(Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES)) * appCapabilities.Length)); for (int i = 0; i < appCapabilities.Length; i++) { Int32 sidSize = Constants.SECURITY_MAX_SID_SIZE; var safeMemory = localDisposalEscrow.Add(new SafeHGlobalBuffer(sidSize)); if (!Methods.CreateWellKnownSid(appCapabilities[i], IntPtr.Zero, safeMemory, ref sidSize)) { throw new SandboxException( "Unable to create well known sid.", new Win32Exception()); } var attribute = new SID_AND_ATTRIBUTES { Attributes = SID_ATTRIBUTES.SE_GROUP_ENABLED, Sid = safeMemory.DangerousGetHandle(), }; Marshal.StructureToPtr(attribute, IntPtr.Add(attributesMemory.DangerousGetHandle(), i * Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES))), fDeleteOld: false); } securityCapabilities.Capabilities = attributesMemory.DangerousGetHandle(); securityCapabilities.CapabilityCount = appCapabilities.Length; } this.disposalEscrow.Subsume(localDisposalEscrow); } }
private static SID_AND_ATTRIBUTES ConvertSecurityIdentifierToSidAndAttributes( SecurityIdentifier securityIdentifier, DisposalEscrow disposalEscrow) { var sidBytes = new byte[securityIdentifier.BinaryLength]; securityIdentifier.GetBinaryForm(sidBytes, 0 /* offset */); var nativeBytes = disposalEscrow.Add(new SafeHGlobalBuffer(sidBytes.Length)); Marshal.Copy(sidBytes, 0 /* startIndex */, nativeBytes.DangerousGetHandle(), sidBytes.Length); return(new SID_AND_ATTRIBUTES { Sid = nativeBytes.DangerousGetHandle(), }); }
/// <summary> /// Creates a new desktop with minimal rights. /// </summary> /// <param name="tracer">A tracer instance.</param> /// <returns>The desktop instance.</returns> public static Desktop Create(ITracer tracer, string mandatoryLevelSacl) { using (var disposalEscrow = new DisposalEscrow()) using (Desktop currentDesktop = Desktop.GetCurrent()) { try { string name = "sbox" + DateTime.UtcNow.Ticks; tracer.Trace(nameof(Desktop), "Creating desktop '{0}'", name); SECURITY_ATTRIBUTES securityAttributes = null; if (mandatoryLevelSacl != null) { tracer.Trace(nameof(Desktop), "Generating security attributes for '{0}'", mandatoryLevelSacl); // If a security descriptor (in SDDL form) was provided convert it to binary form and then marshal // it into native memory to that we can pass it in to the native method. var rawSecurityDescriptor = new RawSecurityDescriptor(mandatoryLevelSacl); var binaryForm = new byte[rawSecurityDescriptor.BinaryLength]; rawSecurityDescriptor.GetBinaryForm(binaryForm, 0 /* offset */); var nativeBinaryForm = disposalEscrow.Add(new SafeHGlobalBuffer(binaryForm.Length)); Marshal.Copy(binaryForm, 0 /* startIndex */, nativeBinaryForm.DangerousGetHandle(), binaryForm.Length); securityAttributes = new SECURITY_ATTRIBUTES { lpSecurityDescriptor = nativeBinaryForm.DangerousGetHandle() }; } // Since we're creating the desktop we'll ask for all rights so that we have the ability to modify security as // required. Since the created desktop will be referenced by name when creating the process this will not impact // the security of the desktop. IntPtr unsafeDesktopHandle = Methods.CreateDesktop( name, device: null, deviceMode: null, flags: 0, accessMask: DESKTOP_RIGHTS.GENERIC_ALL, attributes: securityAttributes); if (unsafeDesktopHandle == IntPtr.Zero) { throw new SandboxException( $"Unable to create new desktop", new Win32Exception()); } tracer.Trace(nameof(Desktop), "Desktop successfully created"); return(new Desktop(unsafeDesktopHandle, ownsHandle: true) { Name = name, }); } finally { // Since CreateDesktop automatically switches to the new desktop we need to make sure that we switch back // to the original one. currentDesktop.MakeCurrent(); } } }
private SafeTokenHandle GetRestrictedToken() { if (this.restrictedTokenHandle == null) { using (var localDisposalEscrow = new DisposalEscrow()) { // The first step in creating a restricted token is to enumerate the existing one and decide which of // the SIDs we want to deny, and which of the SIDs we want to restrict. For our purposes we want: // // DENY all except: // CurrentUser // Everyone // Users // Interactive // Logon // // RESTRICT only: // CurrentUser // Everyone // Users // Logon // Restricted var sidsToDeny = new List <SID_AND_ATTRIBUTES>(); var sidsToRestrict = new List <SID_AND_ATTRIBUTES>(); foreach (IdentityReference identityReference in this.identityProvider.CurrentUser.Groups) { var securityIdentifier = (SecurityIdentifier)identityReference.Translate(typeof(SecurityIdentifier)); if (securityIdentifier.Equals(this.identityProvider.EveryoneSid) || securityIdentifier.Equals(this.identityProvider.UsersSid)) { // Add the group to the restricted list if it's one of the special groups we want to allow. sidsToRestrict.Add( RestrictedProcessProtection.ConvertSecurityIdentifierToSidAndAttributes(securityIdentifier, localDisposalEscrow)); } else if (!securityIdentifier.Equals(this.identityProvider.InteractiveSid)) { // Otherwise add the group to the deny list, but special case the Interactive SID which // shouldn't be denied and also not restricted. sidsToDeny.Add( RestrictedProcessProtection.ConvertSecurityIdentifierToSidAndAttributes(securityIdentifier, localDisposalEscrow)); } } // There are a set of non-group SIDs that we want to always restrict and never deny, so add them to // the appropriate lists. sidsToRestrict.Add( RestrictedProcessProtection.ConvertSecurityIdentifierToSidAndAttributes( this.identityProvider.CurrentUserSid, localDisposalEscrow)); sidsToRestrict.Add( RestrictedProcessProtection.ConvertSecurityIdentifierToSidAndAttributes( this.identityProvider.LogonSid, localDisposalEscrow)); sidsToRestrict.Add( RestrictedProcessProtection.ConvertSecurityIdentifierToSidAndAttributes( this.identityProvider.RestrictedSid, localDisposalEscrow)); this.tracer.Trace(nameof(RestrictedProcessProtection), "Creating restricted token"); // Now that we have all the SIDs in the correct buckets we can call the native method to generate our // new token. SafeTokenHandle newTokenHandle; if (!Methods.CreateRestrictedToken( localDisposalEscrow.Add(new SafeTokenHandle(this.identityProvider.CurrentUser.Token, ownsHandle: false)), RESTRICTED_TOKEN_FLAGS.DISABLE_MAX_PRIVILEGE, (uint)sidsToDeny.Count, sidsToDeny.ToArray(), 0 /* deletePrivilegeCount */, null /* privilegesToDelete */, (uint)sidsToRestrict.Count, sidsToRestrict.ToArray(), out newTokenHandle)) { throw new SandboxException( "Unable to create restricted token", new Win32Exception()); } // We'll add the token here in case something breaks, but we'll remove it before we return the token at // the end of this method. localDisposalEscrow.Add(newTokenHandle); this.tracer.Trace(nameof(RestrictedProcessProtection), "Adding mandatory low SID"); // Create the low integrity SID. SafeSecurityIdentifier restrictedSecurityIdentifierNative; if (!Methods.AllocateAndInitializeSid( ref Constants.SECURITY_MANDATORY_LABEL_AUTHORITY, 1 /* nSubAuthorityCount */, (int)SECURITY_MANDATOR_RID.LOW, 0 /* dwSubAuthority1 */, 0 /* dwSubAuthority2 */, 0 /* dwSubAuthority3 */, 0 /* dwSubAuthority4 */, 0 /* dwSubAuthority5 */, 0 /* dwSubAuthority6 */, 0 /* dwSubAuthority7 */, out restrictedSecurityIdentifierNative)) { throw new SandboxException( "Unable to allocate and initialize low integrity SID", new Win32Exception()); } // Set the integrity level in the access token to low using the low integrity SID we just created. TOKEN_MANDATORY_LABEL managedTokenMandatoryLabel; managedTokenMandatoryLabel.Label.Attributes = SID_ATTRIBUTES.SE_GROUP_INTEGRITY; managedTokenMandatoryLabel.Label.Sid = restrictedSecurityIdentifierNative.DangerousGetHandle(); var nativeTokenMandatoryLabel = localDisposalEscrow.Add(new SafeHGlobalBuffer(Marshal.SizeOf(managedTokenMandatoryLabel))); Marshal.StructureToPtr(managedTokenMandatoryLabel, nativeTokenMandatoryLabel.DangerousGetHandle(), false); if (!Methods.SetTokenInformation( newTokenHandle, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, nativeTokenMandatoryLabel.DangerousGetHandle(), nativeTokenMandatoryLabel.Size + Methods.GetLengthSid(restrictedSecurityIdentifierNative.DangerousGetHandle()))) { throw new SandboxException( "Unable to set token integrity level", new Win32Exception()); } this.tracer.Trace(nameof(RestrictedProcessProtection), "Granting login SID access to token"); // Now modify the access token to set the DACL so that the Logon SID has appropriate access to any // processes started using the token. Without this things mostly work, but the process will be // restricted in ways that .NET doesn't like (E.g. it's not possible to get the token associated with // the process) var defaultDaclDescriptor = new RawSecurityDescriptor( string.Format( RestrictedProcessProtection.DefaultDaclTemplate, this.identityProvider.LogonSid.Value)); var managedDefaultDacl = new byte[defaultDaclDescriptor.DiscretionaryAcl.BinaryLength]; defaultDaclDescriptor.DiscretionaryAcl.GetBinaryForm(managedDefaultDacl, 0 /* offset */); var nativeDefaultDacl = localDisposalEscrow.Add(new SafeHGlobalBuffer(managedDefaultDacl.Length)); Marshal.Copy( managedDefaultDacl, 0 /* startIndex */, nativeDefaultDacl.DangerousGetHandle(), managedDefaultDacl.Length); TOKEN_DEFAULT_DACL managedTokenDefaultDacl; managedTokenDefaultDacl.DefaultDacl = nativeDefaultDacl.DangerousGetHandle(); var nativeTokenDefaultDacl = localDisposalEscrow.Add(new SafeHGlobalBuffer(Marshal.SizeOf(managedTokenDefaultDacl))); Marshal.StructureToPtr(managedTokenDefaultDacl, nativeTokenDefaultDacl.DangerousGetHandle(), false /* fDeleteOld */); if (!Methods.SetTokenInformation( newTokenHandle, TOKEN_INFORMATION_CLASS.TokenDefaultDacl, nativeTokenDefaultDacl.DangerousGetHandle(), nativeTokenDefaultDacl.Size)) { throw new SandboxException( "Unable to set the DACL to give the Logon SID access to the process", new Win32Exception()); } this.disposalEscrow.Transfer(localDisposalEscrow, newTokenHandle); this.restrictedTokenHandle = newTokenHandle; } } return(this.restrictedTokenHandle); }