diff --git a/Sharphound.csproj b/Sharphound.csproj
index 368deb0..476cb39 100644
--- a/Sharphound.csproj
+++ b/Sharphound.csproj
@@ -6,8 +6,8 @@
latest
full
favicon.ico
- 2.4.1
- 2.4.1
+ 2.5.1
+ 2.5.1
SpecterOps
SharpHound
SharpHound
@@ -21,19 +21,19 @@
all
-
-
+
+
-
-
+
+
-
+
-
+
diff --git a/src/BaseContext.cs b/src/BaseContext.cs
index 3ab2934..2497b74 100644
--- a/src/BaseContext.cs
+++ b/src/BaseContext.cs
@@ -22,18 +22,18 @@ public sealed class BaseContext : IDisposable, IContext
private bool disposedValue;
- public BaseContext(ILogger logger, LDAPConfig ldapConfig, Flags flags)
+ public BaseContext(ILogger logger, LdapConfig ldapConfig, Flags flags)
{
Logger = logger;
Flags = flags;
- LDAPUtils = new LDAPUtils();
- LDAPUtils.SetLDAPConfig(ldapConfig);
+ LDAPUtils = new LdapUtils();
+ LDAPUtils.SetLdapConfig(ldapConfig);
CancellationTokenSource = new CancellationTokenSource();
}
public bool IsFaulted { get; set; }
- public ResolvedCollectionMethod ResolvedCollectionMethods { get; set; }
+ public CollectionMethod ResolvedCollectionMethods { get; set; }
public string LdapFilter { get; set; }
public string SearchBase { get; set; }
public string DomainName { get; set; }
@@ -56,7 +56,7 @@ public BaseContext(ILogger logger, LDAPConfig ldapConfig, Flags flags)
public int Jitter { get; set; }
public int PortScanTimeout { get; set; } = 500;
public CancellationTokenSource CancellationTokenSource { get; set; }
- public ILDAPUtils LDAPUtils { get; set; }
+ public ILdapUtils LDAPUtils { get; set; }
public Task CollectionTask { get; set; }
public Flags Flags { get; set; }
@@ -90,12 +90,12 @@ public string GetCachePath()
return path;
}
- public ResolvedCollectionMethod SetupMethodsForLoop()
+ public CollectionMethod SetupMethodsForLoop()
{
var original = ResolvedCollectionMethods;
- const ResolvedCollectionMethod computerCollectionMethods =
- ResolvedCollectionMethod.LocalGroups | ResolvedCollectionMethod.LoggedOn |
- ResolvedCollectionMethod.Session;
+ const CollectionMethod computerCollectionMethods =
+ CollectionMethod.LocalGroups | CollectionMethod.LoggedOn |
+ CollectionMethod.Session;
return original & computerCollectionMethods;
}
diff --git a/src/Client/Context.cs b/src/Client/Context.cs
index 7e24734..ceda178 100644
--- a/src/Client/Context.cs
+++ b/src/Client/Context.cs
@@ -47,7 +47,7 @@ public interface IContext
CancellationTokenSource CancellationTokenSource { get; set; }
ILogger Logger { get; set; }
- ILDAPUtils LDAPUtils { get; set; }
+ ILdapUtils LDAPUtils { get; set; }
string OutputPrefix { get; set; }
string OutputDirectory { get; set; }
@@ -62,7 +62,7 @@ public interface IContext
public string LocalAdminPassword { get; set; }
- ResolvedCollectionMethod ResolvedCollectionMethods { get; set; }
+ CollectionMethod ResolvedCollectionMethods { get; set; }
///
/// Does throttle and jitter for computer requests
@@ -71,7 +71,7 @@ public interface IContext
Task DoDelay();
string GetCachePath();
- ResolvedCollectionMethod SetupMethodsForLoop();
+ CollectionMethod SetupMethodsForLoop();
string ResolveFileName(string filename, string extension, bool addTimestamp);
EnumerationDomain[] Domains { get; set; }
void UpdateLoopTime();
diff --git a/src/Client/Flags.cs b/src/Client/Flags.cs
index 4e5e39b..48e935a 100644
--- a/src/Client/Flags.cs
+++ b/src/Client/Flags.cs
@@ -27,5 +27,6 @@ public class Flags
public bool SearchForest { get; set; }
public bool RecurseDomains { get; set; }
public bool DoLocalAdminSessionEnum { get; set; }
+ public bool ParititonLdapQueries { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Client/Links.cs b/src/Client/Links.cs
index ec4c7e4..131e421 100644
--- a/src/Client/Links.cs
+++ b/src/Client/Links.cs
@@ -9,15 +9,15 @@ namespace Sharphound.Client
/// A context to be populated.
public interface Links
{
- IContext Initialize(IContext context, LDAPConfig options);
+ IContext Initialize(IContext context, LdapConfig options);
- IContext
+ Task
TestConnection(
T context); //Initial LDAP connection test. Search for the well known administrator SID to make sure we can connect successfully.
IContext SetSessionUserName(string overrideUserName, T context);
IContext InitCommonLib(T context);
- IContext GetDomainsForEnumeration(T context);
+ Task GetDomainsForEnumeration(T context);
IContext StartBaseCollectionTask(T context);
Task AwaitBaseRunCompletion(T context);
IContext StartLoopTimer(T context);
diff --git a/src/EnumerationDomain.cs b/src/EnumerationDomain.cs
index 7b211cc..c420161 100644
--- a/src/EnumerationDomain.cs
+++ b/src/EnumerationDomain.cs
@@ -1,9 +1,12 @@
+using System.DirectoryServices.ActiveDirectory;
+
namespace Sharphound
{
public class EnumerationDomain
{
public string Name { get; set; }
public string DomainSid { get; set; }
+ public string TrustType { get; set; }
}
}
diff --git a/src/Extensions.cs b/src/Extensions.cs
index d90a3f2..4735a23 100644
--- a/src/Extensions.cs
+++ b/src/Extensions.cs
@@ -24,7 +24,7 @@ internal static void Merge(this Dictionary s, Dictio
}
}
- public static string GetDNSName(this ISearchResultEntry entry, string overrideDNSName)
+ public static string GetDNSName(this IDirectoryObject entry, string overrideDNSName)
{
var shortName = entry.GetProperty("samaccountname")?.TrimEnd('$');
var dns = entry.GetProperty("dnshostname");
@@ -111,10 +111,10 @@ internal static async IAsyncEnumerable ReadAllAsync(this ChannelReader
/// Removes non-computer collection methods from specified ones for looping
///
///
- internal static ResolvedCollectionMethod GetLoopCollectionMethods(this ResolvedCollectionMethod methods)
+ internal static CollectionMethod GetLoopCollectionMethods(this CollectionMethod methods)
{
- const ResolvedCollectionMethod computerCollectionMethods = ResolvedCollectionMethod.LocalGroups | ResolvedCollectionMethod.LoggedOn |
- ResolvedCollectionMethod.Session;
+ const CollectionMethod computerCollectionMethods = CollectionMethod.LocalGroups | CollectionMethod.LoggedOn |
+ CollectionMethod.Session;
return methods & computerCollectionMethods;
}
}
diff --git a/src/Options.cs b/src/Options.cs
index 37b558f..6cd6e38 100644
--- a/src/Options.cs
+++ b/src/Options.cs
@@ -92,9 +92,12 @@ public class Options
[Option(HelpText = "Override port for LDAP", Default = 0)]
public int LDAPPort { get; set; }
+
+ [Option(HelpText = "Override port for LDAPS", Default = 0)]
+ public int LDAPSSLPort { get; set; }
- [Option(HelpText = "Connect to LDAP SSL instead of regular LDAP", Default = false)]
- public bool SecureLDAP { get; set; }
+ [Option(HelpText = "Only connect to LDAP SSL, disallowing fallback", Default = false)]
+ public bool ForceSecureLDAP { get; set; }
[Option(HelpText = "Disables certificate verification when using LDAPS", Default = false)]
public bool DisableCertVerification { get; set; }
@@ -136,6 +139,9 @@ public class Options
[Option(HelpText = "Collect all LDAP properties from objects")]
public bool CollectAllProperties { get; set; }
+
+ [Option(HelpText = "Split the main ldap query into smaller chunks to attempt to reduce server load")]
+ public bool PartitionLdapQueries { get; set; }
//Loop Options
[Option('l', "Loop", HelpText = "Loop computer collection")]
@@ -153,13 +159,13 @@ public class Options
[Option('v', HelpText = "Enable verbose output", Default = (int)LogLevel.Information)]
public int Verbosity { get; set; }
- internal bool ResolveCollectionMethods(ILogger logger, out ResolvedCollectionMethod resolved, out bool dconly)
+ internal bool ResolveCollectionMethods(ILogger logger, out CollectionMethod resolved, out bool dconly)
{
var arr = CollectionMethods.Count() == 1
? CollectionMethods.First().Split(',')
: CollectionMethods.ToArray();
- resolved = ResolvedCollectionMethod.None;
+ resolved = CollectionMethod.None;
dconly = false;
foreach (var baseMethod in arr)
@@ -177,29 +183,29 @@ internal bool ResolveCollectionMethods(ILogger logger, out ResolvedCollectionMet
resolved |= option switch
{
- CollectionMethodOptions.Group => ResolvedCollectionMethod.Group,
- CollectionMethodOptions.Session => ResolvedCollectionMethod.Session,
- CollectionMethodOptions.LoggedOn => ResolvedCollectionMethod.LoggedOn,
- CollectionMethodOptions.Trusts => ResolvedCollectionMethod.Trusts,
- CollectionMethodOptions.ACL => ResolvedCollectionMethod.ACL,
- CollectionMethodOptions.ObjectProps => ResolvedCollectionMethod.ObjectProps,
- CollectionMethodOptions.RDP => ResolvedCollectionMethod.RDP,
- CollectionMethodOptions.DCOM => ResolvedCollectionMethod.DCOM,
- CollectionMethodOptions.LocalAdmin => ResolvedCollectionMethod.LocalAdmin,
- CollectionMethodOptions.PSRemote => ResolvedCollectionMethod.PSRemote,
- CollectionMethodOptions.SPNTargets => ResolvedCollectionMethod.SPNTargets,
- CollectionMethodOptions.Container => ResolvedCollectionMethod.Container,
- CollectionMethodOptions.GPOLocalGroup => ResolvedCollectionMethod.GPOLocalGroup,
- CollectionMethodOptions.LocalGroup => ResolvedCollectionMethod.LocalGroups,
- CollectionMethodOptions.UserRights => ResolvedCollectionMethod.UserRights,
- CollectionMethodOptions.Default => ResolvedCollectionMethod.Default,
- CollectionMethodOptions.DCOnly => ResolvedCollectionMethod.DCOnly,
- CollectionMethodOptions.ComputerOnly => ResolvedCollectionMethod.ComputerOnly,
- CollectionMethodOptions.CARegistry => ResolvedCollectionMethod.CARegistry,
- CollectionMethodOptions.DCRegistry => ResolvedCollectionMethod.DCRegistry,
- CollectionMethodOptions.CertServices => ResolvedCollectionMethod.CertServices,
- CollectionMethodOptions.All => ResolvedCollectionMethod.All,
- CollectionMethodOptions.None => ResolvedCollectionMethod.None,
+ CollectionMethodOptions.Group => CollectionMethod.Group,
+ CollectionMethodOptions.Session => CollectionMethod.Session,
+ CollectionMethodOptions.LoggedOn => CollectionMethod.LoggedOn,
+ CollectionMethodOptions.Trusts => CollectionMethod.Trusts,
+ CollectionMethodOptions.ACL => CollectionMethod.ACL,
+ CollectionMethodOptions.ObjectProps => CollectionMethod.ObjectProps,
+ CollectionMethodOptions.RDP => CollectionMethod.RDP,
+ CollectionMethodOptions.DCOM => CollectionMethod.DCOM,
+ CollectionMethodOptions.LocalAdmin => CollectionMethod.LocalAdmin,
+ CollectionMethodOptions.PSRemote => CollectionMethod.PSRemote,
+ CollectionMethodOptions.SPNTargets => CollectionMethod.SPNTargets,
+ CollectionMethodOptions.Container => CollectionMethod.Container,
+ CollectionMethodOptions.GPOLocalGroup => CollectionMethod.GPOLocalGroup,
+ CollectionMethodOptions.LocalGroup => CollectionMethod.LocalGroups,
+ CollectionMethodOptions.UserRights => CollectionMethod.UserRights,
+ CollectionMethodOptions.Default => CollectionMethod.Default,
+ CollectionMethodOptions.DCOnly => CollectionMethod.DCOnly,
+ CollectionMethodOptions.ComputerOnly => CollectionMethod.ComputerOnly,
+ CollectionMethodOptions.CARegistry => CollectionMethod.CARegistry,
+ CollectionMethodOptions.DCRegistry => CollectionMethod.DCRegistry,
+ CollectionMethodOptions.CertServices => CollectionMethod.CertServices,
+ CollectionMethodOptions.All => CollectionMethod.All,
+ CollectionMethodOptions.None => CollectionMethod.None,
_ => throw new ArgumentOutOfRangeException()
};
@@ -209,56 +215,56 @@ internal bool ResolveCollectionMethods(ILogger logger, out ResolvedCollectionMet
if (Stealth)
{
var updates = new List();
- if ((resolved & ResolvedCollectionMethod.LoggedOn) != 0)
+ if ((resolved & CollectionMethod.LoggedOn) != 0)
{
- resolved ^= ResolvedCollectionMethod.LoggedOn;
+ resolved ^= CollectionMethod.LoggedOn;
updates.Add("[-] Removed LoggedOn");
}
var localGroupRemoved = false;
- if ((resolved & ResolvedCollectionMethod.RDP) != 0)
+ if ((resolved & CollectionMethod.RDP) != 0)
{
localGroupRemoved = true;
- resolved ^= ResolvedCollectionMethod.RDP;
+ resolved ^= CollectionMethod.RDP;
updates.Add("[-] Removed RDP Collection");
}
- if ((resolved & ResolvedCollectionMethod.DCOM) != 0)
+ if ((resolved & CollectionMethod.DCOM) != 0)
{
localGroupRemoved = true;
- resolved ^= ResolvedCollectionMethod.DCOM;
+ resolved ^= CollectionMethod.DCOM;
updates.Add("[-] Removed DCOM Collection");
}
- if ((resolved & ResolvedCollectionMethod.PSRemote) != 0)
+ if ((resolved & CollectionMethod.PSRemote) != 0)
{
localGroupRemoved = true;
- resolved ^= ResolvedCollectionMethod.PSRemote;
+ resolved ^= CollectionMethod.PSRemote;
updates.Add("[-] Removed PSRemote Collection");
}
- if ((resolved & ResolvedCollectionMethod.LocalAdmin) != 0)
+ if ((resolved & CollectionMethod.LocalAdmin) != 0)
{
localGroupRemoved = true;
- resolved ^= ResolvedCollectionMethod.LocalAdmin;
+ resolved ^= CollectionMethod.LocalAdmin;
updates.Add("[-] Removed LocalAdmin Collection");
}
- if ((resolved & ResolvedCollectionMethod.CARegistry) != 0)
+ if ((resolved & CollectionMethod.CARegistry) != 0)
{
- resolved ^= ResolvedCollectionMethod.CARegistry;
+ resolved ^= CollectionMethod.CARegistry;
updates.Add("[-] Removed CARegistry Collection");
}
- if ((resolved & ResolvedCollectionMethod.DCRegistry) != 0)
+ if ((resolved & CollectionMethod.DCRegistry) != 0)
{
- resolved ^= ResolvedCollectionMethod.DCRegistry;
+ resolved ^= CollectionMethod.DCRegistry;
updates.Add("[-] Removed DCRegistry Collection");
}
if (localGroupRemoved)
{
- resolved |= ResolvedCollectionMethod.GPOLocalGroup;
+ resolved |= CollectionMethod.GPOLocalGroup;
updates.Add("[+] Added GPOLocalGroup");
}
diff --git a/src/Producers/BaseProducer.cs b/src/Producers/BaseProducer.cs
index b8fd9f2..8456cbd 100644
--- a/src/Producers/BaseProducer.cs
+++ b/src/Producers/BaseProducer.cs
@@ -21,11 +21,11 @@ namespace Sharphound.Producers
///
public abstract class BaseProducer
{
- protected readonly Channel Channel;
+ protected readonly Channel Channel;
protected readonly Channel OutputChannel;
protected readonly IContext Context;
- protected BaseProducer(IContext context, Channel channel, Channel outputChannel)
+ protected BaseProducer(IContext context, Channel channel, Channel outputChannel)
{
Context = context;
Channel = channel;
@@ -35,169 +35,21 @@ protected BaseProducer(IContext context, Channel channel, Ch
public abstract Task Produce();
public abstract Task ProduceConfigNC();
- protected LDAPData CreateDefaultNCData()
- {
- var query = new LDAPFilter();
- var props = new List();
- var data = new LDAPData();
- props.AddRange(CommonProperties.BaseQueryProps);
- props.AddRange(CommonProperties.TypeResolutionProps);
-
- var methods = Context.ResolvedCollectionMethods;
-
- if ((methods & ResolvedCollectionMethod.ObjectProps) != 0 || (methods & ResolvedCollectionMethod.ACL) != 0)
- {
- query = query.AddComputers().AddContainers().AddUsers().AddGroups().AddDomains().AddOUs().AddGPOs();
- props.AddRange(CommonProperties.ObjectPropsProps);
-
- if ((methods & ResolvedCollectionMethod.Container) != 0)
- props.AddRange(CommonProperties.ContainerProps);
-
- if ((methods & ResolvedCollectionMethod.Group) != 0)
- {
- props.AddRange(CommonProperties.GroupResolutionProps);
- query = query.AddPrimaryGroups();
- }
-
- if ((methods & ResolvedCollectionMethod.ACL) != 0) props.AddRange(CommonProperties.ACLProps);
-
- if ((methods & ResolvedCollectionMethod.LocalAdmin) != 0 ||
- (methods & ResolvedCollectionMethod.DCOM) != 0 ||
- (methods & ResolvedCollectionMethod.PSRemote) != 0 ||
- (methods & ResolvedCollectionMethod.RDP) != 0 ||
- (methods & ResolvedCollectionMethod.LoggedOn) != 0 ||
- (methods & ResolvedCollectionMethod.Session) != 0 ||
- (methods & ResolvedCollectionMethod.ObjectProps) != 0 ||
- (methods & ResolvedCollectionMethod.UserRights) != 0)
-
- props.AddRange(CommonProperties.ComputerMethodProps);
-
- if ((methods & ResolvedCollectionMethod.Trusts) != 0) props.AddRange(CommonProperties.DomainTrustProps);
-
- if ((methods & ResolvedCollectionMethod.GPOLocalGroup) != 0)
- props.AddRange(CommonProperties.GPOLocalGroupProps);
-
- if ((methods & ResolvedCollectionMethod.SPNTargets) != 0)
- props.AddRange(CommonProperties.SPNTargetProps);
-
- if ((methods & ResolvedCollectionMethod.DCRegistry) != 0)
- props.AddRange(CommonProperties.ComputerMethodProps);
- }
- else
- {
- if ((methods & ResolvedCollectionMethod.Container) != 0)
- {
- query = query.AddComputers().AddContainers().AddUsers().AddGroups().AddDomains().AddOUs().AddGPOs();
- props.AddRange(CommonProperties.ContainerProps);
- }
-
- if ((methods & ResolvedCollectionMethod.Group) != 0)
- {
- query = query.AddGroups().AddPrimaryGroups();
- props.AddRange(CommonProperties.GroupResolutionProps);
- }
-
- if ((methods & ResolvedCollectionMethod.LocalAdmin) != 0 ||
- (methods & ResolvedCollectionMethod.DCOM) != 0 ||
- (methods & ResolvedCollectionMethod.PSRemote) != 0 ||
- (methods & ResolvedCollectionMethod.RDP) != 0 ||
- (methods & ResolvedCollectionMethod.LoggedOn) != 0 ||
- (methods & ResolvedCollectionMethod.Session) != 0 ||
- (methods & ResolvedCollectionMethod.ObjectProps) != 0 ||
- (methods & ResolvedCollectionMethod.UserRights) != 0)
- {
- query = query.AddComputers();
- props.AddRange(CommonProperties.ComputerMethodProps);
- }
-
- if ((methods & ResolvedCollectionMethod.Trusts) != 0)
- {
- query = query.AddDomains();
- props.AddRange(CommonProperties.DomainTrustProps);
- }
+ protected GeneratedLdapParameters CreateDefaultNCData() {
+ var baseData =
+ LdapProducerQueryGenerator.GenerateDefaultPartitionParameters(Context.ResolvedCollectionMethods);
- if ((methods & ResolvedCollectionMethod.SPNTargets) != 0)
- {
- query = query.AddUsers(CommonFilters.NeedsSPN);
- props.AddRange(CommonProperties.SPNTargetProps);
- }
-
- if ((methods & ResolvedCollectionMethod.GPOLocalGroup) != 0)
- {
- query = query.AddOUs();
- props.AddRange(CommonProperties.GPOLocalGroupProps);
- }
-
- if ((methods & ResolvedCollectionMethod.DCRegistry) != 0)
- {
- query = query.AddComputers(CommonFilters.DomainControllers);
- props.AddRange(CommonProperties.ComputerMethodProps);
- }
- }
-
- if (Context.LdapFilter != null) query.AddFilter(Context.LdapFilter, true);
-
- data.Filter = query;
- data.Props = props;
- return data;
+ if (Context.LdapFilter != null) baseData.Filter.AddFilter(Context.LdapFilter, true);
+ return baseData;
}
- protected LDAPData CreateConfigNCData()
+ protected GeneratedLdapParameters CreateConfigNCData()
{
- var query = new LDAPFilter();
- var props = new List();
- var data = new LDAPData();
- props.AddRange(CommonProperties.BaseQueryProps);
- props.AddRange(CommonProperties.TypeResolutionProps);
-
- var methods = Context.ResolvedCollectionMethods;
- var allObjectTypesQuery = new LDAPFilter().AddContainers().AddConfiguration().AddCertificateTemplates().AddCertificateAuthorities().AddEnterpriseCertificationAuthorities().AddIssuancePolicies();
-
- if ((methods & ResolvedCollectionMethod.ObjectProps) != 0)
- {
- query = allObjectTypesQuery;
- props.AddRange(CommonProperties.ObjectPropsProps);
- }
-
- if ((methods & ResolvedCollectionMethod.ACL) != 0)
- {
- query = allObjectTypesQuery;
- props.AddRange(CommonProperties.ACLProps);
- }
-
- if ((methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- query = allObjectTypesQuery;
- props.AddRange(CommonProperties.CertAbuseProps);
- props.AddRange(CommonProperties.ObjectPropsProps);
- props.AddRange(CommonProperties.ContainerProps);
- props.AddRange(CommonProperties.ACLProps);
- }
+ var baseData =
+ LdapProducerQueryGenerator.GenerateConfigurationPartitionParameters(Context.ResolvedCollectionMethods);
- if ((methods & ResolvedCollectionMethod.Container) != 0)
- {
- query = allObjectTypesQuery;
- props.AddRange(CommonProperties.ContainerProps);
- }
-
- if ((methods & ResolvedCollectionMethod.CARegistry) != 0)
- {
- query = query.AddEnterpriseCertificationAuthorities();
- props.AddRange(CommonProperties.CertAbuseProps);
- }
-
-
- if (Context.LdapFilter != null) query.AddFilter(Context.LdapFilter, true);
-
- data.Filter = query;
- data.Props = props;
- return data;
+ if (Context.LdapFilter != null) baseData.Filter.AddFilter(Context.LdapFilter, true);
+ return baseData;
}
}
-
- public class LDAPData
- {
- internal LDAPFilter Filter { get; set; }
- internal IEnumerable Props { get; set; }
- }
}
\ No newline at end of file
diff --git a/src/Producers/ComputerFileProducer.cs b/src/Producers/ComputerFileProducer.cs
index 7069778..b6233df 100644
--- a/src/Producers/ComputerFileProducer.cs
+++ b/src/Producers/ComputerFileProducer.cs
@@ -7,6 +7,7 @@
using Microsoft.Extensions.Logging;
using Sharphound.Client;
using SharpHoundCommonLib;
+using SharpHoundCommonLib.LDAPQueries;
using SharpHoundCommonLib.OutputTypes;
namespace Sharphound.Producers
@@ -16,7 +17,7 @@ namespace Sharphound.Producers
///
internal class ComputerFileProducer : BaseProducer
{
- public ComputerFileProducer(IContext context, Channel channel, Channel outputChannel) : base(context, channel, outputChannel)
+ public ComputerFileProducer(IContext context, Channel channel, Channel outputChannel) : base(context, channel, outputChannel)
{
}
@@ -32,6 +33,24 @@ public override async Task Produce()
var ldapData = CreateDefaultNCData();
+
+ if (Context.Flags.CollectAllProperties)
+ {
+ Context.Logger.LogDebug("CollectAllProperties set. Changing LDAP properties to *");
+ ldapData.Attributes = new[] { "*" };
+ }
+
+ string domainName;
+ if (Context.DomainName == null) {
+ if (!Context.LDAPUtils.GetDomain(out var domainObj)) {
+ Context.Logger.LogError("No domain name specified for computer file producer and unable to resolve a domain name");
+ return;
+ }
+ domainName = domainObj?.Name;
+ } else {
+ domainName = Context.DomainName;
+ }
+
try
{
//Open the file for reading
@@ -44,9 +63,15 @@ public override async Task Produce()
if (cancellationToken.IsCancellationRequested) break;
string sid;
- if (!computer.StartsWith("S-1-5-21"))
+ if (!computer.StartsWith("S-1-5-21")) {
//The computer isn't a SID so try to convert it to one
- sid = await Context.LDAPUtils.ResolveHostToSid(computer, Context.DomainName);
+ if (await Context.LDAPUtils.ResolveHostToSid(computer, domainName) is (true, var tempSid)) {
+ sid = tempSid;
+ } else {
+ Context.Logger.LogError("Failed to resolve host {Computer} to SID", computer);
+ continue;
+ }
+ }
else
//The computer is already a sid, so just store it off
sid = computer;
@@ -54,11 +79,13 @@ public override async Task Produce()
try
{
//Convert the sid to a hex representation and find the entry in the domain
- var hexSid = Helpers.ConvertSidToHexSid(sid);
- var entry = Context.LDAPUtils.QueryLDAP($"(objectsid={hexSid})", SearchScope.Subtree,
- ldapData.Props.ToArray(), cancellationToken, Context.DomainName,
- adsPath: Context.SearchBase).DefaultIfEmpty(null).FirstOrDefault();
- if (entry == null)
+ var entry = await Context.LDAPUtils.Query(new LdapQueryParameters() {
+ LDAPFilter = CommonFilters.SpecificSID(sid),
+ Attributes = ldapData.Attributes,
+ DomainName = domainName,
+ SearchBase = Context.SearchBase
+ }, cancellationToken).FirstOrDefaultAsync(LdapResult.Fail());
+ if (!entry.IsSuccess)
{
//We couldn't find the entry for whatever reason
Context.Logger.LogWarning("Failed to resolve {computer}", computer);
@@ -66,7 +93,7 @@ public override async Task Produce()
}
//Success! Send the computer to be processed
- await Channel.Writer.WriteAsync(entry, cancellationToken);
+ await Channel.Writer.WriteAsync(entry.Value, cancellationToken);
}
catch (Exception e)
{
diff --git a/src/Producers/LdapProducer.cs b/src/Producers/LdapProducer.cs
index fb6027e..99aea43 100644
--- a/src/Producers/LdapProducer.cs
+++ b/src/Producers/LdapProducer.cs
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
-using System.DirectoryServices.Protocols;
-using System.Linq;
using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -14,7 +12,7 @@ namespace Sharphound.Producers
{
public class LdapProducer : BaseProducer
{
- public LdapProducer(IContext context, Channel channel, Channel outputChannel) : base(context, channel, outputChannel)
+ public LdapProducer(IContext context, Channel channel, Channel outputChannel) : base(context, channel, outputChannel)
{
}
@@ -39,48 +37,61 @@ public override async Task Produce()
if (Context.Flags.CollectAllProperties)
{
log.LogDebug("CollectAllProperties set. Changing LDAP properties to *");
- ldapData.Props = new[] { "*" };
+ ldapData.Attributes = new[] { "*" };
}
foreach (var domain in Context.Domains)
{
Context.Logger.LogInformation("Beginning LDAP search for {Domain}", domain.Name);
//Do a basic LDAP search and grab results
- var successfulConnect = false;
- try
- {
- log.LogInformation("Testing ldap connection to {Domain}", domain.Name);
- successfulConnect = utils.TestLDAPConfig(domain.Name);
- }
- catch (Exception e)
- {
- log.LogError(e, "Unable to connect to domain {Domain}", domain.Name);
- continue;
- }
-
- if (!successfulConnect)
- {
- log.LogError("Successful connection made to {Domain} but no data returned", domain.Name);
+ if (await utils.TestLdapConnection(domain.Name) is (false, var message)) {
+ log.LogError("Unable to connect to domain {Domain}: {Message}", domain.Name, message);
continue;
}
Context.CollectedDomainSids.Add(domain.DomainSid);
- foreach (var searchResult in Context.LDAPUtils.QueryLDAP(ldapData.Filter.GetFilter(), SearchScope.Subtree,
- ldapData.Props.Distinct().ToArray(), cancellationToken, domain.Name,
- adsPath: Context.SearchBase,
- includeAcl: (Context.ResolvedCollectionMethods & ResolvedCollectionMethod.ACL) != 0))
- {
- var l = searchResult.DistinguishedName.ToLower();
- if (l.Contains("cn=domainupdates,cn=system"))
- continue;
- if (l.Contains("cn=policies,cn=system") && (l.StartsWith("cn=user") || l.StartsWith("cn=machine")))
- continue;
+ foreach (var filter in ldapData.Filter.GetFilterList()) {
+ foreach (var partitionedFilter in GetPartitionedFilter(filter)) {
+ await foreach (var result in Context.LDAPUtils.PagedQuery(new LdapQueryParameters() {
+ LDAPFilter = partitionedFilter,
+ Attributes = ldapData.Attributes,
+ DomainName = domain.Name,
+ SearchBase = Context.SearchBase,
+ IncludeSecurityDescriptor = Context.ResolvedCollectionMethods.HasFlag(CollectionMethod.ACL)
+ }, cancellationToken)){
+ if (!result.IsSuccess) {
+ Context.Logger.LogError("Error during main ldap query:{Message} ({Code})", result.Error, result.ErrorCode);
+ break;
+ }
+
+ var searchResult = result.Value;
- await Channel.Writer.WriteAsync(searchResult, cancellationToken);
- Context.Logger.LogTrace("Producer wrote {DistinguishedName} to channel", searchResult.DistinguishedName);
+ if (searchResult.TryGetDistinguishedName(out var distinguishedName)) {
+ var lower = distinguishedName.ToLower();
+ if (lower.Contains("cn=domainupdates,cn=system"))
+ continue;
+ if (lower.Contains("cn=policies,cn=system") && (lower.StartsWith("cn=user") || lower.StartsWith("cn=machine")))
+ continue;
+
+ await Channel.Writer.WriteAsync(searchResult, cancellationToken);
+ Context.Logger.LogTrace("Producer wrote {DistinguishedName} to channel", distinguishedName);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private IEnumerable GetPartitionedFilter(string originalFilter) {
+ if (Context.Flags.ParititonLdapQueries) {
+ for (var i = 0; i < 256; i++) {
+ yield return $"(&{originalFilter}(objectguid=\\{i.ToString("x2")}*))";
}
}
+ else {
+ yield return originalFilter;
+ }
}
///
@@ -91,33 +102,63 @@ public override async Task ProduceConfigNC()
{
var cancellationToken = Context.CancellationTokenSource.Token;
var configNcData = CreateConfigNCData();
- var configurationNCsCollected = new List();
+ var configurationNCsCollected = new HashSet(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrEmpty(configNcData.Filter.GetFilter()))
return;
foreach (var domain in Context.Domains)
{
- var configAdsPath = Context.LDAPUtils.GetConfigurationPath(domain.Name);
- if (!configurationNCsCollected.Contains(configAdsPath))
- {
+ if (await Context.LDAPUtils.GetNamingContextPath(domain.Name, NamingContext.Configuration) is
+ (true, var path)) {
+ if (!configurationNCsCollected.Add(path)) {
+ continue;
+ }
+
Context.Logger.LogInformation("Beginning LDAP search for {Domain} Configuration NC", domain.Name);
- // Ensure we only collect the Configuration NC once per forest
- configurationNCsCollected.Add(configAdsPath);
-
- //Do a basic LDAP search and grab results
- foreach (var searchResult in Context.LDAPUtils.QueryLDAP(configNcData.Filter.GetFilter(), SearchScope.Subtree,
- configNcData.Props.Distinct().ToArray(), cancellationToken, domain.Name,
- adsPath: configAdsPath,
- includeAcl: (Context.ResolvedCollectionMethods & ResolvedCollectionMethod.ACL) != 0 || (Context.ResolvedCollectionMethods & ResolvedCollectionMethod.CertServices) != 0))
- {
- await Channel.Writer.WriteAsync(searchResult, cancellationToken);
- Context.Logger.LogTrace("Producer wrote {DistinguishedName} to channel", searchResult.DistinguishedName);
+ foreach (var filter in configNcData.Filter.GetFilterList()) {
+ await foreach (var result in Context.LDAPUtils.PagedQuery(new LdapQueryParameters() {
+ LDAPFilter = filter,
+ Attributes = configNcData.Attributes,
+ DomainName = domain.Name,
+ SearchBase = path,
+ IncludeSecurityDescriptor = Context.ResolvedCollectionMethods.HasFlag(CollectionMethod.ACL)
+ }, cancellationToken)){
+ if (!result.IsSuccess) {
+ Context.Logger.LogError("Error during main ldap query:{Message} ({Code})", result.Error, result.ErrorCode);
+ break;
+ }
+
+ var searchResult = result.Value;
+
+ if (searchResult.TryGetDistinguishedName(out var distinguishedName)) {
+ await Channel.Writer.WriteAsync(searchResult, cancellationToken);
+ Context.Logger.LogTrace("Producer wrote {DistinguishedName} to channel", distinguishedName);
+ }
+ }
+ }
+ } else {
+ foreach (var filter in configNcData.Filter.GetFilterList()) {
+ await foreach (var result in Context.LDAPUtils.PagedQuery(new LdapQueryParameters() {
+ LDAPFilter = filter,
+ Attributes = configNcData.Attributes,
+ DomainName = domain.Name,
+ IncludeSecurityDescriptor = Context.ResolvedCollectionMethods.HasFlag(CollectionMethod.ACL),
+ NamingContext = NamingContext.Configuration
+ }, cancellationToken)){
+ if (!result.IsSuccess) {
+ Context.Logger.LogError("Error during main ldap query:{Message} ({Code})", result.Error, result.ErrorCode);
+ break;
+ }
+
+ var searchResult = result.Value;
+
+ if (searchResult.TryGetDistinguishedName(out var distinguishedName)) {
+ await Channel.Writer.WriteAsync(searchResult, cancellationToken);
+ Context.Logger.LogTrace("Producer wrote {DistinguishedName} to channel", distinguishedName);
+ }
+ }
}
- }
- else
- {
- Context.Logger.LogTrace("Skipping already collected config NC '{path}' for domain {Domain}", configAdsPath, domain.Name);
}
}
}
diff --git a/src/Producers/StealthContext.cs b/src/Producers/StealthContext.cs
index 3c47b24..6880760 100644
--- a/src/Producers/StealthContext.cs
+++ b/src/Producers/StealthContext.cs
@@ -5,13 +5,13 @@ namespace Sharphound.Producers
{
public static class StealthContext
{
- private static Dictionary _stealthTargetSids;
+ private static Dictionary _stealthTargetSids;
///
/// Sets the list of stealth targets or appends to it if necessary
///
///
- internal static void AddStealthTargetSids(Dictionary targets)
+ internal static void AddStealthTargetSids(Dictionary targets)
{
if (_stealthTargetSids == null)
_stealthTargetSids = targets;
@@ -26,7 +26,7 @@ internal static bool IsSidStealthTarget(string sid)
return _stealthTargetSids.ContainsKey(sid);
}
- internal static IEnumerable GetSearchResultEntries()
+ internal static IEnumerable GetSearchResultEntries()
{
return _stealthTargetSids.Values;
}
diff --git a/src/Producers/StealthProducer.cs b/src/Producers/StealthProducer.cs
index a88a30e..dafeece 100644
--- a/src/Producers/StealthProducer.cs
+++ b/src/Producers/StealthProducer.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.DirectoryServices.Protocols;
@@ -7,6 +8,7 @@
using Microsoft.Extensions.Logging;
using Sharphound.Client;
using SharpHoundCommonLib;
+using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.LDAPQueries;
using SharpHoundCommonLib.OutputTypes;
@@ -18,20 +20,20 @@ namespace Sharphound.Producers
internal class StealthProducer : BaseProducer
{
private static bool _stealthTargetsBuilt;
- private readonly IEnumerable _props;
- private readonly IEnumerable _propsConfigNC;
- private readonly LDAPFilter _query;
- private readonly LDAPFilter _queryConfigNC;
+ private readonly string[] _props;
+ private readonly string[] _propsConfigNC;
+ private readonly LdapFilter _query;
+ private readonly LdapFilter _queryConfigNC;
- public StealthProducer(IContext context, Channel channel, Channel outputChannel) : base(context, channel, outputChannel)
+ public StealthProducer(IContext context, Channel channel, Channel outputChannel) : base(context, channel, outputChannel)
{
var ldapData = CreateDefaultNCData();
_query = ldapData.Filter;
- _props = ldapData.Props;
+ _props = ldapData.Attributes;
var configNCData = CreateConfigNCData();
_queryConfigNC = configNCData.Filter;
- _propsConfigNC = configNCData.Props;
+ _propsConfigNC = configNCData.Attributes;
}
///
@@ -47,26 +49,42 @@ public override async Task Produce()
//OutputTasks.StartOutputTimer(context);
//Output our stealth targets to the queue
- foreach (var searchResult in Context.LDAPUtils.QueryLDAP(_query.GetFilter(), SearchScope.Subtree,
- _props.ToArray(), cancellationToken,
- Context.DomainName, adsPath: Context.SearchBase))
- await Channel.Writer.WriteAsync(searchResult, cancellationToken);
+ await foreach (var result in Context.LDAPUtils.PagedQuery(new LdapQueryParameters() {
+ LDAPFilter = _query.GetFilter(),
+ Attributes = _props,
+ SearchBase = Context.SearchBase,
+ DomainName = Context.DomainName
+ }, cancellationToken)) {
+ if (!result.IsSuccess) {
+ Context.Logger.LogError("Error in stealth producer: {Message} ({Code})", result.Error, result.ErrorCode);
+ break;
+ }
+ await Channel.Writer.WriteAsync(result.Value, cancellationToken);
+ }
}
public override async Task ProduceConfigNC()
{
+ if (string.IsNullOrEmpty(_queryConfigNC.GetFilter()))
+ return;
var cancellationToken = Context.CancellationTokenSource.Token;
//If we haven't generated our stealth targets, we'll build it now
if (!_stealthTargetsBuilt)
BuildStealthTargets();
-
- var configAdsPath = Context.LDAPUtils.GetConfigurationPath(Context.DomainName);
-
+
//Output our stealth targets to the queue
- foreach (var searchResult in Context.LDAPUtils.QueryLDAP(_queryConfigNC.GetFilter(), SearchScope.Subtree,
- _propsConfigNC.ToArray(), cancellationToken,
- Context.DomainName, adsPath: configAdsPath))
- await Channel.Writer.WriteAsync(searchResult, cancellationToken);
+ await foreach (var result in Context.LDAPUtils.PagedQuery(new LdapQueryParameters() {
+ LDAPFilter = _query.GetFilter(),
+ Attributes = _props,
+ DomainName = Context.DomainName,
+ NamingContext = NamingContext.Configuration
+ }, cancellationToken)) {
+ if (!result.IsSuccess) {
+ Context.Logger.LogError("Error in stealth producer: {Message} ({Code})", result.Error, result.ErrorCode);
+ break;
+ }
+ await Channel.Writer.WriteAsync(result.Value, cancellationToken);
+ }
}
@@ -75,68 +93,86 @@ private async void BuildStealthTargets()
Context.Logger.LogInformation("Finding Stealth Targets from LDAP Properties");
var targets = await FindPathTargetSids();
- if (!Context.Flags.ExcludeDomainControllers) targets.Merge(FindDomainControllers());
+ if (!Context.Flags.ExcludeDomainControllers) targets.Merge(await FindDomainControllers());
StealthContext.AddStealthTargetSids(targets);
_stealthTargetsBuilt = true;
}
- private Dictionary FindDomainControllers()
- {
- return Context.LDAPUtils.QueryLDAP(CommonFilters.DomainControllers,
- SearchScope.Subtree, _props.ToArray(), Context.DomainName).Where(x => x.GetSid() != null)
- .ToDictionary(x => x.GetSid());
+ private async Task> FindDomainControllers() {
+ var res = new Dictionary();
+ await foreach (var result in Context.LDAPUtils.PagedQuery(new LdapQueryParameters() {
+ LDAPFilter = CommonFilters.DomainControllers,
+ Attributes = _props,
+ DomainName = Context.DomainName
+ })) {
+ if (!result.IsSuccess) {
+ break;
+ }
+
+ if (!result.Value.TryGetSecurityIdentifier(out var sid)) {
+ continue;
+ }
+
+ res.Add(sid, result.Value);
+ }
+
+ return res;
}
///
/// Finds stealth targets using ldap properties.
///
///
- private async Task> FindPathTargetSids()
+ private async Task> FindPathTargetSids()
{
var paths = new ConcurrentDictionary();
- var sids = new Dictionary();
-
- var query = new LDAPFilter();
+ var sids = new Dictionary();
+
+ //Request user objects with the "homedirectory", "scriptpath", or "profilepath" attributes
+ var query = new LdapFilter();
query.AddComputers("(|(homedirectory=*)(scriptpath=*)(profilepath=*))");
foreach (var domain in Context.Domains)
{
- //Request user objects with the "homedirectory", "scriptpath", or "profilepath" attributes
- Parallel.ForEach(Context.LDAPUtils.QueryLDAP(
- query.GetFilter(),
- SearchScope.Subtree,
- new[] { "homedirectory", "scriptpath", "profilepath" }, domain.Name), searchResult =>
- {
- //Grab any properties that exist, filter out null values
- var poss = new[]
- {
- searchResult.GetProperty("homedirectory"), searchResult.GetProperty("scriptpath"),
- searchResult.GetProperty("profilepath")
- }.Where(s => s != null);
-
- // Loop over each possibility, and grab the hostname from the path, adding it to a list
- foreach (var s in poss)
- {
- var split = s.Split('\\');
- if (!(split.Length >= 3)) continue;
- var path = split[2];
- paths.TryAdd(path, new byte());
+ await foreach (var searchResult in Context.LDAPUtils.PagedQuery(new LdapQueryParameters() {
+ LDAPFilter = query.GetFilter(),
+ Attributes = CommonProperties.StealthProperties,
+ DomainName = domain.Name
+ })) {
+ if (searchResult.IsSuccess) {
+ var poss = new[]
+ {
+ searchResult.Value.GetProperty("homedirectory"), searchResult.Value.GetProperty("scriptpath"),
+ searchResult.Value.GetProperty("profilepath")
+ }.Where(s => s != null);
+
+ foreach (var s in poss)
+ {
+ var split = s.Split('\\');
+ if (!(split.Length >= 3)) continue;
+ var path = split[2];
+ paths.TryAdd(path, new byte());
+ }
}
- });
+ }
}
-
-
-
+
// Loop over the paths we grabbed, and resolve them to sids.
foreach (var path in paths.Keys)
{
- var sid = await Context.LDAPUtils.ResolveHostToSid(path, Context.DomainName);
-
- if (sid != null && sid.StartsWith("S-1-5"))
- {
- var searchResult = Context.LDAPUtils.QueryLDAP(CommonFilters.SpecificSID(sid),
- SearchScope.Subtree, _props.ToArray());
- sids.Add(sid, searchResult.FirstOrDefault());
+ if (await Context.LDAPUtils.ResolveHostToSid(path, Context.DomainName) is (true, var sid)) {
+ if (sid != null && sid.StartsWith("S-1-5")) {
+ var searchResult = await Context.LDAPUtils.Query(new LdapQueryParameters() {
+ LDAPFilter = CommonFilters.SpecificSID(sid),
+ SearchScope = SearchScope.Subtree,
+ Attributes = _props,
+ DomainName = Context.DomainName
+ }).FirstOrDefaultAsync(LdapResult.Fail());
+
+ if (searchResult.IsSuccess) {
+ sids.Add(sid, searchResult.Value);
+ }
+ }
}
}
diff --git a/src/Runtime/CollectionTask.cs b/src/Runtime/CollectionTask.cs
index 5bb57ff..edc5361 100644
--- a/src/Runtime/CollectionTask.cs
+++ b/src/Runtime/CollectionTask.cs
@@ -15,7 +15,7 @@ public class CollectionTask
private readonly Channel _compStatusChannel;
private readonly CompStatusWriter _compStatusWriter;
private readonly IContext _context;
- private readonly Channel _ldapChannel;
+ private readonly Channel _ldapChannel;
private readonly ILogger _log;
private readonly Channel _outputChannel;
@@ -28,7 +28,7 @@ public CollectionTask(IContext context)
{
_context = context;
_log = context.Logger;
- _ldapChannel = Channel.CreateBounded(new BoundedChannelOptions(1000)
+ _ldapChannel = Channel.CreateBounded(new BoundedChannelOptions(1000)
{
SingleWriter = true,
SingleReader = false,
@@ -82,7 +82,7 @@ internal async Task StartCollection()
await Task.WhenAll(_taskPool);
_log.LogInformation("Consumers finished, closing output channel");
- foreach (var wkp in _context.LDAPUtils.GetWellKnownPrincipalOutput(_context.DomainName))
+ await foreach (var wkp in _context.LDAPUtils.GetWellKnownPrincipalOutput())
{
if (!wkp.ObjectIdentifier.EndsWith(EnterpriseDCSuffix))
{
diff --git a/src/Runtime/LDAPConsumer.cs b/src/Runtime/LDAPConsumer.cs
index 6960bd5..4aa4046 100644
--- a/src/Runtime/LDAPConsumer.cs
+++ b/src/Runtime/LDAPConsumer.cs
@@ -12,7 +12,7 @@ namespace Sharphound.Runtime
{
public static class LDAPConsumer
{
- internal static async Task ConsumeSearchResults(Channel inputChannel,
+ internal static async Task ConsumeSearchResults(Channel inputChannel,
Channel computerStatusChannel, Channel outputChannel, IContext context,
int id)
{
@@ -24,10 +24,9 @@ internal static async Task ConsumeSearchResults(Channel inpu
await foreach (var item in inputChannel.Reader.ReadAllAsync())
try
{
- var res = item.ResolveBloodHoundInfo();
-
- if (res == null)
+ if (await LdapUtils.ResolveSearchResult(item, context.LDAPUtils) is not (true, var res) || res == null) {
continue;
+ }
log.LogTrace("Consumer {ThreadID} started processing {obj}", threadId, res.DisplayName);
watch.Start();
diff --git a/src/Runtime/ObjectProcessors.cs b/src/Runtime/ObjectProcessors.cs
index 5a03232..4e289ad 100644
--- a/src/Runtime/ObjectProcessors.cs
+++ b/src/Runtime/ObjectProcessors.cs
@@ -8,6 +8,7 @@
using Microsoft.Extensions.Logging;
using Sharphound.Client;
using SharpHoundCommonLib;
+using SharpHoundCommonLib.DirectoryObjects;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.OutputTypes;
using SharpHoundCommonLib.Processors;
@@ -15,10 +16,8 @@
using Group = SharpHoundCommonLib.OutputTypes.Group;
using Label = SharpHoundCommonLib.Enums.Label;
-namespace Sharphound.Runtime
-{
- public class ObjectProcessors
- {
+namespace Sharphound.Runtime {
+ public class ObjectProcessors {
private const string StatusSuccess = "Success";
private readonly ACLProcessor _aclProcessor;
private readonly CertAbuseProcessor _certAbuseProcessor;
@@ -30,25 +29,27 @@ public class ObjectProcessors
private readonly DCRegistryProcessor _dCRegistryProcessor;
private readonly DomainTrustProcessor _domainTrustProcessor;
private readonly GroupProcessor _groupProcessor;
- private readonly LDAPPropertyProcessor _ldapPropertyProcessor;
+ private readonly LdapPropertyProcessor _ldapPropertyProcessor;
private readonly GPOLocalGroupProcessor _gpoLocalGroupProcessor;
private readonly UserRightsAssignmentProcessor _userRightsAssignmentProcessor;
private readonly LocalGroupProcessor _localGroupProcessor;
private readonly ILogger _log;
- private readonly ResolvedCollectionMethod _methods;
+ private readonly CollectionMethod _methods;
private readonly SPNProcessors _spnProcessor;
- public ObjectProcessors(IContext context, ILogger log)
- {
+ public ObjectProcessors(IContext context, ILogger log) {
_context = context;
_aclProcessor = new ACLProcessor(context.LDAPUtils);
_spnProcessor = new SPNProcessors(context.LDAPUtils);
- _ldapPropertyProcessor = new LDAPPropertyProcessor(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);
+ _computerAvailability = new ComputerAvailability(context.PortScanTimeout,
+ skipPortScan: context.Flags.SkipPortScan, skipPasswordCheck: context.Flags.SkipPasswordAgeCheck);
_certAbuseProcessor = new CertAbuseProcessor(context.LDAPUtils);
_dCRegistryProcessor = new DCRegistryProcessor(context.LDAPUtils);
- _computerSessionProcessor = new ComputerSessionProcessor(context.LDAPUtils, doLocalAdminSessionEnum: context.Flags.DoLocalAdminSessionEnum, localAdminUsername: context.LocalAdminUsername, localAdminPassword: context.LocalAdminPassword);
+ _computerSessionProcessor = new ComputerSessionProcessor(context.LDAPUtils,
+ doLocalAdminSessionEnum: context.Flags.DoLocalAdminSessionEnum,
+ localAdminUsername: context.LocalAdminUsername, localAdminPassword: context.LocalAdminPassword);
_groupProcessor = new GroupProcessor(context.LDAPUtils);
_containerProcessor = new ContainerProcessor(context.LDAPUtils);
_gpoLocalGroupProcessor = new GPOLocalGroupProcessor(context.LDAPUtils);
@@ -59,38 +60,36 @@ public ObjectProcessors(IContext context, ILogger log)
_log = log;
}
- internal async Task ProcessObject(ISearchResultEntry entry,
- ResolvedSearchResult resolvedSearchResult, Channel compStatusChannel)
- {
- switch (resolvedSearchResult.ObjectType)
- {
+ internal async Task ProcessObject(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult, Channel compStatusChannel) {
+ switch (resolvedSearchResult.ObjectType) {
case Label.User:
return await ProcessUserObject(entry, resolvedSearchResult);
case Label.Computer:
return await ProcessComputerObject(entry, resolvedSearchResult, compStatusChannel);
case Label.Group:
- return ProcessGroupObject(entry, resolvedSearchResult);
+ return await ProcessGroupObject(entry, resolvedSearchResult);
case Label.GPO:
- return ProcessGPOObject(entry, resolvedSearchResult);
+ return await ProcessGPOObject(entry, resolvedSearchResult);
case Label.Domain:
return await ProcessDomainObject(entry, resolvedSearchResult);
case Label.OU:
return await ProcessOUObject(entry, resolvedSearchResult);
case Label.Container:
case Label.Configuration:
- return ProcessContainerObject(entry, resolvedSearchResult);
+ return await ProcessContainerObject(entry, resolvedSearchResult);
case Label.RootCA:
- return ProcessRootCA(entry, resolvedSearchResult);
+ return await ProcessRootCA(entry, resolvedSearchResult);
case Label.AIACA:
- return ProcessAIACA(entry, resolvedSearchResult);
+ return await ProcessAIACA(entry, resolvedSearchResult);
case Label.EnterpriseCA:
return await ProcessEnterpriseCA(entry, resolvedSearchResult);
case Label.NTAuthStore:
- return ProcessNTAuthStore(entry, resolvedSearchResult);
+ return await ProcessNTAuthStore(entry, resolvedSearchResult);
case Label.CertTemplate:
- return ProcessCertTemplate(entry, resolvedSearchResult);
+ return await ProcessCertTemplate(entry, resolvedSearchResult);
case Label.IssuancePolicy:
- return ProcessIssuancePolicy(entry, resolvedSearchResult);
+ return await ProcessIssuancePolicy(entry, resolvedSearchResult);
case Label.Base:
return null;
default:
@@ -98,85 +97,84 @@ internal async Task ProcessObject(ISearchResultEntry entry,
}
}
- private async Task ProcessUserObject(ISearchResultEntry entry,
- ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new User
- {
+ private static Dictionary GetCommonProperties(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var props = new Dictionary {
+ { "domain", resolvedSearchResult.Domain },
+ { "name", resolvedSearchResult.DisplayName },
+ };
+
+ if (entry.TryGetDistinguishedName(out var distinguishedName)) {
+ props.Add("distinguishedname", distinguishedName.ToUpper());
+ }
+
+ if (!string.IsNullOrWhiteSpace(resolvedSearchResult.DomainSid)) {
+ props.Add("domainsid", resolvedSearchResult.DomainSid);
+ }
+
+ return props;
+ }
+
+ private async Task ProcessUserObject(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new User {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
ret.Properties.Add("samaccountname", entry.GetProperty(LDAPProperties.SAMAccountName));
-
if (entry.IsMSA()) ret.Properties.Add("msa", true);
-
if (entry.IsGMSA()) ret.Properties.Add("gmsa", true);
- if ((_methods & ResolvedCollectionMethod.ACL) != 0)
- {
- var aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry);
+ if ((_methods & CollectionMethod.ACL) != 0) {
+ var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry)
+ .ToArrayAsync(cancellationToken: _cancellationToken);
var gmsa = entry.GetByteProperty(LDAPProperties.GroupMSAMembership);
- ret.Aces = aces.Concat(_aclProcessor.ProcessGMSAReaders(gmsa, resolvedSearchResult.Domain)).ToArray();
+ ret.Aces = aces.Concat(await _aclProcessor.ProcessGMSAReaders(gmsa, resolvedSearchResult.Domain)
+ .ToArrayAsync(cancellationToken: _cancellationToken)).ToArray();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
- if ((_methods & ResolvedCollectionMethod.Group) != 0)
- {
+ if ((_methods & CollectionMethod.Group) != 0) {
var pg = entry.GetProperty(LDAPProperties.PrimaryGroupID);
ret.PrimaryGroupSID = GroupProcessor.GetPrimaryGroupInfo(pg, resolvedSearchResult.ObjectId);
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0)
- {
- var userProps = await _ldapPropertyProcessor.ReadUserProperties(entry);
+ if ((_methods & CollectionMethod.ObjectProps) != 0) {
+ var userProps = await _ldapPropertyProcessor.ReadUserProperties(entry, resolvedSearchResult);
ret.Properties = ContextUtils.Merge(ret.Properties, userProps.Props);
- if (_context.Flags.CollectAllProperties)
- {
+ if (_context.Flags.CollectAllProperties) {
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}
+
ret.HasSIDHistory = userProps.SidHistory;
ret.AllowedToDelegate = userProps.AllowedToDelegate;
}
- if ((_methods & ResolvedCollectionMethod.SPNTargets) != 0)
- {
- var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames);
-
- var targets = new List();
- var enumerator = _spnProcessor.ReadSPNTargets(spn, entry.DistinguishedName)
- .GetAsyncEnumerator(_cancellationToken);
-
- while (await enumerator.MoveNextAsync()) targets.Add(enumerator.Current);
-
- ret.SPNTargets = targets.ToArray();
+ if ((_methods & CollectionMethod.SPNTargets) != 0) {
+ ret.SPNTargets = await _spnProcessor.ReadSPNTargets(resolvedSearchResult, entry)
+ .ToArrayAsync(cancellationToken: _cancellationToken);
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0) {
+ if (entry.TryGetDistinguishedName(out var dn) &&
+ await _containerProcessor.GetContainingObject(dn) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
}
return ret;
}
- private async Task ProcessComputerObject(ISearchResultEntry entry,
- ResolvedSearchResult resolvedSearchResult, Channel compStatusChannel)
- {
- var ret = new Computer
- {
+ private async Task ProcessComputerObject(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult, Channel compStatusChannel) {
+ var ret = new Computer {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
ret.Properties.Add("samaccountname", entry.GetProperty(LDAPProperties.SAMAccountName));
var hasLaps = entry.HasLAPS();
@@ -184,37 +182,36 @@ private async Task ProcessComputerObject(ISearchResultEntry entry,
ret.IsDC = resolvedSearchResult.IsDomainController;
ret.DomainSID = resolvedSearchResult.DomainSid;
- if ((_methods & ResolvedCollectionMethod.ACL) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync(_cancellationToken);
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
- if ((_methods & ResolvedCollectionMethod.Group) != 0)
- {
+ if ((_methods & CollectionMethod.Group) != 0) {
var pg = entry.GetProperty(LDAPProperties.PrimaryGroupID);
ret.PrimaryGroupSID = GroupProcessor.GetPrimaryGroupInfo(pg, resolvedSearchResult.ObjectId);
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0)
- {
- var computerProps = await _ldapPropertyProcessor.ReadComputerProperties(entry);
+ if ((_methods & CollectionMethod.ObjectProps) != 0) {
+ var computerProps = await _ldapPropertyProcessor.ReadComputerProperties(entry, resolvedSearchResult);
ret.Properties = ContextUtils.Merge(ret.Properties, computerProps.Props);
- if (_context.Flags.CollectAllProperties)
- {
+ if (_context.Flags.CollectAllProperties) {
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}
+
ret.AllowedToDelegate = computerProps.AllowedToDelegate;
ret.AllowedToAct = computerProps.AllowedToAct;
ret.HasSIDHistory = computerProps.SidHistory;
ret.DumpSMSAPassword = computerProps.DumpSMSAPassword;
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0) {
+ if (entry.TryGetDistinguishedName(out var dn) &&
+ await _containerProcessor.GetContainingObject(dn) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
}
if (!_methods.IsComputerCollectionSet())
@@ -226,8 +223,7 @@ private async Task ProcessComputerObject(ISearchResultEntry entry,
var availability = await _computerAvailability.IsComputerAvailable(resolvedSearchResult, entry);
- if (!availability.Connectable)
- {
+ if (!availability.Connectable) {
await compStatusChannel.Writer.WriteAsync(availability.GetCSVStatus(resolvedSearchResult.DisplayName),
_cancellationToken);
ret.Status = availability;
@@ -236,12 +232,11 @@ await compStatusChannel.Writer.WriteAsync(availability.GetCSVStatus(resolvedSear
// DCRegistry
if (resolvedSearchResult.IsDomainController &
- (_methods & ResolvedCollectionMethod.DCRegistry) != 0)
- {
- DCRegistryData dCRegistryData = new()
- {
+ (_methods & CollectionMethod.DCRegistry) != 0) {
+ DCRegistryData dCRegistryData = new() {
CertificateMappingMethods = _dCRegistryProcessor.GetCertificateMappingMethods(apiName),
- StrongCertificateBindingEnforcement = _dCRegistryProcessor.GetStrongCertificateBindingEnforcement(apiName)
+ StrongCertificateBindingEnforcement =
+ _dCRegistryProcessor.GetStrongCertificateBindingEnforcement(apiName)
};
ret.DCRegistryData = dCRegistryData;
@@ -249,23 +244,20 @@ await compStatusChannel.Writer.WriteAsync(availability.GetCSVStatus(resolvedSear
var samAccountName = entry.GetProperty(LDAPProperties.SAMAccountName)?.TrimEnd('$');
- if ((_methods & ResolvedCollectionMethod.Session) != 0)
- {
+ if ((_methods & CollectionMethod.Session) != 0) {
await _context.DoDelay();
var sessionResult = await _computerSessionProcessor.ReadUserSessions(apiName,
resolvedSearchResult.ObjectId, resolvedSearchResult.Domain);
ret.Sessions = sessionResult;
if (_context.Flags.DumpComputerStatus)
- await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus
- {
+ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus {
Status = sessionResult.Collected ? StatusSuccess : sessionResult.FailureReason,
Task = "NetSessionEnum",
ComputerName = resolvedSearchResult.DisplayName
}, _cancellationToken);
}
- if ((_methods & ResolvedCollectionMethod.LoggedOn) != 0)
- {
+ if ((_methods & CollectionMethod.LoggedOn) != 0) {
await _context.DoDelay();
var privSessionResult = await _computerSessionProcessor.ReadUserSessionsPrivileged(
resolvedSearchResult.DisplayName, samAccountName,
@@ -273,22 +265,19 @@ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus
ret.PrivilegedSessions = privSessionResult;
if (_context.Flags.DumpComputerStatus)
- await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus
- {
+ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus {
Status = privSessionResult.Collected ? StatusSuccess : privSessionResult.FailureReason,
Task = "NetWkstaUserEnum",
ComputerName = resolvedSearchResult.DisplayName
}, _cancellationToken);
- if (!_context.Flags.NoRegistryLoggedOn)
- {
+ 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 compStatusChannel.Writer.WriteAsync(new CSVComputerStatus {
Status = privSessionResult.Collected ? StatusSuccess : privSessionResult.FailureReason,
Task = "RegistrySessions",
ComputerName = resolvedSearchResult.DisplayName
@@ -296,12 +285,11 @@ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus
}
}
- if ((_methods & ResolvedCollectionMethod.UserRights) != 0)
- {
+ if ((_methods & CollectionMethod.UserRights) != 0) {
await _context.DoDelay();
var userRights = _userRightsAssignmentProcessor.GetUserRightsAssignments(
- resolvedSearchResult.DisplayName, resolvedSearchResult.ObjectId,
- resolvedSearchResult.Domain, resolvedSearchResult.IsDomainController);
+ resolvedSearchResult.DisplayName, resolvedSearchResult.ObjectId,
+ resolvedSearchResult.Domain, resolvedSearchResult.IsDomainController);
ret.UserRights = await userRights.ToArrayAsync();
}
@@ -317,123 +305,100 @@ await compStatusChannel.Writer.WriteAsync(new CSVComputerStatus
return ret;
}
- private Group ProcessGroupObject(ISearchResultEntry entry,
- ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new Group
- {
+ private async Task ProcessGroupObject(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new Group {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
ret.Properties.Add("samaccountname", entry.GetProperty(LDAPProperties.SAMAccountName));
- if ((_methods & ResolvedCollectionMethod.ACL) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
- if ((_methods & ResolvedCollectionMethod.Group) != 0)
- ret.Members = _groupProcessor
+ if ((_methods & CollectionMethod.Group) != 0)
+ ret.Members = await _groupProcessor
.ReadGroupMembers(resolvedSearchResult, entry)
- .ToArray();
+ .ToArrayAsync();
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0)
- {
- var groupProps = LDAPPropertyProcessor.ReadGroupProperties(entry);
+ if ((_methods & CollectionMethod.ObjectProps) != 0) {
+ var groupProps = LdapPropertyProcessor.ReadGroupProperties(entry);
ret.Properties = ContextUtils.Merge(ret.Properties, groupProps);
- if (_context.Flags.CollectAllProperties)
- {
+ if (_context.Flags.CollectAllProperties) {
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0) {
+ if (entry.TryGetDistinguishedName(out var dn) &&
+ await _containerProcessor.GetContainingObject(dn) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
}
return ret;
}
- private async Task ProcessDomainObject(ISearchResultEntry entry,
- ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new Domain
- {
+ private async Task ProcessDomainObject(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new Domain {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
- if ((_methods & ResolvedCollectionMethod.ACL) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
+ ret.InheritanceHashes = _aclProcessor.GetInheritedAceHashes(entry, resolvedSearchResult).ToArray();
}
- if ((_methods & ResolvedCollectionMethod.Trusts) != 0)
- ret.Trusts = _domainTrustProcessor.EnumerateDomainTrusts(resolvedSearchResult.Domain).ToArray();
+ if ((_methods & CollectionMethod.Trusts) != 0)
+ ret.Trusts = await _domainTrustProcessor.EnumerateDomainTrusts(resolvedSearchResult.Domain)
+ .ToArrayAsync();
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0)
- {
- ret.Properties = ContextUtils.Merge(ret.Properties, LDAPPropertyProcessor.ReadDomainProperties(entry));
- if (_context.Flags.CollectAllProperties)
- {
+ if ((_methods & CollectionMethod.ObjectProps) != 0) {
+ ret.Properties = ContextUtils.Merge(ret.Properties, LdapPropertyProcessor.ReadDomainProperties(entry));
+ if (_context.Flags.CollectAllProperties) {
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0)
- {
- ret.Links = _containerProcessor.ReadContainerGPLinks(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.Container) != 0) {
+ ret.Links = await _containerProcessor.ReadContainerGPLinks(resolvedSearchResult, entry).ToArrayAsync();
}
- if ((_methods & ResolvedCollectionMethod.GPOLocalGroup) != 0)
- {
- var gplink = entry.GetProperty(LDAPProperties.GPLink);
- ret.GPOChanges = await _gpoLocalGroupProcessor.ReadGPOLocalGroups(gplink, entry.DistinguishedName);
+ if ((_methods & CollectionMethod.GPOLocalGroup) != 0) {
+ ret.GPOChanges = await _gpoLocalGroupProcessor.ReadGPOLocalGroups(entry);
}
return ret;
}
- private GPO ProcessGPOObject(ISearchResultEntry entry,
- ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new GPO
- {
+ private async Task ProcessGPOObject(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new GPO {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
- if ((_methods & ResolvedCollectionMethod.ACL) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0)
- {
- ret.Properties = ContextUtils.Merge(ret.Properties, LDAPPropertyProcessor.ReadGPOProperties(entry));
- if (_context.Flags.CollectAllProperties)
- {
+ if ((_methods & CollectionMethod.ObjectProps) != 0) {
+ ret.Properties = ContextUtils.Merge(ret.Properties, LdapPropertyProcessor.ReadGPOProperties(entry));
+ if (_context.Flags.CollectAllProperties) {
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}
@@ -442,223 +407,200 @@ private GPO ProcessGPOObject(ISearchResultEntry entry,
return ret;
}
- private async Task ProcessOUObject(ISearchResultEntry entry,
- ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new OU
- {
+ private async Task ProcessOUObject(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new OU {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
- if ((_methods & ResolvedCollectionMethod.ACL) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
+ ret.InheritanceHashes = _aclProcessor.GetInheritedAceHashes(entry, resolvedSearchResult).ToArray();
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0)
- {
- ret.Properties = ContextUtils.Merge(ret.Properties, LDAPPropertyProcessor.ReadOUProperties(entry));
- if (_context.Flags.CollectAllProperties)
- {
+ if ((_methods & CollectionMethod.ObjectProps) != 0) {
+ ret.Properties = ContextUtils.Merge(ret.Properties, LdapPropertyProcessor.ReadOUProperties(entry));
+ if (_context.Flags.CollectAllProperties) {
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0) {
+ if (await _containerProcessor.GetContainingObject(entry) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
+
ret.Properties.Add("blocksinheritance",
- ContainerProcessor.ReadBlocksInheritance(entry.GetProperty("gpoptions")));
- ret.Links = _containerProcessor.ReadContainerGPLinks(resolvedSearchResult, entry).ToArray();
+ ContainerProcessor.ReadBlocksInheritance(entry.GetProperty(LDAPProperties.GroupPolicyOptions)));
+ ret.Links = await _containerProcessor.ReadContainerGPLinks(resolvedSearchResult, entry).ToArrayAsync();
}
- if ((_methods & ResolvedCollectionMethod.GPOLocalGroup) != 0)
- {
- var gplink = entry.GetProperty(LDAPProperties.GPLink);
- ret.GPOChanges = await _gpoLocalGroupProcessor.ReadGPOLocalGroups(gplink, entry.DistinguishedName);
+ if ((_methods & CollectionMethod.GPOLocalGroup) != 0) {
+ ret.GPOChanges = await _gpoLocalGroupProcessor.ReadGPOLocalGroups(entry);
}
+
return ret;
}
- private Container ProcessContainerObject(ISearchResultEntry entry,
- ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new Container
- {
+ private async Task ProcessContainerObject(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new Container {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
- if ((_methods & ResolvedCollectionMethod.Container) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0 || (_methods & CollectionMethod.CertServices) != 0)
+ if (await _containerProcessor.GetContainingObject(entry) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
- if ((_methods & ResolvedCollectionMethod.ACL) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry)
- .ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry)
+ .ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
+ ret.InheritanceHashes = _aclProcessor.GetInheritedAceHashes(entry, resolvedSearchResult).ToArray();
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- if (_context.Flags.CollectAllProperties)
- {
+ if ((_methods & CollectionMethod.ObjectProps) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ ret.Properties =
+ ContextUtils.Merge(LdapPropertyProcessor.ReadContainerProperties(entry), ret.Properties);
+ if (_context.Flags.CollectAllProperties) {
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}
- //ret.Properties = ContextUtils.Merge(ret.Properties, LDAPPropertyProcessor.)
}
return ret;
}
- private RootCA ProcessRootCA(ISearchResultEntry entry, ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new RootCA
- {
+ private async Task ProcessRootCA(IDirectoryObject entry, ResolvedSearchResult resolvedSearchResult) {
+ var ret = new RootCA {
ObjectIdentifier = resolvedSearchResult.ObjectId,
DomainSID = resolvedSearchResult.DomainSid
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
-
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
- if ((_methods & ResolvedCollectionMethod.ACL) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+
+ if ((_methods & CollectionMethod.ACL) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- var props = LDAPPropertyProcessor.ReadRootCAProperties(entry);
+ if ((_methods & CollectionMethod.ObjectProps) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ var props = LdapPropertyProcessor.ReadRootCAProperties(entry);
ret.Properties.Merge(props);
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ if (await _containerProcessor.GetContainingObject(entry) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
}
return ret;
}
- private AIACA ProcessAIACA(ISearchResultEntry entry, ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new AIACA
- {
+ private async Task ProcessAIACA(IDirectoryObject entry, ResolvedSearchResult resolvedSearchResult) {
+ var ret = new AIACA {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
- if ((_methods & ResolvedCollectionMethod.ACL) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- var props = LDAPPropertyProcessor.ReadAIACAProperties(entry);
+ if ((_methods & CollectionMethod.ObjectProps) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ var props = LdapPropertyProcessor.ReadAIACAProperties(entry);
ret.Properties.Merge(props);
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ if (await _containerProcessor.GetContainingObject(entry) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
}
return ret;
}
- private async Task ProcessEnterpriseCA(ISearchResultEntry entry, ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new EnterpriseCA
- {
+ private async Task ProcessEnterpriseCA(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new EnterpriseCA {
ObjectIdentifier = resolvedSearchResult.ObjectId,
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
- if ((_methods & ResolvedCollectionMethod.ACL) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- var props = LDAPPropertyProcessor.ReadEnterpriseCAProperties(entry);
+ if ((_methods & CollectionMethod.ObjectProps) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ var props = LdapPropertyProcessor.ReadEnterpriseCAProperties(entry);
ret.Properties.Merge(props);
- // Enabled/published cert templates
- (IEnumerable resolvedTemplates, IEnumerable unresolvedTemplates) = _certAbuseProcessor.ProcessCertTemplates(entry.GetArrayProperty(LDAPProperties.CertificateTemplates), resolvedSearchResult.Domain);
- ret.EnabledCertTemplates = resolvedTemplates.ToArray();
- ret.Properties.Add("unresolvedpublishedtemplates", unresolvedTemplates.ToArray());
+ // Enabled cert templates
+ if (entry.TryGetArrayProperty(LDAPProperties.CertificateTemplates, out var rawTemplates)) {
+ var (resolvedTemplates, unresolvedTemplates) = await _certAbuseProcessor.ProcessCertTemplates(
+ rawTemplates, resolvedSearchResult.Domain);
+ ret.EnabledCertTemplates = resolvedTemplates.ToArray();
+ ret.Properties.Add("unresolvedpublishedtemplates", unresolvedTemplates.ToArray());
+ }
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ if (await _containerProcessor.GetContainingObject(entry) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
}
- if ((_methods & ResolvedCollectionMethod.CARegistry) != 0)
- {
- // Collect properties from CA server registry
+ if ((_methods & CollectionMethod.CARegistry) != 0) {
+ // Collect properties from CA server registry
var cASecurityCollected = false;
var enrollmentAgentRestrictionsCollected = false;
var isUserSpecifiesSanEnabledCollected = false;
var roleSeparationEnabledCollected = false;
var caName = entry.GetProperty(LDAPProperties.Name);
var dnsHostName = entry.GetProperty(LDAPProperties.DNSHostName);
- if ((_methods & ResolvedCollectionMethod.CARegistry) != 0 && caName != null && dnsHostName != null)
- {
- ret.HostingComputer = await _context.LDAPUtils.ResolveHostToSid(dnsHostName, resolvedSearchResult.Domain);
+ if (caName != null && dnsHostName != null) {
+ _log.LogWarning("CA {Name} host ({Dns}) could not be resolved to a SID.", caName, dnsHostName);
+ if (await _context.LDAPUtils.ResolveHostToSid(dnsHostName, resolvedSearchResult.DomainSid) is
+ (true, var sid) && sid.StartsWith("S-1-")) {
+ ret.HostingComputer = sid;
+ }
// If ResolveHostToSid does not return a valid SID, we don't want to record this host
- if (ret.HostingComputer != null && !ret.HostingComputer.StartsWith("S-1-"))
- {
- _log.LogWarning("CA host could not be resolved to a SID.", dnsHostName, resolvedSearchResult.Domain);
+ if (ret.HostingComputer != null && !ret.HostingComputer.StartsWith("S-1-")) {
ret.HostingComputer = null;
}
- CARegistryData cARegistryData = new()
- {
+ CARegistryData cARegistryData = new() {
IsUserSpecifiesSanEnabled = _certAbuseProcessor.IsUserSpecifiesSanEnabled(dnsHostName, caName),
+ EnrollmentAgentRestrictions = await _certAbuseProcessor.ProcessEAPermissions(caName,
+ resolvedSearchResult.Domain, dnsHostName, ret.HostingComputer),
RoleSeparationEnabled = _certAbuseProcessor.RoleSeparationEnabled(dnsHostName, caName),
- EnrollmentAgentRestrictions = await _certAbuseProcessor.ProcessEAPermissions(caName, resolvedSearchResult.Domain, dnsHostName, 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.
- CASecurity = await _certAbuseProcessor.ProcessRegistryEnrollmentPermissions(caName, resolvedSearchResult.Domain, dnsHostName, ret.HostingComputer)
+ CASecurity = await _certAbuseProcessor.ProcessRegistryEnrollmentPermissions(caName,
+ resolvedSearchResult.Domain, dnsHostName, ret.HostingComputer)
};
cASecurityCollected = cARegistryData.CASecurity.Collected;
@@ -673,117 +615,101 @@ private async Task ProcessEnterpriseCA(ISearchResultEntry entry, R
ret.Properties.Add("isuserspecifiessanenabledcollected", isUserSpecifiesSanEnabledCollected);
ret.Properties.Add("roleseparationenabledcollected", roleSeparationEnabledCollected);
}
-
+
return ret;
}
- private NTAuthStore ProcessNTAuthStore(ISearchResultEntry entry, ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new NTAuthStore
- {
+ private async Task ProcessNTAuthStore(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new NTAuthStore {
ObjectIdentifier = resolvedSearchResult.ObjectId,
DomainSID = resolvedSearchResult.DomainSid
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
- if ((_methods & ResolvedCollectionMethod.ACL) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- var props = LDAPPropertyProcessor.ReadNTAuthStoreProperties(entry);
+ if ((_methods & CollectionMethod.ObjectProps) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ var props = LdapPropertyProcessor.ReadNTAuthStoreProperties(entry);
- // Cert thumbprints
- var rawCertificates = entry.GetByteArrayProperty(LDAPProperties.CACertificate);
- var certificates = from rawCertificate in rawCertificates
- select new X509Certificate2(rawCertificate).Thumbprint;
- ret.Properties.Add("certthumbprints", certificates.ToArray());
+ if (entry.TryGetByteArrayProperty(LDAPProperties.CACertificate, out var rawCertificates)) {
+ var certificates = from rawCertificate in rawCertificates
+ select new X509Certificate2(rawCertificate).Thumbprint;
+ ret.Properties.Add("certthumbprints", certificates.ToArray());
+ }
ret.Properties.Merge(props);
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ if (await _containerProcessor.GetContainingObject(entry) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
}
return ret;
}
- private CertTemplate ProcessCertTemplate(ISearchResultEntry entry, ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new CertTemplate
- {
+ private async Task ProcessCertTemplate(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new CertTemplate {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
- if ((_methods & ResolvedCollectionMethod.ACL) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+ if ((_methods & CollectionMethod.ACL) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- var certTemplatesProps = LDAPPropertyProcessor.ReadCertTemplateProperties(entry);
+ if ((_methods & CollectionMethod.ObjectProps) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ var certTemplatesProps = LdapPropertyProcessor.ReadCertTemplateProperties(entry);
ret.Properties.Merge(certTemplatesProps);
}
- if ((_methods & ResolvedCollectionMethod.Container) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+ if ((_methods & CollectionMethod.Container) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ if (await _containerProcessor.GetContainingObject(entry) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
}
return ret;
}
- private IssuancePolicy ProcessIssuancePolicy(ISearchResultEntry entry,
- ResolvedSearchResult resolvedSearchResult)
- {
- var ret = new IssuancePolicy
- {
+ private async Task ProcessIssuancePolicy(IDirectoryObject entry,
+ ResolvedSearchResult resolvedSearchResult) {
+ var ret = new IssuancePolicy {
ObjectIdentifier = resolvedSearchResult.ObjectId
};
-
- ret.Properties.Add("domain", resolvedSearchResult.Domain);
- ret.Properties.Add("name", resolvedSearchResult.DisplayName);
- ret.Properties.Add("distinguishedname", entry.DistinguishedName.ToUpper());
- ret.Properties.Add("domainsid", resolvedSearchResult.DomainSid);
-
- if ((_methods & ResolvedCollectionMethod.ACL) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.Aces = _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArray();
+
+ ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult));
+
+ if ((_methods & CollectionMethod.ACL) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ ret.Aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry).ToArrayAsync();
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}
-
- if ((_methods & ResolvedCollectionMethod.ObjectProps) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- var issuancePolicyProps = _ldapPropertyProcessor.ReadIssuancePolicyProperties(entry);
+
+ if ((_methods & CollectionMethod.ObjectProps) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ var issuancePolicyProps = await _ldapPropertyProcessor.ReadIssuancePolicyProperties(entry);
ret.Properties.Merge(issuancePolicyProps.Props);
ret.GroupLink = issuancePolicyProps.GroupLink;
}
-
- if ((_methods & ResolvedCollectionMethod.Container) != 0 || (_methods & ResolvedCollectionMethod.CertServices) != 0)
- {
- ret.ContainedBy = _containerProcessor.GetContainingObject(entry.DistinguishedName);
+
+ if ((_methods & CollectionMethod.Container) != 0 || (_methods & CollectionMethod.CertServices) != 0) {
+ if (await _containerProcessor.GetContainingObject(entry) is (true, var container)) {
+ ret.ContainedBy = container;
+ }
}
return ret;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Sharphound.cs b/src/Sharphound.cs
index 920c73a..db87f3f 100644
--- a/src/Sharphound.cs
+++ b/src/Sharphound.cs
@@ -32,62 +32,51 @@
using SharpHoundCommonLib.Processors;
using Timer = System.Timers.Timer;
-namespace Sharphound
-{
+namespace Sharphound {
#region Reference Implementations
- internal class BasicLogger : ILogger
- {
+ internal class BasicLogger : ILogger {
private readonly int _verbosity;
- public BasicLogger(int verbosity)
- {
+ public BasicLogger(int verbosity) {
_verbosity = verbosity;
}
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception,
- Func formatter)
- {
+ Func formatter) {
WriteLevel(logLevel, state.ToString(), exception);
}
- public bool IsEnabled(LogLevel logLevel)
- {
+ public bool IsEnabled(LogLevel logLevel) {
return (int)logLevel >= _verbosity;
}
- public IDisposable BeginScope(TState state)
- {
+ public IDisposable BeginScope(TState state) {
return null;
}
- private void WriteLevel(LogLevel level, string message, Exception e = null)
- {
+ private void WriteLevel(LogLevel level, string message, Exception e = null) {
if (IsEnabled(level))
Console.WriteLine(FormatLog(level, message, e));
}
- private static string FormatLog(LogLevel level, string message, Exception e)
- {
+ private static string FormatLog(LogLevel level, string message, Exception e) {
var time = DateTime.Now;
return $"{time:O}|{level.ToString().ToUpper()}|{message}{(e != null ? $"\n{e}" : "")}";
}
}
- internal class SharpLinks : Links
- {
+ internal class SharpLinks : Links {
///
/// Init and check defaults
///
///
///
///
- public IContext Initialize(IContext context, LDAPConfig options)
- {
+ public IContext Initialize(IContext context, LdapConfig options) {
context.Logger.LogTrace("Entering initialize link");
- JsonConvert.DefaultSettings = () => new JsonSerializerSettings
- {
- Converters = new List {new KindConvertor()}
+ JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
+ Converters = new List { new KindConvertor() }
};
CommonLib.ReconfigureLogging(context.Logger);
//We've successfully parsed arguments, lets do some options post-processing.
@@ -98,18 +87,30 @@ public IContext Initialize(IContext context, LDAPConfig options)
// Check to make sure both LDAP options are set if either is set
if (options.Password != null && options.Username == null ||
- options.Username != null && options.Password == null)
- {
+ options.Username != null && options.Password == null) {
context.Logger.LogTrace("You must specify both LdapUsername and LdapPassword if using these options!");
context.Flags.IsFaulted = true;
return context;
}
-
+
+ if (string.IsNullOrWhiteSpace(context.DomainName)) {
+ if (!context.LDAPUtils.GetDomain(out var d)) {
+ context.Logger.LogCritical("unable to get current domain");
+ context.Flags.IsFaulted = true;
+ } else {
+ context.DomainName = d.Name;
+ context.Logger.LogInformation("Resolved current domain to {Domain}", d.Name);
+ }
+ }
+
//Check some loop options
- if (!context.Flags.Loop) return context;
+ if (!context.Flags.Loop) {
+ context.Logger.LogTrace("Exiting initialize link");
+ return context;
+ }
+
//If loop is set, ensure we actually set options properly
- if (context.LoopDuration == TimeSpan.Zero)
- {
+ if (context.LoopDuration == TimeSpan.Zero) {
context.Logger.LogTrace("Loop specified without a duration. Defaulting to 2 hours!");
context.LoopDuration = TimeSpan.FromHours(2);
}
@@ -117,39 +118,31 @@ public IContext Initialize(IContext context, LDAPConfig options)
if (context.LoopInterval == TimeSpan.Zero)
context.LoopInterval = TimeSpan.FromSeconds(30);
- if (!context.Flags.NoOutput)
- {
+ if (!context.Flags.NoOutput) {
var filename = context.ResolveFileName(Path.GetRandomFileName(), "", false);
- try
- {
- using (File.Create(filename))
- {
+ try {
+ using (File.Create(filename)) {
}
-
+
File.Delete(filename);
- }
- catch (Exception e)
- {
+ } catch (Exception e) {
context.Logger.LogCritical(e, "unable to write to target directory");
context.Flags.IsFaulted = true;
}
}
-
+
+
context.Logger.LogTrace("Exiting initialize link");
return context;
}
- public IContext TestConnection(IContext context)
- {
- context.Logger.LogTrace("Entering TestConnection link");
+ public async Task TestConnection(IContext context) {
+ context.Logger.LogTrace("Entering TestConnection link, testing domain {Domain}", context.DomainName);
//2. TestConnection()
// Initial LDAP connection test. Search for the well known administrator SID to make sure we can connect successfully.
- var result = context.LDAPUtils.TestLDAPConfig(context.DomainName);
-
- if (!result)
- {
- context.Logger.LogError("Unable to connect to LDAP, verify your credentials");
+ if (await context.LDAPUtils.TestLdapConnection(context.DomainName) is (false, var message)) {
+ context.Logger.LogError("Unable to connect to LDAP: {Message}", message);
context.Flags.IsFaulted = true;
}
@@ -157,14 +150,13 @@ public IContext TestConnection(IContext context)
context.Flags.NeedsCancellation = false;
context.Timer = null;
context.LoopEnd = DateTime.Now;
-
+
context.Logger.LogTrace("Exiting TestConnection link");
return context;
}
- public IContext SetSessionUserName(string overrideUserName, IContext context)
- {
+ public IContext SetSessionUserName(string overrideUserName, IContext context) {
context.Logger.LogTrace("Entering SetSessionUserName");
//3. SetSessionUserName()
// Set the current user name for session collection.
@@ -174,29 +166,23 @@ public IContext SetSessionUserName(string overrideUserName, IContext context)
return context;
}
- public IContext InitCommonLib(IContext context)
- {
+ public IContext InitCommonLib(IContext context) {
context.Logger.LogTrace("Entering InitCommonLib");
//4. Create our Cache/Initialize Common Lib
context.Logger.LogTrace("Getting cache path");
var path = context.GetCachePath();
context.Logger.LogTrace("Cache Path: {Path}", path);
Cache cache;
- if (!File.Exists(path))
- {
+ if (!File.Exists(path)) {
context.Logger.LogTrace("Cache file does not exist");
cache = null;
- }
- else
- try
- {
+ } else
+ try {
context.Logger.LogTrace("Loading cache from disk");
var json = File.ReadAllText(path);
cache = JsonConvert.DeserializeObject(json, CacheContractResolver.Settings);
context.Logger.LogInformation("Loaded cache with stats: {stats}", cache?.GetCacheStats());
- }
- catch (Exception e)
- {
+ } catch (Exception e) {
context.Logger.LogError("Error loading cache: {exception}, creating new", e);
cache = null;
}
@@ -206,80 +192,114 @@ public IContext InitCommonLib(IContext context)
return context;
}
- public IContext GetDomainsForEnumeration(IContext context)
- {
+ public async Task GetDomainsForEnumeration(IContext context) {
context.Logger.LogTrace("Entering GetDomainsForEnumeration");
if (context.Flags.RecurseDomains) {
- context.Logger.LogInformation("[RecurseDomains] Cross-domain enumeration may result in reduced data quality");
- context.Domains = BuildRecursiveDomainList(context).ToArray();
- } else if (context.Flags.SearchForest) {
- context.Logger.LogInformation("[SearchForest] Cross-domain enumeration may result in reduced data quality");
- var forest = context.LDAPUtils.GetForest(context.DomainName);
- if (forest == null)
- {
- context.Logger.LogError("Unable to contact forest to get domains for SearchForest");
+ context.Logger.LogInformation(
+ "[RecurseDomains] Cross-domain enumeration may result in reduced data quality");
+ context.Domains = await BuildRecursiveDomainList(context).ToArrayAsync();
+ return context;
+ }
+
+ if (context.Flags.SearchForest) {
+ context.Logger.LogInformation(
+ "[SearchForest] Cross-domain enumeration may result in reduced data quality");
+ if (!context.LDAPUtils.GetDomain(context.DomainName, out var dObj)) {
+ context.Logger.LogError("Unable to get domain object for SearchForest");
+ context.Flags.IsFaulted = true;
+ return context;
+ }
+
+ Forest forest;
+ try {
+ forest = dObj.Forest;
+ } catch (Exception e) {
+ context.Logger.LogError("Unable to get forest object for SearchForest: {Message}", e.Message);
context.Flags.IsFaulted = true;
return context;
}
- context.Domains = (from Domain d in forest.Domains select new EnumerationDomain()
- {
- Name = d.Name,
- DomainSid = d.GetDirectoryEntry().GetSid(),
- }).ToArray();
- context.Logger.LogInformation("Domains for enumeration: {Domains}", JsonConvert.SerializeObject(context.Domains));
+ var temp = new List();
+ foreach (Domain d in forest.Domains) {
+ var entry = d.GetDirectoryEntry().ToDirectoryObject();
+ if (!entry.TryGetSecurityIdentifier(out var domainSid)) {
+ continue;
+ }
+
+ temp.Add(new EnumerationDomain() {
+ Name = d.Name,
+ DomainSid = domainSid
+ });
+ }
+
+ context.Domains = temp.ToArray();
+ context.Logger.LogInformation("Domains for enumeration: {Domains}",
+ JsonConvert.SerializeObject(context.Domains));
+ return context;
+ }
+
+ if (!context.LDAPUtils.GetDomain(context.DomainName, out var domainObject)) {
+ context.Logger.LogError("Unable to resolve a domain to use, manually specify one or check spelling");
+ context.Flags.IsFaulted = true;
return context;
}
- var domainObject = context.LDAPUtils.GetDomain(context.DomainName);
var domain = domainObject?.Name ?? context.DomainName;
- if (domain == null)
- {
+ if (domain == null) {
context.Logger.LogError("Unable to resolve a domain to use, manually specify one or check spelling");
context.Flags.IsFaulted = true;
+ return context;
}
-
- context.Domains = new[] { new EnumerationDomain
- {
- Name = domain,
- DomainSid = domainObject?.GetDirectoryEntry().GetSid() ?? "Unknown"
- }
- };
+
+ if (domainObject != null && domainObject.GetDirectoryEntry().ToDirectoryObject()
+ .TryGetSecurityIdentifier(out var sid)) {
+ context.Domains = new[] {
+ new EnumerationDomain {
+ Name = domain,
+ DomainSid = sid
+ }
+ };
+ } else {
+ context.Domains = new[] {
+ new EnumerationDomain {
+ Name = domain,
+ DomainSid = "Unknown"
+ }
+ };
+ }
+
context.Logger.LogTrace("Exiting GetDomainsForEnumeration");
return context;
}
-
- private IEnumerable BuildRecursiveDomainList(IContext context)
- {
+
+ private async IAsyncEnumerable BuildRecursiveDomainList(IContext context) {
var domainResults = new List();
var enumeratedDomains = new HashSet();
var enumerationQueue = new Queue<(string domainSid, string domainName)>();
var utils = context.LDAPUtils;
var log = context.Logger;
- var domain = utils.GetDomain();
- if (domain == null)
+ if (!utils.GetDomain(out var domain)) {
yield break;
+ }
var trustHelper = new DomainTrustProcessor(utils);
- var dSid = domain.GetDirectoryEntry().GetSid();
+ var dSidSuccess = domain.GetDirectoryEntry().ToDirectoryObject().TryGetSecurityIdentifier(out var dSid);
+
var dName = domain.Name;
enumerationQueue.Enqueue((dSid, dName));
- domainResults.Add(new EnumerationDomain
- {
+ domainResults.Add(new EnumerationDomain {
Name = dName.ToUpper(),
DomainSid = dSid.ToUpper()
});
- while (enumerationQueue.Count > 0)
- {
+ while (enumerationQueue.Count > 0) {
var (domainSid, domainName) = enumerationQueue.Dequeue();
enumeratedDomains.Add(domainSid.ToUpper());
- foreach (var trust in trustHelper.EnumerateDomainTrusts(domainName))
- {
- log.LogDebug("Got trusted domain {Name} with sid {Sid} and {Type}", trust.TargetDomainName.ToUpper(),
+ await foreach (var trust in trustHelper.EnumerateDomainTrusts(domainName)) {
+ log.LogDebug("Got trusted domain {Name} with sid {Sid} and {Type}",
+ trust.TargetDomainName.ToUpper(),
trust.TargetDomainSid.ToUpper(), trust.TrustType.ToString());
- domainResults.Add(new EnumerationDomain
- {
+ domainResults.Add(new EnumerationDomain {
Name = trust.TargetDomainName.ToUpper(),
DomainSid = trust.TargetDomainSid.ToUpper()
});
@@ -293,8 +313,7 @@ private IEnumerable BuildRecursiveDomainList(IContext context
yield return domainResult;
}
- public IContext StartBaseCollectionTask(IContext context)
- {
+ public IContext StartBaseCollectionTask(IContext context) {
context.Logger.LogTrace("Entering StartBaseCollectionTask");
context.Logger.LogInformation("Flags: {flags}", context.ResolvedCollectionMethods.GetIndividualFlags());
//5. Start the collection
@@ -304,28 +323,24 @@ public IContext StartBaseCollectionTask(IContext context)
return context;
}
- public async Task AwaitBaseRunCompletion(IContext context)
- {
+ public async Task AwaitBaseRunCompletion(IContext context) {
// 6. Wait for the collection to complete
await context.CollectionTask;
return context;
}
- public async Task AwaitLoopCompletion(IContext context)
- {
+ public async Task AwaitLoopCompletion(IContext context) {
await context.CollectionTask;
return context;
}
- public IContext DisposeTimer(IContext context)
- {
+ public IContext DisposeTimer(IContext context) {
//14. Dispose the context.
context.Timer?.Dispose();
return context;
}
- public IContext Finish(IContext context)
- {
+ public IContext Finish(IContext context) {
////16. And we're done!
var currTime = DateTime.Now;
context.Logger.LogInformation(
@@ -334,8 +349,7 @@ public IContext Finish(IContext context)
return context;
}
- public IContext SaveCacheFile(IContext context)
- {
+ public IContext SaveCacheFile(IContext context) {
if (context.Flags.MemCache)
return context;
// 15. Program exit started. Save the cache file
@@ -347,13 +361,13 @@ public IContext SaveCacheFile(IContext context)
stream.Write(serialized);
return context;
}
-
- public IContext StartLoop(IContext context)
- {
+
+ public IContext StartLoop(IContext context) {
if (!context.Flags.Loop || context.CancellationTokenSource.IsCancellationRequested) return context;
context.ResolvedCollectionMethods = context.ResolvedCollectionMethods.GetLoopCollectionMethods();
- context.Logger.LogInformation("Creating loop manager with methods {Methods}", context.ResolvedCollectionMethods);
+ context.Logger.LogInformation("Creating loop manager with methods {Methods}",
+ context.ResolvedCollectionMethods);
var manager = new LoopManager(context);
context.Logger.LogInformation("Starting looping");
context.CollectionTask = manager.StartLooping();
@@ -361,15 +375,13 @@ public IContext StartLoop(IContext context)
return context;
}
- public IContext StartLoopTimer(IContext context)
- {
+ public IContext StartLoopTimer(IContext context) {
//If loop is set, set up our timer for the loop now
if (!context.Flags.Loop || context.CancellationTokenSource.IsCancellationRequested) return context;
context.LoopEnd = context.LoopEnd.AddMilliseconds(context.LoopDuration.TotalMilliseconds);
context.Timer = new Timer();
- context.Timer.Elapsed += (_, _) =>
- {
+ context.Timer.Elapsed += (_, _) => {
if (context.Flags.InitialCompleted)
context.CancellationTokenSource.Cancel();
else
@@ -387,30 +399,24 @@ public IContext StartLoopTimer(IContext context)
#region Console Entrypoint
- public class Program
- {
- public static async Task Main(string[] args)
- {
+ public class Program {
+ public static async Task Main(string[] args) {
var logger = new BasicLogger((int)LogLevel.Information);
logger.LogInformation("This version of SharpHound is compatible with the 5.0.0 Release of BloodHound");
- try
- {
- var parser = new Parser(with =>
- {
+ try {
+ var parser = new Parser(with => {
with.CaseInsensitiveEnumValues = true;
with.CaseSensitive = false;
with.HelpWriter = Console.Error;
});
var options = parser.ParseArguments(args);
- await options.WithParsedAsync(async options =>
- {
+ await options.WithParsedAsync(async options => {
if (!options.ResolveCollectionMethods(logger, out var resolved, out var dconly)) return;
logger = new BasicLogger(options.Verbosity);
- var flags = new Flags
- {
+ var flags = new Flags {
Loop = options.Loop,
DumpComputerStatus = options.TrackComputerCalls,
NoRegistryLoggedOn = options.SkipRegistryLoggedOn,
@@ -418,7 +424,7 @@ await options.WithParsedAsync(async options =>
SkipPortScan = options.SkipPortCheck,
SkipPasswordAgeCheck = options.SkipPasswordCheck,
DisableKerberosSigning = options.DisableSigning,
- SecureLDAP = options.SecureLDAP,
+ SecureLDAP = options.ForceSecureLDAP,
InvalidateCache = options.RebuildCache,
NoZip = options.NoZip,
NoOutput = false,
@@ -430,24 +436,23 @@ await options.WithParsedAsync(async options =>
PrettyPrint = options.PrettyPrint,
SearchForest = options.SearchForest,
RecurseDomains = options.RecurseDomains,
- DoLocalAdminSessionEnum = options.DoLocalAdminSessionEnum
+ DoLocalAdminSessionEnum = options.DoLocalAdminSessionEnum,
+ ParititonLdapQueries = options.PartitionLdapQueries
};
- var ldapOptions = new LDAPConfig
- {
+ var ldapOptions = new LdapConfig {
Port = options.LDAPPort,
+ SSLPort = options.LDAPSSLPort,
DisableSigning = options.DisableSigning,
- SSL = options.SecureLDAP,
+ ForceSSL = options.ForceSecureLDAP,
AuthType = AuthType.Negotiate,
DisableCertVerification = options.DisableCertVerification
};
if (options.DomainController != null) ldapOptions.Server = options.DomainController;
- if (options.LDAPUsername != null)
- {
- if (options.LDAPPassword == null)
- {
+ if (options.LDAPUsername != null) {
+ if (options.LDAPPassword == null) {
logger.LogError("You must specify LDAPPassword if using the LDAPUsername options");
return;
}
@@ -455,40 +460,37 @@ await options.WithParsedAsync(async options =>
ldapOptions.Username = options.LDAPUsername;
ldapOptions.Password = options.LDAPPassword;
}
-
+
// Check to make sure both Local Admin Session Enum options are set if either is set
if (options.LocalAdminPassword != null && options.LocalAdminUsername == null ||
- options.LocalAdminUsername != null && options.LocalAdminPassword == null)
- {
- logger.LogError("You must specify both LocalAdminUsername and LocalAdminPassword if using these options!");
+ options.LocalAdminUsername != null && options.LocalAdminPassword == null) {
+ logger.LogError(
+ "You must specify both LocalAdminUsername and LocalAdminPassword if using these options!");
return;
}
// Check to make sure doLocalAdminSessionEnum is set when specifying localadmin and password
- if (options.LocalAdminPassword != null || options.LocalAdminUsername != null)
- {
- if (options.DoLocalAdminSessionEnum == false)
- {
- logger.LogError("You must use the --doLocalAdminSessionEnum switch in combination with --LocalAdminUsername and --LocalAdminPassword!");
+ if (options.LocalAdminPassword != null || options.LocalAdminUsername != null) {
+ if (options.DoLocalAdminSessionEnum == false) {
+ logger.LogError(
+ "You must use the --doLocalAdminSessionEnum switch in combination with --LocalAdminUsername and --LocalAdminPassword!");
return;
}
}
// Check to make sure LocalAdminUsername and LocalAdminPassword are set when using doLocalAdminSessionEnum
- if (options.DoLocalAdminSessionEnum == true)
- {
- if (options.LocalAdminPassword == null || options.LocalAdminUsername == null)
- {
- logger.LogError("You must specify both LocalAdminUsername and LocalAdminPassword if using the --doLocalAdminSessionEnum option!");
+ if (options.DoLocalAdminSessionEnum == true) {
+ if (options.LocalAdminPassword == null || options.LocalAdminUsername == null) {
+ logger.LogError(
+ "You must specify both LocalAdminUsername and LocalAdminPassword if using the --doLocalAdminSessionEnum option!");
return;
}
}
-
- IContext context = new BaseContext(logger, ldapOptions, flags)
- {
+
+ IContext context = new BaseContext(logger, ldapOptions, flags) {
DomainName = options.Domain,
CacheFileName = options.CacheName,
ZipFilename = options.ZipFilename,
@@ -510,7 +512,6 @@ await options.WithParsedAsync(async options =>
IsFaulted = false,
LocalAdminUsername = options.LocalAdminUsername,
LocalAdminPassword = options.LocalAdminPassword
-
};
var cancellationTokenSource = new CancellationTokenSource();
@@ -529,12 +530,12 @@ await options.WithParsedAsync(async options =>
context = links.Initialize(context, ldapOptions);
if (context.Flags.IsFaulted)
return;
- context = links.TestConnection(context);
+ context = await links.TestConnection(context);
if (context.Flags.IsFaulted)
return;
context = links.SetSessionUserName(options.OverrideUserName, context);
context = links.InitCommonLib(context);
- context = links.GetDomainsForEnumeration(context);
+ context = await links.GetDomainsForEnumeration(context);
if (context.Flags.IsFaulted)
return;
context = links.StartBaseCollectionTask(context);
@@ -545,19 +546,16 @@ await options.WithParsedAsync(async options =>
context = links.SaveCacheFile(context);
links.Finish(context);
});
- }
- catch (Exception ex)
- {
+ } catch (Exception ex) {
logger.LogError($"Error running SharpHound: {ex.Message}\n{ex.StackTrace}");
}
}
// Accessor function for the PS1 to work, do not change or remove
- public static void InvokeSharpHound(string[] args)
- {
+ public static void InvokeSharpHound(string[] args) {
Main(args).Wait();
}
}
#endregion
-}
+}
\ No newline at end of file
diff --git a/src/Writers/JsonDataWriter.cs b/src/Writers/JsonDataWriter.cs
index 9bb456a..9d2226d 100644
--- a/src/Writers/JsonDataWriter.cs
+++ b/src/Writers/JsonDataWriter.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
@@ -95,7 +96,8 @@ internal override async Task FlushWriter()
Count = Count,
CollectionMethods = (long)_context.ResolvedCollectionMethods,
DataType = DataType,
- Version = DataVersion
+ Version = DataVersion,
+ CollectorVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString()
};
await _jsonWriter.FlushAsync();