diff --git a/src/LAPS-WebUI.csproj b/src/LAPS-WebUI.csproj index a762de6..803f3e2 100644 --- a/src/LAPS-WebUI.csproj +++ b/src/LAPS-WebUI.csproj @@ -21,11 +21,11 @@ - + - + diff --git a/src/Pages/LAPS.razor b/src/Pages/LAPS.razor index 732344b..8545d78 100644 --- a/src/Pages/LAPS.razor +++ b/src/Pages/LAPS.razor @@ -54,55 +54,57 @@ - + - +
+ @if (!computer.FailedToRetrieveLapsDetails) { - - x is { Version: Enums.LAPSVersion.v1, IsCurrent: true }))> - - - x is { Version: Enums.LAPSVersion.v2, IsCurrent: true }))> - - - x is { Version: Enums.LAPSVersion.v2, IsCurrent: false }))> - - - - Account - Password - Date set - - - - @foreach (LapsInformation entry in computer.LapsInformations!.Where(x => x is { IsCurrent: false, Version: Enums.LAPSVersion.v2 })) - { - - - @entry.Account - - - @entry.Password - - - @entry.PasswordSetDate - - - } - - - - + + x is { Version: Enums.LAPSVersion.v1, IsCurrent: true }))> + + + x is { Version: Enums.LAPSVersion.v2, IsCurrent: true }))> + + + x is { Version: Enums.LAPSVersion.v2, IsCurrent: false }))> + + + + Account + Password + Date set + + + + @foreach (LapsInformation entry in computer.LapsInformations!.Where(x => x is { IsCurrent: false, Version: Enums.LAPSVersion.v2 })) + { + + + @entry.Account + + + @entry.Password + + + @entry.PasswordSetDate + + + } + + + + } else { - - No permission to retrieve LAPS Password or no LAPS Password set! - + + No permission to retrieve LAPS Password or no LAPS Password set! + } +
diff --git a/src/Services/LDAPService.cs b/src/Services/LDAPService.cs index 1b6fde2..774db98 100644 --- a/src/Services/LDAPService.cs +++ b/src/Services/LDAPService.cs @@ -78,7 +78,7 @@ public async Task ClearLapsPassword(string domainName, LdapCredential ldap { throw new Exception("Failed to get LDAP Credentials"); } - using LdapConnection? ldapConnection = await CreateBindAsync(domainName, ldapCredential.UserName, ldapCredential.Password) ?? throw new Exception("LDAP bind failed!"); + using LdapConnection ldapConnection = await CreateBindAsync(domainName, ldapCredential.UserName, ldapCredential.Password) ?? throw new Exception("LDAP bind failed!"); string attribute = version switch { @@ -99,11 +99,45 @@ public async Task ClearLapsPassword(string domainName, LdapCredential ldap return response.ResultCode == ResultCode.Success; } + + private static string EscapeLdapSearchFilter(string searchFilter) + { + StringBuilder escape = new StringBuilder(); + foreach (char current in searchFilter) + { + switch (current) + { + case '\\': + escape.Append(@"\5c"); + break; + case '*': + escape.Append(@"\2a"); + break; + case '(': + escape.Append(@"\28"); + break; + case ')': + escape.Append(@"\29"); + break; + case '\u0000': + escape.Append(@"\00"); + break; + case '/': + escape.Append(@"\2f"); + break; + default: + escape.Append(current); + break; + } + } + return escape.ToString(); + } + public async Task GetAdComputerAsync(string domainName, LdapCredential ldapCredential, string distinguishedName) { - AdComputer? adComputer = null; - Domain? domain = _domains.Value.SingleOrDefault(x => x.Name == domainName) ?? throw new Exception($"No configured domain found with name {domainName}"); + AdComputer? adComputer; + Domain domain = _domains.Value.SingleOrDefault(x => x.Name == domainName) ?? throw new Exception($"No configured domain found with name {domainName}"); if (ldapCredential is null) { @@ -117,11 +151,10 @@ public async Task ClearLapsPassword(string domainName, LdapCredential ldap } string? defaultNamingContext = domain.Ldap.SearchBase; - - string sanitizedDistinguishedName = - distinguishedName.Replace("(", "0x28").Replace(")", "0x29").Replace(@"\", "0x5c"); - LdapEntry? ldapSearchResult = (await ldapConnection.SearchAsync(defaultNamingContext, $"(&(objectCategory=computer)(distinguishedName={sanitizedDistinguishedName}))",null, LdapSearchScope.LDAP_SCOPE_SUB)).SingleOrDefault(); + string ldapFilter = $"(&(objectCategory=computer)(distinguishedName={distinguishedName}))"; + + LdapEntry? ldapSearchResult = (await ldapConnection.SearchAsync(defaultNamingContext, ldapFilter)).SingleOrDefault(); if (ldapSearchResult != null) { @@ -139,8 +172,8 @@ public async Task ClearLapsPassword(string domainName, LdapCredential ldap ComputerName = adComputer.Name, Version = LAPSVersion.v1, Account = null, - Password = ldapSearchResult.DirectoryAttributes["ms-Mcs-AdmPwd"].GetValues().First().ToString(), - PasswordExpireDate = DateTime.FromFileTimeUtc(Convert.ToInt64(ldapSearchResult.DirectoryAttributes["ms-Mcs-AdmPwdExpirationTime"].GetValues().First().ToString())).ToLocalTime(), + Password = ldapSearchResult.DirectoryAttributes["ms-Mcs-AdmPwd"].GetValues().First(), + PasswordExpireDate = DateTime.FromFileTimeUtc(Convert.ToInt64(ldapSearchResult.DirectoryAttributes["ms-Mcs-AdmPwdExpirationTime"].GetValues().First())).ToLocalTime(), IsCurrent = true, PasswordSetDate = null }; @@ -156,12 +189,12 @@ public async Task ClearLapsPassword(string domainName, LdapCredential ldap if (ldapSearchResult.DirectoryAttributes.Any(x => x.Name == fieldName) && (domain.Laps.ForceVersion == LAPSVersion.All || domain.Laps.ForceVersion == LAPSVersion.v2)) { - MsLapsPayload? msLapsPayload = null; + MsLapsPayload? msLapsPayload; string ldapValue; if (domain.Laps.EncryptionDisabled) { - ldapValue = ldapSearchResult.DirectoryAttributes["msLAPS-Password"].GetValues().First().ToString(); + ldapValue = ldapSearchResult.DirectoryAttributes["msLAPS-Password"].GetValues().First(); } else { @@ -178,7 +211,7 @@ public async Task ClearLapsPassword(string domainName, LdapCredential ldap Account = msLapsPayload.ManagedAccountName, Password = msLapsPayload.Password, WasEncrypted = !domain.Laps.EncryptionDisabled, - PasswordExpireDate = DateTime.FromFileTimeUtc(Convert.ToInt64(ldapSearchResult.DirectoryAttributes["msLAPS-PasswordExpirationTime"].GetValues().First().ToString())).ToLocalTime(), + PasswordExpireDate = DateTime.FromFileTimeUtc(Convert.ToInt64(ldapSearchResult.DirectoryAttributes["msLAPS-PasswordExpirationTime"].GetValues().First())).ToLocalTime(), IsCurrent = true, PasswordSetDate = DateTime.FromFileTimeUtc(Int64.Parse(msLapsPayload.PasswordUpdateTime!, System.Globalization.NumberStyles.HexNumber)).ToLocalTime() @@ -275,17 +308,15 @@ private static async Task DecryptLapsPayload(byte[] value, LdapCredentia public async Task> SearchAdComputersAsync(string domainName, LdapCredential ldapCredential, string query) { List result = []; - Domain? domain = _domains.Value.SingleOrDefault(x => x.Name == domainName) ?? throw new Exception($"No configured domain found with name {domainName}"); + Domain domain = _domains.Value.SingleOrDefault(x => x.Name == domainName) ?? throw new Exception($"No configured domain found with name {domainName}"); if (ldapCredential is null) { throw new Exception("Failed to get LDAP Credentials"); } - - string sanitizedQuery = query.Replace("(", "0x28").Replace(")", "0x29").Replace(@"\", "0x5c"); using LdapConnection? ldapConnection = await CreateBindAsync(domainName, ldapCredential.UserName, ldapCredential.Password); - string filter = $"(&(objectCategory=computer)(name={sanitizedQuery}{(sanitizedQuery.EndsWith('*') ? string.Empty : '*')}))"; + string filter = $"(&(objectCategory=computer)(name={query}{(query.EndsWith('*') ? string.Empty : '*')}))"; string[] propertiesToLoad = new string[] { "cn", "distinguishedName" }; string? defaultNamingContext = domain.Ldap.SearchBase; @@ -296,9 +327,9 @@ public async Task> SearchAdComputersAsync(string domainName, Ld throw new Exception("LDAP Bind failed!"); } - IList? ldapSearchResults = await ldapConnection.SearchAsync(defaultNamingContext, filter, propertiesToLoad, LdapSearchScope.LDAP_SCOPE_SUB); + IList? ldapSearchResults = await ldapConnection.SearchAsync(defaultNamingContext, filter, propertiesToLoad); - result.AddRange(ldapSearchResults.Select(o => new AdComputer(o.Dn, o.DirectoryAttributes["cn"].GetValues().First())).ToList()); + result.AddRange(ldapSearchResults.Select(o => new AdComputer(EscapeLdapSearchFilter(o.Dn), o.DirectoryAttributes["cn"].GetValues().First())).ToList()); } catch (Exception ex) {