LDAP vs. ADSI in manipulation of Active Directory Users - c#

I have been requested to expose a web service for managing Active Directory Users via an intranet. I have been advised that LDAP is viewed as a security vulnerability and is not to be used.
Given this constraint, I have managed to connect via ADSI with a DirectoryEntry object like this:
DirectoryEntry de = new DirectoryEntry();
de.Path = "WinNT://TheDomain.local";
de.Username = "NTUser1";
de.Password = "pwdpwdpwd2";
I can loop through the children of this DirectoryEntry get the ones that are users. On the Users, I can see these basic properties: UserFlags, MaxStorage, PasswordAge, PasswordExpired, LoginHours, FullName, Description, BadPasswordAttempts, LastLogin, HomeDirectory, LoginScript, Profile, HomeDirDrive, Parameters, PrimaryGroupID, Name, MinPasswordLength, MaxPasswordAge, MinPasswordAge, PasswordHistoryLength, AutoUnlockInterval, LockoutObservationInterval, MaxBadPasswordsAllowed, objectSid.
There are a number of User properties that are visible in the Active Directory MMC that are not accessible from the DirectoryEntry object including: LastName, NameSuffix, Department, etc...
These other properties are all documented in msdn as being exposed by IADsUser (http://msdn.microsoft.com/en-us/library/aa746340%28VS.85%29.aspx).
1) Is LDAP actually a vulnerable protocol? More so than the ADSI (WinNT) connection shown above? LDAP seems to be pretty common for this purpose.
2) How can I retrieve/set these other properties of the User?
TIA

1- LDAP packet transmission is performed as plaintext, so somebody can capture your data.
If you use LDAPS protocol or TLS-enable your LDAP connection, it is safe. ADSI is just an implementation of LDAP client by Microsoft, and it supports both LDAP and LDAPS connections.
When you use ADSI against your corporate Active Directory, it primarily tries to start a LDAPS connection.
So you are safe of you use ADSI; or you can use any other client or programming library as well if you use secure connection. the default port for LDAPS is 636.
2- To get more information about directory objects, you can use the GetInfoEx method, it loads exactly the attributes you want. Below you can see an example:
http://msdn.microsoft.com/en-us/library/aa746411%28v=vs.85%29.aspx
But some of the properties that you look for, are stored in the Active Directory by attribute names different from the MMC console. e.g. First name is stored as 'givenName' and Last name is stored as 'sn'. Look here to find names of attributes you need;
You can find more information here.

http://www.techgalaxy.net/Docs/Dev/Using_ADSI_and_LDAP_with_AD.htm explains the difference between LDAP and ADSI: http://technet.microsoft.com/en-us/library/cc755809(v=ws.10).aspx includes illustrations.
In short, ADSI is a simplified wrapper around LDAP. If there's any insecurity to it, it's in the binding, which here appears to be SIMPLE (unencrypted plaintext username and password). If you bind the LDAP connection using any other method (or over an SSL connection), it should be secure.

Related

Authenticating RPC Server with NTLM

