You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux环境下.NET Core如何获取Active Directory的ntSecurityDescriptor属性?

在Linux上解析Active Directory的ntSecurityDescriptor属性

问题描述

使用Novell.Directory.Ldap库编写的.NET Core代码可以从AD获取ntSecurityDescriptor属性,但代码中依赖的System.Security.AccessControl相关类型(如CommonSecurityDescriptor、KnownAce等)仅支持Windows系统,无法在Linux环境运行,需要替代方案。

原代码片段:

using (var ldapConnection = new LdapConnection { SecureSocketLayer = false })
{
    ldapConnection.Connect(_ldapHost, _ldapPort);
    ldapConnection.Bind(_loginDN, _password);

    var constraints = new LdapSearchConstraints();
    constraints.SetControls(new LdapControl("1.2.840.113556.1.4.801", true
        , new byte[] {48, 3, 2, 1, 7}));
    
    ldapConnection.Constraints = constraints;
    
    var searchFilter = "(objectClass=user)";
    var searchResults = ldapConnection.Search(
        _searchBase,
        LdapConnection.ScopeSub,
        searchFilter,
        null,
        false
    );
    var accessRules = new List<ActiveDirectoryAccessRule>();
    while (searchResults.HasMore())
    {
        var nextEntry = searchResults.Next();
        var userAttributes = nextEntry.GetAttributeSet();
        var byteValue = userAttributes.GetAttribute("nTSecurityDescriptor").ByteValue;
        var security = new CommonSecurityDescriptor(true, true, byteValue, 0);

        foreach (var acl in security.DiscretionaryAcl)
        {
           var accessRule = new ActiveDirectoryAccessRule(
           ((KnownAce)acl).AccessMask,
           ((KnownAce)acl).SecurityIdentifier.Value,
           ((QualifiedAce)acl).AceQualifier,
           acl.IsInherited,
           acl is ObjectAce ? ((ObjectAce)acl).ObjectAceType : Guid.Empty,
           acl is ObjectAce ? ((ObjectAce)acl).InheritedObjectAceType : Guid.Empty,
           acl is ObjectAce ? ((ObjectAce)acl).ObjectAceFlags : ObjectAceFlags.None,
           acl.PropagationFlags
           );
        accessRules.Add( accessRule );
       }
    }
}

解决方案

方法1:手动解析Security Descriptor二进制数据

ntSecurityDescriptor的二进制格式是公开的Microsoft标准结构,我们可以通过纯托管代码手动解析,实现跨平台支持。

步骤1:定义解析所需的枚举和类

// 安全描述符头部结构
public struct SecurityDescriptorHeader
{
    public byte Revision;
    public byte Reserved;
    public ushort ControlFlags;
    public uint OwnerSidOffset;
    public uint GroupSidOffset;
    public uint SaclOffset;
    public uint DaclOffset;
}

// ACE类型枚举
public enum AceType : byte
{
    AccessAllowed = 0x00,
    AccessDenied = 0x01,
    SystemAudit = 0x02,
    AccessAllowedObject = 0x05,
    AccessDeniedObject = 0x06,
    SystemAuditObject = 0x07
}

// ACE标志枚举
[Flags]
public enum AceFlags : byte
{
    Inherited = 0x08,
    ObjectAceTypePresent = 0x01,
    InheritedObjectAceTypePresent = 0x02
}

// 自定义访问规则类,替代Windows专属的ActiveDirectoryAccessRule
public class CustomAdAccessRule
{
    public uint AccessMask { get; set; }
    public string SecurityIdentifier { get; set; }
    public AceQualifier Qualifier { get; set; }
    public bool IsInherited { get; set; }
    public Guid ObjectAceType { get; set; }
    public Guid InheritedObjectAceType { get; set; }
    public ObjectAceFlags ObjectFlags { get; set; }
    public PropagationFlags PropagationFlags { get; set; }
}

public enum AceQualifier
{
    AccessAllowed,
    AccessDenied,
    SystemAudit
}

[Flags]
public enum ObjectAceFlags
{
    None = 0
}

public enum PropagationFlags
{
    None = 0,
    NoPropagateInherit = 0x1,
    InheritOnly = 0x2
}

步骤2:实现二进制解析方法

