diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs
index cfae61bd..295a501c 100644
--- a/src/CommonLib/Extensions.cs
+++ b/src/CommonLib/Extensions.cs
@@ -393,6 +393,21 @@ public static Label GetLabel(this SearchResultEntry entry)
return objectType;
}
+ ///
+ /// Add multiple entries to a dictionary.
+ ///
+ /// Dictionary (this)
+ /// KeyValuePairs to add
+ /// Key
+ /// Value
+ public static void AddMany(this Dictionary me, IEnumerable> keyValuePairs)
+ {
+ foreach (var kvp in keyValuePairs)
+ {
+ me.Add(kvp.Key, kvp.Value);
+ }
+ }
+
private const string GroupPolicyContainerClass = "groupPolicyContainer";
private const string OrganizationalUnitClass = "organizationalUnit";
private const string DomainClass = "domain";
diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs
index 101752a2..4227eb10 100644
--- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs
+++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs
@@ -32,15 +32,9 @@ public LDAPPropertyProcessor(ILDAPUtils utils)
private static Dictionary GetCommonProps(ISearchResultEntry entry)
{
- return new Dictionary
- {
- {
- "description", entry.GetProperty(LDAPProperties.Description)
- },
- {
- "whencreated", Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated))
- }
- };
+ var props = GetProperties(LDAPProperties.Description, entry);
+ props.AddMany(GetProperties(LDAPProperties.WhenCreated, entry));
+ return props;
}
///
@@ -51,37 +45,11 @@ private static Dictionary GetCommonProps(ISearchResultEntry entr
public static Dictionary ReadDomainProperties(ISearchResultEntry entry)
{
var props = GetCommonProps(entry);
-
- if (!int.TryParse(entry.GetProperty(LDAPProperties.DomainFunctionalLevel), out var level)) level = -1;
-
- props.Add("functionallevel", FunctionalLevelToString(level));
+ props.AddMany(GetProperties(LDAPProperties.DomainFunctionalLevel, entry));
return props;
}
- ///
- /// Converts a numeric representation of a functional level to its appropriate functional level string
- ///
- ///
- ///
- public static string FunctionalLevelToString(int level)
- {
- var functionalLevel = level switch
- {
- 0 => "2000 Mixed/Native",
- 1 => "2003 Interim",
- 2 => "2003",
- 3 => "2008",
- 4 => "2008 R2",
- 5 => "2012",
- 6 => "2012 R2",
- 7 => "2016",
- _ => "Unknown"
- };
-
- return functionalLevel;
- }
-
///
/// Reads specific LDAP properties related to GPOs
///
@@ -90,7 +58,7 @@ public static string FunctionalLevelToString(int level)
public static Dictionary ReadGPOProperties(ISearchResultEntry entry)
{
var props = GetCommonProps(entry);
- props.Add("gpcpath", entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper());
+ props.AddMany(GetProperties(LDAPProperties.GPCFileSYSPath, entry));
return props;
}
@@ -113,17 +81,7 @@ public static Dictionary ReadOUProperties(ISearchResultEntry ent
public static Dictionary ReadGroupProperties(ISearchResultEntry entry)
{
var props = GetCommonProps(entry);
-
- var ac = entry.GetProperty(LDAPProperties.AdminCount);
- if (ac != null)
- {
- var a = int.Parse(ac);
- props.Add("admincount", a != 0);
- }
- else
- {
- props.Add("admincount", false);
- }
+ props.AddMany(GetProperties(LDAPProperties.AdminCount, entry));
return props;
}
@@ -149,28 +107,83 @@ public async Task ReadUserProperties(ISearchResultEntry entry)
var userProps = new UserProperties();
var props = GetCommonProps(entry);
- var uacFlags = (UacFlags)0;
- var uac = entry.GetProperty(LDAPProperties.UserAccountControl);
- if (int.TryParse(uac, out var flag))
- {
- uacFlags = (UacFlags)flag;
- }
+ props.AddMany((GetProperties(LDAPProperties.AllowedToDelegateTo, entry)));
+ userProps.AllowedToDelegate = (await ReadPropertyDelegates(entry)).ToArray();
+
+ props.AddMany(GetProperties(LDAPProperties.UserAccountControl, entry));
+ props.AddMany(GetProperties(LDAPProperties.LastLogon, entry));
+ props.AddMany(GetProperties(LDAPProperties.LastLogonTimestamp, entry));
+ props.AddMany(GetProperties(LDAPProperties.PasswordLastSet, entry));
+ props.AddMany(GetProperties(LDAPProperties.ServicePrincipalNames, entry));
+ props.AddMany(GetProperties(LDAPProperties.DisplayName, entry));
+ props.AddMany(GetProperties(LDAPProperties.Email, entry));
+ props.AddMany(GetProperties(LDAPProperties.Title, entry));
+ props.AddMany(GetProperties(LDAPProperties.HomeDirectory, entry));
+ props.AddMany(GetProperties(LDAPProperties.UserPassword, entry));
+ props.AddMany(GetProperties(LDAPProperties.UnixUserPassword, entry));
+ props.AddMany(GetProperties(LDAPProperties.UnicodePassword, entry));
+ props.AddMany(GetProperties(LDAPProperties.MsSFU30Password, entry));
+ props.AddMany(GetProperties(LDAPProperties.ScriptPath, entry));
+ props.AddMany(GetProperties(LDAPProperties.AdminCount, entry));
+
+ var sidHistory = ReadSidHistory(entry);
+ props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.ToArray());
+ userProps.SidHistory = sidHistory.Select(ssid => ReadSidPrincipal(entry, ssid)).ToArray();
+
+ userProps.Props = props;
+
+ return userProps;
+ }
+
+ ///
+ /// Reads specific LDAP properties related to Computers
+ ///
+ ///
+ ///
+ public async Task ReadComputerProperties(ISearchResultEntry entry)
+ {
+ var compProps = new ComputerProperties();
+ var props = GetCommonProps(entry);
- props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated));
- props.Add("dontreqpreauth", uacFlags.HasFlag(UacFlags.DontReqPreauth));
- props.Add("passwordnotreqd", uacFlags.HasFlag(UacFlags.PasswordNotRequired));
- props.Add("unconstraineddelegation", uacFlags.HasFlag(UacFlags.TrustedForDelegation));
- props.Add("pwdneverexpires", uacFlags.HasFlag(UacFlags.DontExpirePassword));
- props.Add("enabled", !uacFlags.HasFlag(UacFlags.AccountDisable));
- props.Add("trustedtoauth", uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation));
+ props.AddMany(GetProperties(LDAPProperties.UserAccountControl, entry));
- var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName);
+ props.AddMany((GetProperties(LDAPProperties.AllowedToDelegateTo, entry)));
+ compProps.AllowedToDelegate = (await ReadPropertyDelegates(entry)).ToArray();
+
+ compProps.AllowedToAct = ReadAllowedToActPrincipals(entry).ToArray();
+
+ props.AddMany(GetProperties(LDAPProperties.LastLogon, entry));
+ props.AddMany(GetProperties(LDAPProperties.LastLogonTimestamp, entry));
+ props.AddMany(GetProperties(LDAPProperties.PasswordLastSet, entry));
+ props.AddMany(GetProperties(LDAPProperties.ServicePrincipalNames, entry));
+ props.AddMany(GetProperties(LDAPProperties.Email, entry));
+ props.AddMany(GetProperties(LDAPProperties.OperatingSystem, entry));
+
+ var sidHistory = ReadSidHistory(entry);
+ props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.ToArray());
+ compProps.SidHistory = sidHistory.Select(ssid => ReadSidPrincipal(entry, ssid)).ToArray();
+
+ compProps.DumpSMSAPassword = ReadSmsaPrincipals(entry).ToArray();
+
+ compProps.Props = props;
+ return compProps;
+ }
+
+ ///
+ /// Reads principals user or computer may delegate.
+ ///
+ ///
+ ///
+ public async Task> ReadPropertyDelegates(ISearchResultEntry entry)
+ {
var comps = new List();
- if (uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation))
+
+ var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName);
+ var uacFlags = GetUacFlags(entry);
+ if (uacFlags[UacFlags.TrustedToAuthForDelegation])
{
var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo);
- props.Add("allowedtodelegate", delegates);
foreach (var d in delegates)
{
@@ -187,42 +200,19 @@ public async Task ReadUserProperties(ISearchResultEntry entry)
}
}
- userProps.AllowedToDelegate = comps.Distinct().ToArray();
-
- props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon)));
- props.Add("lastlogontimestamp",
- Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp)));
- props.Add("pwdlastset",
- Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet)));
- var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames);
- props.Add("serviceprincipalnames", spn);
- props.Add("hasspn", spn.Length > 0);
- props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName));
- props.Add("email", entry.GetProperty(LDAPProperties.Email));
- props.Add("title", entry.GetProperty(LDAPProperties.Title));
- props.Add("homedirectory", entry.GetProperty(LDAPProperties.HomeDirectory));
- props.Add("userpassword", entry.GetProperty(LDAPProperties.UserPassword));
- props.Add("unixpassword", entry.GetProperty(LDAPProperties.UnixUserPassword));
- props.Add("unicodepassword", entry.GetProperty(LDAPProperties.UnicodePassword));
- props.Add("sfupassword", entry.GetProperty(LDAPProperties.MsSFU30Password));
- props.Add("logonscript", entry.GetProperty(LDAPProperties.ScriptPath));
-
- var ac = entry.GetProperty(LDAPProperties.AdminCount);
- if (ac != null)
- {
- if (int.TryParse(ac, out var parsed))
- props.Add("admincount", parsed != 0);
- else
- props.Add("admincount", false);
- }
- else
- {
- props.Add("admincount", false);
- }
+ return comps.Distinct().ToList();
+ }
+ ///
+ /// Reads history of SIDs for domain object.
+ ///
+ ///
+ ///
+ public List ReadSidHistory(ISearchResultEntry entry)
+ {
+ var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName);
var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory);
var sidHistoryList = new List();
- var sidHistoryPrincipals = new List();
foreach (var sid in sh)
{
string sSid;
@@ -236,68 +226,33 @@ public async Task ReadUserProperties(ISearchResultEntry entry)
}
sidHistoryList.Add(sSid);
-
- var res = _utils.ResolveIDAndType(sSid, domain);
-
- sidHistoryPrincipals.Add(res);
}
- userProps.SidHistory = sidHistoryPrincipals.Distinct().ToArray();
-
- props.Add("sidhistory", sidHistoryList.ToArray());
-
- userProps.Props = props;
-
- return userProps;
+ return sidHistoryList;
}
///
- /// Reads specific LDAP properties related to Computers
+ /// Get SID principal.
///
///
+ ///
///
- public async Task ReadComputerProperties(ISearchResultEntry entry)
+ public TypedPrincipal ReadSidPrincipal(ISearchResultEntry entry, string sidHistoryItem)
{
- var compProps = new ComputerProperties();
- var props = GetCommonProps(entry);
-
- var flags = (UacFlags)0;
- var uac = entry.GetProperty(LDAPProperties.UserAccountControl);
- if (int.TryParse(uac, out var flag))
- {
- flags = (UacFlags)flag;
- }
-
- props.Add("enabled", !flags.HasFlag(UacFlags.AccountDisable));
- props.Add("unconstraineddelegation", flags.HasFlag(UacFlags.TrustedForDelegation));
- props.Add("trustedtoauth", flags.HasFlag(UacFlags.TrustedToAuthForDelegation));
- props.Add("isdc", flags.HasFlag(UacFlags.ServerTrustAccount));
-
var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName);
+ return _utils.ResolveIDAndType(sidHistoryItem, domain);
+ }
- var comps = new List();
- if (flags.HasFlag(UacFlags.TrustedToAuthForDelegation))
- {
- var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo);
- props.Add("allowedtodelegate", delegates);
-
- foreach (var d in delegates)
- {
- var hname = d.Contains("/") ? d.Split('/')[1] : d;
- hname = hname.Split(':')[0];
- var resolvedHost = await _utils.ResolveHostToSid(hname, domain);
- if (resolvedHost != null && (resolvedHost.Contains(".") || resolvedHost.Contains("S-1")))
- comps.Add(new TypedPrincipal
- {
- ObjectIdentifier = resolvedHost,
- ObjectType = Label.Computer
- });
- }
- }
-
- compProps.AllowedToDelegate = comps.Distinct().ToArray();
-
+ ///
+ /// Read principals for identities domain object may act on behalf of.
+ ///
+ ///
+ ///
+ public List ReadAllowedToActPrincipals(ISearchResultEntry entry)
+ {
var allowedToActPrincipals = new List();
+
+ var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName);
var rawAllowedToAct = entry.GetByteProperty(LDAPProperties.AllowedToActOnBehalfOfOtherIdentity);
if (rawAllowedToAct != null)
{
@@ -310,50 +265,18 @@ public async Task ReadComputerProperties(ISearchResultEntry
}
}
- compProps.AllowedToAct = allowedToActPrincipals.ToArray();
-
- props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon)));
- props.Add("lastlogontimestamp",
- Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp)));
- props.Add("pwdlastset",
- Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet)));
- props.Add("serviceprincipalnames", entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames));
- props.Add("email", entry.GetProperty(LDAPProperties.Email));
- var os = entry.GetProperty(LDAPProperties.OperatingSystem);
- var sp = entry.GetProperty(LDAPProperties.ServicePack);
-
- if (sp != null) os = $"{os} {sp}";
-
- props.Add("operatingsystem", os);
-
- var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory);
- var sidHistoryList = new List();
- var sidHistoryPrincipals = new List();
- foreach (var sid in sh)
- {
- string sSid;
- try
- {
- sSid = new SecurityIdentifier(sid, 0).Value;
- }
- catch
- {
- continue;
- }
-
- sidHistoryList.Add(sSid);
-
- var res = _utils.ResolveIDAndType(sSid, domain);
-
- sidHistoryPrincipals.Add(res);
- }
-
- compProps.SidHistory = sidHistoryPrincipals.ToArray();
-
- props.Add("sidhistory", sidHistoryList.ToArray());
+ return allowedToActPrincipals;
+ }
- var hsa = entry.GetArrayProperty(LDAPProperties.HostServiceAccount);
+ ///
+ /// Read Standalone Managed Service Accounts of domain object.
+ ///
+ ///
+ ///
+ public List ReadSmsaPrincipals(ISearchResultEntry entry)
+ {
var smsaPrincipals = new List();
+ var hsa = entry.GetArrayProperty(LDAPProperties.HostServiceAccount);
if (hsa != null)
{
foreach (var dn in hsa)
@@ -365,11 +288,7 @@ public async Task ReadComputerProperties(ISearchResultEntry
}
}
- compProps.DumpSMSAPassword = smsaPrincipals.ToArray();
-
- compProps.Props = props;
-
- return compProps;
+ return smsaPrincipals;
}
///
@@ -583,6 +502,167 @@ public Dictionary ParseAllProperties(ISearchResultEntry entry)
return props;
}
+
+ public static Dictionary GetProperties(string ldapProperty, ISearchResultEntry entry)
+ {
+ var props = new Dictionary();
+ switch (ldapProperty)
+ {
+ case LDAPProperties.Description:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Description));
+ break;
+ case LDAPProperties.WhenCreated:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated)));
+ break;
+ case LDAPProperties.DomainFunctionalLevel:
+ if (!int.TryParse(entry.GetProperty(LDAPProperties.DomainFunctionalLevel), out var level))
+ level = -1;
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), FunctionalLevelToString(level));
+ break;
+ case LDAPProperties.GPCFileSYSPath:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper());
+ break;
+ case LDAPProperties.LastLogon:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon)));
+ break;
+ case LDAPProperties.LastLogonTimestamp:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp)));
+ break;
+ case LDAPProperties.PasswordLastSet:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet)));
+ break;
+ case LDAPProperties.ServicePrincipalNames:
+ var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames);
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), spn);
+ props.Add("hasspn", spn.Length > 0);
+ break;
+ case LDAPProperties.DisplayName:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.DisplayName));
+ break;
+ case LDAPProperties.Email:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Email));
+ break;
+ case LDAPProperties.Title:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Title));
+ break;
+ case LDAPProperties.HomeDirectory:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.HomeDirectory));
+ break;
+ case LDAPProperties.UserPassword:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UserPassword));
+ break;
+ case LDAPProperties.UnixUserPassword:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UnixUserPassword));
+ break;
+ case LDAPProperties.UnicodePassword:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UnicodePassword));
+ break;
+ case LDAPProperties.MsSFU30Password:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.MsSFU30Password));
+ break;
+ case LDAPProperties.ScriptPath:
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.ScriptPath));
+ break;
+ case LDAPProperties.AdminCount:
+ var ac = entry.GetProperty(LDAPProperties.AdminCount);
+ if (ac != null)
+ {
+ int.TryParse(ac, out var a);
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), a != 0);
+ }
+ else
+ {
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), false);
+ }
+ break;
+ case LDAPProperties.UserAccountControl:
+ var allFlags = GetUacFlags(entry);
+ props.Add(PropertyMap.GetUacPropertyName(UacFlags.NotDelegated), allFlags[UacFlags.NotDelegated]);
+ props.Add(PropertyMap.GetUacPropertyName(UacFlags.DontReqPreauth), allFlags[UacFlags.DontReqPreauth]);
+ props.Add(PropertyMap.GetUacPropertyName(UacFlags.PasswordNotRequired), allFlags[UacFlags.PasswordNotRequired]);
+ props.Add(PropertyMap.GetUacPropertyName(UacFlags.TrustedForDelegation), allFlags[UacFlags.TrustedForDelegation]);
+ props.Add(PropertyMap.GetUacPropertyName(UacFlags.DontExpirePassword), allFlags[UacFlags.DontExpirePassword]);
+ // Note that we flip the flag for Account Disable ("enabled" by resolved name)
+ props.Add(PropertyMap.GetUacPropertyName(UacFlags.AccountDisable), !allFlags[UacFlags.AccountDisable]);
+ props.Add(PropertyMap.GetUacPropertyName(UacFlags.TrustedToAuthForDelegation), allFlags[UacFlags.TrustedToAuthForDelegation]);
+ props.Add(PropertyMap.GetUacPropertyName(UacFlags.ServerTrustAccount), allFlags[UacFlags.ServerTrustAccount]);
+ break;
+ case LDAPProperties.OperatingSystem:
+ var os = entry.GetProperty(LDAPProperties.OperatingSystem);
+ var sp = entry.GetProperty(LDAPProperties.ServicePack);
+ if (sp != null) os = $"{os} {sp}";
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), os);
+ break;
+ case LDAPProperties.AllowedToDelegateTo:
+ var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo);
+ props.Add(PropertyMap.GetPropertyName(ldapProperty), delegates);
+ break;
+
+ default:
+ throw new ArgumentException("Cannot resolve to output property name.", ldapProperty);
+ }
+
+ return props;
+ }
+
+ ///
+ /// Converts a numeric representation of a functional level to its appropriate functional level string
+ ///
+ ///
+ ///
+ public static string FunctionalLevelToString(int level)
+ {
+ var functionalLevel = level switch
+ {
+ 0 => "2000 Mixed/Native",
+ 1 => "2003 Interim",
+ 2 => "2003",
+ 3 => "2008",
+ 4 => "2008 R2",
+ 5 => "2012",
+ 6 => "2012 R2",
+ 7 => "2016",
+ _ => "Unknown"
+ };
+
+ return functionalLevel;
+ }
+
+ ///
+ /// Returns all flags of User Account Control and whether or not they're active.
+ ///
+ ///
+ ///
+ public static Dictionary GetUacFlags(ISearchResultEntry entry)
+ {
+ var props = new Dictionary();
+
+ var uacFlags = (UacFlags)0;
+ var uac = entry.GetProperty(LDAPProperties.UserAccountControl);
+ if (int.TryParse(uac, out var flags))
+ {
+ uacFlags = (UacFlags)flags;
+ }
+
+ return ReadFlags(uacFlags);
+ }
+
+ ///
+ /// Get all flags of a domain object by enumeration.
+ ///
+ ///
+ ///
+ ///
+ private static Dictionary ReadFlags(T flags)
+ where T : Enum
+ {
+ return Enum.GetValues(typeof(T))
+ .Cast()
+ .ToDictionary(
+ val => val,
+ val => flags.HasFlag(val)
+ );
+ }
///
/// Parse CertTemplate attribute msPKI-RA-Application-Policies
@@ -726,6 +806,97 @@ private enum IsTextUnicodeFlags
}
}
+ ///
+ /// Provides single-truth mapping of domain object properties.
+ ///
+ public static class PropertyMap
+ {
+ ///
+ /// Get output name of a domain object property.
+ ///
+ ///
+ ///
+ ///
+ public static string GetPropertyName(string ldapProperty)
+ {
+ switch (ldapProperty)
+ {
+ case LDAPProperties.Description:
+ return "description";
+ case LDAPProperties.WhenCreated:
+ return "whencreated";
+ case LDAPProperties.DomainFunctionalLevel:
+ return "functionallevel";
+ case LDAPProperties.GPCFileSYSPath:
+ return "gpcpath";
+ case LDAPProperties.LastLogon:
+ return "lastlogon";
+ case LDAPProperties.LastLogonTimestamp:
+ return "lastlogontimestamp";
+ case LDAPProperties.PasswordLastSet:
+ return "pwdlastset";
+ case LDAPProperties.ServicePrincipalNames:
+ return "serviceprincipalnames";
+ case LDAPProperties.DisplayName:
+ return "displayname";
+ case LDAPProperties.Email:
+ return "email";
+ case LDAPProperties.Title:
+ return "title";
+ case LDAPProperties.HomeDirectory:
+ return "homedirectory";
+ case LDAPProperties.UserPassword:
+ return "userpassword";
+ case LDAPProperties.UnixUserPassword:
+ return "unixpassword";
+ case LDAPProperties.UnicodePassword:
+ return "unicodepassword";
+ case LDAPProperties.MsSFU30Password:
+ return "sfupassword";
+ case LDAPProperties.ScriptPath:
+ return "logonscript";
+ case LDAPProperties.AdminCount:
+ return "admincount";
+ case LDAPProperties.OperatingSystem:
+ return "operatingsystem";
+ case LDAPProperties.AllowedToDelegateTo:
+ return "allowedtodelegate";
+ case LDAPProperties.SIDHistory:
+ return "sidhistory";
+
+ default:
+ throw new ArgumentException("Cannot resolve to output property name.", ldapProperty);
+ }
+ }
+
+ public static string GetUacPropertyName(UacFlags flag)
+ {
+ switch (flag)
+ {
+ case UacFlags.NotDelegated:
+ return "sensitive";
+ case UacFlags.DontReqPreauth:
+ return "dontreqpreauth";
+ case UacFlags.PasswordNotRequired:
+ return "passwordnotreqd";
+ case UacFlags.TrustedForDelegation:
+ return "unconstraineddelegation";
+ case UacFlags.DontExpirePassword:
+ return "pwdneverexpires";
+ // Note that we flip the flag for output
+ case UacFlags.AccountDisable:
+ return "enabled";
+ case UacFlags.TrustedToAuthForDelegation:
+ return "trustedtoauth";
+ case UacFlags.ServerTrustAccount:
+ return "isdc";
+
+ default:
+ throw new ArgumentException("Cannot resolve to output property name.", Enum.GetName(typeof(UacFlags), flag));
+ }
+ }
+ }
+
public class ParsedCertificate
{
public string Thumbprint { get; set; }
diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs
index 5da83d68..2f6fbb4a 100644
--- a/test/unit/LDAPPropertyTests.cs
+++ b/test/unit/LDAPPropertyTests.cs
@@ -1,7 +1,15 @@
using System;
using System.Collections.Generic;
+using System.DirectoryServices;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Security.AccessControl;
+using System.Security.Principal;
using System.Threading.Tasks;
using CommonLibTest.Facades;
+using FluentAssertions.Specialized;
+using Moq;
+using SharpHoundCommonLib;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.OutputTypes;
using SharpHoundCommonLib.Processors;
@@ -845,5 +853,305 @@ public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NotBadPa
Assert.Single(keys);
}
+ [Fact]
+ public async Task LDAPPropertyProcessor_ReadPropertyDelegates_ReturnsPoplatedList()
+ {
+ var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal",
+ new Dictionary
+ {
+ {"useraccountcontrol", 0x1000000.ToString()},
+ {
+ "msds-allowedtodelegateto", new[]
+ {
+ "ldap/PRIMARY.testlab.local/testlab.local",
+ "ldap/PRIMARY.testlab.local",
+ "ldap/PRIMARY",
+ "ldap/WIN10"
+ }
+ }
+ }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer);
+
+ var utils = new MockLDAPUtils();
+ var processor = new LDAPPropertyProcessor(utils);
+ var props = await processor.ReadPropertyDelegates(mock);
+ var delegates = props.Select(d => d.ObjectIdentifier);
+
+ foreach (var principal in mock.GetArrayProperty("msds-allowedtodelegateto"))
+ {
+ var host = await utils.ResolveHostToSid(principal, mock.GetProperty(LDAPProperties.DistinguishedName));
+ Assert.Single(delegates, host);
+ }
+ }
+
+ [WindowsOnlyFact]
+ public void LDAPPropertyProcessor_ReadSidHistory_ReturnsPopulatedList()
+ {
+ var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal",
+ new Dictionary
+ {
+ {
+ "sidhistory", new[]
+ {
+ Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==")
+ }
+ },
+ }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer);
+
+ var processor = new LDAPPropertyProcessor(new MockLDAPUtils());
+ var sids = processor.ReadSidHistory(mock);
+
+ Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sids);
+ Assert.Single(sids);
+ }
+
+ [Fact]
+ public void LDAPPropertyProcessor_ReadSidPrincipal_GetPrincipal()
+ {
+ var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal",
+ new Dictionary(), "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer);
+
+ var sid = "S-1-5-21-3130019616-2776909439-2417379446-1105";
+ var processor = new LDAPPropertyProcessor(new MockLDAPUtils());
+ var principal = processor.ReadSidPrincipal(mock, sid);
+
+ Assert.Equal(new TypedPrincipal
+ {
+ ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105",
+ ObjectType = Label.User
+ }, principal);
+ }
+
+ [Fact]
+ public void LDAPPropertyProcessor_ReadSmsaPrincipals_ReturnsPopulatedList()
+ {
+ var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal",
+ new Dictionary
+ {
+ {
+ "msds-hostserviceaccount", new[]
+ {
+ "CN=dfm,CN=Users,DC=testlab,DC=local",
+ "CN=krbtgt,CN=Users,DC=testlab,DC=local"
+ }
+ }
+ }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer);
+
+ var processor = new LDAPPropertyProcessor(new MockLDAPUtils());
+ var smsaPrincipals = processor.ReadSmsaPrincipals(mock);
+ var sids = smsaPrincipals.Select(p => p.ObjectIdentifier);
+
+ Assert.Single(sids, "S-1-5-21-3130019616-2776909439-2417379446-1105");
+ Assert.Single(sids, "S-1-5-21-3130019616-2776909439-2417379446-502");
+ }
+
+ public static IEnumerable