Impersonate a user - c#

We are developing a C# .NET windows service.
Our service is running under the system account, and we are trying to impersonate the logged in user USER.
The impersonation works ok, i.e. when calling System.Security.Principal.WindowsIdentity.GetCurrent() after the impersonation we get the correct user 'USER'.
The problem is that when we try to access the user profile we do not get the expected results.
One example is accessing the registry CURRENT_USER. We get an access denied error.
When using a third party function, which we assume uses the registry in part, we get the details for the "real" (prior to impersonation) user.
Also when callingEnvironment.ExpandEnvironmentVariables("%TEMP%") we get the system profile instead of the logged-in user profile.
Is there a way to completly impersonate a different user?.
I know we can use LoadUserProfile to get a specific user profile, but this is not good for us, because we are running a third party dll that uses the current user profile.
Our impersonation code is base on this

As you have discovered, impersonation won't set up HKEY_CURRENT_USER or the environment.
This is because the impersonation token is per-thread, and HKCU and environment are per-process.
If you need to access the user's usual environment, you will need to use HKEY_USERS\SID and the impersonated user's SID e.g. HKEY_USERS\S-1-5-21-12345678-12345678-12345678-1234 for example. Call LoadUserProfile to ensure the key is loaded. (If it is supposed to be the the currently logged-on user it should be loaded already, so you should probably not do that, but check it exists and return an error if not).
You can also work out what their usual environment would be, because this is under the key "Environment" within the HKCU. You merely need to combine that with the system environment.
If the third party DLL actually requires HKCU and the environment to be set up correctly, you will need to create a process within the user's logon session to host the DLL, and send the results of whatever operation back somehow. If only the environment is required, you can create a child process and set the environment manually.
However you haven't said why you want to do this. It sounds like you have settled on this as a portion of the solution to a larger problem. If possible I'd recommend that you see if there is a way to do what you need without getting the user's environment or HKCU at all.
Why can't the DLL just run directly in the user's own session? Why is a service required at all? Can you re-architect your solution so there is a user-mode part which runs in the logon session and hosts the third-party DLL, and it communicates with the service so that the service only does what is absolutely required?

I notice that the code doesn't call LoadUserProfile, so the users profile isn't loaded.
Note in the remarks of that function, HKEY_CURRENT_USER still isn't replaced though.
I think you can solve this issue (before calling the third party DLL) by calling RegOverridePredefKey.
Note that a lot of voodoo might be involved in getting this all working right - I'd try to make sure the override happens as late as possible before the third party call, and is reverted as soon as possible afterwards (hoping that this is all a single call to the library).
As an alternative, I'd seriously try looking around for a different 3rd party offering that doesn't require all of this jumping through hoops.

Related

Add missing permissions to a specific AppPool identity to allow calling CreateProcessWithLogonW from ASP.NET

TL;DR: I need to call CreateProcessWithLogonW from my IIS-hosted ASP.NET application in order to call an external executable under a different account and delegate a job to it. How can I do this?
The reason I can't simply use Process.Start API is that the external executable has a number of COM dependencies that need to be configured. This entails making sure a number of registry entries are there and that the account has required launch and activation permissions which need to be set via DCOM config. Suffice to say this has proven quite unmanageable to do for each of the AppPool identities on every server we have.
The idea is to create a single additional interactive account on each system, configure it as necessary, and then test that the external executable can be successfully run by hand.
The problem now becomes how to start this executable from ASP.NET app and have it run under the new account (just the executable, not the whole ASP.NET app!).
Simply calling CreateProcessWithLogonW results in "Access denied" error, which I've been told is the consequence of the AppPool identity's under-privileged nature.
I've been searching the internet and I've seen a lot of answers on how to grant file-system permissions to AppPool identities. Nothing about permissions for CreateProcessWithLogonW, however, short of changing the identity to something else.
But I'm still wondering if there is a way to configure the AppPool identity for this?
I don't feel comfortable changing the identity to Local Service or Local System as has been suggested elsewhere. I'm worried it could enable unknown additional permissions that are not really needed by the ASP.NET app, which could in turn change the security implications of a rather large code base.
I admit I'm probably having a "Here be dragons!!!" type of reaction to identity changing, but I'm also honestly wondering why I couldn't simply add one additional permission to an existing identity instead?

How do I verify a COM object can be instantiated from under another user?

