Skip to content

Commit 57aa962

Browse files
author
Michael Cuomo
authored
Revert "Revert "feat: AdminSDHolder updates - BED-6221 (#165)" (#173)"
This reverts commit 34b27df.
1 parent d53cc04 commit 57aa962

File tree

5 files changed

+158
-29
lines changed

5 files changed

+158
-29
lines changed

Sharphound.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,4 @@
4444
<Message Text="Test" />
4545
<Exec Command="powershell -ep bypass -c &quot;. '$(ProjectDir)src\Powershell\Out-CompressedDLL.ps1';Out-CompressedDll -FilePath '$(TargetPath)' -TemplatePath '$(ProjectDir)src\\Powershell\Template.ps1' | Out-File -Encoding ASCII '$(TargetDir)$(TargetName).ps1'&quot;" />
4646
</Target>
47-
</Project>
47+
</Project>

src/BaseContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public BaseContext(ILogger logger, LdapConfig ldapConfig, Flags flags)
2929
LDAPUtils = new LdapUtils();
3030
LDAPUtils.SetLdapConfig(ldapConfig);
3131
CancellationTokenSource = new CancellationTokenSource();
32+
AdminSDHolderHash = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
3233
}
3334

3435
public bool IsFaulted { get; set; }
@@ -122,6 +123,11 @@ public string ResolveFileName(string filename, string extension, bool addTimesta
122123
public string LocalAdminPassword { get; set; }
123124
public bool LocalAdminSessionEnum { get; set; }
124125

126+
/// <summary>
127+
/// Dictionary mapping domain names to their AdminSDHolder authoritative security descriptor hash
128+
/// </summary>
129+
public Dictionary<string, string> AdminSDHolderHash { get; set; }
130+
125131
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
126132
// ~Context()
127133
// {

src/Client/Context.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,10 @@ public interface IContext
7676
EnumerationDomain[] Domains { get; set; }
7777
void UpdateLoopTime();
7878
public HashSet<string> CollectedDomainSids { get; }
79+
80+
/// <summary>
81+
/// Dictionary mapping domain names to their AdminSDHolder authoritative security descriptor hash
82+
/// </summary>
83+
Dictionary<string, string> AdminSDHolderHash { get; set; }
7984
}
8085
}

src/Producers/LdapProducer.cs

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using SharpHoundCommonLib;
88
using SharpHoundCommonLib.Enums;
99
using SharpHoundCommonLib.OutputTypes;
10+
using SharpHoundCommonLib.Processors;
1011

