Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -603,3 +603,6 @@ FodyWeavers.xsd
# JetBrains Rider
.idea/
*.sln.iml

# Codex local workspace state
.codex
3 changes: 2 additions & 1 deletion src/CommonLib/Enums/CollectionMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ 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,
ComputerOnly = LocalGroups | Session | UserRights | CARegistry | DCRegistry | WebClientService | SmbInfo | NTLMRegistry,
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
Expand Down
3 changes: 3 additions & 0 deletions src/CommonLib/Enums/DataType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
3 changes: 2 additions & 1 deletion src/CommonLib/Enums/EdgeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static class EdgeNames
public const string WriteGPLink = "WriteGPLink";
public const string WriteAltSecurityIdentities = "WriteAltSecurityIdentities";
public const string WritePublicInformation = "WritePublicInformation";
public const string SeverIs = "SeverIs";

//CertAbuse edges
public const string WritePKIEnrollmentFlag = "WritePKIEnrollmentFlag";
Expand All @@ -32,4 +33,4 @@ public static class EdgeNames
public const string ManageCertificates = "ManageCertificates";
public const string Enroll = "Enroll";
}
}
}
2 changes: 2 additions & 0 deletions src/CommonLib/Enums/LDAPProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
5 changes: 4 additions & 1 deletion src/CommonLib/Enums/Labels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public enum Label
AIACA,
EnterpriseCA,
NTAuthStore,
IssuancePolicy
IssuancePolicy,
Site,
SiteServer,
SiteSubnet
}
}
3 changes: 3 additions & 0 deletions src/CommonLib/Enums/ObjectClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
12 changes: 10 additions & 2 deletions src/CommonLib/LdapProducerQueryGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -131,6 +132,13 @@ public static GeneratedLdapParameters GenerateConfigurationPartitionParameters(C
properties.AddRange(CommonProperties.CertAbuseProps);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Properties for site objects will need to be added here as well or ObjectProps will miss it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I get it. Do you want me to add site properties here?
image

Shouldn't CertAbuseProps also be there then?

}

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()
Expand Down
18 changes: 18 additions & 0 deletions src/CommonLib/LdapQueries/CommonProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
}
}
39 changes: 39 additions & 0 deletions src/CommonLib/LdapQueries/LdapFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,45 @@ public LdapFilter AddComputersNoMSAs(params string[] conditions) {
return this;
}

/// <summary>
/// Add a filter that will match Active Directory sites
/// </summary>
/// <param name="conditions"></param>
/// <returns></returns>
public LdapFilter AddSites(params string[] conditions)
{
_filterParts.Add(BuildString(
"(objectClass=site)",
conditions));
return this;
}

/// <summary>
/// Add a filter that will match Active Directory site servers
/// </summary>
/// <param name="conditions"></param>
/// <returns></returns>
public LdapFilter AddSiteServers(params string[] conditions)
{
_filterParts.Add(BuildString(
"(objectClass=server)",
conditions));
return this;
}

/// <summary>
/// Add a filter that will match Active Directory site subnets
/// </summary>
/// <param name="conditions"></param>
/// <returns></returns>
public LdapFilter AddSiteSubnets(params string[] conditions)
{
_filterParts.Add(BuildString(
"(objectClass=subnet)",
conditions));
return this;
}

/// <summary>
/// Adds a generic user specified filter
/// </summary>
Expand Down
54 changes: 51 additions & 3 deletions src/CommonLib/LdapUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,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;
}
Expand All @@ -1228,7 +1240,7 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN
if (!directoryObject.GetObjectIdentifier(out var objectIdentifier)) {
return (false, default);
}

var res = new ResolvedSearchResult {
ObjectId = objectIdentifier
};
Expand Down Expand Up @@ -1284,11 +1296,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);
Expand Down Expand Up @@ -1409,6 +1420,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:
Expand Down
12 changes: 12 additions & 0 deletions src/CommonLib/OutputTypes/Site.cs
Original file line number Diff line number Diff line change
@@ -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<string>();
//public TypedPrincipal[] Servers { get; set; } = Array.Empty<TypedPrincipal>();
public GPLink[] Links { get; set; } = Array.Empty<GPLink>();
}
}
7 changes: 7 additions & 0 deletions src/CommonLib/OutputTypes/SiteServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SharpHoundCommonLib.OutputTypes
{
public class SiteServer : OutputBase
{
public TypedPrincipal SeverIs { get; set; }
}
}
7 changes: 7 additions & 0 deletions src/CommonLib/OutputTypes/SiteSubnet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SharpHoundCommonLib.OutputTypes
{
public class SiteSubnet : OutputBase
{

}
}
17 changes: 14 additions & 3 deletions src/CommonLib/Processors/ACLProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
};
}

Expand Down Expand Up @@ -449,6 +452,11 @@ public IAsyncEnumerable<ACE> ProcessACL(byte[] ntSecurityDescriptor, string obje

public async IAsyncEnumerable<ACE> ProcessACL(byte[] ntSecurityDescriptor, string objectDomain,
Label objectType, bool hasLaps, bool checkForOwnerRights, string objectName) {
if (objectType is Label.SiteServer or Label.SiteSubnet) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these 2 object types not have ACLs?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They do, but there are currently no known attack techniques that abuse permissions such as GenericAll on these objects.

Because SiteServer nodes have a traversable outbound ServerIs edge to DC Computer nodes, adding traversable ACL edges could produce false-positive attack paths since there is no known technique for using these permissions to reach the associated Computer node.

This would be less problematic for SiteSubnet nodes because they have no outbound edges.

Given this, should we include ACL edges on either of these node types anyway?

_log.LogDebug("Skipping ACL processing for {ObjectType} object {ObjectName}", objectType, objectName);
yield break;
}

await BuildGuidCache(objectDomain);

if (ntSecurityDescriptor == null) {
Expand Down Expand Up @@ -734,7 +742,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,
Expand Down Expand Up @@ -776,7 +787,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,
Expand Down
23 changes: 23 additions & 0 deletions src/CommonLib/Processors/LdapPropertyProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,29 @@ public async Task<IssuancePolicyProperties> ReadIssuancePolicyProperties(IDirect
return ret;
}

public static Dictionary<string, object> ReadSiteProperties(IDirectoryObject entry)
{
var props = GetCommonProps(entry);
return props;
}


public static Dictionary<string, object> 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<string, object> ReadSiteSubnetProperties(IDirectoryObject entry)
{
var props = GetCommonProps(entry);
props.Add("cn", entry.GetProperty(LDAPProperties.CanonicalName));
props.Add("siteObject", entry.GetProperty(LDAPProperties.SiteObject));
Comment thread
JonasBK marked this conversation as resolved.
Outdated
return props;
}

/// <summary>
/// Attempts to parse all LDAP attributes outside of the ones already collected and converts them to a human readable
/// format using a best guess
Expand Down
Loading
Loading