Skip to content
1 change: 1 addition & 0 deletions src/CommonLib/Processors/ACEGuids.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class ACEGuids
public const string UserForceChangePassword = "00299570-246d-11d0-a768-00aa006e0529";
public const string AllGuid = "00000000-0000-0000-0000-000000000000";
public const string WriteMember = "bf9679c0-0de6-11d0-a285-00aa003049e2";
public const string MembershipPropertySet = "bc0ac240-79a9-11d0-9020-00c04fc2d4cf"; // property set https://learn.microsoft.com/en-us/windows/win32/adschema/r-membership
public const string WriteAllowedToAct = "3f78c3e5-f79a-46bd-a0b8-9d18116ddc79";
public const string WriteSPN = "f3a64788-5306-11d1-a9c5-0000f80367c1";
public const string AddKeyPrincipal = "5b47d60f-6090-40b2-9f37-2a4de88f3063";
Expand Down
4 changes: 2 additions & 2 deletions src/CommonLib/Processors/ACLProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ public async IAsyncEnumerable<ACE> ProcessACL(byte[] ntSecurityDescriptor, strin
if (aceRights.HasFlag(ActiveDirectoryRights.Self) &&
!aceRights.HasFlag(ActiveDirectoryRights.WriteProperty) &&
!aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) && objectType == Label.Group &&
aceType is ACEGuids.WriteMember or ACEGuids.AllGuid)
aceType is ACEGuids.WriteMember or ACEGuids.MembershipPropertySet or ACEGuids.AllGuid)
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
Expand Down Expand Up @@ -786,7 +786,7 @@ or Label.NTAuthStore
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
else if (objectType == Label.Group && aceType == ACEGuids.WriteMember)
else if (objectType == Label.Group && (aceType is ACEGuids.WriteMember or ACEGuids.MembershipPropertySet))
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
Expand Down
105 changes: 60 additions & 45 deletions src/CommonLib/Processors/CertAbuseProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@ public class CertAbuseProcessor
private readonly AdaptiveTimeout _getMachineSidAdaptiveTimeout;
private readonly AdaptiveTimeout _openSamServerAdaptiveTimeout;
private readonly IRegistryAccessor _registryAccessor;
private readonly ISAMServerAccessor _samServerAccessor;

public delegate Task ComputerStatusDelegate(CSVComputerStatus status);
public event ComputerStatusDelegate ComputerStatusEvent;

public CertAbuseProcessor(ILdapUtils utils, IRegistryAccessor registryAccessor, ILogger log = null) {
public CertAbuseProcessor(ILdapUtils utils, IRegistryAccessor registryAccessor, ISAMServerAccessor samServerAccessor, ILogger log = null) {
_utils = utils;
_registryAccessor = registryAccessor;
_samServerAccessor = samServerAccessor;
_log = log ?? Logging.LogProvider.CreateLogger("CAProc");
_getMachineSidAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(ISAMServer.GetMachineSid)));
_openSamServerAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(SAMServer.OpenServer)));
_openSamServerAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(ISAMServerAccessor.OpenServer)));
}

/// <summary>
Expand Down Expand Up @@ -407,11 +409,11 @@ await _utils.ResolveIDAndType(sid.Value, computerDomain) is (true, var resolvedP
return await _utils.ResolveIDAndType(sid.Value, computerDomain);
}