I am currently trying to improve an RPC Server I'm responsible for, both server and client run on the same machine locally, however I would like to restrict the server so that it only allows administrator (including built in /LocalSystem account) to connect to the rpc server through a named pipe.
First of all I am using the following library as a wrapper for the RPCserverApi/RPCClientApi:
https://github.com/csharptest/CSharpTest.Net.RpcLibrary
I create the Server like so:
server = new RpcServerApi(IId, MaxCalls, ushort.MaxValue, true);
server.AddProtocol(RpcProtseq.ncacn_np, Id, MaxCalls);
// Set authentication
server.AddAuthentication(RpcAuthentication.RPC_C_AUTHN_WINNT);
However when I check the named pipes security it still shows like it's not restricted at all, and my client can still connect even though I have yet to change that to specify authentication.
In addition I can check the access to that named pipe and I get:
\\.\pipe\myNamedPipe
RW Everyone
RW NT AUTHORITY\ANONYMOUS LOGON
RW BUILTIN\Administrators
Okay, So for anyone else that ran into this problem There's a few things I needed to do which was not exposed in the library I was using. So instead I created my own wrapper.
When Registering the Rpc Interface with RpcServerRegisterIf2() I had to pass through the flag:
RPC_IF_ALLOW_SECURE_ONLY
Then In addition when setting up the protocols for the RpcServer: RpcServerUseProtseqEp() I also had to pass through an SDDL, to describe the restrictions on the end point. You can find a description of SDDL's here:
https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language
To do this I created an Ace String, then used ConvertStringSecurityDescriptorToSecurityDescriptor() to create the correct object. This then locked down the end point like:
\\.\pipe\myNamedPipe
RW BUILTIN\Administrators
But also it enforced on the server that only authenticated accounts could reach it
My issue originally reported was full of misunderstandings about RPC Servers and Named pipes, I thoroughly recommend reading and understanding the following articles, as they were very helpful to me.
https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html
https://csandker.io/2021/02/21/Offensive-Windows-IPC-2-RPC.html

Connecting or Accessing Okta LDAP Interface using c# .NET Client

I have a client which uses Okta LDAP Interface facility. We have a LDAP v3 tool which connects with AD, Open LDAP other LDAP v3 supported servers.
We want to integrate Okta LDAP Interface into our tool as it is LDAPv3 Compatible. Our Code is based on .NET framework + C Sharp.
We are facing some issues/challenges while connecting with Okta LDAP Interface.
We use System.DirectoryServices by Microsoft library provided by microsoft currently. But facing issues with LDAP Interface.
For StartTLS/389
I get the error :
Unwilling to perform. LDAP Error Code 53
More: A secure connection cannot be established. To admin: This service requires TLS. LDAP
For SSL/636
Error: The server is not operational.
Links:
https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices?view=netframework-4.8
https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices.directoryentry?view=netframework-4.8
https://ldapwiki.com/wiki/LDAP_UNWILLING_TO_PERFORM
var oktaLDAPPath = "LDAP://dev-506668.ldap.oktapreview.com:636/ou=users,dc=dev-506668,dc=oktapreview,dc=com";
var un = "uid=*******,dc=dev-506668,dc=oktapreview,dc=com";
var pass = "*******";
var filter = "((objectClass=*))";
try
{
using (var userDirectoryEntry = new DirectoryEntry(oktaLDAPPath, un, pass,AuthenticationTypes.SecureSocketsLayer))
{
using (var directorySearcher = new DirectorySearcher(userDirectoryEntry, filter) { PageSize = 100 })
{
directorySearcher.FindOne();
}
}
}
catch (DirectoryServicesCOMException dex)
{
}
catch (Exception ex)
{
}
Thanks
Update: So I did some testing for myself. I see what's going on.
If you do a DNS lookup on dev-506668.ldap.oktapreview.com, it gives you a CNAME result to op1-ldapi-fb96b0a1937080bd.elb.us-east-1.amazonaws.com.
A browser will use the IP address of the CNAME, but still make the request with the host name that you originally gave it. However, for some reason, when starting an LDAP connection, Windows is using the CNAME to iniatate the connection.
In other words, Windows is changing the request to LDAP://op1-ldapi-fb96b0a1937080bd.elb.us-east-1.amazonaws.com:636. But then it receives the SSL certificate which has the name *.ldap.oktapreview.com and it panics because that doesn't match the name it used to make the request (op1-ldapi-fb96b0a1937080bd.elb.us-east-1.amazonaws.com).
I verified all of this using Wireshark, monitoring traffic on port 636. The SSL Client Hello is using op1-ldapi-fb96b0a1937080bd.elb.us-east-1.amazonaws.com instead of dev-506668.ldap.oktapreview.com.
I don't know of a way to make it not do that. DirectoryEntry has no way to override how it verifies the SSL certificate either. LdapConnection does, which you can see here, but it might be a little harder to work with. I've never used it. (you probably should do some verification of your own and no just return true like that example does).
This might be something you can share with Okta Support anyway.
Original answer:
It sounds like your computer does not trust the SSL certificate that is used on the server. To verify this, I use Chrome. You have to start Chrome like this:
chrome.exe --explicitly-allowed-ports=636
Then you can put this in the address bar:
https://dev-506668.ldap.oktapreview.com:636
If the certificate is not trusted, you will get a big error saying so. You can click the 'Advanced' button to see the reason Chrome gives for it not being trusted. But Chrome will also let you inspect the certificate by clicking on "Not secure" in the address bar to the left of the address, then click 'Certificate'.
There a couple reasons it might not be trusted:
The fully-qualified domain name you are using (dev-506668.ldap.oktapreview.com) doesn't match what is on the certificate. If that's the case, you might be able to just change the domain name you use to match the certificate.
The certificate is not issued by a trusted authority. It could be a self-signed certificate. If this is the case, then you should see an "Install Certificate" button when you view the certificate, which you can use to explicitly trust the certificate. See here for screenshots, starting on step 3. This will only apply to the current computer.
I recently ran into this issue and was pointed to the following solution:
Add the following registry value then restart the server and see if it fixes the issue.
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\LDAP\UseHostnameAsAlias
DWORD, set the value to 1
See https://support.microsoft.com/en-us/topic/an-error-occurs-when-you-try-to-establish-ssl-connections-to-the-nodes-by-using-the-alias-name-from-an-ldaps-client-computer-that-is-running-windows-7-or-windows-server-2008-r2-49b6ee93-2c68-a892-8133-612d208dd1b1 for more details.

