Skip to content

Commit 88d8303

Browse files
author
Michael Cuomo
authored
Alter ReadGroupProperties to Add HasSIDHistory +Minor Version (#223)
* feat: Update ReadGroupProperties to Return HasSidHistory. Minor Version bump from Breaking Change * test: Add Test for new Collection of SIDHistory on Groups * chore: Creating a Shared ProcessSidHistory for Shared Logic * chore: Rename ReadGroupProperties to ReadGroupPropertiesAsync
1 parent d613c4e commit 88d8303

File tree

3 files changed

+84
-47
lines changed

3 files changed

+84
-47
lines changed

src/CommonLib/OutputTypes/Group.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ namespace SharpHoundCommonLib.OutputTypes
55
public class Group : OutputBase
66
{
77
public TypedPrincipal[] Members { get; set; } = Array.Empty<TypedPrincipal>();
8+
public TypedPrincipal[] HasSIDHistory { get; set; } = Array.Empty<TypedPrincipal>();
89
}
910
}

src/CommonLib/Processors/LdapPropertyProcessor.cs

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -177,20 +177,33 @@ public static Dictionary<string, object> ReadOUProperties(IDirectoryObject entry
177177
var props = GetCommonProps(entry);
178178
return props;
179179
}
180+
181+
public Task<GroupProperties> ReadGroupPropertiesAsync(IDirectoryObject entry,
182+
ResolvedSearchResult searchResult) {
183+
return ReadGroupPropertiesAsync(entry, searchResult.Domain);
184+
}
180185