public static List<CustomAdAccessRule> ParseNtSecurityDescriptor(byte[] sdBytes)
{
    var accessRules = new List<CustomAdAccessRule>();
    if (sdBytes == null || sdBytes.Length == 0) return accessRules;

    using var ms = new MemoryStream(sdBytes);
    using var reader = new BinaryReader(ms);

    // 读取安全描述符头部
    var header = new SecurityDescriptorHeader
    {
        Revision = reader.ReadByte(),
        Reserved = reader.ReadByte(),
        ControlFlags = reader.ReadUInt16(),
        OwnerSidOffset = reader.ReadUInt32(),
        GroupSidOffset = reader.ReadUInt32(),
        SaclOffset = reader.ReadUInt32(),
        DaclOffset = reader.ReadUInt32()
    };

    // 检查是否存在DACL(自主访问控制列表)
    if ((header.ControlFlags & 0x0004) == 0) // SE_DACL_PRESENT标志
        return accessRules;

    // 跳转到DACL起始位置
    ms.Seek(header.DaclOffset, SeekOrigin.Begin);

    // 读取DACL头部
    var aclRevision = reader.ReadByte();
    var aclReserved = reader.ReadByte();
    var aclSize = reader.ReadUInt16();
    var aceCount = reader.ReadUInt16();
    var aclReserved2 = reader.ReadUInt16();

    // 遍历每个ACE
    for (var i = 0; i < aceCount; i++)
    {
        var aceType = (AceType)reader.ReadByte();
        var aceFlags = (AceFlags)reader.ReadByte();
        var aceSize = reader.ReadUInt16();
        var accessMask = reader.ReadUInt32();

        // 解析SID
        var sidString = ParseSid(reader);

        // 解析Object ACE专属字段(如果是Object类型ACE)
        Guid objectType = Guid.Empty;
        Guid inheritedObjectType = Guid.Empty;
        ObjectAceFlags objFlags = ObjectAceFlags.None;

        if (aceType is AceType.AccessAllowedObject or AceType.AccessDeniedObject or AceType.SystemAuditObject)
        {
            objFlags = (ObjectAceFlags)reader.ReadUInt32();
            if ((aceFlags & AceFlags.ObjectAceTypePresent) != 0)
                objectType = new Guid(reader.ReadBytes(16));
            if ((aceFlags & AceFlags.InheritedObjectAceTypePresent) != 0)
                inheritedObjectType = new Guid(reader.ReadBytes(16));
        }

        // 构造自定义访问规则
        var rule = new CustomAdAccessRule
        {
            AccessMask = accessMask,
            SecurityIdentifier = sidString,
            Qualifier = aceType switch
            {
                AceType.AccessAllowed or AceType.AccessAllowedObject => AceQualifier.AccessAllowed,
                AceType.AccessDenied or AceType.AccessDeniedObject => AceQualifier.AccessDenied,
                AceType.SystemAudit or AceType.SystemAuditObject => AceQualifier.SystemAudit,
                _ => AceQualifier.AccessAllowed
            },
            IsInherited = (aceFlags & AceFlags.Inherited) != 0,
            ObjectAceType = objectType,
            InheritedObjectAceType = inheritedObjectType,
            ObjectFlags = objFlags,
            PropagationFlags = PropagationFlags.None // 可根据ACE Flags扩展解析逻辑
        };

        accessRules.Add(rule);
    }

    return accessRules;
}

// 解析SID二进制数据为字符串格式(如S-1-5-21-xxxx-xxxx-xxxx-xxxx)
private static string ParseSid(BinaryReader reader)
{
    var revision = reader.ReadByte();
    var subAuthorityCount = reader.ReadByte();
    var identifierAuthority = reader.ReadBytes(6);
    
    // 将6字节的Identifier Authority转换为UInt64(注意字节序)
    var authValue = BitConverter.ToUInt64(new byte[] { 0, 0 }.Concat(identifierAuthority).ToArray(), 0);
    
    var sidParts = new List<string> { $"S-{revision}-{authValue}" };
    
    for (var i = 0; i < subAuthorityCount; i++)
    {
        sidParts.Add(reader.ReadUInt32().ToString());
    }
    
    return string.Join("-", sidParts);
}

步骤3:修改原代码适配解析逻辑

using (var ldapConnection = new LdapConnection { SecureSocketLayer = false })
{
    ldapConnection.Connect(_ldapHost, _ldapPort);
    ldapConnection.Bind(_loginDN, _password);

    var constraints = new LdapSearchConstraints();
    constraints.SetControls(new LdapControl("1.2.840.113556.1.4.801", true
        , new byte[] {48, 3, 2, 1, 7}));
    
    ldapConnection.Constraints = constraints;
    
    var searchFilter = "(objectClass=user)";
    // 只请求需要的属性,提升查询性能
    var searchAttributes = new[] { "nTSecurityDescriptor", "cn" };
    var searchResults = ldapConnection.Search(
        _searchBase,
        LdapConnection.ScopeSub,
        searchFilter,
        searchAttributes,
        false
    );
    
    var accessRules = new List<CustomAdAccessRule>();
    while (searchResults.HasMore())
    {
        var nextEntry = searchResults.Next();
        var sdAttribute = nextEntry.GetAttribute("nTSecurityDescriptor");
        if (sdAttribute == null) continue;
        
        var sdBytes = sdAttribute.ByteValue;
        var parsedRules = ParseNtSecurityDescriptor(sdBytes);
        accessRules.AddRange(parsedRules);
    }
}

注意事项

  • 手动解析需严格遵循Microsoft的安全描述符和ACE二进制格式标准,需处理不同版本的SD Revision(当前代码适配Revision 1,是AD中最常用的版本)
  • SID解析时要注意Identifier Authority的字节序转换,避免生成错误的SID字符串
  • 可根据业务需求扩展CustomAdAccessRule类,添加更多属性(如ACE的继承标志等)
  • 若需处理SACL(系统访问控制列表),可参照DACL的解析逻辑扩展代码

内容的提问来源于stack exchange,提问作者Amir M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 00:42:02