diff --git a/src/CommonLib/Helpers.cs b/src/CommonLib/Helpers.cs
index 6b89ae9c9..19a51a37c 100644
--- a/src/CommonLib/Helpers.cs
+++ b/src/CommonLib/Helpers.cs
@@ -7,10 +7,6 @@
using System.Text.RegularExpressions;
using SharpHoundCommonLib.Enums;
using Microsoft.Extensions.Logging;
-using System.IO;
-using System.Security;
-using SharpHoundCommonLib.Processors;
-using Microsoft.Win32;
using System.Threading.Tasks;
namespace SharpHoundCommonLib {
@@ -151,7 +147,7 @@ public static string DistinguishedNameToDomain(string distinguishedName) {
}
///
- /// Converts a domain name to a distinguished name using simple string substitution
+ /// Converts a domain name to a distinguished name using simple string substitution
///
///
///
@@ -258,44 +254,6 @@ public static bool IsSidFiltered(string sid) {
return false;
}
- public static RegistryResult GetRegistryKeyData(string target, string subkey, string subvalue, ILogger log) {
- var data = new RegistryResult();
-
- try {
- var baseKey = OpenRemoteRegistry(target);
- var value = baseKey.GetValue(subkey, subvalue);
- data.Value = value;
-
- data.Collected = true;
- }
- catch (IOException e) {
- log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}",
- target, subkey, subvalue);
- data.FailureReason = "Target machine was not found or not connectable";
- }
- catch (SecurityException e) {
- log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}",
- target, subkey, subvalue);
- data.FailureReason = "User does not have the proper permissions to perform this operation";
- }
- catch (UnauthorizedAccessException e) {
- log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}",
- target, subkey, subvalue);
- data.FailureReason = "User does not have the necessary registry rights";
- }
- catch (Exception e) {
- log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}",
- target, subkey, subvalue);
- data.FailureReason = e.Message;
- }
-
- return data;
- }
-
- public static IRegistryKey OpenRemoteRegistry(string target) {
- return SHRegistryKey.Connect(RegistryHive.LocalMachine, target).GetAwaiter().GetResult();
- }
-
public static string[] AuthenticationOIDs = new string[] {
CommonOids.ClientAuthentication,
CommonOids.PKINITClientAuthentication,
diff --git a/src/CommonLib/IRegistryAccessor.cs b/src/CommonLib/IRegistryAccessor.cs
new file mode 100644
index 000000000..0a80174f1
--- /dev/null
+++ b/src/CommonLib/IRegistryAccessor.cs
@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Security;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Win32;
+using SharpHoundCommonLib.Processors;
+
+namespace SharpHoundCommonLib {
+ public interface IRegistryAccessor {
+ public RegistryResult GetRegistryKeyData(string target, string subkey, string subvalue);
+ public IRegistryKey OpenRemoteRegistry(string target);
+ public Task Connect(RegistryHive hive, string machineName);
+ }
+
+ public class RegistryAccessor : IRegistryAccessor {
+ private readonly ILogger _log;
+ private readonly AdaptiveTimeout _adaptiveTimeout;
+
+ public RegistryAccessor(ILogger log = null) {
+ _log = log ?? Logging.LogProvider.CreateLogger(nameof(RegistryAccessor));
+ _adaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromSeconds(10), _log);
+ }
+
+ public RegistryResult GetRegistryKeyData(string target, string subkey, string subvalue) {
+ var data = new RegistryResult();
+
+ try {
+ using (var baseKey = OpenRemoteRegistry(target)) {
+ var value = baseKey.GetValue(subkey, subvalue);
+ data.Value = value;
+ data.Collected = true;
+ }
+ }
+ catch (IOException e) {
+ _log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}",
+ target, subkey, subvalue);
+ data.FailureReason = "Target machine was not found or not connectable";
+ }
+ catch (SecurityException e) {
+ _log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}",
+ target, subkey, subvalue);
+ data.FailureReason = "User does not have the proper permissions to perform this operation";
+ }
+ catch (UnauthorizedAccessException e) {
+ _log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}",
+ target, subkey, subvalue);
+ data.FailureReason = "User does not have the necessary registry rights";
+ }
+ catch (Exception e) {
+ _log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}",
+ target, subkey, subvalue);
+ data.FailureReason = e.Message;
+ }
+
+ return data;
+ }
+
+ public IRegistryKey OpenRemoteRegistry(string target) {
+ return Connect(RegistryHive.LocalMachine, target).GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Gets a handle to a remote registry.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task Connect(RegistryHive hive, string machineName) {
+ var remoteKey = await _adaptiveTimeout.ExecuteWithTimeout((_) => RegistryKey.OpenRemoteBaseKey(hive, machineName));
+ if (remoteKey.IsSuccess)
+ return new SHRegistryKey(remoteKey.Value);
+ throw new TimeoutException($"Failed to connect to registry on {machineName}: {remoteKey.Error}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommonLib/IRegistryKey.cs b/src/CommonLib/IRegistryKey.cs
index fd266265f..510efe32c 100644
--- a/src/CommonLib/IRegistryKey.cs
+++ b/src/CommonLib/IRegistryKey.cs
@@ -1,18 +1,16 @@
using System;
-using System.Threading.Tasks;
using Microsoft.Win32;
namespace SharpHoundCommonLib {
- public interface IRegistryKey {
+ public interface IRegistryKey: IDisposable {
public object GetValue(string subkey, string name);
public string[] GetSubKeyNames();
}
- public class SHRegistryKey : IRegistryKey, IDisposable {
+ public class SHRegistryKey : IRegistryKey {
private readonly RegistryKey _currentKey;
- private static readonly AdaptiveTimeout _adaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromSeconds(10), Logging.LogProvider.CreateLogger(nameof(SHRegistryKey)));
-
- private SHRegistryKey(RegistryKey registryKey) {
+
+ public SHRegistryKey(RegistryKey registryKey) {
_currentKey = registryKey;
}
@@ -23,38 +21,8 @@ public object GetValue(string subkey, string name) {
public string[] GetSubKeyNames() => _currentKey.GetSubKeyNames();
- ///
- /// Gets a handle to a remote registry.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static async Task Connect(RegistryHive hive, string machineName) {
- var remoteKey = await _adaptiveTimeout.ExecuteWithTimeout((_) => RegistryKey.OpenRemoteBaseKey(hive, machineName));
- if (remoteKey.IsSuccess)
- return new SHRegistryKey(remoteKey.Value);
- throw new TimeoutException($"Failed to connect to registry on {machineName}: {remoteKey.Error}");
- }
-
public void Dispose() {
_currentKey.Dispose();
}
}
-
- public class MockRegistryKey : IRegistryKey {
- public virtual object GetValue(string subkey, string name) {
- //Unimplemented
- return default;
- }
-
- public virtual string[] GetSubKeyNames() {
- throw new NotImplementedException();
- }
- }
}
\ No newline at end of file
diff --git a/src/CommonLib/Processors/CertAbuseProcessor.cs b/src/CommonLib/Processors/CertAbuseProcessor.cs
index 86513f5af..7da4f46c5 100644
--- a/src/CommonLib/Processors/CertAbuseProcessor.cs
+++ b/src/CommonLib/Processors/CertAbuseProcessor.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
@@ -8,7 +7,6 @@
using Microsoft.Extensions.Logging;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.OutputTypes;
-using SharpHoundRPC;
using SharpHoundRPC.Wrappers;
using Encoder = Microsoft.Security.Application.Encoder;
@@ -20,24 +18,29 @@ public class CertAbuseProcessor
private readonly ILdapUtils _utils;
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, 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)));
}
///
/// This function should be called with the security data fetched from .
/// The resulting ACEs will contain the owner of the CA as well as Management rights.
///
- ///
+ ///
///
///
+ ///
///
public async Task ProcessRegistryEnrollmentPermissions(string caName, string objectDomain, string computerName, string computerObjectId)
{
@@ -47,9 +50,23 @@ public async Task ProcessRegistryEnrollmentPermissions(str
data.Collected = aceData.Collected;
if (!aceData.Collected)
{
+ await SendComputerStatus(new CSVComputerStatus {
+ Status = aceData.FailureReason,
+ Task = nameof(ProcessRegistryEnrollmentPermissions),
+ ComputerName = computerName,
+ ObjectId = computerObjectId,
+ });
+
data.FailureReason = aceData.FailureReason;
return data;
}
+
+ await SendComputerStatus(new CSVComputerStatus {
+ Status = CSVComputerStatus.StatusSuccess,
+ Task = nameof(ProcessRegistryEnrollmentPermissions),
+ ComputerName = computerName,
+ ObjectId = computerObjectId,
+ });
if (aceData.Value == null)
{
@@ -167,9 +184,23 @@ public async Task ProcessEAPermissions(string
ret.Collected = regData.Collected;
if (!ret.Collected)
{
+ await SendComputerStatus(new CSVComputerStatus {
+ Status = regData.FailureReason,
+ Task = nameof(ProcessEAPermissions),
+ ComputerName = computerName,
+ ObjectId = computerObjectId,
+ });
+
ret.FailureReason = regData.FailureReason;
return ret;
}
+
+ await SendComputerStatus(new CSVComputerStatus {
+ Status = CSVComputerStatus.StatusSuccess,
+ Task = nameof(ProcessEAPermissions),
+ ComputerName = computerName,
+ ObjectId = computerObjectId,
+ });
if (regData.Value == null)
{
@@ -179,6 +210,12 @@ public async Task ProcessEAPermissions(string
var isDomainController = await _utils.IsDomainController(computerObjectId, objectDomain);
var machineSid = await GetMachineSid(computerName, computerObjectId);
var descriptor = new RawSecurityDescriptor(regData.Value as byte[], 0);
+
+ if (descriptor.DiscretionaryAcl is null)
+ {
+ return ret;
+ }
+
var enrollmentAgentRestrictions = new List();
foreach (var genericAce in descriptor.DiscretionaryAcl)
{
@@ -218,13 +255,12 @@ public async Task ProcessEAPermissions(string
///
///
///
- [ExcludeFromCodeCoverage]
private RegistryResult GetCASecurity(string target, string caName)
{
var regSubKey = $"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{caName}";
const string regValue = "Security";
- return Helpers.GetRegistryKeyData(target, regSubKey, regValue, _log);
+ return _registryAccessor.GetRegistryKeyData(target, regSubKey, regValue);
}
///
@@ -233,13 +269,12 @@ private RegistryResult GetCASecurity(string target, string caName)
///
///
///
- [ExcludeFromCodeCoverage]
private RegistryResult GetEnrollmentAgentRights(string target, string caName)
{
var regSubKey = $"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{caName}";
var regValue = "EnrollmentAgentRights";
- return Helpers.GetRegistryKeyData(target, regSubKey, regValue, _log);
+ return _registryAccessor.GetRegistryKeyData(target, regSubKey, regValue);
}
///
@@ -249,22 +284,36 @@ private RegistryResult GetEnrollmentAgentRights(string target, string caName)
/// https://blog.keyfactor.com/hidden-dangers-certificate-subject-alternative-names-sans
///
///
+ ///
///
- [ExcludeFromCodeCoverage]
- public BoolRegistryAPIResult IsUserSpecifiesSanEnabled(string target, string caName)
+ public async Task IsUserSpecifiesSanEnabled(string target, string caName, string computerObjectId)
{
var ret = new BoolRegistryAPIResult();
- var subKey =
+ var regSubKey =
$"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{caName}\\PolicyModules\\CertificateAuthority_MicrosoftDefault.Policy";
- const string subValue = "EditFlags";
- var data = Helpers.GetRegistryKeyData(target, subKey, subValue, _log);
+ const string regValue = "EditFlags";
+ var data = _registryAccessor.GetRegistryKeyData(target, regSubKey, regValue);
ret.Collected = data.Collected;
if (!data.Collected)
{
+ await SendComputerStatus(new CSVComputerStatus {
+ Status = data.FailureReason,
+ Task = nameof(IsUserSpecifiesSanEnabled),
+ ComputerName = target,
+ ObjectId = computerObjectId
+ });
+
ret.FailureReason = data.FailureReason;
return ret;
}
+
+ await SendComputerStatus(new CSVComputerStatus {
+ Status = CSVComputerStatus.StatusSuccess,
+ Task = nameof(IsUserSpecifiesSanEnabled),
+ ComputerName = target,
+ ObjectId = computerObjectId
+ });
if (data.Value == null)
{
@@ -278,28 +327,42 @@ public BoolRegistryAPIResult IsUserSpecifiesSanEnabled(string target, string caN
}
///
- /// This function checks a registry setting on the target host for the specified CA to see if role seperation is enabled.
+ /// This function checks a registry setting on the target host for the specified CA to see if role separation is enabled.
/// If enabled, you cannot perform any CA actions if you have both ManageCA and ManageCertificates permissions. Only CA admins can modify the setting.
///
/// https://www.itprotoday.com/security/q-how-can-i-make-sure-given-windows-account-assigned-only-single-certification-authority-ca
///
///
+ ///
///
///
- [ExcludeFromCodeCoverage]
- public BoolRegistryAPIResult RoleSeparationEnabled(string target, string caName)
+ public async Task IsRoleSeparationEnabled(string target, string caName, string computerObjectId)
{
var ret = new BoolRegistryAPIResult();
var regSubKey = $"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{caName}";
const string regValue = "RoleSeparationEnabled";
- var data = Helpers.GetRegistryKeyData(target, regSubKey, regValue, _log);
+ var data = _registryAccessor.GetRegistryKeyData(target, regSubKey, regValue);
ret.Collected = data.Collected;
if (!data.Collected)
{
+ await SendComputerStatus(new CSVComputerStatus {
+ Status = data.FailureReason,
+ Task = nameof(IsRoleSeparationEnabled),
+ ComputerName = target,
+ ObjectId = computerObjectId
+ });
+
ret.FailureReason = data.FailureReason;
return ret;
}
+
+ await SendComputerStatus(new CSVComputerStatus {
+ Status = CSVComputerStatus.StatusSuccess,
+ Task = nameof(IsRoleSeparationEnabled),
+ ComputerName = target,
+ ObjectId = computerObjectId
+ });
if (data.Value == null)
{
@@ -346,11 +409,11 @@ await _utils.ResolveIDAndType(sid.Value, computerDomain) is (true, var resolvedP
return await _utils.ResolveIDAndType(sid.Value, computerDomain);
}
- private async Task GetMachineSid(string computerName, string computerObjectId)
+ internal async Task 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
@@ -360,7 +423,7 @@ private async Task 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,
@@ -377,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);
}
@@ -396,22 +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();
var index = 0;
var accessType = ace.AceType.ToString();
var agent = await GetRegistryPrincipal(ace.SecurityIdentifier, computerDomain, computerName, isDomainController,
computerObjectId, machineSid);
-
- var opaque = ace.GetOpaque();
+
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);
}
@@ -420,50 +496,53 @@ 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 OpenSamServer(string computerName)
{
- var result = _openSamServerAdaptiveTimeout.ExecuteRPCWithTimeout((_) => SAMServer.OpenServer(computerName)).GetAwaiter().GetResult();
- if (result.IsFailed)
- {
- return SharpHoundRPC.Result.Fail(result.SError);
- }
-
- return SharpHoundRPC.Result.Ok(result.Value);
+ return _openSamServerAdaptiveTimeout.ExecuteRPCWithTimeout((_) => _samServerAccessor.OpenServer(computerName)).GetAwaiter().GetResult();
}
private async Task SendComputerStatus(CSVComputerStatus status)
{
if (ComputerStatusEvent is not null) await ComputerStatusEvent(status);
}
-
}
public class EnrollmentAgentRestriction
diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs
index 729df1820..52c2a8c1c 100644
--- a/src/CommonLib/Processors/ComputerSessionProcessor.cs
+++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs
@@ -24,6 +24,7 @@ public class ComputerSessionProcessor {
private readonly string _localAdminPassword;
private readonly AdaptiveTimeout _readUserSessionsAdaptiveTimeout;
private readonly AdaptiveTimeout _readUserSessionsPriviledgedAdaptiveTimeout;
+ private readonly IRegistryAccessor _registryAccessor;
public ComputerSessionProcessor(ILdapUtils utils,
NativeMethods nativeMethods = null, ILogger log = null, string currentUserName = null,
@@ -38,6 +39,7 @@ public ComputerSessionProcessor(ILdapUtils utils,
_localAdminPassword = localAdminPassword;
_readUserSessionsAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(ReadUserSessions)));
_readUserSessionsPriviledgedAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(ReadUserSessionsPrivileged)));
+ _registryAccessor = new RegistryAccessor();
}
public event ComputerStatusDelegate ComputerStatusEvent;
@@ -293,7 +295,7 @@ public async Task ReadUserSessionsRegistry(string computerName
_log.LogDebug("Running RegSessionEnum for {ObjectName}", computerName);
try {
- using (var key = await SHRegistryKey.Connect(RegistryHive.Users, computerName)) {
+ using (var key = await _registryAccessor.Connect(RegistryHive.Users, computerName)) {
ret.Collected = true;
await SendComputerStatus(new CSVComputerStatus {
Status = CSVComputerStatus.StatusSuccess,
diff --git a/src/CommonLib/Processors/DCRegistryProcessor.cs b/src/CommonLib/Processors/DCRegistryProcessor.cs
index 4c229d025..2726a45a6 100644
--- a/src/CommonLib/Processors/DCRegistryProcessor.cs
+++ b/src/CommonLib/Processors/DCRegistryProcessor.cs
@@ -1,5 +1,4 @@
using SharpHoundCommonLib.OutputTypes;
-using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -9,12 +8,15 @@ namespace SharpHoundCommonLib.Processors
public class DCRegistryProcessor
{
private readonly ILogger _log;
+ private readonly IRegistryAccessor _registryAccessor;
+
public readonly ILdapUtils _utils;
public delegate Task ComputerStatusDelegate(CSVComputerStatus status);
public DCRegistryProcessor(ILdapUtils utils, ILogger log = null)
{
_utils = utils;
+ _registryAccessor = new RegistryAccessor(log);
_log = log ?? Logging.LogProvider.CreateLogger("DCRegProc");
}
@@ -30,7 +32,7 @@ public IntRegistryAPIResult GetCertificateMappingMethods(string target)
var ret = new IntRegistryAPIResult();
const string subKey = @"SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel";
const string subValue = "CertificateMappingMethods";
- var data = Helpers.GetRegistryKeyData(target, subKey, subValue, _log);
+ var data = _registryAccessor.GetRegistryKeyData(target, subKey, subValue);
ret.Collected = data.Collected;
if (!data.Collected)
@@ -62,7 +64,7 @@ public IntRegistryAPIResult GetStrongCertificateBindingEnforcement(string target
var ret = new IntRegistryAPIResult();
const string subKey = @"SYSTEM\CurrentControlSet\Services\Kdc";
const string subValue = "StrongCertificateBindingEnforcement";
- var data = Helpers.GetRegistryKeyData(target, subKey, subValue, _log);
+ var data = _registryAccessor.GetRegistryKeyData(target, subKey, subValue);
ret.Collected = data.Collected;
if (!data.Collected)
@@ -94,7 +96,7 @@ public StrRegistryAPIResult GetVulnerableNetlogonSecurityDescriptor(string targe
var ret = new StrRegistryAPIResult();
const string subKey = @"SYSTEM\CurrentControlSet\Services\Netlogon\Parameters";
const string subValue = "VulnerableChannelAllowList";
- var data = Helpers.GetRegistryKeyData(target, subKey, subValue, _log);
+ var data = _registryAccessor.GetRegistryKeyData(target, subKey, subValue);
ret.Collected = data.Collected;
if (!data.Collected)
diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs
index 93454b174..c2048b04f 100644
--- a/src/CommonLib/Processors/LocalGroupProcessor.cs
+++ b/src/CommonLib/Processors/LocalGroupProcessor.cs
@@ -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;
@@ -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)));
@@ -42,7 +44,7 @@ public LocalGroupProcessor(ILdapUtils utils, ILogger log = null) {
public virtual SharpHoundRPC.Result 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.Fail(result.SError);
diff --git a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs
index e17de5b64..5fc5edc77 100644
--- a/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs
+++ b/src/SharpHoundRPC/SAMRPCNative/SAMMethods.cs
@@ -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;
diff --git a/src/SharpHoundRPC/Wrappers/ISAMServerAccessor.cs b/src/SharpHoundRPC/Wrappers/ISAMServerAccessor.cs
new file mode 100644
index 000000000..65851a212
--- /dev/null
+++ b/src/SharpHoundRPC/Wrappers/ISAMServerAccessor.cs
@@ -0,0 +1,27 @@
+using SharpHoundRPC.SAMRPCNative;
+
+namespace SharpHoundRPC.Wrappers
+{
+ public interface ISAMServerAccessor
+ {
+ Result OpenServer(string computerName, SAMEnums.SamAccessMasks requestedConnectAccess =
+ SAMEnums.SamAccessMasks.SamServerConnect |
+ SAMEnums.SamAccessMasks.SamServerEnumerateDomains |
+ SAMEnums.SamAccessMasks.SamServerLookupDomain);
+ }
+
+ public class SAMServerAccessor : ISAMServerAccessor
+ {
+ public Result 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/SharpHoundRPC/Wrappers/SAMServer.cs b/src/SharpHoundRPC/Wrappers/SAMServer.cs
index 872faa497..e405d1a88 100644
--- a/src/SharpHoundRPC/Wrappers/SAMServer.cs
+++ b/src/SharpHoundRPC/Wrappers/SAMServer.cs
@@ -22,19 +22,6 @@ public SAMServer(SAMHandle handle, string computerName) : base(handle)
public string ComputerName { get; }
- public static Result 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> GetDomains()
{
var (status, rids, count) = SAMMethods.SamEnumerateDomainsInSamServer(Handle);
diff --git a/test/unit/CertAbuseProcessorTest.cs b/test/unit/CertAbuseProcessorTest.cs
index 17efe86fd..11c2185c1 100644
--- a/test/unit/CertAbuseProcessorTest.cs
+++ b/test/unit/CertAbuseProcessorTest.cs
@@ -1,141 +1,781 @@
using System;
-using System.DirectoryServices;
+using System.Collections.Generic;
+using System.Security.AccessControl;
+using System.Security.Principal;
+using System.Threading;
using System.Threading.Tasks;
-using CommonLibTest.Facades;
+using CommonLibTest.CollectionDefinitions;
using Moq;
-using Newtonsoft.Json;
using SharpHoundCommonLib;
using SharpHoundCommonLib.Processors;
using Xunit;
-using Xunit.Abstractions;
-
-namespace CommonLibTest {
- //TODO: Make these tests work
- public class CertAbuseProcessorTest : IDisposable {
- private const string CASecurityFixture =
- "AQAUhCABAAAwAQAAFAAAAEQAAAACADAAAgAAAALAFAD//wAAAQEAAAAAAAEAAAAAAsAUAP//AAABAQAAAAAABQcAAAACANwABwAAAAADGAABAAAAAQIAAAAAAAUgAAAAIAIAAAADGAACAAAAAQIAAAAAAAUgAAAAIAIAAAADJAABAAAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAADJAACAAAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAADJAABAAAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQBwIAAAADJAACAAAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQBwIAAAADFAAAAgAAAQEAAAAAAAULAAAAAQIAAAAAAAUgAAAAIAIAAAECAAAAAAAFIAAAACACAAA=";
-
- private readonly ITestOutputHelper _testOutputHelper;
-
- public CertAbuseProcessorTest(ITestOutputHelper testOutputHelper) {
- _testOutputHelper = testOutputHelper;
- }
-
- public void Dispose() {
- }
-
- // [Fact]
- // public void CertAbuseProcessor_GetCASecurity_HappyPath()
- // {
- // var mockProcessor = new Mock(new MockLDAPUtils(), null);
- //
- // var mockRegistryKey = new Mock();
- // mockRegistryKey.Setup(x => x.GetValue(It.IsAny(), It.IsAny()))
- // .Returns(new byte[] { 0x20, 0x20 });
- // mockProcessor.Setup(x => x.OpenRemoteRegistry(It.IsAny())).Returns(mockRegistryKey.Object);
- //
- // var processor = mockProcessor.Object;
- // var results = processor.GetCASecurity("testlab.local", "blah");
- // Assert.True(results.Collected);
- // }
-
- // [Fact]
- // public void CertAbuseProcessor_GetTrustedCerts_EmptyForNonRoot()
- // {
- // var mockUtils = new Mock();
- // mockUtils.Setup(x => x.IsForestRoot(It.IsAny())).Returns(false);
- // var processor = new CertAbuseProcessor(mockUtils.Object);
- //
- // var results = processor.GetTrustedCerts("testlab.local");
- // Assert.Empty(results);
- // }
- //
- // [Fact]
- // public void CertAbuseProcessor_GetTrustedCerts_NullConfigPath_ReturnsEmpty()
- // {
- // var mockUtils = new Mock();
- // mockUtils.Setup(x => x.IsForestRoot(It.IsAny())).Returns(true);
- // mockUtils.Setup(x => x.GetConfigurationPath(It.IsAny())).Returns((string)null);
- // var processor = new CertAbuseProcessor(mockUtils.Object);
- //
- // var results = processor.GetTrustedCerts("testlab.local");
- // Assert.Empty(results);
- // }
- //
- // [Fact]
- // public void CertAbuseProcessor_GetRootCAs_EmptyForNonRoot()
- // {
- // var mockUtils = new Mock();
- // mockUtils.Setup(x => x.IsForestRoot(It.IsAny())).Returns(false);
- // var processor = new CertAbuseProcessor(mockUtils.Object);
- //
- // var results = processor.GetRootCAs("testlab.local");
- // Assert.Empty(results);
- // }
- //
- // [Fact]
- // public void CertAbuseProcessor_GetRootCAs_NullConfigPath_ReturnsEmpty()
- // {
- // var mockUtils = new Mock();
- // mockUtils.Setup(x => x.IsForestRoot(It.IsAny())).Returns(true);
- // mockUtils.Setup(x => x.GetConfigurationPath(It.IsAny())).Returns((string)null);
- // var processor = new CertAbuseProcessor(mockUtils.Object);
- //
- // var results = processor.GetRootCAs("testlab.local");
- // Assert.Empty(results);
- // }
-
- // [Fact]
- // public async Task CertAbuseProcessor_ProcessCAPermissions_NullSecurity_ReturnsNull()
- // {
- // var processor = new CertAbuseProcessor(new MockLdapUtils());
- //
- // var results = await processor.ProcessRegistryEnrollmentPermissions(null, "DUMPSTER.FIRE", null, "test");
- //
- // Assert.Equal("Value cannot be null. (Parameter 'machineName')", results.FailureReason);
- // Assert.False(results.Collected);
- // Assert.Null(results.Data);
- // }
-
- // [WindowsOnlyFact]
- // public void CertAbuseProcessor_ProcessCAPermissions_ReturnsCorrectValues()
- // {
- // var mockUtils = new Mock();
- // var sd = new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity());
- // mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(sd);
- // var processor = new CertAbuseProcessor(mockUtils.Object);
- // var bytes = Helpers.B64ToBytes(CASecurityFixture);
- //
- // var results = processor.ProcessCAPermissions(bytes, "TESTLAB.LOCAL", "test", false);
- // _testOutputHelper.WriteLine(JsonConvert.SerializeObject(results, Formatting.Indented));
- // Assert.Contains(results,
- // x => x.RightName == EdgeNames.Owns && x.PrincipalSID == "TESTLAB.LOCAL-S-1-5-32-544" &&
- // x.PrincipalType == Label.Group && !x.IsInherited);
- // Assert.Contains(results,
- // x => x.RightName == EdgeNames.Enroll && x.PrincipalSID == "TESTLAB.LOCAL-S-1-5-11" &&
- // !x.IsInherited);
- // Assert.Contains(results,
- // x => x.RightName == EdgeNames.ManageCA && x.PrincipalSID == "TESTLAB.LOCAL-S-1-5-32-544" &&
- // !x.IsInherited);
- // Assert.Contains(results,
- // x => x.RightName == EdgeNames.ManageCertificates && x.PrincipalSID == "TESTLAB.LOCAL-S-1-5-32-544" &&
- // !x.IsInherited);
- // Assert.Contains(results,
- // x => x.RightName == EdgeNames.ManageCA &&
- // x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-512" &&
- // !x.IsInherited);
- // Assert.Contains(results,
- // x => x.RightName == EdgeNames.ManageCertificates &&
- // x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-512" &&
- // !x.IsInherited);
- // Assert.Contains(results,
- // x => x.RightName == EdgeNames.ManageCA &&
- // x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-519" &&
- // !x.IsInherited);
- // Assert.Contains(results,
- // x => x.RightName == EdgeNames.ManageCertificates &&
- // x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-519" &&
- // !x.IsInherited);
- // }
+using SharpHoundCommonLib.Enums;
+using SharpHoundCommonLib.OutputTypes;
+using SharpHoundRPC.SAMRPCNative;
+using SharpHoundRPC.Wrappers;
+
+namespace CommonLibTest
+{
+ [Collection(nameof(CacheTestCollectionDefinition))]
+ public class CertAbuseProcessorTest
+ {
+ private readonly Mock _mockLdapUtils;
+ private readonly Mock _mockRegistryAccessor;
+ private readonly Mock _mockSAMServerAccessor;
+ private readonly CertAbuseProcessor _certAbuseProcessor;
+
+ private const string DomainName = "TEST.LOCAL";
+ private const string CAName = "TEST-CA";
+ private const string TargetName = "target.test.local";
+ private const string TargetDomainSid = "S-1-5-21-123456789-123456789-123456789";
+ private const string FailureReason = "Registry Lookup Failure";
+
+ private CSVComputerStatus _receivedCompStatus;
+
+ public CertAbuseProcessorTest() {
+ _mockLdapUtils = new Mock();
+ _mockRegistryAccessor = new Mock();
+ _mockSAMServerAccessor = new Mock();
+ _certAbuseProcessor = new CertAbuseProcessor(_mockLdapUtils.Object, _mockRegistryAccessor.Object, _mockSAMServerAccessor.Object);
+
+ _certAbuseProcessor.ComputerStatusEvent += status => {
+ _receivedCompStatus = status;
+ return Task.CompletedTask;
+ };
+
+ Cache.SetCacheInstance(Cache.CreateNewCache());
+ }
+
+ [Theory]
+ [InlineData(0x00040000, true)]
+ [InlineData(0x00000000, false)]
+ public async Task CertAbuseProcessor_IsUserSpecifiesSanEnabled_ReturnsResult(int editFlags, bool expectedResult) {
+ const string subKey = $"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{CAName}\\PolicyModules\\CertificateAuthority_MicrosoftDefault.Policy";
+ const string subValue = "EditFlags";
+
+ _mockRegistryAccessor
+ .Setup(ra => ra.GetRegistryKeyData(
+ TargetName,
+ subKey,
+ subValue))
+ .Returns(new RegistryResult
+ {
+ Collected = true,
+ Value = editFlags
+ });
+
+ var results = await _certAbuseProcessor.IsUserSpecifiesSanEnabled(TargetName, CAName, TargetDomainSid);
+
+ //Validate result
+ Assert.True(results.Collected);
+ Assert.Equal(expectedResult, results.Value);
+ Assert.Null(results.FailureReason);
+
+ //Validate CompStatus Log
+ Assert.Equal(TargetName, _receivedCompStatus.ComputerName);
+ Assert.Equal(nameof(CertAbuseProcessor.IsUserSpecifiesSanEnabled), _receivedCompStatus.Task);
+ Assert.Equal(CSVComputerStatus.StatusSuccess, _receivedCompStatus.Status);
+ Assert.Equal(TargetDomainSid, _receivedCompStatus.ObjectId);
+ }
+
+ [Fact]
+ public async Task CertAbuseProcessor_IsUserSpecifiesSanEnabled_HandlesFailedLookup() {
+ const string subKey = $"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{CAName}\\PolicyModules\\CertificateAuthority_MicrosoftDefault.Policy";
+ const string subValue = "EditFlags";
+
+ _mockRegistryAccessor
+ .Setup(ra => ra.GetRegistryKeyData(
+ TargetName,
+ subKey,
+ subValue))
+ .Returns(new RegistryResult
+ {
+ Collected = false,
+ FailureReason = FailureReason
+ });
+
+ var results = await _certAbuseProcessor.IsUserSpecifiesSanEnabled(TargetName, CAName, TargetDomainSid);
+
+ //Validate result
+ Assert.False(results.Collected);
+ Assert.Equal(FailureReason, results.FailureReason);
+
+ //Validate CompStatus Log
+ Assert.Equal(TargetName, _receivedCompStatus.ComputerName);
+ Assert.Equal(nameof(_certAbuseProcessor.IsUserSpecifiesSanEnabled), _receivedCompStatus.Task);
+ Assert.Equal(FailureReason, _receivedCompStatus.Status);
+ Assert.Equal(TargetDomainSid, _receivedCompStatus.ObjectId);
+ }
+
+ [Theory]
+ [InlineData(1, true)]
+ [InlineData(0, false)]
+ public async Task CertAbuseProcessor_IsRoleSeparationEnabled_ReturnsResult(int roleSeparationEnabled, bool expectedResult) {
+ const string subKey = $"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{CAName}";
+ const string subValue = "RoleSeparationEnabled";
+
+ _mockRegistryAccessor
+ .Setup(ra => ra.GetRegistryKeyData(
+ TargetName,
+ subKey,
+ subValue))
+ .Returns(new RegistryResult
+ {
+ Collected = true,
+ Value = roleSeparationEnabled
+ });
+
+ var results = await _certAbuseProcessor.IsRoleSeparationEnabled(TargetName, CAName, TargetDomainSid);
+
+ //Validate result
+ Assert.True(results.Collected);
+ Assert.Equal(expectedResult, results.Value);
+ Assert.Null(results.FailureReason);
+
+ //Validate CompStatus Log
+ Assert.Equal(TargetName, _receivedCompStatus.ComputerName);
+ Assert.Equal(nameof(CertAbuseProcessor.IsRoleSeparationEnabled), _receivedCompStatus.Task);
+ Assert.Equal(CSVComputerStatus.StatusSuccess, _receivedCompStatus.Status);
+ Assert.Equal(TargetDomainSid, _receivedCompStatus.ObjectId);
+ }
+
+ [Fact]
+ public async Task CertAbuseProcessor_IsRoleSeparationEnabled_HandlesFailedLookup() {
+ const string subKey = $"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{CAName}";
+ const string subValue = "RoleSeparationEnabled";
+
+ _mockRegistryAccessor
+ .Setup(ra => ra.GetRegistryKeyData(
+ TargetName,
+ subKey,
+ subValue))
+ .Returns(new RegistryResult
+ {
+ Collected = false,
+ FailureReason = FailureReason
+ });
+
+ var results = await _certAbuseProcessor.IsRoleSeparationEnabled(TargetName, CAName, TargetDomainSid);
+
+ //Validate result
+ Assert.False(results.Collected);
+ Assert.Equal(FailureReason, results.FailureReason);
+
+ //Validate CompStatus Log
+ Assert.Equal(TargetName, _receivedCompStatus.ComputerName);
+ Assert.Equal(nameof(_certAbuseProcessor.IsRoleSeparationEnabled), _receivedCompStatus.Task);
+ Assert.Equal(FailureReason, _receivedCompStatus.Status);
+ Assert.Equal(TargetDomainSid, _receivedCompStatus.ObjectId);
+ }
+
+ [Fact]
+ public async Task CertAbuseProcessor_ProcessEAPermissions_ReturnsEmptyResult() {
+ const string subKey = $"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{CAName}";
+ const string subValue = "EnrollmentAgentRights";
+
+ _mockRegistryAccessor
+ .Setup(ra => ra.GetRegistryKeyData(
+ TargetName,
+ subKey,
+ subValue))
+ .Returns(new RegistryResult
+ {
+ Collected = true
+ });
+
+ var results = await _certAbuseProcessor.ProcessEAPermissions(CAName, DomainName, TargetName, TargetDomainSid);
+
+ //Validate result
+ Assert.True(results.Collected);
+ Assert.Empty(results.Restrictions);
+ Assert.Null(results.FailureReason);
+
+ //Validate CompStatus Log
+ Assert.Equal(TargetName, _receivedCompStatus.ComputerName);
+ Assert.Equal(nameof(CertAbuseProcessor.ProcessEAPermissions), _receivedCompStatus.Task);
+ Assert.Equal(CSVComputerStatus.StatusSuccess, _receivedCompStatus.Status);
+ Assert.Equal(TargetDomainSid, _receivedCompStatus.ObjectId);
+ }
+
+ [Fact]
+ public async Task CertAbuseProcessor_ProcessEAPermissions_HandlesFailedLookup() {
+ const string subKey = $"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{CAName}";
+ const string subValue = "EnrollmentAgentRights";
+
+ _mockRegistryAccessor
+ .Setup(ra => ra.GetRegistryKeyData(
+ TargetName,
+ subKey,
+ subValue))
+ .Returns(new RegistryResult
+ {
+ Collected = false,
+ FailureReason = FailureReason
+ });
+
+ var results = await _certAbuseProcessor.ProcessEAPermissions(CAName, DomainName, TargetName, TargetDomainSid);
+
+ //Validate result
+ Assert.False(results.Collected);
+ Assert.Equal(FailureReason, results.FailureReason);
+
+ //Validate CompStatus Log
+ Assert.Equal(TargetName, _receivedCompStatus.ComputerName);
+ Assert.Equal(nameof(_certAbuseProcessor.ProcessEAPermissions), _receivedCompStatus.Task);
+ Assert.Equal(FailureReason, _receivedCompStatus.Status);
+ Assert.Equal(TargetDomainSid, _receivedCompStatus.ObjectId);
+ }
+
+ public static IEnumerable