public AuthorizationResponse GetAuthorizationResponse(IUser user, IComputer computer, AccessMask requestedAccess) { try { requestedAccess.ValidateAccessMask(); var info = this.authzBuilder.GetAuthorizationInformation(user, computer); if (info.MatchedComputerTargets.Count == 0) { this.logger.LogTrace($"User {user.MsDsPrincipalName} is denied access to the password for computer {computer.MsDsPrincipalName} because the computer did not match any of the configured targets"); return(BuildAuthZResponseFailed(requestedAccess, AuthorizationResponseCode.NoMatchingRuleForComputer)); } IList <SecurityDescriptorTarget> successTargets; if (requestedAccess.HasFlag(AccessMask.LocalAdminPassword)) { successTargets = info.SuccessfulLapsTargets; } else if (requestedAccess.HasFlag(AccessMask.LocalAdminPasswordHistory)) { successTargets = info.SuccessfulLapsHistoryTargets; } else if (requestedAccess.HasFlag(AccessMask.Jit)) { successTargets = info.SuccessfulJitTargets; } else { throw new AccessManagerException($"An invalid access mask combination was requested: {requestedAccess}"); } if (successTargets.Count == 0 || !(info.EffectiveAccess.HasFlag(requestedAccess))) { this.logger.LogTrace($"User {user.MsDsPrincipalName} is denied {requestedAccess} access for computer {computer.MsDsPrincipalName}"); return(BuildAuthZResponseFailed( requestedAccess, AuthorizationResponseCode.NoMatchingRuleForUser, GetNotificationRecipients(info.FailedTargets, false))); } else { var matchedTarget = successTargets[0]; this.logger.LogTrace($"User {user.MsDsPrincipalName} is authorized for {requestedAccess} access to computer {computer.MsDsPrincipalName} from target {matchedTarget.Id}"); return(BuildAuthZResponseSuccess(requestedAccess, matchedTarget, computer)); } } finally { this.authzBuilder.ClearCache(user, computer); } }
private PowerShellAuthorizationResponse AccessMaskToPowerShellAuthorizationResponse(AccessMask allowedAccessMask, AccessMask deniedAccessMask) { PowerShellAuthorizationResponse response = new PowerShellAuthorizationResponse { IsLocalAdminPasswordAllowed = allowedAccessMask.HasFlag(AccessMask.LocalAdminPassword), IsLocalAdminPasswordHistoryAllowed = allowedAccessMask.HasFlag(AccessMask.LocalAdminPasswordHistory), IsJitAllowed = allowedAccessMask.HasFlag(AccessMask.Jit), IsLocalAdminPasswordDenied = deniedAccessMask.HasFlag(AccessMask.LocalAdminPassword), IsLocalAdminPasswordHistoryDenied = deniedAccessMask.HasFlag(AccessMask.LocalAdminPasswordHistory), IsJitDenied = deniedAccessMask.HasFlag(AccessMask.Jit) }; return(response); }
/// <summary> /// Check the status code of create response /// </summary> /// <param name="isNonAdmin">true for non admin credential</param> /// <param name="createOption">The create option set in create request</param> /// <param name="accessMask">The access mark set in create request</param> /// <param name="header">Header of create response</param> /// <param name="response">create response</param> /// <param name="fileNameType">file name type</param> private void CheckCreateResponse(bool isNonAdmin, CreateOptions_Values createOption, AccessMask accessMask, Packet_Header header, CREATE_Response response, FileNameType fileNameType) { switch (fileNameType) { case FileNameType.SymbolicLinkInMiddle: { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_STOPPED_ON_SYMLINK, header.Status, "3.3.5.9: If any intermediate component of the path specified in the create request is a symbolic link, " + "the server MUST return an error as specified in section 2.2.2.1. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); break; } case FileNameType.SymbolicLinkAtLast: { if (!createOption.HasFlag(CreateOptions_Values.FILE_OPEN_REPARSE_POINT)) { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_STOPPED_ON_SYMLINK, header.Status, "3.3.5.9: If the final component of the path is a symbolic link, the server behavior depends on whether the flag FILE_OPEN_REPARSE_POINT was specified in the CreateOptions field of the request. " + "If FILE_OPEN_REPARSE_POINT was specified, the server MUST open the underlying file or directory and return a handle to it. " + "Otherwise, the server MUST return an error as specified in section 2.2.2.1. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } break; } case FileNameType.InvalidSymbolicLink: { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_STOPPED_ON_SYMLINK, header.Status, "3.3.5.9: If the underlying object store returns a failure indicating that the attempted open operation failed due to the presence of a symbolic link in the target path name, " + "the server MUST fail the create operation with the error code STATUS_STOPPED_ON_SYMLINK. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); break; } case FileNameType.NotExistedValidFileName: { if (createOption.HasFlag(CreateOptions_Values.FILE_DELETE_ON_CLOSE) && !(accessMask.HasFlag(AccessMask.DELETE) || accessMask.HasFlag(AccessMask.GENERIC_ALL))) { if (testConfig.Platform == Platform.NonWindows) { BaseTestSite.Assert.AreNotEqual( Smb2Status.STATUS_SUCCESS, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and Treeconnect.MaximalAccess does not include DELETE or GENERIC, the server SHOULD<283> fail the request with STATUS_ACCESS_DENIED. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } else if (testConfig.Platform == Platform.WindowsServer2008 || testConfig.Platform == Platform.WindowsServer2008R2) { //TD does not specify the behavior of windows 2008 and 2008R2, not check here } else if (testConfig.Platform == Platform.WindowsServer2012) { //For platform windows 2012 BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_INVALID_PARAMETER, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and Treeconnect.MaximalAccess does not include DELETE or GENERIC, the server SHOULD<283> fail the request with STATUS_ACCESS_DENIED. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } else { //For platform windows 2012R2 and above BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_ACCESS_DENIED, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and Treeconnect.MaximalAccess does not include DELETE or GENERIC, the server SHOULD<283> fail the request with STATUS_ACCESS_DENIED. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } } else if (createOption.HasFlag(CreateOptions_Values.FILE_DELETE_ON_CLOSE) && isNonAdmin) { //NonAdminAccountCredential does not include DELETE or GENERIC_ALL in MaximalAccess if (testConfig.Platform == Platform.NonWindows) { BaseTestSite.Assert.AreNotEqual( Smb2Status.STATUS_SUCCESS, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and Treeconnect.MaximalAccess does not include DELETE or GENERIC, the server SHOULD<283> fail the request with STATUS_ACCESS_DENIED. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } else if (testConfig.Platform == Platform.WindowsServer2008 || testConfig.Platform == Platform.WindowsServer2008R2) { //TD does not specify te behavior of windows 2008 and 2008R2, not check here } else { //For platform win2012 and 2012R2 BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_ACCESS_DENIED, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and Treeconnect.MaximalAccess does not include DELETE or GENERIC, the server SHOULD<283> fail the request with STATUS_ACCESS_DENIED. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } } else { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_SUCCESS, header.Status, "{0} should be successful, actually server returns with {1}.", header.Command, Smb2Status.GetStatusCode(header.Status)); } break; } case FileNameType.ExistedValidFileName: { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_SUCCESS, header.Status, "{0} should be successful, actually server returns with {1}.", header.Command, Smb2Status.GetStatusCode(header.Status)); } break; case FileNameType.NotExistedValidFileNameWithDotDirectoryName: { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_SUCCESS, header.Status, "{0} should be successful, actually server returns with {1}.", header.Command, Smb2Status.GetStatusCode(header.Status)); } break; case FileNameType.NotExistedValidFileNameWithDoubleDotDirectoryName: { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_INVALID_PARAMETER, header.Status, "3.3.5.9: Windows-based servers accept the path names containing Dot Directory Names specified in [MS-FSCC] section 2.1.5.1 and attempt to normalize the path name by removing the pathname components of \".\" and \"..\"." + "Windows-based servers fail the CREATE request with STATUS_INVALID_PARAMETER if the file name in the Buffer field of the request begins in the form \"subfolder\\..\\\", for example \"x\\..\\y.txt\". " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } break; default: throw new ArgumentException("fileNameType"); } }
/// <summary> /// Check the status code of create response /// </summary> /// <param name="isNonAdmin">true for non admin credential</param> /// <param name="createOption">The create option set in create request</param> /// <param name="accessMask">The access mark set in create request</param> /// <param name="header">Header of create response</param> /// <param name="response">create response</param> /// <param name="fileNameType">file name type</param> private void CheckCreateResponse(bool isNonAdmin, CreateOptions_Values createOption, AccessMask accessMask, Packet_Header header, CREATE_Response response, FileNameType fileNameType) { switch (fileNameType) { case FileNameType.SymbolicLinkInMiddle: { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_STOPPED_ON_SYMLINK, header.Status, "3.3.5.9: If any intermediate component of the path specified in the create request is a symbolic link, " + "the server MUST return an error as specified in section 2.2.2.1. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); break; } case FileNameType.SymbolicLinkAtLast: { if (!createOption.HasFlag(CreateOptions_Values.FILE_OPEN_REPARSE_POINT)) { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_STOPPED_ON_SYMLINK, header.Status, "3.3.5.9: If the final component of the path is a symbolic link, the server behavior depends on whether the flag FILE_OPEN_REPARSE_POINT was specified in the CreateOptions field of the request. " + "If FILE_OPEN_REPARSE_POINT was specified, the server MUST open the underlying file or directory and return a handle to it. " + "Otherwise, the server MUST return an error as specified in section 2.2.2.1. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } break; } case FileNameType.InvalidSymbolicLink: { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_STOPPED_ON_SYMLINK, header.Status, "3.3.5.9: If the underlying object store returns a failure indicating that the attempted open operation failed due to the presence of a symbolic link in the target path name, " + "the server MUST fail the create operation with the error code STATUS_STOPPED_ON_SYMLINK. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); break; } case FileNameType.NotExistedValidFileName: { if (createOption.HasFlag(CreateOptions_Values.FILE_DELETE_ON_CLOSE) && !(accessMask.HasFlag(AccessMask.DELETE) || accessMask.HasFlag(AccessMask.GENERIC_ALL))) { if (testConfig.Platform == Platform.NonWindows) { BaseTestSite.Assert.AreNotEqual( Smb2Status.STATUS_SUCCESS, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and any of the following conditions is TRUE, the server SHOULD<242> fail the request with STATUS_ACCESS_DENIED. " + "DesiredAccess does not include DELETE or GENERIC_ALL. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } else if (testConfig.Platform == Platform.WindowsServer2008 || testConfig.Platform == Platform.WindowsServer2008R2) { //TD does not specify the behavior of windows 2008 and 2008R2, not check here } else if(testConfig.Platform == Platform.WindowsServer2012) { //For platform windows 2012 BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_INVALID_PARAMETER, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and any of the following conditions is TRUE, the server SHOULD<242> fail the request with STATUS_ACCESS_DENIED. " + "DesiredAccess does not include DELETE or GENERIC_ALL. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } else { //For platform windows 2012R2 and above BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_ACCESS_DENIED, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and any of the following conditions is TRUE, the server SHOULD<242> fail the request with STATUS_ACCESS_DENIED. " + "DesiredAccess does not include DELETE or GENERIC_ALL. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } } else if (createOption.HasFlag(CreateOptions_Values.FILE_DELETE_ON_CLOSE) && isNonAdmin) { //NonAdminAccountCredential does not include DELETE or GENERIC_ALL in MaximalAccess if (testConfig.Platform == Platform.NonWindows) { BaseTestSite.Assert.AreNotEqual( Smb2Status.STATUS_SUCCESS, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and any of the following conditions is TRUE, the server SHOULD<242> fail the request with STATUS_ACCESS_DENIED. " + "Treeconnect.MaximalAccess does not include DELETE or GENERIC_ALL. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } else if (testConfig.Platform == Platform.WindowsServer2008 || testConfig.Platform == Platform.WindowsServer2008R2) { //TD does not specify te behavior of windows 2008 and 2008R2, not check here } else { //For platform win2012 and 2012R2 BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_ACCESS_DENIED, header.Status, "3.3.5.9: If the FILE_DELETE_ON_CLOSE flag is set in CreateOptions and any of the following conditions is TRUE, the server SHOULD<242> fail the request with STATUS_ACCESS_DENIED. " + "Treeconnect.MaximalAccess does not include DELETE or GENERIC_ALL. " + "Actually server returns with {0}.", Smb2Status.GetStatusCode(header.Status)); } } else { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_SUCCESS, header.Status, "{0} should be successful, actually server returns with {1}.", header.Command, Smb2Status.GetStatusCode(header.Status)); } break; } case FileNameType.ExistedValidFileName: { BaseTestSite.Assert.AreEqual( Smb2Status.STATUS_SUCCESS, header.Status, "{0} should be successful, actually server returns with {1}.", header.Command, Smb2Status.GetStatusCode(header.Status)); } break; default: throw new ArgumentException("fileNameType"); } }
public async Task <AuthorizationResponse> GetAuthorizationResponse(IUser user, IComputer computer, AccessMask requestedAccess, IPAddress ip) { try { requestedAccess.ValidateAccessMask(); var info = this.authzBuilder.GetAuthorizationInformation(user, computer); if (info.MatchedComputerTargets.Count == 0) { this.logger.LogTrace($"User {user.MsDsPrincipalName} is denied {requestedAccess} access to the computer {computer.MsDsPrincipalName} because the computer did not match any of the configured targets"); return(BuildAuthZResponseFailed(requestedAccess, AuthorizationResponseCode.NoMatchingRuleForComputer)); } IList <SecurityDescriptorTarget> successTargets; if (requestedAccess.HasFlag(AccessMask.LocalAdminPassword)) { successTargets = info.SuccessfulLapsTargets; } else if (requestedAccess.HasFlag(AccessMask.LocalAdminPasswordHistory)) { successTargets = info.SuccessfulLapsHistoryTargets; } else if (requestedAccess.HasFlag(AccessMask.Jit)) { successTargets = info.SuccessfulJitTargets; } else if (requestedAccess.HasFlag(AccessMask.BitLocker)) { successTargets = info.SuccessfulBitLockerTargets; } else { throw new AccessManagerException($"An invalid access mask combination was requested: {requestedAccess}"); } var matchedTarget = successTargets?.FirstOrDefault(t => t.IsActive()); if (!info.EffectiveAccess.HasFlag(requestedAccess) || matchedTarget == null) { this.logger.LogTrace($"User {user.MsDsPrincipalName} is denied {requestedAccess} access for computer {computer.MsDsPrincipalName}"); return(BuildAuthZResponseFailed( requestedAccess, AuthorizationResponseCode.NoMatchingRuleForUser, GetNotificationRecipients(info.FailedTargets, false))); } else { var rateLimitResult = await this.rateLimiter.GetRateLimitResult(user.Sid, ip, requestedAccess); if (rateLimitResult.IsRateLimitExceeded) { return(BuildAuthZResponseRateLimitExceeded(user, computer, requestedAccess, rateLimitResult, ip, matchedTarget)); } this.logger.LogTrace($"User {user.MsDsPrincipalName} is authorized for {requestedAccess} access to computer {computer.MsDsPrincipalName} from target {matchedTarget.Id}"); return(BuildAuthZResponseSuccess(requestedAccess, matchedTarget, computer)); } } finally { this.authzBuilder.ClearCache(user, computer); } }
public AuthorizationInformation BuildAuthorizationInformation(IUser user, IComputer computer, IList <SecurityDescriptorTarget> matchedComputerTargets) { AuthorizationInformation info = new AuthorizationInformation { MatchedComputerTargets = matchedComputerTargets, EffectiveAccess = 0, Computer = computer, User = user }; if (info.MatchedComputerTargets.Count == 0) { return(info); } using AuthorizationContext c = authorizationContextProvider.GetAuthorizationContext(user, computer.Sid); DiscretionaryAcl masterDacl = new DiscretionaryAcl(false, false, info.MatchedComputerTargets.Count); int matchedTargetCount = 0; foreach (var target in info.MatchedComputerTargets) { CommonSecurityDescriptor sd; if (target.IsInactive()) { continue; } if (target.AuthorizationMode == AuthorizationMode.PowershellScript) { if (!this.licenseManager.IsFeatureEnabled(LicensedFeatures.PowerShellAcl)) { continue; } sd = this.powershell.GenerateSecurityDescriptor(user, computer, target.Script, 30); } else { if (string.IsNullOrWhiteSpace(target.SecurityDescriptor)) { this.logger.LogTrace($"Ignoring target {target.Id} with empty security descriptor"); continue; } sd = new CommonSecurityDescriptor(false, false, new RawSecurityDescriptor(target.SecurityDescriptor)); } if (sd == null) { this.logger.LogTrace($"Ignoring target {target.Id} with null security descriptor"); continue; } foreach (var ace in sd.DiscretionaryAcl.OfType <CommonAce>()) { AccessMask mask = (AccessMask)ace.AccessMask; if (mask.HasFlag(AccessMask.LocalAdminPasswordHistory) && ace.AceType == AceType.AccessAllowed) { if (!this.licenseManager.IsFeatureEnabled(LicensedFeatures.LapsHistory)) { mask &= ~AccessMask.LocalAdminPasswordHistory; } } if (mask != 0) { masterDacl.AddAccess((AccessControlType)ace.AceType, ace.SecurityIdentifier, (int)mask, ace.InheritanceFlags, ace.PropagationFlags); } } int i = matchedTargetCount; if (c.AccessCheck(sd, (int)AccessMask.LocalAdminPassword)) { info.SuccessfulLapsTargets.Add(target); matchedTargetCount++; } if (c.AccessCheck(sd, (int)AccessMask.LocalAdminPasswordHistory)) { info.SuccessfulLapsHistoryTargets.Add(target); matchedTargetCount++; } if (c.AccessCheck(sd, (int)AccessMask.Jit)) { info.SuccessfulJitTargets.Add(target); matchedTargetCount++; } if (c.AccessCheck(sd, (int)AccessMask.BitLocker)) { info.SuccessfulBitLockerTargets.Add(target); matchedTargetCount++; } // If the ACE did not grant any permissions to the user, consider it a failure response if (i == matchedTargetCount) { info.FailedTargets.Add(target); } } if (matchedTargetCount > 0) { info.SecurityDescriptor = new CommonSecurityDescriptor(false, false, ControlFlags.DiscretionaryAclPresent, new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), null, null, masterDacl); this.logger.LogTrace($"Resultant security descriptor for computer {computer.MsDsPrincipalName}: {info.SecurityDescriptor.GetSddlForm(AccessControlSections.All)}"); info.EffectiveAccess |= c.AccessCheck(info.SecurityDescriptor, (int)AccessMask.LocalAdminPassword) ? AccessMask.LocalAdminPassword : 0; info.EffectiveAccess |= c.AccessCheck(info.SecurityDescriptor, (int)AccessMask.Jit) ? AccessMask.Jit : 0; info.EffectiveAccess |= c.AccessCheck(info.SecurityDescriptor, (int)AccessMask.LocalAdminPasswordHistory) ? AccessMask.LocalAdminPasswordHistory : 0; info.EffectiveAccess |= c.AccessCheck(info.SecurityDescriptor, (int)AccessMask.BitLocker) ? AccessMask.BitLocker : 0; } this.logger.LogTrace($"User {user.MsDsPrincipalName} has effective access of {info.EffectiveAccess} on computer {computer.MsDsPrincipalName}"); return(info); }