Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 9 additions & 1 deletion src/CommonLib/DirectoryObjects/DirectoryObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ public static bool IsGMSA(this IDirectoryObject directoryObject) {

return classes.Contains(ObjectClass.GMSAClass, StringComparer.InvariantCultureIgnoreCase);
}


public static bool IsDMSA(this IDirectoryObject directoryObject) {
if (!directoryObject.TryGetArrayProperty(LDAPProperties.ObjectClass, out var classes)) {
return false;
}

return classes.Contains(ObjectClass.DMSAClass, StringComparer.InvariantCultureIgnoreCase);
}

public static bool GetObjectIdentifier(this IDirectoryObject directoryObject, out string objectIdentifier) {
// Builtin container has a SID (always "S-1-5-32"). We use the ObjectGUID as ID like for other containers
if (directoryObject.TryGetArrayProperty(LDAPProperties.ObjectClass, out var objectClasses) &&
Expand Down
1 change: 1 addition & 0 deletions src/CommonLib/Enums/DataType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ public static class DataType
public const string Sites = "sites";
public const string SiteServers = "siteservers";
public const string SiteSubnets = "sitesubnets";
public const string DMSAs = "dmsas";
}
}
3 changes: 3 additions & 0 deletions src/CommonLib/Enums/EdgeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public static class EdgeNames
public const string WriteGPLink = "WriteGPLink";
public const string WriteAltSecurityIdentities = "WriteAltSecurityIdentities";
public const string WritePublicInformation = "WritePublicInformation";
public const string WriteSupersededManagedAccountLink = "WriteSupersededManagedAccountLink";
public const string WriteSupersededServiceAccountState = "WriteSupersededServiceAccountState";
public const string CreateDMSA = "CreateDMSA";
public const string ServerIs = "ServerIs";

//CertAbuse edges
Expand Down
1 change: 1 addition & 0 deletions src/CommonLib/Enums/LDAPProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public static class LDAPProperties
{
public const string GroupMSAMembership = "msds-groupmsamembership";
public const string ManagedAccountPrecededByLink = "msds-managedaccountprecededbylink";
public const string UserAccountControl = "useraccountcontrol";
public const string IsDeleted = "isdeleted";
public const string SAMAccountType = "samaccounttype";
Expand Down
3 changes: 2 additions & 1 deletion src/CommonLib/Enums/Labels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public enum Label
IssuancePolicy,
Site,
SiteServer,
SiteSubnet
SiteSubnet,
DMSA
}
}
1 change: 1 addition & 0 deletions src/CommonLib/Enums/ObjectClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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 DMSAClass = "msds-delegatedmanagedserviceaccount";
public const string SiteClass = "site";
public const string SiteServerClass = "server";
public const string SiteSubnetClass = "subnet";
Expand Down
2 changes: 1 addition & 1 deletion src/CommonLib/LdapQueries/CommonProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static class CommonProperties
LDAPProperties.MinPwdLength, LDAPProperties.PwdProperties, LDAPProperties.MinPwdAge,
LDAPProperties.MaxPwdAge, LDAPProperties.PwdHistoryLength, LDAPProperties.LockoutDuration,
LDAPProperties.LockoutThreshold, LDAPProperties.LockOutObservationWindow, LDAPProperties.GroupType,
LDAPProperties.PrincipalName
LDAPProperties.PrincipalName, LDAPProperties.ManagedAccountPrecededByLink
};

public static readonly string[] ContainerProps =
Expand Down
19 changes: 13 additions & 6 deletions src/CommonLib/LdapUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1166,12 +1166,18 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN
return true;
}