I have the following setup. There's a Azure web role process (WaIISHost.exe) running elevated and there's IIS7 application pool running under a local user. The local user is being created by the elevated WaIISHost.exe when it first runs. There's also an out-proc COM object registered with DCOM.
I want to be sure that the IIS process will be able to instantiate the COM object. So I need to somehow login under the local user and try to instantiate the COM object.
My current approach is to call LogonUser() (requesting interactive logon) via P/Invoke, obtain a logon token, then start a new thread and from that thread call ImpersonateLoggedOnUser() to switch to the other user and once that is done the other thread tries to instantiate the COM object.
All great, except the interactive logon is only for Administrators by default now so my approach no longer works - I get
Logon failure: the user has not been granted the requested logon type at this computer
yet if I just skip the check the IIS later instantiates the COM object just fine.
So I need to change my validation code.
One option is to try add the required privilege (interactive logon) to the user, but that's lots of P/Invoke code plus there's a chance that at come point Microsoft changes something else and I'm disallowed to adjust privileges altogether.
Is there another way to verify that a COM object can be successfully instantiated from under another user account?
This is not a universal answer, but fine for this specific scenario. One possible solution is to implement a special URL in IIS that will accept local requests only (check for HttpRequestBase.IsLocal) and try to instantiate the COM object. The code that previously tried impersonation will now send an HTTP request to that URL (on the same computer).

Application/user privileges issues

I have built an application using C# which accesses the registry and installation folder for read/write information. A normal user having limited privileges is getting an error while accessing/writing the information (in registry or installation folder).
Is there a way in which all types of users are able to run that application smoothly?
It is possible, although it's not completely straightforward.
You're gonna have to impersonate another user (who, in turn, has to have all the required privileges).
Check this question for details: Windows Impersonation from C#
There are two ways, the simple and complex. First - install the program per user rather than per computer. Second - to write a service that will operate under the privileged user and perform the necessary procedures (accessing/writing the information) for your application.

How to reliably check the windows domain id of current user on a workstation

I am using C# and .Net Framework 4.
I am looking for a foolproof method to get the login id of the currently logged in windows user that is not susceptible to impersonation or hacking. I am looking for this in the form of: DOMAINNAME\USERNAME
e.g. SOMEDOMAIN\JohnDoe
Currently the best I have is:
var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var currentLoginId = identity.Name;
Is this open to impersonation (outside of someone knowing both the username and password) and if so is there a better way of doing this?
There can be at least four different identities involved at this point:
The identity assigned to the thread
The identity assigned to the process
The account of the user who started the process
The account of the user who logged onto the PC
In your code you're getting (1). This is normally fine, and is usually the same as (2).
To retrieve (2), you could:
Call WindowsIdentity.GetCurrent to get the impersonated identity
Undo impersonation by calling the Win32 RevertToSelf function
Look at WindowsIdentity.GetCurrent to get the underlying process identity
Reset the thread identity by impersonating the identity from step (1)
(2) and (3) will be the same unless you've written unmanaged code that changes the process identity. As #Daniel points out, (3) and (4) could legitimately be different in the presence of the Windows "run as" command.
Edit: You can trust that the current WindowsIdentity is who it says it is, insofar as you can trust any given piece of data in your application.
You could spoof it by attaching a debugger to your process and faking the return value from this call, but if you're going to do that, you might as well fake the piece of code where you pass the user name to the database.
The 100% safe way to do this is to use integrated authentication/SSPI to connect to the database.
Only a partial answer:
I believe System.Security.Principal.WindowsIdentity.GetCurrent(); will return the windows user account associaed with the currently executing thread. If the thread or application was started under a different user account from the logged in user, you won't get what you're after using this.
If you can be sure that your aplication was not started using the "run-as" feature (or programatic equialent) and you're not doing anything internally with respect to the thread's identity, you can probably be sure this is the logged in user's account but I'm not 100% on this.
It may be possible to find the user account associated with the widows "session" within which the application is running by using ADSI (see System.DirectoryServices).

Path.GetTempFileName -- Directory name is invalid