problem connecting to Active Directory server in C# .NET

I'm currently writing some software in C# which needs to connect to an AD server and get some user details. When I connect using the code below it works against most AD servers that I connect to but there are a couple where it fails with an error of "Logon failure: unknown user name or bad password.". The server name / credentials I'm using are definately correct as I've tested them with an LDAP Browser and the AD server is using standard security (port 389 etc). Can anyone offer any advice?
Cheers
Tim
DirectoryEntry d = new DirectoryEntry("LDAP://" + domain, admin_username, admin_password);
try
{
object x = d.NativeObject;
}
catch
{
throw;
}
I've had similar issues programming .net / AD in the past. One thing I found useful is using an LDAP viewer to see if I can connect to certain servers, etc. In this way, I can at least determine if it is a .NET error (perhaps my code), a credential error, etc.
I use the free/lite version of Softerra's LDAP viewer (http://www.ldapbrowser.com/download.htm) although I'm sure there are many others to choose from out there. If you try the one listed here, make sure to download the 'LDAP browser' and not 'LDAP Administrator'. The browser is the free one.
Try connecting to the same LDAP path you're having trouble with in code, using a LDAP browser/viewer. This will at least as step one determine if it is a .NET/code issue or not. If you can't connect via the browser, it can be helpful to play around with the connection options, such as port, domain (FQDN), etc.
Hope this might help narrow things down.
Active Directory allows at least three different logon name styles:
LDAP - i.e. LDAP DN. For example: cn=JohnS, ou=Users, dc=example, dc=com
NTLM. For example: EXAMPLE\JohnS
Kerberos principal name: For example: johns#example.com
However, you cannot login with just JohnS like you do with Windows box. It's a very common mistake.

How do I make my application Integrated Loginable?