181186
/// <summary>
182187
/// Reads specific LDAP properties related to Groups
183188
/// </summary>
184189
/// <param name="entry"></param>
190+
/// <param name="domain"></param>
185191
/// <returns></returns>
186-
public static Dictionary<string, object> ReadGroupProperties(IDirectoryObject entry) {
192+
public async Task<GroupProperties> ReadGroupPropertiesAsync(IDirectoryObject entry, string domain)
193+
{
194+
var groupProperties = new GroupProperties();
187195
var props = GetCommonProps(entry);
188196
entry.TryGetLongProperty(LDAPProperties.AdminCount, out var ac);
189197
props.Add("admincount", ac != 0);
190198
entry.TryGetLongProperty(LDAPProperties.GroupType, out var groupType);
191199
props.Add("groupscope", GetGroupScope(groupType));
200+
entry.TryGetByteArrayProperty(LDAPProperties.SIDHistory, out var sh);
201+
var (sidHistoryStrings, sidHistoryPrincipals) = await ProcessSidHistory(sh, domain);
202+
groupProperties.SidHistory = sidHistoryPrincipals;
203+
props.Add("sidhistory", sidHistoryStrings);
204+
groupProperties.Props = props;
192205

193-
return props;
206+
return groupProperties;
194207
}
195208

196209
/// <summary>
@@ -307,25 +320,9 @@ await SendComputerStatus(new CSVComputerStatus {
307320
props.Add("supportedencryptiontypes", encryptionTypes);
308321

309322
entry.TryGetByteArrayProperty(LDAPProperties.SIDHistory, out var sh);
310-
var sidHistoryList = new List<string>();
311-
var sidHistoryPrincipals = new List<TypedPrincipal>();
312-
foreach (var sid in sh) {
313-
string sSid;
314-
try {
315-
sSid = new SecurityIdentifier(sid, 0).Value;
316-
} catch {
317-
continue;
318-
}
319-
320-
sidHistoryList.Add(sSid);
321-
322-
if (await _utils.ResolveIDAndType(sSid, domain) is (true, var res))
323-
sidHistoryPrincipals.Add(res);
324-
}
325-
326-
userProps.SidHistory = sidHistoryPrincipals.Distinct().ToArray();
327-
328-
props.Add("sidhistory", sidHistoryList.ToArray());
323+
var (sidHistoryStrings, sidHistoryPrincipals) = await ProcessSidHistory(sh, domain);
324+
userProps.SidHistory = sidHistoryPrincipals;
325+
props.Add("sidhistory", sidHistoryStrings);
329326

330327
userProps.Props = props;
331328

@@ -424,25 +421,9 @@ await SendComputerStatus(new CSVComputerStatus {
424421
props.Add("operatingsystem", os);
425422

426423
entry.TryGetByteArrayProperty(LDAPProperties.SIDHistory, out var sh);
427-
var sidHistoryList = new List<string>();
428-
var sidHistoryPrincipals = new List<TypedPrincipal>();
429-
foreach (var sid in sh) {
430-
string sSid;
431-
try {
432-
sSid = new SecurityIdentifier(sid, 0).Value;
433-
} catch {
434-
continue;
435-
}
436-
437-
sidHistoryList.Add(sSid);
438-
439-
if (await _utils.ResolveIDAndType(sSid, domain) is (true, var res))
440-
sidHistoryPrincipals.Add(res);
441-
}
442-
443-
compProps.SidHistory = sidHistoryPrincipals.ToArray();
444-
445-
props.Add("sidhistory", sidHistoryList.ToArray());
424+
var (sidHistoryStrings, sidHistoryPrincipals) = await ProcessSidHistory(sh, domain);
425+
compProps.SidHistory = sidHistoryPrincipals;
426+
props.Add("sidhistory", sidHistoryStrings);
446427

447428
var smsaPrincipals = new List<TypedPrincipal>();
448429
if (entry.TryGetArrayProperty(LDAPProperties.HostServiceAccount, out var hsa)) {
@@ -789,6 +770,30 @@ private static List<string> ConvertEncryptionTypes(string encryptionTypes) {
789770

790771
return supportedEncryptionTypes;
791772
}
773+
774+
private async Task<(string[] sidHistoryStrings, TypedPrincipal[] sidHistoryPrincipals)>
775+
ProcessSidHistory(byte[][] sidHistory, string domain) {
776+
var sidHistoryList = new List<string>();
777+
var sidHistoryPrincipals = new List<TypedPrincipal>();
778+
779+
if (sidHistory == null) return ([], []);
780+
781+
foreach (var sid in sidHistory) {
782+
string sSid;
783+
try {
784+
sSid = new SecurityIdentifier(sid, 0).Value;
785+
} catch {
786+
continue;
787+
}
788+
789+
sidHistoryList.Add(sSid);
790+
791+
if (await _utils.ResolveIDAndType(sSid, domain) is (true, var res))
792+
sidHistoryPrincipals.Add(res);
793+
}
794+
795+
return (sidHistoryList.ToArray(), sidHistoryPrincipals.Distinct().ToArray());
796+
}
792797

793798
private static string ConvertNanoDuration(long duration) {
794799
// In case duration is long.MinValue, Math.Abs will overflow. Value represents Forever or Never
@@ -984,6 +989,12 @@ public ParsedCertificate(byte[] rawCertificate) {
984989
}
985990
}
986991

992+
public class GroupProperties
993+
{
994+
public Dictionary<string, object> Props { get; set; } = new();
995+
public TypedPrincipal[] SidHistory { get; set; } = Array.Empty<TypedPrincipal>();
996+
}
997+
987998
public class UserProperties {
988999
public Dictionary<string, object> Props { get; set; } = new();
9891000
public TypedPrincipal[] AllowedToDelegate { get; set; } = Array.Empty<TypedPrincipal>();

test/unit/LdapPropertyTests.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.ComponentModel;
43
using System.DirectoryServices;
54
using System.Security.Cryptography;
65
using System.Security.Cryptography.X509Certificates;
@@ -120,55 +119,81 @@ public void LDAPPropertyProcessor_ReadOUProperties_TestGoodData()
120119
}
121120

122121
[Fact]
123-
public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData()
122+
public async Task LDAPPropertyProcessor_ReadGroupProperties_TestGoodData()
124123
{
125124
var mock = new MockDirectoryObject("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal",
126125
new Dictionary<string, object>
127126
{
128127
{"description", "Test"},
129128
{"admincount", "1"}
130129
}, "S-1-5-21-3130019616-2776909439-2417379446-512","");
130+
var processor = new LdapPropertyProcessor(new MockLdapUtils());
131131

132-
var test = LdapPropertyProcessor.ReadGroupProperties(mock);
132+
var groupProperties = await processor.ReadGroupPropertiesAsync(mock, "domain");
133+
var test = groupProperties.Props;
133134
Assert.Contains("description", test.Keys);
134135
Assert.Equal("Test", test["description"] as string);
135136
Assert.Contains("admincount", test.Keys);
136137
Assert.True((bool)test["admincount"]);
137138
}
138139

139140
[Fact]
140-
public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData_FalseAdminCount()
141+
public async Task LDAPPropertyProcessor_ReadGroupProperties_TestGoodData_FalseAdminCount()
141142
{
142143
var mock = new MockDirectoryObject("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal",
143144
new Dictionary<string, object>
144145
{
145146
{"description", "Test"},
146147
{"admincount", "0"}
147148
}, "S-1-5-21-3130019616-2776909439-2417379446-512","");
149+
var processor = new LdapPropertyProcessor(new MockLdapUtils());
148150

149-
var test = LdapPropertyProcessor.ReadGroupProperties(mock);
151+
var groupProperties = await processor.ReadGroupPropertiesAsync(mock, "domain");
152+
var test = groupProperties.Props;
150153
Assert.Contains("description", test.Keys);
151154
Assert.Equal("Test", test["description"] as string);
152155
Assert.Contains("admincount", test.Keys);
153156
Assert.False((bool)test["admincount"]);
154157
}
155158

156159
[Fact]
157-
public void LDAPPropertyProcessor_ReadGroupProperties_NullAdminCount()
160+
public async Task LDAPPropertyProcessor_ReadGroupProperties_NullAdminCount()
158161
{
159162
var mock = new MockDirectoryObject("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal",
160163
new Dictionary<string, object>
161164
{
162165
{"description", "Test"}
163166
}, "S-1-5-21-3130019616-2776909439-2417379446-512","");
167+
var processor = new LdapPropertyProcessor(new MockLdapUtils());
164168

165-
var test = LdapPropertyProcessor.ReadGroupProperties(mock);
169+
var groupProperties = await processor.ReadGroupPropertiesAsync(mock, "domain");
170+
var test = groupProperties.Props;
166171
Assert.Contains("description", test.Keys);
167172
Assert.Equal("Test", test["description"] as string);
168173
Assert.Contains("admincount", test.Keys);
169174
Assert.False((bool)test["admincount"]);
170175
}
171176

177+
[WindowsOnlyFact]
178+
public async Task LDAPPropertyProcessor_ReadGroupProperties_Returns_HasSIDHistory()
179+
{
180+
var sid = new SecurityIdentifier("S-1-5-21-3130019616-2776909439-2417379446-519");
181+
byte[] bytes = new byte[sid.BinaryLength];
182+
sid.GetBinaryForm(bytes, 0);
183+
var mock = new MockDirectoryObject("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal",
184+
new Dictionary<string, object>
185+
{
186+
{"description", "Test"},
187+
{LDAPProperties.SIDHistory, new byte[][] { bytes }},
188+
}, "S-1-5-21-3130019616-2776909439-2417379446-512","");
189+
var processor = new LdapPropertyProcessor(new MockLdapUtils());
190+
191+
var groupProperties = await processor.ReadGroupPropertiesAsync(mock, "domain");
192+
Assert.NotEmpty(groupProperties.SidHistory);
193+
Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446-519", groupProperties.SidHistory[0].ObjectIdentifier);
194+
Assert.Equal(Label.Group, groupProperties.SidHistory[0].ObjectType);
195+
}
196+
172197
[Fact]
173198
public async Task LDAPPropertyProcessor_ReadUserProperties_TestTrustedToAuth()
174199
{

0 commit comments

Comments
 (0)