Running into a problem where on certain servers we get an error that the directory name is invalid when using Path.GetTempFileName. Further investigation shows that it is trying to write a file to c:\Documents and Setting\computername\aspnet\local settings\temp (found by using Path.GetTempPath). This folder exists so I'm assuming this must be a permissions issue with respect to the asp.net account.
I've been told by some that Path.GetTempFileName should be pointing to C:\Windows\Microsoft.NET\Framework\v2.0.50727\temporaryasp.net files.
I've also been told that this problem may be due to the order in which IIS and .NET where installed on the server. I've done the typical 'aspnet_regiis -i' and checked security on the folders etc. At this point I'm stuck.
Can anyone shed some light on this?
**Update:**Turns out that providing 'IUSR_ComputerName' access to the folder does the trick. Is that the correct procedure? I don't seem to recall doing that in the past, and obviously, want to follow best practices to maintain security. This is, after all, part of a file upload process.
This is probably a combination of impersonation and a mismatch of different authentication methods occurring.
There are many pieces; I'll try to go over them one by one.
Impersonation is a technique to "temporarily" switch the user account under which a thread is running. Essentially, the thread briefly gains the same rights and access -- no more, no less -- as the account that is being impersonated. As soon as the thread is done creating the web page, it "reverts" back to the original account and gets ready for the next call. This technique is used to access resources that only the user logged into your web site has access to. Hold onto the concept for a minute.
Now, by default ASP.NET runs a web site under a local account called ASPNET. Again, by default, only the ASPNET account and members of the Administrators group can write to that folder. Your temporary folder is under that account's purview. This is the second piece of the puzzle.
Impersonation doesn't happen on its own. It needs to be turn on intentionally in your web.config.
<identity impersonate="true" />
If the setting is missing or set to false, your code will execute pure and simply under the ASPNET account mentioned above. Given your error message, I'm positive that you have impersonation=true. There is nothing wrong with that! Impersonation has advantages and disadvantages that go beyond this discussion.
There is one question left: when you use impersonation, which account gets impersonated?
Unless you specify the account in the web.config (full syntax of the identity element here), the account impersonated is the one that the IIS handed over to ASP.NET. And that depends on how the user has authenticated (or not) into the site. That is your third and final piece.
The IUSR_ComputerName account is a low-rights account created by IIS. By default, this account is the account under which a web call runs if the user could not be authenticated. That is, the user comes in as an "anonymous".
In summary, this is what is happening to you:
Your user is trying to access the web site, and IIS could not authenticate the person for some reason. Because Anonymous access is ON, (or you would not see IUSRComputerName accessing the temp folder), IIS allows the user in anyway, but as a generic user. Your ASP.NET code runs and impersonates this generic IUSR___ComputerName "guest" account; only now the code doesn't have access to the things that the ASPNET account had access to, including its own temporary folder.
Granting IUSR_ComputerName WRITE access to the folder makes your symptoms go away.
But that just the symptoms. You need to review why is the person coming as "Anonymous/Guest"?
There are two likely scenarios:
a) You intended to use IIS for authentication, but the authentication settings in IIS for some of your servers are wrong.
In that case, you need to disable Anonymous access on those servers so that the usual authentication mechanisms take place. Note that you might still need to grant to your users access to that temporary folder, or use another folder instead, one to which your users already have access.
I have worked with this scenario many times, and quite frankly it gives you less headaches to forgo the Temp folder; create a dedicated folder in the server, set the proper permissions, and set its location in web.config.
b) You didn't want to authenticate people anyway, or you wanted to use ASP.NET Forms Authentication (which uses IIS's Anonymous access to bypass checks in IIS and lets ASP.NET handle the authentication directly)
This case is a bit more complicated.
You should go to IIS and disable all forms of authentication other than "Anonymous Access". Note that you can't do that in the developer's box, because the debugger needs Integrated Authentication to be enabled. So your debugging box will behave a bit different than the real server; just be aware of that.
Then, you need to decide whether you should turn impersonation OFF, or conversely, to specify the account to impersonate in the web.config. Do the first if your web server doesn't need outside resources (like a database). Do the latter if your web site does need to run under an account that has access to a database (or some other outside resource).
You have two more alternatives to specify the account to impersonate. One, you could go to IIS and change the "anonymous" account to be one with access to the resource instead of the one IIS manages for you. The second alternative is to stash the account and password encrypted in the registry. That step is a bit complicated and also goes beyond the scope of this discussion.
Good luck!
I encountered this error while diagnosing a console app that was writing in temp files. In one of my test iterations I purged all the files/directories in temp for a 'clean-slate' run. I resolved this self inflicted issue by logging out and back in again.
Could be because IIS_WPG does not have access to a temp folder. If you think it is a permission issue, run a Procmon on asp.net worker process and check for AccessDenied errors.
I was having the same problem with one of my ASP.Net applications. I was getting Path.GetTempPath() but it was throwing an exception of:
"Could not write to file "C:\Windows\Temp\somefilename", exception: Access to the path "C:\Windows\Temp\somefilename" is denied."
I tried a few suggestions on this page, but nothing helped.
In the end, I went onto the web server (IIS server) and changed permissions on the server's "C:\Windows\Temp" directory to give the "Everyone" user full read-write permissions.
And then, finally, the exception went away, and my users could download files from the application. Phew!
You can use Path.GetTempPath() to find out which directory to which it's trying to write.

Categories