1112
namespace Sharphound.Producers
1213
{
@@ -54,29 +55,101 @@ public override async Task Produce()
5455

5556
Context.CollectedDomainSids.Add(domain.DomainSid);
5657

57-
foreach (var filter in ldapData.Filter.GetFilterList()) {
58-
foreach (var partitionedFilter in GetPartitionedFilter(filter)) {
59-
await foreach (var result in Context.LDAPUtils.PagedQuery(new LdapQueryParameters() {
60-
LDAPFilter = partitionedFilter,
61-
Attributes = ldapData.Attributes,
62-
DomainName = domain.Name,
63-
SearchBase = Context.SearchBase,
64-
IncludeSecurityDescriptor = Context.ResolvedCollectionMethods.HasFlag(CollectionMethod.ACL)
65-
}, cancellationToken)){
66-
if (!result.IsSuccess) {
58+
// Only collect AdminSDHolder data if ACL collection is enabled
59+
if (Context.ResolvedCollectionMethods.HasFlag(CollectionMethod.ACL))
60+
{
61+
Context.Logger.LogInformation("Collecting AdminSDHolder data for {Domain}", domain.Name);
62+
if (await Context.LDAPUtils.GetNamingContextPath(domain.Name, NamingContext.Default) is
63+
(true, var domainDN))
64+
{
65+
// Now use the retrieved distinguished name for the search base
66+
var adminSdHolderParameters = new LdapQueryParameters()
67+
{
68+
LDAPFilter = "(objectClass=*)",
69+
Attributes = new[]
70+
{ "nTSecurityDescriptor", "distinguishedName", "objectClass", "objectSid" },
71+
DomainName = domain.Name,
72+
SearchBase = $"cn=adminsdholder,cn=system,{domainDN}",
73+
IncludeSecurityDescriptor = true
74+
};
75+
76+
// Query and get the first result
77+
var adminSdHolderResult = await Context.LDAPUtils
78+
.Query(adminSdHolderParameters, cancellationToken)
79+
.FirstOrDefaultAsync(LdapResult<IDirectoryObject>.Fail());
80+
81+
82+
if (adminSdHolderResult.IsSuccess)
83+
{
84+
var searchResult = adminSdHolderResult.Value;
85+
// Don't write AdminSDHolder to the channel as it's only needed for its security descriptor
86+
// await Channel.Writer.WriteAsync(searchResult, cancellationToken);
87+
88+
89+
// Create the ACL hash using ACLProcessor
90+
if (searchResult.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var sd))
91+
{
92+
// enable for debugging purposes
93+
//var B64 = Convert.ToBase64String(sd);
94+
//Context.Logger.LogDebug("{Domain} AdminSDHolder SD Bytes: {Bytes}", domain.Name, B64);
95+
96+
// Create an instance of ACLProcessor - _aclProcessor from ObjectProcessors isn't in this context
97+
var aclProcessor = new ACLProcessor(Context.LDAPUtils);
98+
99+
// Calculate the authoritative SD based on a hash of the implicit ACLs & AclProtected
100+
var authoritativeSd = aclProcessor.CalculateImplicitACLHash(sd);
101+
102+
// Store the hashes in the Context for later use
103+
Context.AdminSDHolderHash[domain.Name] = authoritativeSd;
104+
105+
Context.Logger.LogInformation(
106+
"AdminSDHolder ACL hash {Hash} calculated for {Domain}.", authoritativeSd,
107+
domain.Name);
108+
}
109+
110+
Context.Logger.LogTrace("AdminSDHolder data collected for {Domain}", domain.Name);
111+
}
112+
else
113+
{
114+
Context.Logger.LogWarning("Failed to collect AdminSDHolder data for {Domain}: {Message}",
115+
domain.Name, adminSdHolderResult.Error);
116+
}
117+
}
118+
else
119+
{
120+
Context.Logger.LogWarning("Failed to get distinguished name for domain {Domain}", domain.Name);
121+
}
122+
}
123+
124+
foreach (var filter in ldapData.Filter.GetFilterList())
125+
{
126+
foreach (var partitionedFilter in GetPartitionedFilter(filter))
127+
{
128+
await foreach (var result in Context.LDAPUtils.PagedQuery(new LdapQueryParameters()
129+
{
130+
LDAPFilter = partitionedFilter,
131+
Attributes = ldapData.Attributes,
132+
DomainName = domain.Name,
133+
SearchBase = Context.SearchBase,
134+
IncludeSecurityDescriptor = Context.ResolvedCollectionMethods.HasFlag(CollectionMethod.ACL)
135+
}, cancellationToken))
136+
{
137+
if (!result.IsSuccess)
138+
{
67139
Context.Logger.LogError("Error during main ldap query:{Message} ({Code})", result.Error, result.ErrorCode);
68140
break;
69141
}
70142

71143
var searchResult = result.Value;
72144

73-
if (searchResult.TryGetDistinguishedName(out var distinguishedName)) {
145+
if (searchResult.TryGetDistinguishedName(out var distinguishedName))
146+
{
74147
var lower = distinguishedName.ToLower();
75148
if (lower.Contains("cn=domainupdates,cn=system"))
76149
continue;
77150
if (lower.Contains("cn=policies,cn=system") && (lower.StartsWith("cn=user") || lower.StartsWith("cn=machine")))
78151
continue;
79-
152+
80153
await Channel.Writer.WriteAsync(searchResult, cancellationToken);
81154
Context.Logger.LogTrace("Producer wrote {DistinguishedName} to channel", distinguishedName);
82155
}
@@ -85,7 +158,7 @@ public override async Task Produce()
85158
}
86159
}
87160
}
88-
161+
89162
private IEnumerable<string> GetPartitionedFilter(string originalFilter) {
90163
if (Context.Flags.ParititonLdapQueries) {
91164
for (var i = 0; i < 256; i++) {
@@ -117,7 +190,7 @@ public override async Task ProduceConfigNC()
117190
if (!configurationNCsCollected.Add(path)) {
118191
continue;
119192
}
120-
193+
121194
Context.Logger.LogInformation("Beginning LDAP search for {Domain} Configuration NC", domain.Name);
122195
foreach (var filter in configNcData.Filter.GetFilterList()) {
123196
await foreach (var result in Context.LDAPUtils.PagedQuery(new LdapQueryParameters() {

src/Runtime/ObjectProcessors.cs

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,22 @@ private static Dictionary<string, object> GetCommonProperties(IDirectoryObject e
120120
return props;
121121
}
122122

123+
// Helper method to handle AdminSDHolder processing
124+
private string GetAdminSdHolderHash(string domain)
125+
{
126+
if (_context.AdminSDHolderHash != null &&
127+
_context.AdminSDHolderHash.TryGetValue(domain, out var hash))
128+
{
129+
return hash;
130+
}
131+
return null;
132+
}
123133

124134
private async Task<User> ProcessUserObject(IDirectoryObject entry,
125-
ResolvedSearchResult resolvedSearchResult) {
126-
var ret = new User {
135+
ResolvedSearchResult resolvedSearchResult)
136+
{
137+
var ret = new User
138+
{
127139
ObjectIdentifier = resolvedSearchResult.ObjectId
128140
};
129141

@@ -134,7 +146,11 @@ private async Task<User> ProcessUserObject(IDirectoryObject entry,
134146
if (entry.IsGMSA()) ret.Properties.Add("gmsa", true);
135147
ret.DomainSID = resolvedSearchResult.DomainSid;
136148

137-
if (_methods.HasFlag(CollectionMethod.ACL)) {
149+
if (_methods.HasFlag(CollectionMethod.ACL))
150+
{
151+
// AdminSDHolderProtected only on security principal nodes: User, Computer, Group
152+
var adminSdHolderHash = GetAdminSdHolderHash(resolvedSearchResult.Domain);
153+
138154
var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true)
139155
.ToArrayAsync(cancellationToken: _cancellationToken);
140156
ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid));
@@ -144,17 +160,25 @@ private async Task<User> ProcessUserObject(IDirectoryObject entry,
144160
.ToArrayAsync(cancellationToken: _cancellationToken)).ToArray();
145161
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
146162
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
163+
var isAdminSdHolderProtected = _aclProcessor.IsAdminSDHolderProtected(entry, adminSdHolderHash);
164+
if (isAdminSdHolderProtected != null)
165+
{
166+
ret.Properties.Add("adminsdholderprotected", isAdminSdHolderProtected);
167+
}
147168
}
148169

149-
if (_methods.HasFlag(CollectionMethod.Group)) {
170+
if (_methods.HasFlag(CollectionMethod.Group))
171+
{
150172
var pg = entry.GetProperty(LDAPProperties.PrimaryGroupID);
151173
ret.PrimaryGroupSID = GroupProcessor.GetPrimaryGroupInfo(pg, resolvedSearchResult.ObjectId);
152174
}
153175

154-
if (_methods.HasFlag(CollectionMethod.ObjectProps)) {
176+
if (_methods.HasFlag(CollectionMethod.ObjectProps))
177+
{
155178
var userProps = await _ldapPropertyProcessor.ReadUserProperties(entry, resolvedSearchResult);
156179
ret.Properties = ContextUtils.Merge(ret.Properties, userProps.Props);
157-
if (_context.Flags.CollectAllProperties) {
180+
if (_context.Flags.CollectAllProperties)
181+
{
158182
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
159183
ret.Properties);
160184
}
@@ -164,14 +188,17 @@ private async Task<User> ProcessUserObject(IDirectoryObject entry,
164188
ret.UnconstrainedDelegation = userProps.UnconstrainedDelegation;
165189
}
166190

167-
if (_methods.HasFlag(CollectionMethod.SPNTargets)) {
191+
if (_methods.HasFlag(CollectionMethod.SPNTargets))
192+
{
168193
ret.SPNTargets = await _spnProcessor.ReadSPNTargets(resolvedSearchResult, entry)
169194
.ToArrayAsync(cancellationToken: _cancellationToken);
170195
}
171196

172-
if (_methods.HasFlag(CollectionMethod.Container)) {
197+
if (_methods.HasFlag(CollectionMethod.Container))
198+
{
173199
if (entry.TryGetDistinguishedName(out var dn) &&
174-
await _containerProcessor.GetContainingObject(dn) is (true, var container)) {
200+
await _containerProcessor.GetContainingObject(dn) is (true, var container))
201+
{
175202
ret.ContainedBy = container;
176203
}
177204
}
@@ -197,14 +224,23 @@ Channel<CSVComputerStatus> compStatusChannel
197224
ret.IsDC = resolvedSearchResult.IsDomainController;
198225
ret.DomainSID = resolvedSearchResult.DomainSid;
199226

200-
if (_methods.HasFlag(CollectionMethod.ACL)) {
227+
if (_methods.HasFlag(CollectionMethod.ACL))
228+
{
229+
// AdminSDHolderProtected only on security principal nodes: User, Computer, Group
230+
var adminSdHolderHash = GetAdminSdHolderHash(resolvedSearchResult.Domain);
231+
201232
var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true)
202233
.ToArrayAsync(cancellationToken: _cancellationToken);
203234
ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid));
204235
ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid));
205236
ret.Aces = aces;
206237
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
207238
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
239+
var isAdminSdHolderProtected = _aclProcessor.IsAdminSDHolderProtected(entry, adminSdHolderHash);
240+
if (isAdminSdHolderProtected != null)
241+
{
242+
ret.Properties.Add("adminsdholderprotected", isAdminSdHolderProtected);
243+
}
208244
}
209245

210246
if (_methods.HasFlag(CollectionMethod.Group)) {
@@ -329,7 +365,7 @@ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus {
329365
// var cred = _context.Flags.DoLocalAdminSessionEnum
330366
// ? new NetworkCredential(_context.LocalAdminUsername, _context.LocalAdminPassword, ".")
331367
// : null;
332-
//
368+
//
333369
// var evntProcessor = new EventLogProcessor(
334370
// _context.LDAPUtils,
335371
// _log,
@@ -378,11 +414,11 @@ private async void ProcessDomainController(ResolvedSearchResult resolvedSearchRe
378414
ret.Properties.Add("ldapavailable", ldapServices.HasLdap);
379415
ret.Properties.Add("ldapsavailable", ldapServices.HasLdaps);
380416
if (ldapServices.IsChannelBindingDisabled.Collected) {
381-
ret.Properties.Add("ldapsepa", !ldapServices.IsChannelBindingDisabled.Result);
417+
ret.Properties.Add("ldapsepa", !ldapServices.IsChannelBindingDisabled.Result);
382418
}
383419

384420
if (ldapServices.IsSigningRequired.Collected) {
385-
ret.Properties.Add("ldapsigning", ldapServices.IsSigningRequired.Result);
421+
ret.Properties.Add("ldapsigning", ldapServices.IsSigningRequired.Result);
386422
}
387423
}
388424
}
@@ -396,14 +432,23 @@ private async Task<Group> ProcessGroupObject(IDirectoryObject entry,
396432
ret.Properties = new Dictionary<string, object>(GetCommonProperties(entry, resolvedSearchResult));
397433
ret.Properties.Add("samaccountname", entry.GetProperty(LDAPProperties.SAMAccountName));
398434

399-
if (_methods.HasFlag(CollectionMethod.ACL)) {
435+
if (_methods.HasFlag(CollectionMethod.ACL))
436+
{
437+
// AdminSDHolderProtected only on security principal nodes: User, Computer, Group
438+
var adminSdHolderHash = GetAdminSdHolderHash(resolvedSearchResult.Domain);
439+
400440
var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true)
401441
.ToArrayAsync(cancellationToken: _cancellationToken);
402442
ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid));
403443
ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid));
404444
ret.Aces = aces;
405445
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
406446
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
447+
var isAdminSdHolderProtected = _aclProcessor.IsAdminSDHolderProtected(entry, adminSdHolderHash);
448+
if (isAdminSdHolderProtected != null)
449+
{
450+
ret.Properties.Add("adminsdholderprotected", isAdminSdHolderProtected);
451+
}
407452
}
408453

409454
if (_methods.HasFlag(CollectionMethod.Group))

0 commit comments

Comments
 (0)