Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Random fixes #59

Merged
merged 5 commits into from Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions Sharphound.csproj
Expand Up @@ -6,8 +6,8 @@
<LangVersion>latest</LangVersion>
<DebugType>full</DebugType>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<Version>1.1.1</Version>
<FileVersion>1.1.1</FileVersion>
<Version>2.0.0</Version>
<FileVersion>2.0.0</FileVersion>
<Company>SpecterOps</Company>
<Product>SharpHound</Product>
<AssemblyName>SharpHound</AssemblyName>
Expand All @@ -24,10 +24,11 @@
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SharpHoundCommon" Version="3.0.3" />
<PackageReference Include="SharpHoundCommon" Version="3.0.4" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="System.Threading.Channels" Version="6.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.3.1" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/BaseContext.cs
Expand Up @@ -114,7 +114,7 @@ public string ResolveFileName(string filename, string extension, bool addTimesta
return finalPath;
}

public string[] Domains { get; set; }
public EnumerationDomain[] Domains { get; set; }

// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~Context()
Expand Down
2 changes: 1 addition & 1 deletion src/Client/Context.cs
Expand Up @@ -69,7 +69,7 @@ public interface IContext
string GetCachePath();
ResolvedCollectionMethod SetupMethodsForLoop();
string ResolveFileName(string filename, string extension, bool addTimestamp);
string[] Domains { get; set; }
EnumerationDomain[] Domains { get; set; }
void UpdateLoopTime();
}
}
1 change: 1 addition & 0 deletions src/Client/Flags.cs
Expand Up @@ -23,5 +23,6 @@ public class Flags
public bool DCOnly { get; set; }
public bool PrettyPrint { get; set; }
public bool SearchForest { get; set; }
public bool RecurseDomains { get; set; }
}
}
7 changes: 7 additions & 0 deletions src/EnumerationDomain.cs
@@ -0,0 +1,7 @@
namespace Sharphound;

public class EnumerationDomain
{
public string Name { get; set; }
public string DomainSid { get; set; }
}
2 changes: 2 additions & 0 deletions src/Options.cs
Expand Up @@ -22,6 +22,8 @@ public class Options

[Option('s', "searchforest", Default = false, HelpText = "Search all available domains in the forest")]
public bool SearchForest { get; set; }
[Option("recursedomains", Default = false, HelpText = "Recurse domain trusts to search")]
public bool RecurseDomains { get; set; }

