Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LdapUtils refactor #136

Closed
wants to merge 73 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
ee7d009
wip: wip
rvazarkar Jun 7, 2024
2b2ef57
DC Connection Cache Breakout (#129)
definitelynotagoblin Jun 10, 2024
78e5d9d
wip: wip
rvazarkar Jun 10, 2024
8f37393
wip: remove authtype parameter
rvazarkar Jun 11, 2024
673cacd
wip: wip
rvazarkar Jun 12, 2024
faf9501
wip: store off temp result in paged query
rvazarkar Jun 12, 2024
3fcbc9d
wip: fill out the rest of pagedquery
rvazarkar Jun 12, 2024
6d5cd65
wip: implement connection pool
rvazarkar Jun 18, 2024
284aa99
wip: add connection pool manager
rvazarkar Jun 20, 2024
3a6e165
wip: more wip stuff
rvazarkar Jun 24, 2024
71c6156
wip: more stuff
rvazarkar Jun 25, 2024
fa3bfe7
wip: more ldap utils work
rvazarkar Jun 26, 2024
6d9003f
wip: update acl processor
rvazarkar Jun 27, 2024
87ed4c4
WIP: rewiring processors to ILdapUtilsNew
definitelynotagoblin Jun 28, 2024
48e4eff
wip: add some missing functions
rvazarkar Jun 28, 2024
34dc064
Merge branch 'utils_rewrite' into anemeth/utils_rewiring
definitelynotagoblin Jun 28, 2024
8093ec9
LdapPropertyProcessor: use new distinguished name lookup
definitelynotagoblin Jun 28, 2024
e365e18
wip: processor updates
rvazarkar Jul 1, 2024
023f227
wip: more processor fixes
rvazarkar Jul 1, 2024
e57db76
wip: fix block scoping
rvazarkar Jul 1, 2024
ef2b38e
Make some corrections on UserRightsAssignmentProcessor and SearchResu…
definitelynotagoblin Jul 2, 2024
4673bbd
wip: remove some dead code, add some missing functions
rvazarkar Jul 2, 2024
e50598e
Merge remote-tracking branch 'origin/anemeth/utils_rewiring' into uti…
rvazarkar Jul 2, 2024
9e690d5
wip: get stuff building
rvazarkar Jul 2, 2024
bd077d6
wip: rename files
rvazarkar Jul 2, 2024
3e94ec3
wip: version update
rvazarkar Jul 2, 2024
ddeb003
chore: add test connection
rvazarkar Jul 2, 2024
2bf6ee4
chore: add disposable to ldap utils
rvazarkar Jul 2, 2024
9a3feaf
wip: rename connection wrapper
rvazarkar Jul 3, 2024
9c767f9
wip: more fixes identified during during testing
rvazarkar Jul 3, 2024
05498d6
wip: fix
rvazarkar Jul 3, 2024
6353acf
chore: move some code around
rvazarkar Jul 5, 2024
020af28
chore: more moving code
rvazarkar Jul 5, 2024
4bb9313
Don't let TestLdapConnection bomb out on exception
definitelynotagoblin Jul 5, 2024
9e4434b
Give up on a domain if NoLdapDataException thrown (or any exception r…
definitelynotagoblin Jul 5, 2024
12a3357
Namespace syntax corrections
definitelynotagoblin Jul 5, 2024
cda27fd
wip: comment out tests for initial build
rvazarkar Jul 8, 2024
ab6f0fd
Merge remote-tracking branch 'origin/utils_rewrite' into utils_rewrite
rvazarkar Jul 8, 2024
da873bc
wip: comment out more tests
rvazarkar Jul 8, 2024
0e9aa08
wip: more test comment
rvazarkar Jul 8, 2024
de7bcf8
wip: break more tests
rvazarkar Jul 8, 2024
ab478e9
chore: some test updates
rvazarkar Jul 9, 2024
be570f3
wip: fix ACLProcessorTest.cs
rvazarkar Jul 9, 2024
b346631
chore: fix test
rvazarkar Jul 9, 2024
8ec0cae
chore: some pragma fixes
rvazarkar Jul 9, 2024
fbc281b
chore: more mock fixes
rvazarkar Jul 9, 2024
c7f6169
wip: fix computer session tests, mark as available on any platform
rvazarkar Jul 9, 2024
9f9e96e
wip: fix container tests
rvazarkar Jul 9, 2024
2b84698
wip: fix domain trust tests
rvazarkar Jul 9, 2024
d680705
Bringing tests back online WIP
definitelynotagoblin Jul 9, 2024
bc79072
wip: fix gpolocalgroup processor tests
rvazarkar Jul 9, 2024
505b216
Merge remote-tracking branch 'origin/utils_rewrite' into utils_rewrite
rvazarkar Jul 9, 2024
b350347
wip: fix group processor tests
rvazarkar Jul 9, 2024
b5b1a9f
wip: uncomment tests
rvazarkar Jul 9, 2024
886239c
fix: resolvelabel mistake
rvazarkar Jul 9, 2024
8496778
LdapUtilsTest first pass
definitelynotagoblin Jul 9, 2024
1dcccde
wip: ldap utils tests
rvazarkar Jul 9, 2024
4a03e01
IDirectoryObject Refactor (#135)
rvazarkar Jul 22, 2024
7757f2f
chore: delete backup file
rvazarkar Jul 22, 2024
f062342
chore: supress/fix nits
rvazarkar Jul 22, 2024
5d8905c
Replace System.Linq.Async with custom implementations (#137)
rvazarkar Jul 23, 2024
4b7c739
chore: fix incorrect log
rvazarkar Jul 23, 2024
7d9a9ba
chore: drop log verbosity for reg key opening
rvazarkar Jul 23, 2024
cab3c15
chore: some logging updates
rvazarkar Jul 24, 2024
1b7652c
chore: revert bad change
rvazarkar Jul 24, 2024
42b0b42
chore: cleanups
rvazarkar Jul 25, 2024
ba5420e
feat: collector version number meta tag (#139)
JonasBK Jul 25, 2024
ec07256
chore: add some missing properties
rvazarkar Jul 25, 2024
d5e4bad
chore: visibility changes + remove old comment
rvazarkar Jul 25, 2024
f80bc17
Update WellKnownPrincipal.cs (#140)
Argentix03 Jul 26, 2024
8652c1a
chore: update Dockerfile
rvazarkar Jul 26, 2024
1e30a51
Merge remote-tracking branch 'origin/utils_rewrite' into utils_rewrite
rvazarkar Jul 26, 2024
b114fb9
chore: change version to 4.0.0
rvazarkar Jul 26, 2024
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM mono:6.12.0

# Install .NET SDK
ENV DOTNET_VERSION=5.0
ENV DOTNET_VERSION=7.0

RUN curl -sSL https://dot.net/v1/dotnet-install.sh \
| bash -s -- -Channel $DOTNET_VERSION -InstallDir /usr/share/dotnet \
Expand Down
23 changes: 23 additions & 0 deletions src/CommonLib/AsyncEnumerable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace SharpHoundCommonLib;

public static class AsyncEnumerable {
public static IAsyncEnumerable<T> Empty<T>() => EmptyAsyncEnumerable<T>.Instance;

private sealed class EmptyAsyncEnumerable<T> : IAsyncEnumerable<T> {
public static readonly EmptyAsyncEnumerable<T> Instance = new();
private readonly IAsyncEnumerator<T> _enumerator = new EmptyAsyncEnumerator<T>();
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) {
return _enumerator;
}
}

private sealed class EmptyAsyncEnumerator<T> : IAsyncEnumerator<T> {
public ValueTask DisposeAsync() => default;
public ValueTask<bool> MoveNextAsync() => new(false);
public T Current => default;
}
}
14 changes: 1 addition & 13 deletions src/CommonLib/Cache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,6 @@ internal static bool GetMachineSid(string key, out string value)
return false;
}

internal static void AddConvertedValue(string key, string value)
{
CacheInstance?.ValueToIdCache.TryAdd(key, value);
}

internal static void AddPrefixedValue(string key, string domain, string value)
{
CacheInstance?.ValueToIdCache.TryAdd(GetPrefixKey(key, domain), value);
Expand All @@ -107,14 +102,7 @@ internal static void AddGCCache(string key, string[] value)

internal static bool GetGCCache(string key, out string[] value)
{
if (CacheInstance != null) return CacheInstance.GlobalCatalogCache.TryGetValue(key, out value);
value = null;
return false;
}

internal static bool GetConvertedValue(string key, out string value)
{
if (CacheInstance != null) return CacheInstance.ValueToIdCache.TryGetValue(key, out value);
if (CacheInstance != null) return CacheInstance.GlobalCatalogCache.TryGetValue(key.ToUpper(), out value);
value = null;
return false;
}
Expand Down
136 changes: 136 additions & 0 deletions src/CommonLib/ConnectionPoolManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System;
using System.Collections.Concurrent;
using System.DirectoryServices;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SharpHoundCommonLib.Processors;

namespace SharpHoundCommonLib {
public class ConnectionPoolManager : IDisposable{
private readonly ConcurrentDictionary<string, LdapConnectionPool> _pools = new();
private readonly LdapConfig _ldapConfig;
private readonly string[] _translateNames = { "Administrator", "admin" };
private readonly ConcurrentDictionary<string, string> _resolvedIdentifiers = new(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _log;
private readonly PortScanner _portScanner;

public ConnectionPoolManager(LdapConfig config, ILogger log = null, PortScanner scanner = null) {
_ldapConfig = config;
_log = log ?? Logging.LogProvider.CreateLogger("ConnectionPoolManager");
_portScanner = scanner ?? new PortScanner();
}

public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool connectionFaulted = false) {
if (connectionWrapper == null) {
return;
}
//I don't think this is possible, but at least account for it
if (!_pools.TryGetValue(connectionWrapper.PoolIdentifier, out var pool)) {
_log.LogWarning("Could not find pool for {Identifier}", connectionWrapper.PoolIdentifier);
connectionWrapper.Connection.Dispose();
return;
}

pool.ReleaseConnection(connectionWrapper, connectionFaulted);
}

public async Task<(bool Success, string Message)> TestDomainConnection(string identifier, bool globalCatalog) {
var (success, connection, message) = await GetLdapConnection(identifier, globalCatalog);
ReleaseConnection(connection);
return (success, message);
}

public async Task<(bool Success, LdapConnectionWrapper ConnectionWrapper, string Message)> GetLdapConnection(
string identifier, bool globalCatalog) {
var resolved = ResolveIdentifier(identifier);

if (!_pools.TryGetValue(resolved, out var pool)) {
pool = new LdapConnectionPool(identifier, resolved, _ldapConfig,scanner: _portScanner);
_pools.TryAdd(resolved, pool);
}

if (globalCatalog) {
return await pool.GetGlobalCatalogConnectionAsync();
}
return await pool.GetConnectionAsync();
}

public async Task<(bool Success, LdapConnectionWrapper connectionWrapper, string Message)> GetLdapConnectionForServer(
string identifier, string server, bool globalCatalog) {
var resolved = ResolveIdentifier(identifier);

if (!_pools.TryGetValue(resolved, out var pool)) {
pool = new LdapConnectionPool(resolved, identifier, _ldapConfig,scanner: _portScanner);
_pools.TryAdd(resolved, pool);
}

return await pool.GetConnectionForSpecificServerAsync(server, globalCatalog);
}

private string ResolveIdentifier(string identifier) {
if (_resolvedIdentifiers.TryGetValue(identifier, out var resolved)) {
return resolved;
}


if (GetDomainSidFromDomainName(identifier, out var sid)) {
_log.LogDebug("Resolved identifier {Identifier} to {Resolved}", identifier, sid);
_resolvedIdentifiers.TryAdd(identifier, sid);
return sid;
}

return identifier;
}

private bool GetDomainSidFromDomainName(string domainName, out string domainSid) {
if (Cache.GetDomainSidMapping(domainName, out domainSid)) return true;

try {
var entry = new DirectoryEntry($"LDAP://{domainName}").ToDirectoryObject();
if (entry.TryGetSecurityIdentifier(out var sid)) {
Cache.AddDomainSidMapping(domainName, sid);
domainSid = sid;
return true;
}
}
catch {
//we expect this to fail sometimes
}

if (LdapUtils.GetDomain(domainName, _ldapConfig, out var domainObject))
try {
if (domainObject.GetDirectoryEntry().ToDirectoryObject().TryGetSecurityIdentifier(out domainSid)) {
Cache.AddDomainSidMapping(domainName, domainSid);
return true;
}
}
catch {
//we expect this to fail sometimes (not sure why, but better safe than sorry)
}

foreach (var name in _translateNames)
try {
var account = new NTAccount(domainName, name);
var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
domainSid = sid.AccountDomainSid.ToString();
Cache.AddDomainSidMapping(domainName, domainSid);
return true;
}
catch {
//We expect this to fail if the username doesn't exist in the domain
}

return false;
}

public void Dispose() {
foreach (var kv in _pools)
{
kv.Value.Dispose();
}

_pools.Clear();
}
}
}
188 changes: 188 additions & 0 deletions src/CommonLib/DirectoryObjects/DirectoryEntryWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text;

namespace SharpHoundCommonLib;

public class DirectoryEntryWrapper : IDirectoryObject {
private readonly DirectoryEntry _entry;

public DirectoryEntryWrapper(DirectoryEntry entry) {
_entry = entry;
}

public bool TryGetDistinguishedName(out string value) {
return TryGetProperty(LDAPProperties.DistinguishedName, out value);
}

private bool CheckCache(string propertyName) {
try {
if (!_entry.Properties.Contains(propertyName))
_entry.RefreshCache(new[] { propertyName });

return _entry.Properties.Contains(propertyName);
}
catch {
return false;
}
}

public bool TryGetProperty(string propertyName, out string value) {
value = string.Empty;
if (!CheckCache(propertyName)) {
return false;
}

var s = _entry.Properties[propertyName].Value;
value = s switch {
string st => st,
int i => i.ToString(),
_ => null
};

return value != null;
}

public bool TryGetByteProperty(string propertyName, out byte[] value) {
value = Array.Empty<byte>();
if (!CheckCache(propertyName)) {
return false;
}

var prop = _entry.Properties[propertyName].Value;
if (prop is not byte[] b) return false;
value = b;
return true;
}

public bool TryGetArrayProperty(string propertyName, out string[] value) {
value = Array.Empty<string>();
if (!CheckCache(propertyName)) {
return false;
}

var dest = new List<string>();
foreach (var val in _entry.Properties[propertyName]) {
if (val is string s) {
dest.Add(s);
}
}

value = dest.ToArray();
return true;
}

public bool TryGetByteArrayProperty(string propertyName, out byte[][] value) {
value = Array.Empty<byte[]>();
if (!CheckCache(propertyName)) {
return false;
}

var raw = _entry.Properties[propertyName].Value;
if (raw is not byte[][] b) {
return false;
}
value = b;
return true;
}

public bool TryGetIntProperty(string propertyName, out int value) {
value = 0;
if (!CheckCache(propertyName)) return false;

if (!TryGetProperty(propertyName, out var s)) {
return false;
}

return int.TryParse(s, out value);
}

public bool TryGetCertificateArrayProperty(string propertyName, out X509Certificate2[] value) {
value = Array.Empty<X509Certificate2>();
if (!TryGetByteArrayProperty(propertyName, out var bytes)) {
return false;
}

if (bytes.Length == 0) {
return true;
}

var result = new List<X509Certificate2>();

foreach (var b in bytes) {
try {
var cert = new X509Certificate2(b);
result.Add(cert);
}
catch {
//pass
}
}

value = result.ToArray();
return true;
}

public bool TryGetSecurityIdentifier(out string securityIdentifier) {
securityIdentifier = string.Empty;
if (!CheckCache(LDAPProperties.ObjectSID)) {
return false;
}

var raw = _entry.Properties[LDAPProperties.ObjectSID][0];
try {
securityIdentifier = raw switch {
byte[] b => new SecurityIdentifier(b, 0).ToString(),
string st => new SecurityIdentifier(Encoding.ASCII.GetBytes(st), 0).ToString(),
_ => default
};

return securityIdentifier != default;
}
catch {
return false;
}
}

public bool TryGetGuid(out string guid) {
guid = string.Empty;
if (!TryGetByteProperty(LDAPProperties.ObjectGUID, out var raw)) {
return false;
}

try {
guid = new Guid(raw).ToString().ToUpper();
return true;
} catch {
return false;
}
}

public string GetProperty(string propertyName) {
CheckCache(propertyName);
return _entry.Properties[propertyName].Value as string;
}

public byte[] GetByteProperty(string propertyName) {
CheckCache(propertyName);
return _entry.Properties[propertyName].Value as byte[];
}

public int PropertyCount(string propertyName) {
if (!CheckCache(propertyName)) {
return 0;
}

var prop = _entry.Properties[propertyName];
return prop.Count;

}

public IEnumerable<string> PropertyNames() {
foreach (var property in _entry.Properties.PropertyNames)
yield return property.ToString().ToLower();
}
}
Loading
Loading