Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/Runtime/CollectionTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ internal async Task<string> StartCollection()
await _outputChannel.Writer.WriteAsync(wkp);
}


_outputChannel.Writer.Complete();
_compStatusChannel?.Writer.Complete();
_log.LogInformation("Output channel closed, waiting for output task to complete");
Expand All @@ -112,7 +111,7 @@ internal async Task<string> StartCollection()
internal async Task ConsumeSearchResults()
{
var log = _context.Logger;
var processor = new ObjectProcessors(_context, log);
var processor = new ObjectProcessors(_context, log, _compStatusChannel);
var watch = new Stopwatch();
var threadId = Thread.CurrentThread.ManagedThreadId;

Expand All @@ -130,7 +129,7 @@ internal async Task ConsumeSearchResults()

log.LogTrace("Consumer {ThreadID} started processing {obj} ({type})", threadId, res.DisplayName, res.ObjectType);
watch.Start();
var processed = await processor.ProcessObject(item, res, _compStatusChannel);
var processed = await processor.ProcessObject(item, res);
watch.Stop();
log.LogTrace("Consumer {ThreadID} took {time} ms to process {obj}", threadId,
watch.Elapsed.TotalMilliseconds, res.DisplayName);
Expand All @@ -147,6 +146,8 @@ internal async Task ConsumeSearchResults()
{
log.LogError(e, "error in consumer");
}

processor.ClearEventHandlers();

log.LogDebug("Consumer task on thread {id} completed", Thread.CurrentThread.ManagedThreadId);
}
Expand Down
6 changes: 4 additions & 2 deletions src/Runtime/LDAPConsumer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal static async Task ConsumeSearchResults(Channel<IDirectoryObject> inputC
int id)
{
var log = context.Logger;
var processor = new ObjectProcessors(context, log);
var processor = new ObjectProcessors(context, log, computerStatusChannel);
var watch = new Stopwatch();
var threadId = Thread.CurrentThread.ManagedThreadId;

Expand All @@ -34,7 +34,7 @@ internal static async Task ConsumeSearchResults(Channel<IDirectoryObject> inputC

log.LogTrace("Consumer {ThreadID} started processing {obj} ({type})", threadId, res.DisplayName, res.ObjectType);
watch.Start();
var processed = await processor.ProcessObject(item, res, computerStatusChannel);
var processed = await processor.ProcessObject(item, res);
watch.Stop();
log.LogTrace("Consumer {ThreadID} took {time} ms to process {obj}", threadId,
watch.Elapsed.TotalMilliseconds, res.DisplayName);
Expand All @@ -51,6 +51,8 @@ internal static async Task ConsumeSearchResults(Channel<IDirectoryObject> inputC
{
log.LogError(e, "error in consumer");
}

processor.ClearEventHandlers();

log.LogDebug("Consumer task on thread {id} completed", Thread.CurrentThread.ManagedThreadId);
}
Expand Down
76 changes: 49 additions & 27 deletions src/Runtime/ObjectProcessors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,17 @@ public class ObjectProcessors {
private readonly WebClientServiceProcessor _webClientProcessor;
private readonly SmbProcessor _smbProcessor;
private readonly ConcurrentDictionary<string, RegistryProcessor> _registryProcessorMap = new();
public ObjectProcessors(IContext context, ILogger log) {
private readonly Channel<CSVComputerStatus> _compStatusChannel;

public ObjectProcessors(IContext context, ILogger log, Channel<CSVComputerStatus> compStatusChannel) {
_context = context;
_aclProcessor = new ACLProcessor(context.LDAPUtils);
_spnProcessor = new SPNProcessors(context.LDAPUtils);
_ldapPropertyProcessor = new LdapPropertyProcessor(context.LDAPUtils);
_domainTrustProcessor = new DomainTrustProcessor(context.LDAPUtils);
_computerAvailability = new ComputerAvailability(context.PortScanTimeout,
skipPortScan: context.Flags.SkipPortScan, skipPasswordCheck: context.Flags.SkipPasswordAgeCheck);
_certAbuseProcessor = new CertAbuseProcessor(context.LDAPUtils);
_certAbuseProcessor = new CertAbuseProcessor(context.LDAPUtils, new RegistryAccessor());
_dcRegistryProcessor = new DCRegistryProcessor(context.LDAPUtils);
_computerSessionProcessor = new ComputerSessionProcessor(context.LDAPUtils,
doLocalAdminSessionEnum: context.Flags.DoLocalAdminSessionEnum,
Expand All @@ -63,15 +65,41 @@ public ObjectProcessors(IContext context, ILogger log) {
_methods = context.ResolvedCollectionMethods;
_cancellationToken = context.CancellationTokenSource.Token;
_log = log;
_compStatusChannel = compStatusChannel;

_localGroupProcessor.ComputerStatusEvent += HandleCompStatusEvent;
_computerSessionProcessor.ComputerStatusEvent += HandleCompStatusEvent;
_userRightsAssignmentProcessor.ComputerStatusEvent += HandleCompStatusEvent;
_computerAvailability.ComputerStatusEvent += HandleCompStatusEvent;
_spnProcessor.ComputerStatusEvent += HandleCompStatusEvent;
_ldapPropertyProcessor.ComputerStatusEvent += HandleCompStatusEvent;
_certAbuseProcessor.ComputerStatusEvent += HandleCompStatusEvent;
}

internal async Task<OutputBase> ProcessObject(IDirectoryObject entry,
ResolvedSearchResult resolvedSearchResult, Channel<CSVComputerStatus> compStatusChannel) {
internal void ClearEventHandlers() {
_localGroupProcessor.ComputerStatusEvent -= HandleCompStatusEvent;
_computerSessionProcessor.ComputerStatusEvent -= HandleCompStatusEvent;
_userRightsAssignmentProcessor.ComputerStatusEvent -= HandleCompStatusEvent;
_computerAvailability.ComputerStatusEvent -= HandleCompStatusEvent;
_spnProcessor.ComputerStatusEvent -= HandleCompStatusEvent;
_ldapPropertyProcessor.ComputerStatusEvent -= HandleCompStatusEvent;
_certAbuseProcessor.ComputerStatusEvent -= HandleCompStatusEvent;
}

private async Task HandleCompStatusEvent(CSVComputerStatus status) {
try {
await _compStatusChannel.Writer.WriteAsync(status, _cancellationToken);
} catch (Exception e) {
_log.LogWarning(e, "Caught exception writing to compstatus writer");
}
}

internal async Task<OutputBase> ProcessObject(IDirectoryObject entry, ResolvedSearchResult resolvedSearchResult) {
switch (resolvedSearchResult.ObjectType) {
case Label.User:
return await ProcessUserObject(entry, resolvedSearchResult);
case Label.Computer:
return await ProcessComputerObject(entry, resolvedSearchResult, compStatusChannel);
return await ProcessComputerObject(entry, resolvedSearchResult);
case Label.Group:
return await ProcessGroupObject(entry, resolvedSearchResult);
case Label.GPO:
Expand All @@ -88,7 +116,7 @@ internal async Task<OutputBase> ProcessObject(IDirectoryObject entry,
case Label.AIACA:
return await ProcessAIACA(entry, resolvedSearchResult);
case Label.EnterpriseCA:
return await ProcessEnterpriseCA(entry, resolvedSearchResult, compStatusChannel);
return await ProcessEnterpriseCA(entry, resolvedSearchResult);
case Label.NTAuthStore:
return await ProcessNTAuthStore(entry, resolvedSearchResult);
case Label.CertTemplate:
Expand Down Expand Up @@ -208,8 +236,7 @@ await _containerProcessor.GetContainingObject(dn) is (true, var container))

private async Task<Computer> ProcessComputerObject(
IDirectoryObject entry,
ResolvedSearchResult resolvedSearchResult,
Channel<CSVComputerStatus> compStatusChannel
ResolvedSearchResult resolvedSearchResult
) {
var ret = new Computer {
ObjectIdentifier = resolvedSearchResult.ObjectId,
Expand Down Expand Up @@ -280,8 +307,7 @@ await _containerProcessor.GetContainingObject(dn) is (true, var container)) {
var availability = await _computerAvailability.IsComputerAvailable(resolvedSearchResult, entry);

if (!availability.Connectable) {
await compStatusChannel.Writer.WriteAsync(availability.GetCSVStatus(resolvedSearchResult.DisplayName),
_cancellationToken);
await HandleCompStatusEvent(availability.GetCSVStatus(resolvedSearchResult.DisplayName));
ret.Status = availability;
return ret;
}
Expand All @@ -297,12 +323,12 @@ await compStatusChannel.Writer.WriteAsync(availability.GetCSVStatus(resolvedSear
resolvedSearchResult.ObjectId, resolvedSearchResult.Domain);
ret.Sessions = sessionResult;
if (_context.Flags.DumpComputerStatus)
await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus {
await HandleCompStatusEvent(new CSVComputerStatus {
Status = sessionResult.Collected ? StatusSuccess : sessionResult.FailureReason,
Task = "NetSessionEnum",
ComputerName = resolvedSearchResult.DisplayName,
ObjectId = resolvedSearchResult.ObjectId,
}, _cancellationToken);
});
}

if (_methods.HasFlag(CollectionMethod.LoggedOn)) {
Expand All @@ -313,25 +339,25 @@ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus {
ret.PrivilegedSessions = privSessionResult;

if (_context.Flags.DumpComputerStatus)
await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus {
await HandleCompStatusEvent(new CSVComputerStatus {
Status = privSessionResult.Collected ? StatusSuccess : privSessionResult.FailureReason,
Task = "NetWkstaUserEnum",
ComputerName = resolvedSearchResult.DisplayName,
ObjectId = resolvedSearchResult.ObjectId,
}, _cancellationToken);
});

if (!_context.Flags.NoRegistryLoggedOn) {
await _context.DoDelay();
var registrySessionResult = await _computerSessionProcessor.ReadUserSessionsRegistry(apiName,
resolvedSearchResult.Domain, resolvedSearchResult.ObjectId);
ret.RegistrySessions = registrySessionResult;
if (_context.Flags.DumpComputerStatus)
await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus {
await HandleCompStatusEvent(new CSVComputerStatus {
Status = registrySessionResult.Collected ? StatusSuccess : registrySessionResult.FailureReason,
Task = "RegistrySessions",
ComputerName = resolvedSearchResult.DisplayName,
ObjectId = resolvedSearchResult.ObjectId,
}, _cancellationToken);
});
}
}

Expand Down Expand Up @@ -704,9 +730,7 @@ private async Task<AIACA> ProcessAIACA(IDirectoryObject entry, ResolvedSearchRes
return ret;
}

private async Task<EnterpriseCA> ProcessEnterpriseCA(IDirectoryObject entry,
ResolvedSearchResult resolvedSearchResult,
Channel<CSVComputerStatus> compStatusChannel) {
private async Task<EnterpriseCA> ProcessEnterpriseCA(IDirectoryObject entry, ResolvedSearchResult resolvedSearchResult) {
var ret = new EnterpriseCA {
ObjectIdentifier = resolvedSearchResult.ObjectId,
Properties = new Dictionary<string, object>(GetCommonProperties(entry, resolvedSearchResult))
Expand Down Expand Up @@ -749,14 +773,13 @@ private async Task<EnterpriseCA> ProcessEnterpriseCA(IDirectoryObject entry,
if (await _context.LDAPUtils.ResolveHostToSid(dnsHostName, resolvedSearchResult.DomainSid) is
(true, var sid) && sid.StartsWith("S-1-")) {
ret.HostingComputer = sid;
await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus
await HandleCompStatusEvent(new CSVComputerStatus
{
Status = ComputerStatus.Success,
ComputerName = resolvedSearchResult.DisplayName,
Task = nameof(ProcessEnterpriseCA),
ObjectId = resolvedSearchResult.ObjectId,
},
_cancellationToken);
});
} else {
_log.LogWarning("CA {Name} host ({Dns}) could not be resolved to a SID.", caName, dnsHostName);
}
Expand All @@ -779,24 +802,23 @@ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus
if (caName != null && dnsHostName != null) {
if (await _context.LDAPUtils.ResolveHostToSid(dnsHostName, resolvedSearchResult.DomainSid) is
(true, var sid) && sid.StartsWith("S-1-")) {
await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus
await HandleCompStatusEvent(new CSVComputerStatus
{
Status = ComputerStatus.Success,
ComputerName = resolvedSearchResult.DisplayName,
Task = nameof(ProcessEnterpriseCA),
ObjectId = sid,
},
_cancellationToken);
});
ret.HostingComputer = sid;
} else {
_log.LogWarning("CA {Name} host ({Dns}) could not be resolved to a SID.", caName, dnsHostName);
}

CARegistryData cARegistryData = new() {
IsUserSpecifiesSanEnabled = _certAbuseProcessor.IsUserSpecifiesSanEnabled(dnsHostName, caName),
IsUserSpecifiesSanEnabled = await _certAbuseProcessor.IsUserSpecifiesSanEnabled(dnsHostName, caName, ret.HostingComputer),
EnrollmentAgentRestrictions = await _certAbuseProcessor.ProcessEAPermissions(caName,
resolvedSearchResult.Domain, dnsHostName, ret.HostingComputer),
RoleSeparationEnabled = _certAbuseProcessor.RoleSeparationEnabled(dnsHostName, caName),
RoleSeparationEnabled = await _certAbuseProcessor.IsRoleSeparationEnabled(dnsHostName, caName, ret.HostingComputer),

// The CASecurity exist in the AD object DACL and in registry of the CA server. We prefer to use the values from registry as they are the ground truth.
// If changes are made on the CA server, registry and the AD object is updated. If changes are made directly on the AD object, the CA server registry is not updated.
Expand Down