I have a Asp.net web site built on C# with Forms Authentication. We use an Active Directory to authenticate the users, and everything works fine. But today we realized that it's possible to login to any account by just entering the username and click Login, without supplying any password! This is only happening on the development environment running on localhost (thank god!), but I don't like it...
I've never seen this behaviour before, and would really like someone to explain how this could happen. Is this a developer feature built by Microsoft? Or did someone at my office make a backdoor without telling the rest? I will investigate this last option further, but until then - have anyone encountered this before?
Big thanks in advance!
EDIT:
This is where the authentication returns true for every username I throw at it - with a blank password. Other passwords return false.
using (var context = new PrincipalContext(ContextType.Domain))
{
result = context.ValidateCredentials(username, password);
}
PrincipalContext is the default from System.DirectoryServices.AccountManagement
After some more investigation I found this on MSDN which states:
The ValidateCredentials method binds to the server specified in the constructor. If the username and password parameters are null, the credentials specified in the constructor are validated. If no credential were specified in the constructor, and the username and password parameters are null, this method validates the default credentials for the current principal.
and together with this information in the documentation of the constructor of PrincipalContext:
public PrincipalContext(System.DirectoryServices.AccountManagement.ContextType contextType, string name):
contextType: A System.DirectoryServices.AccountManagement.ContextType enumeration value specifying the type of store for the principal context.
name: The name of the domain or server for System.DirectoryServices.AccountManagement.ContextType.Domain context types, the machine name for System.DirectoryServices.AccountManagement.ContextType.Machine context types, or the name of the server and port hosting the System.DirectoryServices.AccountManagement.ContextType.ApplicationDirectory instance. If the name is null for a System.DirectoryServices.AccountManagement.ContextType.Domain context type this context is a domain controller for the domain of the user principal under which the thread is running. If the name is null for a System.DirectoryServices.AccountManagement.ContextType.Machine context type, this is the local machine name. This parameter cannot be null for System.DirectoryServices.AccountManagement.ContextType.ApplicationDirectory context types.
This leads me to conclude that since I don't use the name property in the constructor of the PrincipalContext, the domain controller will run under my own principal when on my dev machine. This could mean that it uses my users priveliges, which of course are much higher than the machine accounts the production servers are running as. This in turn could make all calls to Validate with nullas password automatically validate due to the higher level of privelige.
At least, this is my theory... Comments and thoughts are welcome, I will be closing this question soon.
Sounds like the problem is in the code. For a user to be AD authed the password needs to match. A security tokens is generated from AD, and this can't be done without the proper password or impersination (which also requires password).
Is the code using SELECT user FROM users WHERE password LIKE '%password%'? I've seen that done before! :(
Why don't you add null validation for password before calling ValidateCredentials? On a side note, client side authentication might help as well.
Why not you try to
handle NULL Parameter Exception.
check first both credentials have values.
using (var context = new PrincipalContext(ContextType.Domain))
{
if (string.IsNullOrEmpty(UserName) && string.IsNullOrEmpty(Password))
{
throw new ArgumentNullException();
result = null; // Or redirect to Login Page
}
else
{
result = context.ValidateCredentials(username, password);
}
}
Related
I have an ASP.NET 3.5 application that I recently extended with multiple membership and role providers to "attach" a second application within this application. I do not have direct access to the IIS configuration, so I can't break this off into a separate application directory.
That said, I have successfully separated the logins; however, after I login, I am able to verify the groups the user belongs to through custom role routines, and I am capable of having identical usernames with different passwords for both "applications."
The problem that I am running into is when I create a user with an identical username to the other membership (which uses web.config roles on directories), I am able to switch URLs manually to the other application, and it picks up the username, and loads the roles for that application. Obviously, this is bad, as it allows a user to create a username of someone who has access to the other application, and cross into the other application with the roles of the other user.
How can I mitigate this? If I am limited to one application to work with, with multiple role and membership providers, and the auth cookie stores the username that is apparently transferable, is there anything I can do?
I realize the situation is not ideal, but these are the imposed limitations at the moment.
Example Authentication (upon validation):
FormsAuthentication.SetAuthCookie(usr.UserName, false);
This cookie needs to be based on the user token I suspect, rather than UserName in order to separate the two providers? Is that possible?
Have you tried specifying the applicationName attribute in your membership connection string?
https://msdn.microsoft.com/en-us/library/6e9y4s5t.aspx?f=255&MSPPError=-2147217396
Perhaps not the answer I'd prefer to go with, but I was able to separate the two by having one application use the username for the auth cookie, and the other use the ProviderUserKey (guid). This way the auth cookie would not be recognized from one "application" to the other.
FormsAuthentication.SetAuthCookie(user.ProviderUserKey.ToString(), false);
This required me to handle things a little oddly, but it simply came down to adding some extension methods, and handling a lot of membership utilities through my own class (which I was doing anyhow).
ex. Extension Method:
public static string GetUserName(this IPrincipal ip)
{
return MNMember.MNMembership.GetUser(new Guid(ip.Identity.Name), false).UserName;
}
Where MNMember is a static class, MNMembership is returning the secondary membership provider, and GetUser is the standard function of membership providers.
var validRoles = new List<string>() { "MNExpired", "MNAdmins", "MNUsers" };
var isValidRole = validRoles.Intersect(uroles).Any();
if (isValidRole)
{
var userIsAdmin = uroles.Contains("MNAdmins");
if (isAdmin && !userIsAdmin)
{
Response.Redirect("/MNLogin.aspx");
}
else if (!userIsAdmin && !uroles.Contains("MNUsers"))
{
Response.Redirect("/MNLogin.aspx");
}...
Where isAdmin is checking to see if a subdirectory shows up in the path.
Seems hacky, but also seems to work.
Edit:Now that I'm not using the username as the token, I should be able to go back to using the web.config for directory security, which means the master page hack should be able to be removed. (theoretically?)
Edit 2:Nope - asp.net uses the username auth cookie to resolve the roles specified in the web.config.
I have an MVC 4 application that allows a user to change their Active Directory password through a password reset functionality page. I have the following piece of code that sets the new password:
DirectoryEntry de = sr.GetDirectoryEntry();
de.Invoke("SetPassword", new object[] { newPassword });
de.Properties["LockOutTime"].Value = 0;
Upon trying to submit the form with the new password details I am having the following error written to the application event log:
0x80070005 (E_ACCESSDENIED))
I have set the Identity property of the Application Pool to NetworkService and had thought that this would have resolved the issue of connecting. Is there anything else further I need to ensure so that my ASPNET application can connect to the AD.
tl;dr
In our case, this started happening randomly. Turns out it's because our self-signed SSL certificate had expired. After creating a new one in IIS, the problem was resolved.
Explanation
This thread lead me to the cause.
I will briefly recap what SetPassword does here so you can see why you need it. That particular ADSI method really bundles 3 methods under the covers. It first tries to set the password over a secure SSL channel using LDAP. Next, it tries to set using Kerberos set password protocol. Finally, it uses NetUserSetInfo to attempt to set it.
The main problem is that only the first two methods will generally respect the credentials that you put on the DirectoryEntry. If you provide the proper credentials and an SSL channel for instance, the LDAP change password mechanism will work with those credentials...
If you check the NetUserSetInfo method, you will notice there is no place to put a username/password for authorization. In other words, it can only use the unmanaged thread's security context. This means that in order for it to work, it would have to impersonate the username/password combination you provided programmatically first...
LDAP over SSL is apparently the best way to go (and was the method that we had been using), and it appears (clarifications welcome) that once our self-signed SSL certificate had expired, it skipped Kerberos and fell back to NetUserSetInfo, which failed because it was not using the credentials we provided. (Or it just failed on Kerberos, as the poster said he had never seen the credentials passed in for Kerberos)
So after creating a new self-signed certificate (for COMPUTER.DOMAIN.local), the problem was resolved.
Here is the code (in case anyone's looking for it):
DirectoryEntry myDE = new DirectoryEntry(#"LDAP://OU=GroupName,DC=DOMAIN,DC=local");
myDE.Username = "administrator";
myDE.Password = "adminPassword";
DirectoryEntries myEntries = myDE.Children;
DirectoryEntry myDEUser = myEntries.Find("CN=UserName");
myDEUser.Invoke("SetPassword", new object[] { "NewPassword" });
myDEUser.Properties["LockOutTime"].Value = 0;
// the following 2 lines are free =)
myDEUser.Properties["userAccountControl"].Value = (int)myDEUser.Properties["userAccountControl"].Value | 0x10000; // don't expire password
myDEUser.Properties["userAccountControl"].Value = (int)myDEUser.Properties["userAccountControl"].Value & ~0x0002; // ensure account is enabled
myDEUser.CommitChanges();
I think this is a very fundamental question - but i am not sure how to do it.
I am trying to test an application with different user login ID's (because these users have different roles).The application uses the login information of the system user and has no login of its own. The user.identity.name is used to get the value. However I would like to override this value to test for different user logins. How can I do this?
When you set your authentication ticket, change it there. I'm assuming it's using Forms Auth (logging in as user).
FormsAuthentication.RedirectFromLoginPage("Joe",false);
If using Windows Authentication you could use impersonation.
Another alternative, if using Windows Authentication, is to modify your browser setting to prompt you for a login. Then login as the different user.
you could always mock it with something like Moq
Mock<ControllerContext> ControllerContextMock;
string UserName = "TestUser";
ControllerContextMock = new Mock<ControllerContext>();
ControllerContextMock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(UserName);
this is how I do my unit/behavior testing
per my Comment below I'm adding a wrapper around the get user Name
public string OverideName;
private string GetUserName()
{
string name;
if(OverideName != null && OverideName.Langth>0)
{
name = OverideName;
}else
{
name = User.Identity.Name;
}
return name;
}
For most older asp.net web form testing this is really the only way to test stuff
I'm wanting to do the same.
My thoughts are to create a "change identity" page (only accessible by those with admin roles in my application). They can then choose to be a different person / role for the purpose of testing the application.
Each page in my application tests to see if a session set user id and role is valid (set on first use of the application) and loads the page to show the information / functionality that that user / role is meant to see. So this "change identity" function would set the session user id and role to that of the user under test, and allow the person testing to behave as if they are the other person. Also providing a reset option to reset yourself back to you.
Simpliest solution is when user updates name - log him out, and ask for login.
its work for me
I am hosting CassiniDev 4.0 in my windows service running an MVC 3.0 configuration site for my service.
I have the web.config setup to use windows authentication. When I look at the HttpContext.User in the web site, it shows the identity that the service is running under, not the itentity of the user making the request. The User.AuthenticationType is NTLM, which is correct, BTW.
This seems pretty clearly to be a bug, but wanted to run it by the community to see if there is some configuration I am missing.
It seems like it might be a variation on this issue postedlast week:
SecurityIdentifiers in Cassini-dev's NTLM authentication
This is definitely a bug in Cassini Dev. It looks like this method is returning the wrong token: Request.GetUserToken(). The code:
public override IntPtr GetUserToken()
{
return _host.GetProcessToken();
}
And here _host.GetProcessToken() is a pointer to a security token belonging to the user who owns the Cassini process, it is not the token belonging to the user that's logged in. What needs to happen is the NtlmAuth object needs to pass the security token back to the Request object so that it can be returned when this method is called instead of the host's token. Not really sure what the best way to do this is but you can see in the NtlmAuth class, the security token is acquired here:
IntPtr phToken = IntPtr.Zero;
if (Interop.QuerySecurityContextToken(ref _securityContext, ref phToken) != 0)
{
return false;
}
phToken is the security token but it needs to get back to the Request object and not call Interop.CloseHandle(phToken); later in that method, where it frees the token. Note that CloseHandle() needs to be called on the token eventually, otherwise a new one will be issued for every request made by a logged in user but unused ones will never get freed. One possible place to do this is in the Request object, which subclasses SimpleWorkerRequest and you can override the EndOfRequest method to call CloseHandle() on the security token.
The System.Security.Principal.WindowsIdentity.Impersonate function takes a System.intptr parameter, which seems awfully useless in my situation (with my limited understanding).
I am developing an intranet application that uses integrated security to authorize users page-by-page against a role associated with their Windows Identity. I have no passwords or anything of the sort. Simply a Windows username. For testing purposes, how could I possibly impersonate a Windows user based on their username? The "impersonate" method jumped out at me as obvious, but it takes an unexpected parameter.
Thanks in advance :)
In a nutshell, no. But you are on the right track. You either have to get the user by simulating a login with LoginUserA ( C Win32 Api ) or set your IIS site to Windows Authentication.
In that case, your Page will have a property named User of type IPrincipal, which you can then use to run as that user. For example ( sorry, C# code ).
IPrincipal p = this.User;
WindowsIdentity id = (WindowsIdentity)p.Identity;
WindowsImpersonationContext wic = id.Impersonate();
try {
// do stuff as that user
}
finally {
wic.Undo();
}
Are you using anything windows-specific from the WindowsPrincipal or is it just a handy way to get auth/auth without having to manage users? If you need to be windows-based, Serapth has the right method. If you are just really using it as a convenient auth/auth store, then you should probably write your code to interface with IPrincipal. You can then inject your own implementations of IPrincipal with the desired values into either the HttpContext.User or Thread.CurrentThread.Principal depending on the nature of your tests and app.
User tokens (the value that the IntPtr in Impersonate represents) represent more than the username. They are a handle into an internal windows context that include information about the user, authentication tokens, current security rights, etc. It's because of this that you can't impersonate without a password. You have to actually "log in" to create a token via the LogonUser method.
What I've done in the past is create a special "Test" user account with a password that I can just keep in code or a config file.
You shouldn't need to impersonate to check role membership. Simply use the constructor for a WindowsIdentity that takes a UPN (userPrincipalName) constructed from the username according to your UPN conventions, then create a WindowsPrincipal from that identity and use IsInRole to check membership in a group. This works on W2K3 and a Windows 2003 domain, but not in other circumstances.
var userIdentity = new WindowsIdentity( username + "#domain" );
var principal = new WindowsPrincipal( userIdentity );
var inRole = principal.IsInRole( "roleName" );
You may also want to consider using either the standard or a custom role provider that will allow you to use the Roles interface to check membership.