diff --git a/src/CommonLib/Enums/CollectionMethod.cs b/src/CommonLib/Enums/CollectionMethod.cs
index 19191d126..458a3a05b 100644
--- a/src/CommonLib/Enums/CollectionMethod.cs
+++ b/src/CommonLib/Enums/CollectionMethod.cs
@@ -27,6 +27,7 @@ public enum CollectionMethod {
WebClientService = 1 << 21,
SmbInfo = 1 << 22,
NTLMRegistry = 1 << 23,
+ Site = 1 << 24,
//TODO: Re-introduce this when we're ready for Event Log collection
//EventLogs = 1 << 23,
LocalGroups = DCOM | RDP | LocalAdmin | PSRemote,
@@ -34,7 +35,7 @@ public enum CollectionMethod {
DCOnly = ACL | Container | Group | ObjectProps | Trusts | GPOLocalGroup | CertServices,
Default = Group | Session | Trusts | ACL | ObjectProps | LocalGroups | SPNTargets | Container | CertServices |
- LdapServices | SmbInfo | WebClientService,
+ LdapServices | SmbInfo | WebClientService | Site,
All = Default | LoggedOn | GPOLocalGroup | UserRights | CARegistry | DCRegistry | WebClientService |
LdapServices | NTLMRegistry
diff --git a/src/CommonLib/Enums/DataType.cs b/src/CommonLib/Enums/DataType.cs
index c96be5be9..2553fd529 100644
--- a/src/CommonLib/Enums/DataType.cs
+++ b/src/CommonLib/Enums/DataType.cs
@@ -15,5 +15,8 @@ public static class DataType
public const string EnterpriseCAs = "enterprisecas";
public const string CertTemplates = "certtemplates";
public const string IssuancePolicies = "issuancepolicies";
+ public const string Sites = "sites";
+ public const string SiteServers = "siteservers";
+ public const string SiteSubnets = "sitesubnets";
}
}
diff --git a/src/CommonLib/Enums/LDAPProperties.cs b/src/CommonLib/Enums/LDAPProperties.cs
index 0bf6b726e..da1298d05 100644
--- a/src/CommonLib/Enums/LDAPProperties.cs
+++ b/src/CommonLib/Enums/LDAPProperties.cs
@@ -96,5 +96,7 @@ public static class LDAPProperties
public const string LockOutObservationWindow = "lockoutobservationwindow";
public const string PrincipalName = "msds-principalname";
public const string GroupType = "grouptype";
+ public const string ServerReference = "serverreference";
+ public const string SiteObject = "siteobject";
}
}
diff --git a/src/CommonLib/Enums/Labels.cs b/src/CommonLib/Enums/Labels.cs
index b0bacb68e..76e05812e 100644
--- a/src/CommonLib/Enums/Labels.cs
+++ b/src/CommonLib/Enums/Labels.cs
@@ -18,6 +18,9 @@ public enum Label
AIACA,
EnterpriseCA,
NTAuthStore,
- IssuancePolicy
+ IssuancePolicy,
+ Site,
+ SiteServer,
+ SiteSubnet
}
}
diff --git a/src/CommonLib/Enums/ObjectClass.cs b/src/CommonLib/Enums/ObjectClass.cs
index f8bab0fc9..db98dd5e4 100644
--- a/src/CommonLib/Enums/ObjectClass.cs
+++ b/src/CommonLib/Enums/ObjectClass.cs
@@ -12,4 +12,7 @@ public static class ObjectClass {
public const string OIDContainerClass = "msPKI-Enterprise-Oid";
public const string GMSAClass = "msds-groupmanagedserviceaccount";
public const string MSAClass = "msds-managedserviceaccount";
+ public const string SiteClass = "site";
+ public const string SiteServerClass = "server";
+ public const string SiteSubnetClass = "subnet";
}
\ No newline at end of file
diff --git a/src/CommonLib/LdapProducerQueryGenerator.cs b/src/CommonLib/LdapProducerQueryGenerator.cs
index 25c6cb9bf..c3ce2e25f 100644
--- a/src/CommonLib/LdapProducerQueryGenerator.cs
+++ b/src/CommonLib/LdapProducerQueryGenerator.cs
@@ -104,9 +104,10 @@ public static GeneratedLdapParameters GenerateConfigurationPartitionParameters(C
properties.AddRange(CommonProperties.TypeResolutionProps);
if (methods.HasFlag(CollectionMethod.ACL) || methods.HasFlag(CollectionMethod.ObjectProps) ||
- methods.HasFlag(CollectionMethod.Container) || methods.HasFlag(CollectionMethod.CertServices)) {
+ methods.HasFlag(CollectionMethod.Container) || methods.HasFlag(CollectionMethod.CertServices) ||
+ methods.HasFlag(CollectionMethod.Site)) {
filter = filter.AddContainers().AddConfiguration().AddCertificateTemplates().AddCertificateAuthorities()
- .AddEnterpriseCertificationAuthorities().AddIssuancePolicies();
+ .AddEnterpriseCertificationAuthorities().AddIssuancePolicies().AddSites().AddSiteServers().AddSiteSubnets();
if (methods.HasFlag(CollectionMethod.ObjectProps)) {
properties.AddRange(CommonProperties.ObjectPropsProps);
@@ -131,6 +132,13 @@ public static GeneratedLdapParameters GenerateConfigurationPartitionParameters(C
properties.AddRange(CommonProperties.CertAbuseProps);
}
+ if (methods.HasFlag(CollectionMethod.Site))
+ {
+ properties.AddRange(CommonProperties.SiteProps);
+ properties.AddRange(CommonProperties.SiteServerProps);
+ properties.AddRange(CommonProperties.SiteSubnetProps);
+ }
+
return new GeneratedLdapParameters {
Filter = filter,
Attributes = properties.Distinct().ToArray()
diff --git a/src/CommonLib/LdapQueries/CommonProperties.cs b/src/CommonLib/LdapQueries/CommonProperties.cs
index 508b5490c..88cef7462 100644
--- a/src/CommonLib/LdapQueries/CommonProperties.cs
+++ b/src/CommonLib/LdapQueries/CommonProperties.cs
@@ -98,5 +98,23 @@ public static class CommonProperties
public static readonly string[] StealthProperties = {
LDAPProperties.HomeDirectory, LDAPProperties.ScriptPath, LDAPProperties.ProfilePath
};
+
+ public static readonly string[] SiteProps =
+ {
+ LDAPProperties.DisplayName, LDAPProperties.Name, LDAPProperties.ObjectGUID, LDAPProperties.GPLink,
+ LDAPProperties.GroupPolicyOptions, LDAPProperties.ObjectClass
+ };
+
+ public static readonly string[] SiteServerProps =
+ {
+ LDAPProperties.DisplayName, LDAPProperties.Name, LDAPProperties.ObjectGUID, LDAPProperties.ObjectClass, LDAPProperties.DNSHostName,
+ LDAPProperties.ServerReference
+ };
+
+ public static readonly string[] SiteSubnetProps =
+ {
+ LDAPProperties.DisplayName, LDAPProperties.Name, LDAPProperties.CanonicalName, LDAPProperties.ObjectGUID, LDAPProperties.ObjectClass,
+ LDAPProperties.SiteObject
+ };
}
}
\ No newline at end of file
diff --git a/src/CommonLib/LdapQueries/LdapFilter.cs b/src/CommonLib/LdapQueries/LdapFilter.cs
index e98660ce1..a16584dce 100644
--- a/src/CommonLib/LdapQueries/LdapFilter.cs
+++ b/src/CommonLib/LdapQueries/LdapFilter.cs
@@ -215,6 +215,45 @@ public LdapFilter AddComputersNoMSAs(params string[] conditions) {
return this;
}
+ ///
+ /// Add a filter that will match Active Directory sites
+ ///
+ ///
+ ///
+ public LdapFilter AddSites(params string[] conditions)
+ {
+ _filterParts.Add(BuildString(
+ "(objectClass=site)",
+ conditions));
+ return this;
+ }
+
+ ///
+ /// Add a filter that will match Active Directory site servers
+ ///
+ ///
+ ///
+ public LdapFilter AddSiteServers(params string[] conditions)
+ {
+ _filterParts.Add(BuildString(
+ "(objectClass=server)",
+ conditions));
+ return this;
+ }
+
+ ///
+ /// Add a filter that will match Active Directory site subnets
+ ///
+ ///
+ ///
+ public LdapFilter AddSiteSubnets(params string[] conditions)
+ {
+ _filterParts.Add(BuildString(
+ "(objectClass=subnet)",
+ conditions));
+ return this;
+ }
+
///
/// Adds a generic user specified filter
///
diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs
index 14612da12..8c33a614c 100644
--- a/src/CommonLib/LdapUtils.cs
+++ b/src/CommonLib/LdapUtils.cs
@@ -1205,6 +1205,18 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN
type = Label.IssuancePolicy;
}
}
+ else if (objectClasses.Contains(ObjectClass.SiteClass, StringComparer.OrdinalIgnoreCase))
+ {
+ type = Label.Site;
+ }
+ else if (objectClasses.Contains(ObjectClass.SiteServerClass, StringComparer.OrdinalIgnoreCase))
+ {
+ type = Label.SiteServer;
+ }
+ else if (objectClasses.Contains(ObjectClass.SiteSubnetClass, StringComparer.OrdinalIgnoreCase))
+ {
+ type = Label.SiteSubnet;
+ }
return type != Label.Base;
}
@@ -1214,7 +1226,7 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN
if (!directoryObject.GetObjectIdentifier(out var objectIdentifier)) {
return (false, default);
}
-
+
var res = new ResolvedSearchResult {
ObjectId = objectIdentifier
};
@@ -1270,11 +1282,10 @@ await utils.GetDomainNameFromSid(objectIdentifier) is (true, var domainName)) {
if (await utils.GetWellKnownPrincipal(objectIdentifier, domain) is (true, var convertedPrincipal)) {
res.ObjectId = convertedPrincipal.ObjectIdentifier;
}
-
return (true, res);
}
- res.ObjectType = await ComputeLabel(directoryObject, objectIdentifier, domain, utils);
+ res.ObjectType = await ComputeLabel(directoryObject, objectIdentifier, domain, utils);
directoryObject.TryGetProperty(LDAPProperties.SAMAccountName, out var samAccountName);
res.DisplayName = ComputeDisplayName(directoryObject, domain, res.ObjectType, samAccountName);
@@ -1395,6 +1406,43 @@ private static string ComputeDisplayName(IDirectoryObject directoryObject, strin
displayName = $"UNKNOWN@{domain}";
}
+ break;
+ }
+ case Label.Site: {
+ if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name))
+ {
+ displayName = $"{name}@{domain}";
+ }
+ else
+ {
+ displayName = $"UNKNOWN@{domain}";
+ }
+ break;
+ }
+ case Label.SiteServer:
+ {
+ // Not specifying @{domain} here since Site servers may belong to other domains, so this might confuse the user
+ if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name))
+ {
+ displayName = $"{name}";
+ }
+ else
+ {
+ displayName = $"UNKNOWN";
+ }
+ break;
+ }
+ case Label.SiteSubnet:
+ {
+ // Not specifying @{domain} here since subnets are not domain-specific
+ if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name))
+ {
+ displayName = $"{name}";
+ }
+ else
+ {
+ displayName = $"UNKNOWN";
+ }
break;
}
default:
diff --git a/src/CommonLib/OutputTypes/Site.cs b/src/CommonLib/OutputTypes/Site.cs
new file mode 100644
index 000000000..b2ce5c241
--- /dev/null
+++ b/src/CommonLib/OutputTypes/Site.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace SharpHoundCommonLib.OutputTypes
+{
+ public class Site : OutputBase
+ {
+ // Subnets and Servers are common site children; keep them optional and empty by default.
+ //public string[] Subnets { get; set; } = Array.Empty();
+ //public TypedPrincipal[] Servers { get; set; } = Array.Empty();
+ public GPLink[] Links { get; set; } = Array.Empty();
+ }
+}
\ No newline at end of file
diff --git a/src/CommonLib/OutputTypes/SiteServer.cs b/src/CommonLib/OutputTypes/SiteServer.cs
new file mode 100644
index 000000000..07c1542fc
--- /dev/null
+++ b/src/CommonLib/OutputTypes/SiteServer.cs
@@ -0,0 +1,7 @@
+namespace SharpHoundCommonLib.OutputTypes
+{
+ public class SiteServer : OutputBase
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/CommonLib/OutputTypes/SiteSubnet.cs b/src/CommonLib/OutputTypes/SiteSubnet.cs
new file mode 100644
index 000000000..568277f0b
--- /dev/null
+++ b/src/CommonLib/OutputTypes/SiteSubnet.cs
@@ -0,0 +1,7 @@
+namespace SharpHoundCommonLib.OutputTypes
+{
+ public class SiteSubnet : OutputBase
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs
index 383b69aff..a79c0a16b 100644
--- a/src/CommonLib/Processors/ACLProcessor.cs
+++ b/src/CommonLib/Processors/ACLProcessor.cs
@@ -38,7 +38,10 @@ static ACLProcessor() {
{ Label.EnterpriseCA, "ee4aa692-3bba-11d2-90cc-00c04fd91ab1" },
{ Label.NTAuthStore, "3fdfee50-47f4-11d1-a9c3-0000f80367c1" },
{ Label.CertTemplate, "e5209ca2-3bba-11d2-90cc-00c04fd91ab1" },
- { Label.IssuancePolicy, "37cfd85c-6719-4ad8-8f9e-8678ba627563" }
+ { Label.IssuancePolicy, "37cfd85c-6719-4ad8-8f9e-8678ba627563" },
+ { Label.Site, "bf967ab3-0de6-11d0-a285-00aa003049e2" },
+ { Label.SiteServer, "bf967a92-0de6-11d0-a285-00aa003049e2" },
+ { Label.SiteSubnet, "b7b13124-b82e-11d0-afee-0000f80367c1" }
};
}
@@ -734,7 +737,10 @@ or Label.RootCA
or Label.EnterpriseCA
or Label.AIACA
or Label.NTAuthStore
- or Label.IssuancePolicy)
+ or Label.IssuancePolicy
+ or Label.Site
+ or Label.SiteServer
+ or Label.SiteSubnet)
if (aceType is ACEGuids.AllGuid or "")
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
@@ -776,7 +782,7 @@ or Label.NTAuthStore
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
- else if (objectType is Label.OU or Label.Domain && aceType == ACEGuids.WriteGPLink)
+ else if (objectType is Label.OU or Label.Domain or Label.Site && aceType == ACEGuids.WriteGPLink)
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
diff --git a/src/CommonLib/Processors/LdapPropertyProcessor.cs b/src/CommonLib/Processors/LdapPropertyProcessor.cs
index 4899b390f..3d38fb461 100644
--- a/src/CommonLib/Processors/LdapPropertyProcessor.cs
+++ b/src/CommonLib/Processors/LdapPropertyProcessor.cs
@@ -643,6 +643,29 @@ public async Task ReadIssuancePolicyProperties(IDirect
return ret;
}
+ public static Dictionary ReadSiteProperties(IDirectoryObject entry)
+ {
+ var props = GetCommonProps(entry);
+ return props;
+ }
+
+
+ public static Dictionary ReadSiteServerProperties(IDirectoryObject entry)
+ {
+ var props = GetCommonProps(entry);
+ props.Add("dnshostname", entry.GetProperty(LDAPProperties.DNSHostName));
+ props.Add("serverreference", entry.GetProperty(LDAPProperties.ServerReference));
+ return props;
+ }
+
+ public static Dictionary ReadSiteSubnetProperties(IDirectoryObject entry)
+ {
+ var props = GetCommonProps(entry);
+ props.Add("cn", entry.GetProperty(LDAPProperties.CanonicalName));
+ props.Add("siteObject", entry.GetProperty(LDAPProperties.SiteObject));
+ return props;
+ }
+
///
/// Attempts to parse all LDAP attributes outside of the ones already collected and converts them to a human readable
/// format using a best guess
diff --git a/src/CommonLib/Processors/SiteProcessor.cs b/src/CommonLib/Processors/SiteProcessor.cs
new file mode 100644
index 000000000..a38bcdbbb
--- /dev/null
+++ b/src/CommonLib/Processors/SiteProcessor.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using SharpHoundCommonLib.OutputTypes;
+
+namespace SharpHoundCommonLib.Processors
+{
+ public class SiteProcessor
+ {
+ private readonly ILogger _log;
+ private readonly ILdapUtils _utils;
+
+ public SiteProcessor(ILdapUtils utils, ILogger log = null)
+ {
+ _utils = utils;
+ _log = log ?? Logging.LogProvider.CreateLogger("SiteProc");
+ }
+
+
+ ///
+ /// Helper function to pass commonlib types to GetContainingSiteForServer
+ ///
+ ///
+ ///
+ public async Task<(bool Success, TypedPrincipal principal)> GetContainingSiteForServer(IDirectoryObject entry)
+ {
+ if (entry.TryGetDistinguishedName(out var dn))
+ {
+ _log.LogTrace("Reading containing site for server {DN}", dn);
+ return await GetContainingSiteForServer(dn);
+ }
+
+ return (false, default);
+ }
+
+ ///
+ /// Helper function to pass commonlib types to GetContainingSiteForSubnet
+ ///
+ ///
+ ///
+ public async Task<(bool Success, TypedPrincipal principal)> GetContainingSiteForSubnet(Dictionary subnetProperties)
+ {
+ if (subnetProperties.TryGetValue("siteObject", out var siteObject))
+ {
+ return await GetContainingSiteForSubnet(siteObject.ToString());
+ }
+ return (false, default);
+ }
+
+ ///
+ /// Uses the distinguishedname of a site server object to get its containing site by stripping the two first parts and using the remainder to find the container object
+ /// Saves lots of LDAP calls compared to enumerating container info directly
+ ///
+ ///
+ ///
+ public async Task<(bool Success, TypedPrincipal Principal)> GetContainingSiteForServer(string distinguishedName)
+ {
+ var servercontainerdn = Helpers.RemoveDistinguishedNamePrefix(distinguishedName);
+ var sitedn = Helpers.RemoveDistinguishedNamePrefix(servercontainerdn);
+ return await _utils.ResolveDistinguishedName(sitedn);
+ }
+
+ ///
+ /// Uses the siteObject of a subnet to get its containing site
+ ///
+ ///
+ ///
+ public async Task<(bool Success, TypedPrincipal Principal)> GetContainingSiteForSubnet(string siteObject)
+ {
+ return await _utils.ResolveDistinguishedName(siteObject);
+ }
+
+ public IAsyncEnumerable ReadSiteGPLinks(ResolvedSearchResult result, IDirectoryObject entry)
+ {
+ if (entry.TryGetProperty(LDAPProperties.GPLink, out var links))
+ {
+ return ReadSiteGPLinks(links);
+ }
+
+ return AsyncEnumerable.Empty();
+ }
+
+ ///
+ /// Reads the "gplink" property from a SearchResult and converts the links into the acceptable SharpHound format
+ ///
+ ///
+ ///
+ public async IAsyncEnumerable ReadSiteGPLinks(string gpLink)
+ {
+ if (gpLink == null)
+ yield break;
+
+ foreach (var link in Helpers.SplitGPLinkProperty(gpLink))
+ {
+ var enforced = link.Status.Equals("2");
+
+ var res = await _utils.ResolveDistinguishedName(link.DistinguishedName);
+
+ if (res.Success)
+ {
+ yield return new GPLink
+ {
+ GUID = res.Principal.ObjectIdentifier,
+ IsEnforced = enforced
+ };
+ }
+ }
+ }
+ }
+}
\ No newline at end of file