[Option(HelpText = "Stealth Collection (Prefer DCOnly whenever possible!)")]
public bool Stealth { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/PowerShell/Template.ps1
Expand Up @@ -34,7 +34,7 @@
DcOnly - Collects Group Membership, ACLs, ObjectProps, Trusts, Containers, and GPO Admins
All - Collect all data

This can be a list of comma seperated valued as well to run multiple collection methods!
This can be a list of comma separated valued as well to run multiple collection methods!

.PARAMETER Domain

Expand Down
5 changes: 4 additions & 1 deletion src/Producers/BaseProducer.cs
Expand Up @@ -5,6 +5,7 @@
using SharpHoundCommonLib;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.LDAPQueries;
using SharpHoundCommonLib.OutputTypes;

namespace Sharphound.Producers
{
Expand All @@ -14,12 +15,14 @@ namespace Sharphound.Producers
public abstract class BaseProducer
{
protected readonly Channel<ISearchResultEntry> Channel;
protected readonly Channel<OutputBase> OutputChannel;
protected readonly IContext Context;

protected BaseProducer(IContext context, Channel<ISearchResultEntry> channel)
protected BaseProducer(IContext context, Channel<ISearchResultEntry> channel, Channel<OutputBase> outputChannel)
{
Context = context;
Channel = channel;
OutputChannel = outputChannel;
}

public abstract Task Produce();
Expand Down
3 changes: 2 additions & 1 deletion src/Producers/ComputerFileProducer.cs
Expand Up @@ -7,6 +7,7 @@
using Microsoft.Extensions.Logging;
using Sharphound.Client;
using SharpHoundCommonLib;
using SharpHoundCommonLib.OutputTypes;

namespace Sharphound.Producers
{
Expand All @@ -15,7 +16,7 @@ namespace Sharphound.Producers
/// </summary>
internal class ComputerFileProducer : BaseProducer
{
public ComputerFileProducer(IContext context, Channel<ISearchResultEntry> channel) : base(context, channel)
public ComputerFileProducer(IContext context, Channel<ISearchResultEntry> channel, Channel<OutputBase> outputChannel) : base(context, channel, outputChannel)
{
}

Expand Down
39 changes: 36 additions & 3 deletions src/Producers/LdapProducer.cs
@@ -1,17 +1,20 @@
using System.DirectoryServices.Protocols;
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;
using Sharphound.Client;
using SharpHoundCommonLib;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.OutputTypes;

namespace Sharphound.Producers
{
public class LdapProducer : BaseProducer
{
public LdapProducer(IContext context, Channel<ISearchResultEntry> channel) : base(context, channel)
public LdapProducer(IContext context, Channel<ISearchResultEntry> channel, Channel<OutputBase> outputChannel) : base(context, channel, outputChannel)
{
}

Expand All @@ -25,12 +28,42 @@ public override async Task Produce()

var ldapData = CreateLDAPData();

var log = Context.Logger;
var utils = Context.LDAPUtils;

foreach (var domain in Context.Domains)
{
Context.Logger.LogInformation("Beginning LDAP search for {Domain}", domain);
//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);
continue;
}

await OutputChannel.Writer.WriteAsync(new Domain
{
ObjectIdentifier = domain.DomainSid,
Properties = new Dictionary<string, object>
{
{ "collected", true },
}
});

foreach (var searchResult in Context.LDAPUtils.QueryLDAP(ldapData.Filter.GetFilter(), SearchScope.Subtree,
ldapData.Props.Distinct().ToArray(), cancellationToken, domain,
ldapData.Props.Distinct().ToArray(), cancellationToken, domain.Name,
adsPath: Context.SearchBase,
includeAcl: (Context.ResolvedCollectionMethods & ResolvedCollectionMethod.ACL) != 0))
{
Expand Down
5 changes: 3 additions & 2 deletions src/Producers/StealthProducer.cs
Expand Up @@ -8,6 +8,7 @@
using Sharphound.Client;
using SharpHoundCommonLib;
using SharpHoundCommonLib.LDAPQueries;
using SharpHoundCommonLib.OutputTypes;

namespace Sharphound.Producers
{
Expand All @@ -20,7 +21,7 @@ internal class StealthProducer : BaseProducer
private readonly IEnumerable<string> _props;
private readonly LDAPFilter _query;

public StealthProducer(IContext context, Channel<ISearchResultEntry> channel) : base(context, channel)
public StealthProducer(IContext context, Channel<ISearchResultEntry> channel, Channel<OutputBase> outputChannel) : base(context, channel, outputChannel)
{
var ldapData = CreateLDAPData();
_query = ldapData.Filter;
Expand Down Expand Up @@ -81,7 +82,7 @@ private async void BuildStealthTargets()
Parallel.ForEach(Context.LDAPUtils.QueryLDAP(
query.GetFilter(),
SearchScope.Subtree,
new[] { "homedirectory", "scriptpath", "profilepath" }, domain), searchResult =>
new[] { "homedirectory", "scriptpath", "profilepath" }, domain.Name), searchResult =>
{
//Grab any properties that exist, filter out null values
var poss = new[]
Expand Down
6 changes: 3 additions & 3 deletions src/Runtime/CollectionTask.cs
Expand Up @@ -49,11 +49,11 @@ public CollectionTask(IContext context)
_outputWriter = new OutputWriter(context, _outputChannel);

if (context.Flags.Stealth)
_producer = new StealthProducer(context, _ldapChannel);
_producer = new StealthProducer(context, _ldapChannel, _outputChannel);
else if (context.ComputerFile != null)
_producer = new ComputerFileProducer(context, _ldapChannel);
_producer = new ComputerFileProducer(context, _ldapChannel, _outputChannel);
else
_producer = new LdapProducer(context, _ldapChannel);
_producer = new LdapProducer(context, _ldapChannel, _outputChannel);
}

internal async Task<string> StartCollection()
Expand Down
28 changes: 18 additions & 10 deletions src/Runtime/ObjectProcessors.cs
Expand Up @@ -200,6 +200,7 @@ public ObjectProcessors(IContext context, ILogger log)

if ((_methods & ResolvedCollectionMethod.Session) != 0)
{
await _context.DoDelay();
var sessionResult = await _computerSessionProcessor.ReadUserSessions(apiName,
resolvedSearchResult.ObjectId, resolvedSearchResult.Domain);
ret.Sessions = sessionResult;
Expand All @@ -214,6 +215,7 @@ public ObjectProcessors(IContext context, ILogger log)

if ((_methods & ResolvedCollectionMethod.LoggedOn) != 0)
{
await _context.DoDelay();
var privSessionResult = await _computerSessionProcessor.ReadUserSessionsPrivileged(
resolvedSearchResult.DisplayName, samAccountName,
resolvedSearchResult.ObjectId);
Expand All @@ -227,20 +229,25 @@ public ObjectProcessors(IContext context, ILogger log)
ComputerName = resolvedSearchResult.DisplayName
}, _cancellationToken);

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

if ((_methods & ResolvedCollectionMethod.UserRights) != 0)
{
await _context.DoDelay();
var userRights = _userRightsAssignmentProcessor.GetUserRightsAssignments(
resolvedSearchResult.DisplayName, resolvedSearchResult.ObjectId,
resolvedSearchResult.Domain, resolvedSearchResult.IsDomainController);
Expand All @@ -250,6 +257,7 @@ public ObjectProcessors(IContext context, ILogger log)
if (!_methods.IsLocalGroupCollectionSet())
return ret;

await _context.DoDelay();
var localGroups = _localGroupProcessor.GetLocalGroups(resolvedSearchResult.DisplayName,
resolvedSearchResult.ObjectId, resolvedSearchResult.Domain,
resolvedSearchResult.IsDomainController);
Expand Down
72 changes: 65 additions & 7 deletions src/Sharphound.cs
Expand Up @@ -29,6 +29,7 @@
using Sharphound.Client;
using Sharphound.Runtime;
using SharpHoundCommonLib;
using SharpHoundCommonLib.Processors;
using Timer = System.Timers.Timer;

namespace Sharphound
Expand Down Expand Up @@ -208,8 +209,10 @@ public IContext InitCommonLib(IContext context)
public IContext GetDomainsForEnumeration(IContext context)
{
context.Logger.LogTrace("Entering GetDomainsForEnumeration");
if (context.Flags.SearchForest)
{
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)
Expand All @@ -219,22 +222,76 @@ public IContext GetDomainsForEnumeration(IContext context)
return context;
}

context.Domains = (from Domain d in forest.Domains select d.Name).ToArray();
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));
return context;
}

var domain = context.LDAPUtils.GetDomain(context.DomainName)?.Name ?? context.DomainName;
var domainObject = context.LDAPUtils.GetDomain(context.DomainName);
var domain = domainObject?.Name ?? context.DomainName;
if (domain == null)
{
context.Logger.LogError("unable to resolve a domain to use, manually specify one or check spelling");
context.Logger.LogError("Unable to resolve a domain to use, manually specify one or check spelling");
context.Flags.IsFaulted = true;
}

context.Domains = new[] { domain };
context.Domains = new[] { new EnumerationDomain
{
Name = domain,
DomainSid = domainObject?.GetDirectoryEntry().GetSid() ?? "Unknown"
}
};
context.Logger.LogTrace("Exiting GetDomainsForEnumeration");
return context;
}

private IEnumerable<EnumerationDomain> BuildRecursiveDomainList(IContext context)
{
var domainResults = new List<EnumerationDomain>();
var enumeratedDomains = new HashSet<string>();
var enumerationQueue = new Queue<(string domainSid, string domainName)>();
var utils = context.LDAPUtils;
var log = context.Logger;
var domain = utils.GetDomain();
if (domain == null)
yield break;

var trustHelper = new DomainTrustProcessor(utils);
var dSid = domain.GetDirectoryEntry().GetSid();
var dName = domain.Name;
enumerationQueue.Enqueue((dSid, dName));
domainResults.Add(new EnumerationDomain
{
Name = dName.ToUpper(),
DomainSid = dSid.ToUpper()
});

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(),
trust.TargetDomainSid.ToUpper(), trust.TrustType.ToString());
domainResults.Add(new EnumerationDomain
{
Name = trust.TargetDomainName.ToUpper(),
DomainSid = trust.TargetDomainSid.ToUpper()
});

if (!enumeratedDomains.Contains(trust.TargetDomainSid))
enumerationQueue.Enqueue((trust.TargetDomainSid, trust.TargetDomainName));
}
}

foreach (var domainResult in domainResults.GroupBy(x => x.DomainSid).Select(x => x.First()))
yield return domainResult;
}

public IContext StartBaseCollectionTask(IContext context)
{
Expand Down Expand Up @@ -371,7 +428,8 @@ public static async Task Main(string[] args)
CollectAllProperties = options.CollectAllProperties,
DCOnly = dconly,
PrettyPrint = options.PrettyPrint,
SearchForest = options.SearchForest
SearchForest = options.SearchForest,
RecurseDomains = options.RecurseDomains
};

var ldapOptions = new LDAPConfig
Expand Down