//Override GMSA/MSA account to treat them as users for the graph
if (objectClasses != null &&
(objectClasses.Contains(ObjectClass.MSAClass, StringComparer.OrdinalIgnoreCase) ||
objectClasses.Contains(ObjectClass.GMSAClass, StringComparer.OrdinalIgnoreCase))) {
type = Label.User;
return true;
if (objectClasses != null) {
if (objectClasses.Contains(ObjectClass.DMSAClass, StringComparer.OrdinalIgnoreCase)) {
type = Label.DMSA;
return true;
}

//Override GMSA/MSA account to treat them as users for the graph
if (objectClasses.Contains(ObjectClass.MSAClass, StringComparer.OrdinalIgnoreCase) ||
objectClasses.Contains(ObjectClass.GMSAClass, StringComparer.OrdinalIgnoreCase)) {
type = Label.User;
return true;
}
}

if (samAccountType != null) {
Expand Down Expand Up @@ -1344,6 +1350,7 @@ private static string ComputeDisplayName(IDirectoryObject directoryObject, strin
string displayName;
switch (label) {
case Label.User:
case Label.DMSA:
case Label.Group:
case Label.Base:
if (!string.IsNullOrWhiteSpace(samAccountName)) {
Expand Down
4 changes: 3 additions & 1 deletion src/CommonLib/OutputTypes/Computer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public class DCRegistryData {
public IntRegistryAPIResult CertificateMappingMethods { get; set; }
public IntRegistryAPIResult StrongCertificateBindingEnforcement { get; set; }
public StrRegistryAPIResult VulnerableNetlogonSecurityDescriptor { get; set; }
public StrRegistryAPIResult CurrentBuildNumber { get; set; }
public IntRegistryAPIResult CurrentBuildRevision { get; set; }
}

public class ComputerStatus {
Expand All @@ -85,4 +87,4 @@ public CSVComputerStatus GetCSVStatus(string computerName) {
};
}
}
}
}
7 changes: 7 additions & 0 deletions src/CommonLib/OutputTypes/DMSA.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SharpHoundCommonLib.OutputTypes
{
public class DMSA : OutputBase
{
public TypedPrincipal PrecededBy { get; set; }
}
}
3 changes: 3 additions & 0 deletions src/CommonLib/Processors/ACEGuids.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public class ACEGuids
public const string WriteTitle = "bf967a55-0de6-11d0-a285-00aa003049e2"; // Not an edge, just used for testing
public const string WriteAltSecurityIdentities = "00fbf30c-91fe-11d1-aebc-0000f80367c1";
public const string WritePublicInformation = "e48d0154-bcf8-11d1-8702-00c04fb96050";
public const string WriteSupersededManagedAccountLink = "3752e002-43be-48c8-b3ca-2cb2fffbc8a1";
public const string WriteSupersededServiceAccountState = "a4693096-a4c3-4ecd-b8cb-73cd6398b7a2";
public const string DMSAClass = "0feb936f-47b3-49f2-9386-1dedc2c23765";

//Cert abuse ACEs
public const string PKINameFlag = "ea1dddc4-60ff-416e-8cc0-17cee534bce7";
Expand Down
62 changes: 50 additions & 12 deletions src/CommonLib/Processors/ACLProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ static ACLProcessor() {
{ 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" }
{ Label.SiteSubnet, "b7b13124-b82e-11d0-afee-0000f80367c1" },
{ Label.DMSA, ACEGuids.DMSAClass }
};
}

Expand Down Expand Up @@ -724,6 +725,21 @@ await CountCustomDenyAce(ace, customDenyAceAccumulator, objectDomain, objectType
}
}

if (objectType is Label.OU or Label.Container &&
(aceRights.HasFlag(ActiveDirectoryRights.CreateChild) ||
aceRights.HasFlag(ActiveDirectoryRights.GenericAll)) &&
aceType is ACEGuids.DMSAClass or ACEGuids.AllGuid or "") {
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
IsInherited = inherited,
RightName = EdgeNames.CreateDMSA,
InheritanceHash = aceInheritanceHash,
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
}

//// This log is exceptionally noisy, disabling
// _log.LogTrace("Processing ACE with rights {Rights} and guid {GUID} on object {Name}", aceRights,
// aceType, objectName);
Expand Down Expand Up @@ -996,17 +1012,39 @@ or Label.IssuancePolicy
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.WriteAltSecurityIdentities)
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
IsInherited = inherited,
RightName = EdgeNames.WriteAltSecurityIdentities,
InheritanceHash = aceInheritanceHash,
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.WritePublicInformation)
else if (objectType is Label.User or Label.Computer &&
aceType == ACEGuids.WriteSupersededManagedAccountLink)
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
IsInherited = inherited,
RightName = EdgeNames.WriteSupersededManagedAccountLink,
InheritanceHash = aceInheritanceHash,
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
else if (objectType is Label.User or Label.Computer &&
aceType == ACEGuids.WriteSupersededServiceAccountState)
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
IsInherited = inherited,
RightName = EdgeNames.WriteSupersededServiceAccountState,
InheritanceHash = aceInheritanceHash,
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.WriteAltSecurityIdentities)
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
IsInherited = inherited,
RightName = EdgeNames.WriteAltSecurityIdentities,
InheritanceHash = aceInheritanceHash,
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.WritePublicInformation)
yield return new ACE
{
PrincipalType = resolvedPrincipal.ObjectType,
Expand Down
67 changes: 66 additions & 1 deletion src/CommonLib/Processors/DCRegistryProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,71 @@ public DCRegistryProcessor(ILdapUtils utils, ILogger log = null)
_log = log ?? Logging.LogProvider.CreateLogger("DCRegProc");
}