private async Task<SecurityIdentifier> GetMachineSid(string computerName, string computerObjectId)
internal async Task<SecurityIdentifier> GetMachineSid(string computerName, string computerObjectId)
{
SecurityIdentifier machineSid = null;

//Try to get the machine sid for the computer if its not already cached
//Try to get the machine sid for the computer if it's not already cached
if (!Cache.GetMachineSid(computerObjectId, out var tempMachineSid))
{
// Open a handle to the server
Expand All @@ -421,7 +423,7 @@ private async Task<SecurityIdentifier> GetMachineSid(string computerName, string
_log.LogTrace("OpenServer failed on {ComputerName}: {Error}", computerName, openServerResult.SError);
await SendComputerStatus(new CSVComputerStatus
{
Task = "SamConnect",
Task = nameof(OpenSamServer),
ComputerName = computerName,
Status = openServerResult.SError,
ObjectId = computerObjectId,
Expand All @@ -438,14 +440,21 @@ await SendComputerStatus(new CSVComputerStatus
{
Status = getMachineSidResult.SError,
ComputerName = computerName,
Task = "GetMachineSid",
Task = nameof(GetMachineSid),
ObjectId = computerObjectId,
});
//If we can't get a machine sid, we wont be able to make local principals with unique object ids, or differentiate local/domain objects
//If we can't get a machine sid, we won't be able to make local principals with unique object ids, or differentiate local/domain objects
_log.LogWarning("Unable to get machineSid for {Computer}: {Status}", computerName, getMachineSidResult.SError);
return null;
}

await SendComputerStatus(new CSVComputerStatus {
Status = CSVComputerStatus.StatusSuccess,
Task = nameof(GetMachineSid),
ComputerName = computerName,
ObjectId = computerObjectId
});

machineSid = getMachineSidResult.Value;
Cache.AddMachineSid(computerObjectId, machineSid.Value);
}
Expand All @@ -457,26 +466,28 @@ await SendComputerStatus(new CSVComputerStatus
return machineSid;
}

private async Task<(bool success, EnrollmentAgentRestriction restriction)> CreateEnrollmentAgentRestriction(QualifiedAce ace, string computerDomain, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid) {
internal async Task<(bool success, EnrollmentAgentRestriction restriction)> CreateEnrollmentAgentRestriction(QualifiedAce ace, string computerDomain, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid)
{
var opaque = ace.GetOpaque();

if(opaque is null)
return (false, default);

var targets = new List<TypedPrincipal>();
var index = 0;

var accessType = ace.AceType.ToString();
var agent = await GetRegistryPrincipal(ace.SecurityIdentifier, computerDomain, computerName, isDomainController,
computerObjectId, machineSid);

var opaque = ace.GetOpaque();

if(opaque is null)
return (false, default);

var sidCount = BitConverter.ToUInt32(opaque, 0);
index += 4;

for (var i = 0; i < sidCount; i++) {
var sid = new SecurityIdentifier(opaque, index);
if (await GetRegistryPrincipal(sid, computerDomain, computerName, isDomainController, computerObjectId,
machineSid) is (true, var regPrincipal)) {
if (await GetRegistryPrincipal(sid, computerDomain, computerName, isDomainController, computerObjectId, machineSid)
is (true, var regPrincipal))
{
targets.Add(regPrincipal);
}

Expand All @@ -485,43 +496,47 @@ await SendComputerStatus(new CSVComputerStatus

var finalTargets = targets.ToArray();
var allTemplates = index >= opaque.Length;
if (index < opaque.Length) {
var template = Encoding.Unicode.GetString(opaque, index, opaque.Length - index - 2).Replace("\u0000", string.Empty);
if (await _utils.ResolveCertTemplateByProperty(Encoder.LdapFilterEncode(template), LDAPProperties.CanonicalName, computerDomain) is (true, var resolvedTemplate)) {
return (true, new EnrollmentAgentRestriction {
Template = resolvedTemplate,
Agent = agent.Principal,
AllTemplates = allTemplates,
AccessType = accessType,
Targets = finalTargets
});
}

if (await _utils.ResolveCertTemplateByProperty(
Encoder.LdapFilterEncode(template), LDAPProperties.CertTemplateOID, computerDomain) is
(true, var resolvedOidTemplate)) {
return (true, new EnrollmentAgentRestriction {
Template = resolvedOidTemplate,
Agent = agent.Principal,
AllTemplates = allTemplates,
AccessType = accessType,
Targets = finalTargets
});
}

if (allTemplates) {
return (true, new EnrollmentAgentRestriction {
Agent = agent.Principal,
AllTemplates = allTemplates,
AccessType = accessType,
Targets = finalTargets
});
}

var template = Encoding.Unicode.GetString(opaque, index, opaque.Length - index - 2).Replace("\u0000", string.Empty);
if (await _utils.ResolveCertTemplateByProperty(Encoder.LdapFilterEncode(template), LDAPProperties.CanonicalName, computerDomain)
is (true, var resolvedTemplate))
{
return (true, new EnrollmentAgentRestriction {
Template = resolvedTemplate,
Agent = agent.Principal,
AllTemplates = allTemplates,
AccessType = accessType,
Targets = finalTargets
});
}

if (await _utils.ResolveCertTemplateByProperty(Encoder.LdapFilterEncode(template), LDAPProperties.CertTemplateOID, computerDomain)
is (true, var resolvedOidTemplate))
{
return (true, new EnrollmentAgentRestriction {
Template = resolvedOidTemplate,
Agent = agent.Principal,
AllTemplates = allTemplates,
AccessType = accessType,
Targets = finalTargets
});
}

return (false, default);
}

public virtual SharpHoundRPC.Result<ISAMServer> OpenSamServer(string computerName)
{
var result = _openSamServerAdaptiveTimeout.ExecuteRPCWithTimeout((_) => SAMServer.OpenServer(computerName)).GetAwaiter().GetResult();
if (result.IsFailed)
{
return SharpHoundRPC.Result<ISAMServer>.Fail(result.SError);
}

return SharpHoundRPC.Result<ISAMServer>.Ok(result.Value);
return _openSamServerAdaptiveTimeout.ExecuteRPCWithTimeout((_) => _samServerAccessor.OpenServer(computerName)).GetAwaiter().GetResult();
}

private async Task SendComputerStatus(CSVComputerStatus status)
Expand Down
6 changes: 4 additions & 2 deletions src/CommonLib/Processors/LocalGroupProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class LocalGroupProcessor
public delegate Task ComputerStatusDelegate(CSVComputerStatus status);
private readonly ILogger _log;
private readonly ILdapUtils _utils;
private readonly ISAMServerAccessor _samServerAccessor;
private readonly AdaptiveTimeout _getMachineSidAdaptiveTimeout;
private readonly AdaptiveTimeout _openSamServerAdaptiveTimeout;
private readonly AdaptiveTimeout _getDomainsAdaptiveTimeout;
Expand All @@ -27,9 +28,10 @@ public class LocalGroupProcessor

public LocalGroupProcessor(ILdapUtils utils, ILogger log = null) {
_utils = utils;
_samServerAccessor = new SAMServerAccessor();
_log = log ?? Logging.LogProvider.CreateLogger("LocalGroupProcessor");
_getMachineSidAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(ISAMServer.GetMachineSid)));
_openSamServerAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(SAMServer.OpenServer)));
_openSamServerAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(ISAMServerAccessor.OpenServer)));
_getDomainsAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(ISAMServer.GetDomains)));
_openDomainAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(ISAMServer.OpenDomain)));
_getAliasesAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(ISAMDomain.GetAliases)));
Expand All @@ -42,7 +44,7 @@ public LocalGroupProcessor(ILdapUtils utils, ILogger log = null) {

public virtual SharpHoundRPC.Result<ISAMServer> OpenSamServer(string computerName)
{
var result = _openSamServerAdaptiveTimeout.ExecuteRPCWithTimeout((_) => SAMServer.OpenServer(computerName)).GetAwaiter().GetResult();
var result = _openSamServerAdaptiveTimeout.ExecuteRPCWithTimeout((_) => _samServerAccessor.OpenServer(computerName)).GetAwaiter().GetResult();
if (result.IsFailed)
{
return SharpHoundRPC.Result<ISAMServer>.Fail(result.SError);
Expand Down
2 changes: 0 additions & 2 deletions src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using SharpHoundRPC.Handles;
using SharpHoundRPC.Shared;

Expand Down
27 changes: 27 additions & 0 deletions src/SharpHoundRPC/Wrappers/ISAMServerAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using SharpHoundRPC.SAMRPCNative;

namespace SharpHoundRPC.Wrappers
{
public interface ISAMServerAccessor
{
Result<ISAMServer> OpenServer(string computerName, SAMEnums.SamAccessMasks requestedConnectAccess =
SAMEnums.SamAccessMasks.SamServerConnect |
SAMEnums.SamAccessMasks.SamServerEnumerateDomains |
SAMEnums.SamAccessMasks.SamServerLookupDomain);
}

public class SAMServerAccessor : ISAMServerAccessor
{
public Result<ISAMServer> OpenServer(string computerName, SAMEnums.SamAccessMasks requestedConnectAccess =
SAMEnums.SamAccessMasks.SamServerConnect |
SAMEnums.SamAccessMasks.SamServerEnumerateDomains |
SAMEnums.SamAccessMasks.SamServerLookupDomain)
{
var (status, handle) = SAMMethods.SamConnect(computerName, requestedConnectAccess);

return status.IsError()
? status
: new SAMServer(handle, computerName);
}
}
}
13 changes: 0 additions & 13 deletions src/SharpHoundRPC/Wrappers/SAMServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,6 @@ public SAMServer(SAMHandle handle, string computerName) : base(handle)

public string ComputerName { get; }

public static Result<SAMServer> OpenServer(string computerName, SAMEnums.SamAccessMasks requestedConnectAccess =
SAMEnums.SamAccessMasks.SamServerConnect |
SAMEnums.SamAccessMasks
.SamServerEnumerateDomains |
SAMEnums.SamAccessMasks.SamServerLookupDomain)
{
var (status, handle) = SAMMethods.SamConnect(computerName, requestedConnectAccess);

return status.IsError()
? status
: new SAMServer(handle, computerName);
}

public Result<IEnumerable<(string Name, int Rid)>> GetDomains()
{
var (status, rids, count) = SAMMethods.SamEnumerateDomainsInSamServer(Handle);
Expand Down
Loading
Loading