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
相关产品推荐
相关产品推荐