internal DCRegistryProcessor(ILdapUtils utils, IRegistryAccessor registryAccessor, ILogger log = null)
{
_utils = utils;
_registryAccessor = registryAccessor;
_log = log ?? Logging.LogProvider.CreateLogger("DCRegProc");
}

/// <summary>
/// Gets the Windows build number reported by a domain controller.
/// </summary>
/// <param name="target">Domain controller to query.</param>
/// <returns>The CurrentBuildNumber registry value and collection status.</returns>
public StrRegistryAPIResult GetCurrentBuildNumber(string target)
{
var ret = new StrRegistryAPIResult();
const string subKey = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
const string subValue = "CurrentBuildNumber";
var data = _registryAccessor.GetRegistryKeyData(target, subKey, subValue);

if (!data.Collected)
{
ret.FailureReason = data.FailureReason;
return ret;
}

if (data.Value == null)
{
ret.FailureReason = "Registry value was not found";
return ret;
}

ret.Collected = true;
ret.Value = (string)data.Value;
return ret;
}

/// <summary>
/// Gets the Windows update build revision reported by a domain controller.
/// </summary>
/// <param name="target">Domain controller to query.</param>
/// <returns>The UBR registry value and collection status.</returns>
public IntRegistryAPIResult GetCurrentBuildRevision(string target)
{
var ret = new IntRegistryAPIResult();
const string subKey = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
const string subValue = "UBR";
var data = _registryAccessor.GetRegistryKeyData(target, subKey, subValue);

if (!data.Collected)
{
ret.FailureReason = data.FailureReason;
return ret;
}

if (data.Value == null)
{
ret.FailureReason = "Registry value was not found";
return ret;
}

ret.Collected = true;
ret.Value = (int)data.Value;
return ret;
}

/// <summary>
/// This function gets the CertificateMappingMethods registry value stored on DCs.
/// </summary>
Expand Down Expand Up @@ -115,4 +180,4 @@ public StrRegistryAPIResult GetVulnerableNetlogonSecurityDescriptor(string targe
return ret;
}
}
}
}
70 changes: 70 additions & 0 deletions test/unit/ACLProcessorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2438,6 +2438,76 @@ public async Task ACLProcessor_ProcessACL_GenericWrite_Computer_WritePublicInfor
Assert.Equal(actual.RightName, expectedRightName);
}

