【问题标题】:C# How to get the AD user cannot change the password property from LDAP attribute userAccountControl?C#如何获取AD用户不能从LDAP属性userAccountControl更改密码属性?
【发布时间】:2022-01-22 17:27:43
【问题描述】:

我正在尝试使用库 Novell.Directory.LdapASP .NET Core 5 中获取用户帐户控制属性。当我搜索用户属性时,我发现属性名称userAccountControl 设置为某个数字。搜索解决方案后,我能够找到:

bool isUserActive = false;
bool userMustChangePassword = false;
bool passwordNeverExpires = false;
bool passwordCannotBeChanged = false;

var flags = Convert.ToInt32(attributeSet.GetAttribute("userAccountControl").StringValue);
isUserActive = !Convert.ToBoolean(flags & 0x0002); //1. checks if user is enabled
if ((flags == 66048)) //65536+512
{
  passwordNeverExpires = true; //2. Password never expires property
}
long value = Convert.ToInt64(attributeSet.GetAttribute("pwdLastSet").StringValue);
if (value == 0)
{
    userMustChangePassword = true; //3. User must change password at next login
}

但我无法弄清楚如何获得 User cannot change password 以及 account is locked 属性?或者我如何比较像0x0040 这样的二进制值?请帮忙

编辑:

我尝试了@Gabriel Luci 在https://www.gabescode.com/active-directory/2019/07/25/nt-security-descriptors.html 中给出的步骤并尝试了以下代码:

var act = attributeSet.GetAttribute("nTSecurityDescriptor").ByteValue;
ADsSecurityUtility secUtility = new ADsSecurityUtility();
IADsSecurityDescriptor convertAttrToSD = (IADsSecurityDescriptor)secUtility.ConvertSecurityDescriptor(act, (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_RAW, (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_IID);
var byteArray = (byte[])secUtility.ConvertSecurityDescriptor(
                            convertAttrToSD,
                            (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_IID,
                            (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_RAW
                         );
var security = new CommonSecurityDescriptor(true, true, byteArray, 0);

如果我检查security,它会显示

我无法查看用户无法更改密码设置?

编辑 2: 根据@Gabriel Luci 更新的答案,它对我有用:

var constraints = new LdapSearchConstraints();
constraints.SetControls(new LdapControl("1.2.840.113556.1.4.801", true, new byte[] { 48, 3, 2, 1, 7 }));
var getNtSecurityByteValue=attributeSet.GetAttribute("nTSecurityDescriptor").ByteValue;
var security = new CommonSecurityDescriptor(true, true, getNtSecurityByteValue, 0);
var self = new SecurityIdentifier(WellKnownSidType.SelfSid, null);
var userChangePassword = new Guid("AB721A53-1E2F-11D0-9819-00AA0040529B");
foreach (var ace in security.DiscretionaryAcl)
{
   if(ace.GetType().Name == "ObjectAce")
   {
      ObjectAce objAce = (ObjectAce)ace;
      if (objAce.AceType == AceType.AccessDeniedObject && objAce.SecurityIdentifier == self && objAce.ObjectAceType == userChangePassword)
      {
          cannotChangePassword = true;
          break;
      }
   }
}

【问题讨论】:

标签: c# asp.net-core active-directory user-account-control


【解决方案1】:

userAccountControl 值是一个位标志,这意味着数字的二进制表示中的每个位都是“开”或“关”,具体取决于它是 1 还是 0。所以十进制值没有意义。

当您检查它是否已启用时,您已经在正确检查该值:

isUserActive = !Convert.ToBoolean(flags & 0x0002); //1. checks if user is enabled

同样,您在检查任何其他标志时也应该这样做。每个值都列在in the documentation

当您检查密码是否设置为永不过期时,您是在比较十进制值,这并不总是给您正确的答案。相反,检查位值:

passwordNeverExpires = Convert.ToBoolean(flags & 0x10000);

类似帐户被锁定:

var accountLocked = Convert.ToBoolean(flags & 0x0010);

对于用户无法更改密码设置,不幸的是,这更加困难并且需要读取用户帐户的权限,而我从未使用 Novell.Directory.Ldap 库完成过。但我可以尝试为您指明正确的方向。

帐户权限在nTSecurityDescriptor 属性中。阅读有关如何从该属性获取字节数组的问题:How to read/set NT-Security-Descriptor attributes?

我写了一篇关于如何将字节数组转换为可用格式的文章:Active Directory: Handling NT Security Descriptor attributes

然后,您将寻找在“用户无法更改密码”复选框被选中时添加的两个权限:

  1. 拒绝将密码更改为“所有人”
  2. 拒绝将密码更改为“SELF”

您可能只需要寻找 #2 就可以侥幸逃脱。

更新:我终于亲自尝试了。我以前从未使用过Novell.Directory.Ldap 库,所以这对我来说是新的。

this answer 的帮助下,我发现您需要为其设置一个LDAP 控件才能完全返回nTSecurityDescriptor 属性:

var constraints = new LdapSearchConstraints();
constraints.SetControls(new LdapControl("1.2.840.113556.1.4.801", true
                                           , new byte[] {48, 3, 2, 1, 7}));

检索对象后,您可以像这样检查权限:

var byteValue = attributeSet.GetAttribute("nTSecurityDescriptor").ByteValue;
var security = new CommonSecurityDescriptor(true, true, byteValue, 0);

var self = new SecurityIdentifier(WellKnownSidType.SelfSid, null);
var userChangePassword = new Guid("AB721A53-1E2F-11D0-9819-00AA0040529B");

var cannotChangePassword = false;

foreach (var ace in (security.DiscretionaryAcl)) {
    if (ace is ObjectAce objAce && objAce.AceType == AceType.AccessDeniedObject
            && objAce.SecurityIdentifier == self && objAce.ObjectAceType == userChangePassword) {
        cannotChangePassword = true;
        break;
    }
}

User-Change-Password 权限的 GUID 取自 Control Access Rights documentation

请注意,您不需要使用IADsSecurityDescriptor,因此您不需要引用Interop.ActiveDs。这是因为我们已经以字节数组的形式给出了值。

【讨论】:

  • 感谢您的回复。 var userCannotChangePassword = Convert.ToBoolean(flags & 0x0040); 这总是返回 false,尽管我有启用此选项的用户。
  • @Varsh 显然你是对的,我知道,但我忘记了。我只是快速搜索并找到了我在 2018 年写的关于此的答案:stackoverflow.com/a/53959606/1202807
  • 嘿,我之前已经浏览过这个链接docs.microsoft.com/en-ca/windows/win32/adsi/…,但实际上并没有在这里了解如何实现它。
  • @Varsh 我用一些资源更新了我的答案,这些资源应该为您指明正确的方向。不幸的是,这并不容易。
  • @Varsh 我用更好的类型检查方法更新了代码中的循环。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-14
  • 1970-01-01
  • 1970-01-01
  • 2013-03-04
相关资源
最近更新 更多