I am always doing the following Login module:
Create a table with Username and Password
Login by checking the user table.
So how do I use integrated Login just like the application like Sql Server? I don't mean by passing a connection string, I mean it will check the credential of my application without putting any username and password.
Thanks
EDIT:
I don't mean the SQL Server integrated Login, I mean my application's integrated login, is it possible?
For example, my application will read the AD's name and compare to the current username, then I don't need the user to type in a password for my application.
I am not sure how do I do that and where to start, since if I search Integrated Login, 99.9% of the results from Google will give me the connection string, which I am not looking for that.
[This answer assumes that you're on an Active Directory domain]
Are you using active directory windows authentication? If this is the case you can use the integrated security option in the connection string.
Here's an example connection string using integrated security:
"Data Source=MyServer;Initial Catalog=db name;Integrated Security=True"
This would reduce the complexity allowing you to forget about having to ask the user for a username and password because it'll pick up the windows credentials that they've logged into the workstation with.
Edit:
If the application is a website then you'll need to set the SQL server as "trusted for delegation" in active directory (see http://msdn.microsoft.com/en-us/library/aa905162(SQL.80).aspx). This will allow the webserver to delegate the user's windows login credentials to the SQL server. If you don't do this then the SQL connection will be anonymous.
Assuming that your application will be running with Active Directory on the local network I think there are a few options, depending on how do you want to implement authorization.
If you want/need to do authorization with some custom rules/logic then the first thing you need is to determine who ran the application:
Environment.UserName in conjunction with Environment.UserDomainName will give you enough information;
Alternatively (and more secure) you have use the following code snippet:
AppDomain.CurrentDomain.SetPrincipalPolicy(
System.Security.Principal.PrincipalPolicy.WindowsPrincipal);
var identity = Thread.CurrentPrincipal.Identity;
From the identity you can extract Security Identifier which is designed to be globally unique. (But not that pretty as domain\username pair.)
After getting current user's name you can apply whatever authorization rules you want.

How should I validate a user's credentials against an ADAM instance over SSL?

Apologies in advance as I haven't had much experience with directories before.
I have an ASP.net application, and I have to validate its users against an Active Directory Application Mode instance running on Server 2k3. I was previously attempting a connection with DirectoryEntry and catching the COMException if the user's credentials (userPrincipalName & password) were wrong, but I had a number of problems when trying to bind as users who weren't a member of any ADAM groups (which is a requirement).
I recently found the System.DirectoryServices.AccountManagement library, which seems a lot more promising, but although it works on my local machine, I'm having some troubles when testing this in our testbed environment. Chances are I'm simply misunderstanding how to use these objects correctly, as I wasn't able to find any great documentation on the matter. Currently I am creating a PrincipalContext with a Windows username and password, then calling the AuthenticateCredentials with the user's userPrincipalName and password. Here's a very short exert of what I'm doing:
using (var serviceContext = new PrincipalContext(
ContextType.ApplicationDirectory,
serverAddress,
rootContainer,
ContextOptions.Negotiate | ContextOptions.SecureSocketLayer,
serviceAccountUsername,
serviceAccountPassword)) {
bool credentialsValid = serviceContext.ValidateCredentials(userID, password, ContextOptions.SecureSocketLayer | ContextOptions.SimpleBind)
}
If the user's credentials are valid, I then go on to perform other operations with that principal context. As I said, this works for both users with and without roles in my own environment, but not in our testbed environment. My old DirectoryEntry way of checking the user's credentials still works with the same configuration.
After a very long morning, I was able to figure out the problem!
The exception message I was receiving when calling ValidateCredentials was extremely vague. After installing Visual Studio 2008 in the test environment (which is on the other side of the country, mind you!), I was able to debug and retrieve the HRESULT of the error. After some very deep searching in to Google, I found some very vague comments about "SSL Warnings" being picked up as other exceptions, and that enabling "SCHANNEL logging" (which I'm very unfamiliar with!) might reveal some more insight. So, after switching that on in the registry and retrying the connection, I was presented with this:
The certificate received from the remote server does not contain the expected name. It is therefore not possible to determine whether we are connecting to the correct server. The server name we were expecting is ADAMServer. The SSL connection request has failed. The attached data contains the server certificate.
I found this rather strange, as the old method of connecting via SSL worked fine. In any case, my co-worker was able to spot the problem - the name on the SSL certificate that had been issued on the server was that of the DNS name ("adam2.net") and not the host name ("adamserver"). Although I'm told that's the norm, it just wasn't resolving the correct name when using PrincipalContext.
Long story short; re-issuing a certificate with the computer name and not the DNS name fixed the problem!

Categories