From 416ee048f57da111ffaa54aba321a3d3e6c4a8af Mon Sep 17 00:00:00 2001 From: Michael Cuomo Date: Thu, 2 Oct 2025 17:36:01 -0400 Subject: [PATCH 1/2] chore: Add ObjectID to ComputerStatus --- src/CommonLib/CSVComputerStatus.cs | 3 ++- .../Processors/ComputerAvailability.cs | 17 +++++++++++------ .../Processors/ComputerSessionProcessor.cs | 8 ++++++-- src/CommonLib/Processors/SPNProcessors.cs | 1 + 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/CommonLib/CSVComputerStatus.cs b/src/CommonLib/CSVComputerStatus.cs index faf34c191..f0efa145f 100644 --- a/src/CommonLib/CSVComputerStatus.cs +++ b/src/CommonLib/CSVComputerStatus.cs @@ -9,6 +9,7 @@ public class CSVComputerStatus public string ComputerName { get; set; } public string Task { get; set; } public string Status { get; set; } + public string ObjectId { get; set; } /// /// Converts to CSV format for output @@ -16,7 +17,7 @@ public class CSVComputerStatus /// public string ToCsv() { - return $"{StringToCsvCell(ComputerName)}, {StringToCsvCell(Task)}, {StringToCsvCell(Status)}"; + return $"{StringToCsvCell(ComputerName)}, {StringToCsvCell(Task)}, {StringToCsvCell(Status)}, {StringToCsvCell(ObjectId)}"; } /// diff --git a/src/CommonLib/Processors/ComputerAvailability.cs b/src/CommonLib/Processors/ComputerAvailability.cs index 489ebde1a..54bc41049 100644 --- a/src/CommonLib/Processors/ComputerAvailability.cs +++ b/src/CommonLib/Processors/ComputerAvailability.cs @@ -47,7 +47,7 @@ public Task IsComputerAvailable(ResolvedSearchResult result, IDi var pwdlastset = entry.GetProperty(LDAPProperties.PasswordLastSet); var lastLogon = entry.GetProperty(LDAPProperties.LastLogonTimestamp); - return IsComputerAvailable(name, os, pwdlastset, lastLogon); + return IsComputerAvailable(name, os, pwdlastset, lastLogon, result.ObjectId); } /// @@ -60,16 +60,18 @@ public Task IsComputerAvailable(ResolvedSearchResult result, IDi /// The LDAP operatingsystem attribute value /// The LDAP pwdlastset attribute value /// The LDAP lastlogontimestamp attribute value + /// The objectId that pertains to the computer. /// A ComputerStatus object that represents the availability of the computer public async Task IsComputerAvailable(string computerName, string operatingSystem, - string pwdLastSet, string lastLogon) { + string pwdLastSet, string lastLogon, string objectId = null) { if (operatingSystem != null && !operatingSystem.StartsWith("Windows", StringComparison.OrdinalIgnoreCase)) { _log.LogTrace("{ComputerName} is not available because operating system {OperatingSystem} is not valid", computerName, operatingSystem); await SendComputerStatus(new CSVComputerStatus { Status = ComputerStatus.NonWindowsOS, Task = "ComputerAvailability", - ComputerName = computerName + ComputerName = computerName, + ObjectId = objectId, }); return new ComputerStatus { Connectable = false, @@ -84,7 +86,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { Status = ComputerStatus.NotActive, Task = "ComputerAvailability", - ComputerName = computerName + ComputerName = computerName, + ObjectId = objectId, }); return new ComputerStatus { Connectable = false, @@ -103,7 +106,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { Status = ComputerStatus.PortNotOpen, Task = "ComputerAvailability", - ComputerName = computerName + ComputerName = computerName, + ObjectId = objectId, }); return new ComputerStatus { Connectable = false, @@ -116,7 +120,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "ComputerAvailability", - ComputerName = computerName + ComputerName = computerName, + ObjectId = objectId, }); return new ComputerStatus { diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index 2486b3d0e..a7f008915 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -87,7 +87,8 @@ public async Task ReadUserSessions(string computerName, string await SendComputerStatus(new CSVComputerStatus { Status = result.GetErrorStatus(), Task = "NetSessionEnum", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerSid, }); _log.LogTrace("NetSessionEnum failed on {ComputerName}: {Status}", computerName, result.Status); ret.Collected = false; @@ -99,7 +100,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "NetSessionEnum", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerSid, }); ret.Collected = true; @@ -144,6 +146,7 @@ await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "NetSessionEnum", ComputerName = computerSessionName, + ObjectId = resolvedComputerSID, }); } @@ -166,6 +169,7 @@ await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "NetSessionEnum", ComputerName = computerSessionName, + ObjectId = resolvedComputerSID, }); results.Add(new Session { ComputerSID = resolvedComputerSID, diff --git a/src/CommonLib/Processors/SPNProcessors.cs b/src/CommonLib/Processors/SPNProcessors.cs index 8293368a9..9066311e8 100644 --- a/src/CommonLib/Processors/SPNProcessors.cs +++ b/src/CommonLib/Processors/SPNProcessors.cs @@ -59,6 +59,7 @@ await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = nameof(ReadSPNTargets), ComputerName = Helpers.StripServicePrincipalName(spn).ToUpper().TrimEnd('$'), + ObjectId = host }); yield return new SPNPrivilege { ComputerSID = host, From fccb7134120feba4ff24100ba4fb15db90f5a03f Mon Sep 17 00:00:00 2001 From: Michael Cuomo Date: Fri, 3 Oct 2025 21:36:49 -0400 Subject: [PATCH 2/2] chore: Add ObjectId to CSVComputerStatus --- .../Processors/CertAbuseProcessor.cs | 6 +++-- .../Processors/ComputerSessionProcessor.cs | 12 ++++++---- src/CommonLib/Processors/DCLdapProcessor.cs | 14 +++++++---- .../Processors/LdapPropertyProcessor.cs | 2 ++ .../Processors/LocalGroupProcessor.cs | 24 ++++++++++++------- src/CommonLib/Processors/SmbProcessor.cs | 11 +++++---- .../UserRightsAssignmentProcessor.cs | 12 ++++++---- test/unit/DCLdapProcessorTest.cs | 4 ++-- test/unit/SmbProcessorTest.cs | 2 +- 9 files changed, 57 insertions(+), 30 deletions(-) diff --git a/src/CommonLib/Processors/CertAbuseProcessor.cs b/src/CommonLib/Processors/CertAbuseProcessor.cs index 4da05f191..86513f5af 100644 --- a/src/CommonLib/Processors/CertAbuseProcessor.cs +++ b/src/CommonLib/Processors/CertAbuseProcessor.cs @@ -362,7 +362,8 @@ await SendComputerStatus(new CSVComputerStatus { Task = "SamConnect", ComputerName = computerName, - Status = openServerResult.SError + Status = openServerResult.SError, + ObjectId = computerObjectId, }); return null; } @@ -376,7 +377,8 @@ await SendComputerStatus(new CSVComputerStatus { Status = getMachineSidResult.SError, ComputerName = computerName, - Task = "GetMachineSid" + Task = "GetMachineSid", + ObjectId = computerObjectId, }); //If we can't get a machine sid, we wont be able to make local principals with unique object ids, or differentiate local/domain objects _log.LogWarning("Unable to get machineSid for {Computer}: {Status}", computerName, getMachineSidResult.SError); diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index a7f008915..729df1820 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -229,7 +229,8 @@ public async Task ReadUserSessionsPrivileged(string computerNa await SendComputerStatus(new CSVComputerStatus { Status = result.GetErrorStatus(), Task = "NetWkstaUserEnum", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerSid, }); _log.LogTrace("NetWkstaUserEnum failed on {ComputerName}: {Status}", computerName, result.Status); ret.Collected = false; @@ -241,7 +242,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { Status = result.Status.ToString(), Task = "NetWkstaUserEnum", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerSid, }); ret.Collected = true; @@ -296,7 +298,8 @@ public async Task ReadUserSessionsRegistry(string computerName await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "RegistrySessionEnum", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerSid, }); _log.LogTrace("Registry session enum succeeded on {ComputerName}", computerName); var results = new List(); @@ -323,7 +326,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { Status = e.Message, Task = "RegistrySessionEnum", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerSid, }); ret.Collected = false; ret.FailureReason = e.Message; diff --git a/src/CommonLib/Processors/DCLdapProcessor.cs b/src/CommonLib/Processors/DCLdapProcessor.cs index 1fe544355..d23639f4c 100644 --- a/src/CommonLib/Processors/DCLdapProcessor.cs +++ b/src/CommonLib/Processors/DCLdapProcessor.cs @@ -45,7 +45,7 @@ public DCLdapProcessor(int connectionTimeoutMs, string dcHostname, ILogger log = public event ComputerStatusDelegate ComputerStatusEvent; - public async Task Scan(string computerName) { + public async Task Scan(string computerName, string computerObjectId) { var hasLdap = await TestLdapPort(); var hasLdaps = await TestLdapsPort(); SharpHoundRPC.Result isSigningRequired = new(), @@ -63,14 +63,16 @@ public async Task Scan(string computerName) { await SendComputerStatus(new CSVComputerStatus { Status = isSigningRequired.Error, Task = "DCLdapIsSigningRequired", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerObjectId }); _log.LogTrace("DCLdapScan failed on IsSigningRequired for {ComputerName}: {Status}", computerName, isSigningRequired.Status); } else { await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "DCLdapIsSigningRequired", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerObjectId }); } @@ -78,14 +80,16 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { Status = isChannelBindingDisabled.Error, Task = "DCLdapIsChannelBindingDisabled", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerObjectId, }); _log.LogTrace("DCLdapScan failed on IsChannelBindingDisabled for {ComputerName}: {Status}", computerName, isSigningRequired.Status); } else { await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "DCLdapIsChannelBindingDisabled", - ComputerName = computerName + ComputerName = computerName, + ObjectId = computerObjectId, }); } diff --git a/src/CommonLib/Processors/LdapPropertyProcessor.cs b/src/CommonLib/Processors/LdapPropertyProcessor.cs index accb43074..ffd5b3f32 100644 --- a/src/CommonLib/Processors/LdapPropertyProcessor.cs +++ b/src/CommonLib/Processors/LdapPropertyProcessor.cs @@ -269,6 +269,7 @@ await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = nameof(ReadUserProperties), ComputerName = Helpers.StripServicePrincipalName(d).ToUpper().TrimEnd('$'), + ObjectId = resolvedHost.SecurityIdentifier, }); comps.Add(new TypedPrincipal { ObjectIdentifier = resolvedHost.SecurityIdentifier, @@ -384,6 +385,7 @@ await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = nameof(ReadComputerProperties), ComputerName = d, + ObjectId = resolvedHost.SecurityIdentifier, }); comps.Add(new TypedPrincipal { ObjectIdentifier = resolvedHost.SecurityIdentifier, diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index 984ed53a4..93454b174 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -77,7 +77,8 @@ await SendComputerStatus(new CSVComputerStatus { Task = "SamConnect", ComputerName = computerName, - Status = openServerResult.SError + Status = openServerResult.SError, + ObjectId = computerObjectId, }); yield break; } @@ -96,7 +97,8 @@ await SendComputerStatus(new CSVComputerStatus { Status = getMachineSidResult.SError, ComputerName = computerName, - Task = "GetMachineSid" + Task = "GetMachineSid", + ObjectId = computerObjectId, }); //If we can't get a machine sid, we wont be able to make local principals with unique object ids, or differentiate local/domain objects _log.LogWarning("Unable to get machineSid for {Computer}: {Status}. Abandoning local group processing", computerName, getMachineSidResult.SError); @@ -120,7 +122,8 @@ await SendComputerStatus(new CSVComputerStatus { Task = "GetDomains", ComputerName = computerName, - Status = getDomainsResult.SError + Status = getDomainsResult.SError, + ObjectId = computerObjectId, }); yield break; } @@ -141,7 +144,8 @@ await SendComputerStatus(new CSVComputerStatus { Task = $"OpenDomain - {domainResult.Name}", ComputerName = computerName, - Status = openDomainResult.SError + Status = openDomainResult.SError, + ObjectId = computerObjectId, }); if (openDomainResult.IsTimeout) { yield break; @@ -161,7 +165,8 @@ await SendComputerStatus(new CSVComputerStatus { Task = $"GetAliases - {domainResult.Name}", ComputerName = computerName, - Status = getAliasesResult.SError + Status = getAliasesResult.SError, + ObjectId = computerObjectId, }); if (getAliasesResult.IsTimeout) { @@ -193,7 +198,8 @@ await SendComputerStatus(new CSVComputerStatus { Task = $"OpenAlias - {alias.Name}", ComputerName = computerName, - Status = openAliasResult.SError + Status = openAliasResult.SError, + ObjectId = computerObjectId, }); ret.Collected = false; ret.FailureReason = $"SamOpenAliasInDomain failed with status {openAliasResult.SError}"; @@ -214,7 +220,8 @@ await SendComputerStatus(new CSVComputerStatus { Task = $"GetMembersInAlias - {alias.Name}", ComputerName = computerName, - Status = getMembersResult.SError + Status = getMembersResult.SError, + ObjectId = computerObjectId, }); ret.Collected = false; ret.FailureReason = $"SamGetMembersInAlias failed with status {getMembersResult.SError}"; @@ -229,7 +236,8 @@ await SendComputerStatus(new CSVComputerStatus { Task = $"GetMembersInAlias - {alias.Name}", ComputerName = computerName, - Status = CSVComputerStatus.StatusSuccess + Status = CSVComputerStatus.StatusSuccess, + ObjectId = computerObjectId, }); var results = new List(); diff --git a/src/CommonLib/Processors/SmbProcessor.cs b/src/CommonLib/Processors/SmbProcessor.cs index 44c2d7116..3aef09860 100644 --- a/src/CommonLib/Processors/SmbProcessor.cs +++ b/src/CommonLib/Processors/SmbProcessor.cs @@ -25,14 +25,15 @@ public SmbProcessor(int timeoutMs, ISmbScanner smbScanner = null, ILogger log = } public event ComputerStatusDelegate ComputerStatusEvent; - public virtual async Task> Scan(string host) { + public virtual async Task> Scan(string host, string securityIdentifier) { var result = await _scanHostAdaptiveTimeout.ExecuteRPCWithTimeout((timeoutToken) => _smbScanner.ScanHost(host, 445, timeoutToken)); if (result.IsFailed) { await SendComputerStatus(new CSVComputerStatus { Status = result.Error, Task = "SmbScan", - ComputerName = host + ComputerName = host, + ObjectId = securityIdentifier, }); _log.LogTrace("SmbScan failed on {ComputerName}: {Status}", host, result.Error); return APIResult.Failure(result.Error); @@ -43,7 +44,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { Status = result.Error ?? "Unknown error", Task = "SmbScan", - ComputerName = host + ComputerName = host, + ObjectId = securityIdentifier, }); _log.LogTrace("SmbScan failed on {ComputerName} - null result: {Status}", host, result.Status); return APIResult.Failure(result.Error ?? "Unknown error"); @@ -53,7 +55,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "SmbScan", - ComputerName = host + ComputerName = host, + ObjectId = securityIdentifier, }); var info = new SmbInfo() { diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index 3a3d4f03d..d8f8a8a11 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -60,7 +60,8 @@ public async IAsyncEnumerable GetUserRightsAssign await SendComputerStatus(new CSVComputerStatus { Task = "LSAOpenPolicy", ComputerName = computerName, - Status = policyOpenResult.Error + Status = policyOpenResult.Error, + ObjectId = computerObjectId, }); yield break; } @@ -78,7 +79,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { ComputerName = computerName, Status = getMachineSidResult.SError, - Task = "LSAGetMachineSID" + Task = "LSAGetMachineSID", + ObjectId = computerObjectId, }); yield break; } @@ -106,7 +108,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { ComputerName = computerName, Status = enumerateAccountsResult.SError, - Task = "LSAEnumerateAccountsWithUserRight" + Task = "LSAEnumerateAccountsWithUserRight", + ObjectId = computerObjectId, }); ret.FailureReason = $"LSAEnumerateAccountsWithUserRights returned {enumerateAccountsResult.SError}"; @@ -121,7 +124,8 @@ await SendComputerStatus(new CSVComputerStatus { await SendComputerStatus(new CSVComputerStatus { ComputerName = computerName, Status = CSVComputerStatus.StatusSuccess, - Task = "LSAEnumerateAccountsWithUserRight" + Task = "LSAEnumerateAccountsWithUserRight", + ObjectId = computerObjectId, }); var resolved = new List(); diff --git a/test/unit/DCLdapProcessorTest.cs b/test/unit/DCLdapProcessorTest.cs index e8ae0a653..277598652 100644 --- a/test/unit/DCLdapProcessorTest.cs +++ b/test/unit/DCLdapProcessorTest.cs @@ -43,7 +43,7 @@ public async Task DCLdapProcessor_Scan() { receivedStatus.Add(status); return Task.CompletedTask; }; - var results = await processor.Scan("primary.testlab.local"); + var results = await processor.Scan("primary.testlab.local", ""); Assert.Equal(2, receivedStatus.Count); var status = receivedStatus[0]; @@ -71,7 +71,7 @@ public async Task DCLdapProcessor_Scan_Failed() { receivedStatus.Add(status); return Task.CompletedTask; }; - var results = await processor.Scan("primary.testlab.local"); + var results = await processor.Scan("primary.testlab.local", ""); Assert.Equal(2, receivedStatus.Count); var status = receivedStatus[0]; diff --git a/test/unit/SmbProcessorTest.cs b/test/unit/SmbProcessorTest.cs index 4b037330b..af19665ed 100644 --- a/test/unit/SmbProcessorTest.cs +++ b/test/unit/SmbProcessorTest.cs @@ -41,7 +41,7 @@ public async Task SmbProcessor_TestTimeout() { receivedStatus.Add(status); return Task.CompletedTask; }; - var results = await mockProcessor.Scan("primary.testlab.local"); + var results = await mockProcessor.Scan("primary.testlab.local", ""); Assert.Single(receivedStatus); var status = receivedStatus[0];