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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Client/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public enum CollectionMethodOptions
LdapServices,
SmbInfo,
NTLMRegistry,
Site,
// Re-introduce this when we're ready for Event Log collection
// EventLogs,
All
Expand Down
3 changes: 2 additions & 1 deletion src/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class Options
// Options that affect what is collected
[Option('c', "collectionmethods", Default = new[] { "Default" },
HelpText =
"Collection Methods: Group, LocalGroup, LocalAdmin, RDP, DCOM, PSRemote, Session, Trusts, ACL, Container, ComputerOnly, GPOLocalGroup, LoggedOn, ObjectProps, SPNTargets, UserRights, Default, DCOnly, CARegistry, DCRegistry, CertServices, WebClientService, LdapServices, SmbInfo, NTLMRegistry, All")]
"Collection Methods: Group, LocalGroup, LocalAdmin, RDP, DCOM, PSRemote, Session, Trusts, ACL, Container, ComputerOnly, GPOLocalGroup, LoggedOn, ObjectProps, SPNTargets, UserRights, Default, DCOnly, CARegistry, DCRegistry, CertServices, Site, WebClientService, LdapServices, SmbInfo, NTLMRegistry, All")]
public IEnumerable<string> CollectionMethods { get; set; }

[Option('d', "domain", Default = null, HelpText = "Specify domain to enumerate")]
Expand Down Expand Up @@ -208,6 +208,7 @@ internal bool ResolveCollectionMethods(ILogger logger, out CollectionMethod reso
CollectionMethodOptions.LdapServices => CollectionMethod.LdapServices,
CollectionMethodOptions.SmbInfo => CollectionMethod.SmbInfo,
CollectionMethodOptions.NTLMRegistry => CollectionMethod.NTLMRegistry,
CollectionMethodOptions.Site => CollectionMethod.Site,
// Re-introduce this when we're ready for Event Log collection
// CollectionMethodOptions.EventLogs => CollectionMethod.EventLogs,
CollectionMethodOptions.All => CollectionMethod.All,
Expand Down
128 changes: 128 additions & 0 deletions src/Runtime/ObjectProcessors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class ObjectProcessors {
private readonly SPNProcessors _spnProcessor;
private readonly WebClientServiceProcessor _webClientProcessor;
private readonly SmbProcessor _smbProcessor;
private readonly SiteProcessor _siteProcessor;
private readonly ConcurrentDictionary<string, RegistryProcessor> _registryProcessorMap = new();
public ObjectProcessors(IContext context, ILogger log) {
_context = context;
Expand All @@ -60,6 +61,7 @@ public ObjectProcessors(IContext context, ILogger log) {
_localGroupProcessor = new LocalGroupProcessor(context.LDAPUtils);
_webClientProcessor = new WebClientServiceProcessor(log);
_smbProcessor = new SmbProcessor(context.PortScanTimeout);
_siteProcessor = new SiteProcessor(context.LDAPUtils);
_methods = context.ResolvedCollectionMethods;
_cancellationToken = context.CancellationTokenSource.Token;
_log = log;
Expand Down Expand Up @@ -95,6 +97,12 @@ internal async Task<OutputBase> ProcessObject(IDirectoryObject entry,
return await ProcessCertTemplate(entry, resolvedSearchResult);
case Label.IssuancePolicy:
return await ProcessIssuancePolicy(entry, resolvedSearchResult);
case Label.Site:
return await ProcessSiteObject(entry, resolvedSearchResult);
case Label.SiteServer:
return await ProcessSiteServerObject(entry, resolvedSearchResult);
case Label.SiteSubnet:
return await ProcessSiteSubnetObject(entry, resolvedSearchResult);
case Label.Base:
return null;
default:
Expand Down Expand Up @@ -926,5 +934,125 @@ private async Task<IssuancePolicy> ProcessIssuancePolicy(IDirectoryObject entry,

return ret;
}

private async Task<Site> ProcessSiteObject(IDirectoryObject entry,
ResolvedSearchResult resolvedSearchResult)
{
var ret = new Site
{
ObjectIdentifier = resolvedSearchResult.ObjectId
};

ret.Properties = new Dictionary<string, object>(GetCommonProperties(entry, resolvedSearchResult));

if (_methods.HasFlag(CollectionMethod.ACL))
{
var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true)
.ToArrayAsync(cancellationToken: _cancellationToken);
ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid));
ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid));
ret.Aces = aces;
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}

if (_methods.HasFlag(CollectionMethod.ObjectProps))
{
ret.Properties =
ContextUtils.Merge(LdapPropertyProcessor.ReadSiteProperties(entry), ret.Properties);
if (_context.Flags.CollectAllProperties)
{
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}
}

ret.Links = await _siteProcessor.ReadSiteGPLinks(resolvedSearchResult, entry).ToArrayAsync();

return ret;
}

private async Task<SiteServer> ProcessSiteServerObject(IDirectoryObject entry,
ResolvedSearchResult resolvedSearchResult)
{
var ret = new SiteServer
{
ObjectIdentifier = resolvedSearchResult.ObjectId
};

ret.Properties = new Dictionary<string, object>(GetCommonProperties(entry, resolvedSearchResult));


if (await _siteProcessor.GetContainingSiteForServer(entry) is (true, var container))
{
ret.ContainedBy = container;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if (_methods.HasFlag(CollectionMethod.ACL))
{
var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true)
.ToArrayAsync(cancellationToken: _cancellationToken);
ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid));
ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid));
ret.Aces = aces;
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}