[Theory]
[InlineData(Label.User, ACEGuids.WriteSupersededManagedAccountLink, EdgeNames.WriteSupersededManagedAccountLink)]
[InlineData(Label.Computer, ACEGuids.WriteSupersededManagedAccountLink, EdgeNames.WriteSupersededManagedAccountLink)]
[InlineData(Label.User, ACEGuids.WriteSupersededServiceAccountState, EdgeNames.WriteSupersededServiceAccountState)]
[InlineData(Label.Computer, ACEGuids.WriteSupersededServiceAccountState, EdgeNames.WriteSupersededServiceAccountState)]
public async Task ACLProcessor_ProcessACL_WriteProperty_DMSAVictimAttribute(Label objectType,
string attributeGuid, string expectedRightName) {
const string expectedPrincipalSid = "S-1-5-21-3130019616-2776909439-2417379446-512";
var rule = CreateRuleDescriptor(expectedPrincipalSid, AccessControlType.Allow,
ActiveDirectoryRights.WriteProperty, objectType: new Guid(attributeGuid));
rule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);

var processor = CreateCombinedAclProcessor(new[] { rule.Object });
var result = await processor.ProcessACL(new byte[] { 1 }, _testDomainName, objectType, true).ToArrayAsync();

var actual = Assert.Single(result);
Assert.Equal(expectedPrincipalSid, actual.PrincipalSID);
Assert.Equal(expectedRightName, actual.RightName);
}

[Theory]
[InlineData(ACEGuids.DMSAClass, Label.OU)]
[InlineData(ACEGuids.AllGuid, Label.OU)]
[InlineData(ACEGuids.DMSAClass, Label.Container)]
public async Task ACLProcessor_ProcessACL_CreateChild_DMSAOnParent(string objectGuid, Label parentType) {
const string expectedPrincipalSid = "S-1-5-21-3130019616-2776909439-2417379446-512";
var rule = CreateRuleDescriptor(expectedPrincipalSid, AccessControlType.Allow,
ActiveDirectoryRights.CreateChild, objectType: new Guid(objectGuid));
rule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);

var processor = CreateCombinedAclProcessor(new[] { rule.Object });
var result = await processor.ProcessACL(new byte[] { 1 }, _testDomainName, parentType, false)
.ToArrayAsync();

var actual = Assert.Single(result);
Assert.Equal(expectedPrincipalSid, actual.PrincipalSID);
Assert.Equal(EdgeNames.CreateDMSA, actual.RightName);
}

[Fact]
public async Task ACLProcessor_ProcessACL_GenericAll_OnOUIncludesCreateDMSA() {
const string expectedPrincipalSid = "S-1-5-21-3130019616-2776909439-2417379446-512";
var rule = CreateRuleDescriptor(expectedPrincipalSid, AccessControlType.Allow,
ActiveDirectoryRights.GenericAll, objectType: new Guid(ACEGuids.AllGuid));
rule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);

var processor = CreateCombinedAclProcessor(new[] { rule.Object });
var result = await processor.ProcessACL(new byte[] { 1 }, _testDomainName, Label.OU, false)
.ToArrayAsync();

Assert.Contains(result, ace => ace.RightName == EdgeNames.CreateDMSA);
Assert.Contains(result, ace => ace.RightName == EdgeNames.GenericAll);
}

[Fact]
public async Task ACLProcessor_ProcessACL_GenericAll_DMSA() {
const string expectedPrincipalSid = "S-1-5-21-3130019616-2776909439-2417379446-512";
var rule = CreateRuleDescriptor(expectedPrincipalSid, AccessControlType.Allow,
ActiveDirectoryRights.GenericAll, objectType: new Guid(ACEGuids.AllGuid));
rule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);

var processor = CreateCombinedAclProcessor(new[] { rule.Object });
var result = await processor.ProcessACL(new byte[] { 1 }, _testDomainName, Label.DMSA, false)
.ToArrayAsync();

var actual = Assert.Single(result);
Assert.Equal(expectedPrincipalSid, actual.PrincipalSID);
Assert.Equal(EdgeNames.GenericAll, actual.RightName);
}

[Fact]
public async Task ACLProcessor_ProcessACLWithCustomDenyAces_ReturnsRegularAcesAndDenyCounts() {
var denyRule = CreateRuleDescriptor("S-1-5-21-3130019616-2776909439-2417379446-3100",
Expand Down
Loading
Loading