if (_methods.HasFlag(CollectionMethod.ObjectProps))
{
ret.Properties =
ContextUtils.Merge(LdapPropertyProcessor.ReadSiteServerProperties(entry), ret.Properties);
if (_context.Flags.CollectAllProperties)
{
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}
}

return ret;
}

private async Task<SiteSubnet> ProcessSiteSubnetObject(IDirectoryObject entry,
ResolvedSearchResult resolvedSearchResult)
{
var ret = new SiteSubnet
{
ObjectIdentifier = resolvedSearchResult.ObjectId
};

ret.Properties = new Dictionary<string, object>(GetCommonProperties(entry, resolvedSearchResult));

if (_methods.HasFlag(CollectionMethod.ACL))
{
var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true)
.ToArrayAsync(cancellationToken: _cancellationToken);
ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid));
ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid));
ret.Aces = aces;
ret.IsACLProtected = _aclProcessor.IsACLProtected(entry);
ret.Properties.Add("isaclprotected", ret.IsACLProtected);
}

if (_methods.HasFlag(CollectionMethod.ObjectProps))
{
ret.Properties =
ContextUtils.Merge(LdapPropertyProcessor.ReadSiteSubnetProperties(entry), ret.Properties);
if (_context.Flags.CollectAllProperties)
{
ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry),
ret.Properties);
}

// Can only deduce containing site for a subnet if we read the object properties, including siteObject

if (await _siteProcessor.GetContainingSiteForSubnet(ret.Properties) is (true, var container))
{
ret.ContainedBy = container;
}
}

return ret;
}
}
}
39 changes: 29 additions & 10 deletions src/Runtime/OutputWriter.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
using System;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using Microsoft.Extensions.Logging;
using Sharphound.Client;
using Sharphound.Writers;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.OutputTypes;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using System.IO;
using System.Linq;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Timers;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using Microsoft.Extensions.Logging;
using Sharphound.Client;
using Sharphound.Writers;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.OutputTypes;

namespace Sharphound.Runtime
{
Expand All @@ -34,6 +35,9 @@ public class OutputWriter
private readonly JsonDataWriter<NTAuthStore> _nTAuthStoreOutput;
private readonly JsonDataWriter<CertTemplate> _certTemplateOutput;
private readonly JsonDataWriter<IssuancePolicy> _issuancePolicyOutput;
private readonly JsonDataWriter<Site> _siteOutput;
private readonly JsonDataWriter<SiteServer> _siteServerOutput;
private readonly JsonDataWriter<SiteSubnet> _siteSubnetOutput;


private int _completedCount;
Expand All @@ -57,6 +61,9 @@ public OutputWriter(IContext context, Channel<OutputBase> outputChannel)
_nTAuthStoreOutput = new JsonDataWriter<NTAuthStore>(_context, DataType.NTAuthStores);
_certTemplateOutput = new JsonDataWriter<CertTemplate>(_context, DataType.CertTemplates);
_issuancePolicyOutput = new JsonDataWriter<IssuancePolicy>(_context, DataType.IssuancePolicies);
_siteOutput = new JsonDataWriter<Site>(_context, DataType.Sites);
_siteServerOutput = new JsonDataWriter<SiteServer>(_context, DataType.SiteServers);
_siteSubnetOutput = new JsonDataWriter<SiteSubnet>(_context, DataType.SiteSubnets);

_runTimer = new Stopwatch();
_statusTimer = new Timer(_context.StatusInterval);
Expand Down Expand Up @@ -145,12 +152,20 @@ internal async Task<string> StartWriter()
case IssuancePolicy issuancePolicy:
await _issuancePolicyOutput.AcceptObject(issuancePolicy);
break;
case Site site:
await _siteOutput.AcceptObject(site);
break;
case SiteServer siteServer:
await _siteServerOutput.AcceptObject(siteServer);
break;
case SiteSubnet siteSubnet:
await _siteSubnetOutput.AcceptObject(siteSubnet);
break;
default:
throw new ArgumentOutOfRangeException(nameof(item));
}
}

Console.WriteLine("Closing writers");
return await FlushWriters();
}

Expand All @@ -169,6 +184,9 @@ private async Task<string> FlushWriters()
await _nTAuthStoreOutput.FlushWriter();
await _certTemplateOutput.FlushWriter();
await _issuancePolicyOutput.FlushWriter();
await _siteOutput.FlushWriter();
await _siteServerOutput.FlushWriter();
await _siteSubnetOutput.FlushWriter();
CloseOutput();
var fileName = ZipFiles();
return fileName;
Expand Down Expand Up @@ -198,7 +216,8 @@ private string ZipFiles()
_containerOutput.GetFilename(), _domainOutput.GetFilename(), _gpoOutput.GetFilename(),
_ouOutput.GetFilename(), _rootCAOutput.GetFilename(), _aIACAOutput.GetFilename(),
_enterpriseCAOutput.GetFilename(), _nTAuthStoreOutput.GetFilename(),
_certTemplateOutput.GetFilename(),_issuancePolicyOutput.GetFilename()
_certTemplateOutput.GetFilename(),_issuancePolicyOutput.GetFilename(),
_siteOutput.GetFilename(), _siteServerOutput.GetFilename(), _siteSubnetOutput.GetFilename(),
});

foreach (var entry in fileList.Where(x => !string.IsNullOrEmpty(x)))
Expand Down