Several times I've heard the following question asked:
I have an ASP.NET application and I need access to network resources that my account running ASP.NET does not have. How can I change the permissions at runtime without setting up Impersonation or using a high privilige account for my ASP.NET user account?
In short, how can you raise permissions of an ASP.NET request at runtime to perform some task that requires rights that the standard account ASP.NET runs under cannot handle?
To accomplish this you can use various system API calls (LogonUser, ImpersonateLoggedOnUser and RevertToSelf) to change the currently active account ASP.NET runs under. This would be Environment.UserName as opposed to Page.User. Environment.UserName returns the threads currently logged on user. Page.User returns the name that ASP.NET verifies through Authentication and this user in most cases is independent of the Windows logon that is running the current thread. For anonymous requests Page.User is blank, while Environment.User will be NETWORK SERVICE (or ASPNET on IIS5).
The only time when Page.User reflects Environment.User is when Impersonation is enabled in which case the ASP.NET automatically changes the impersonation on the underlying ASP.NET thread to match of who's logged on. For anonymous users this will be the IUSR_ account or if logged on the user who logged on.
With the API calls mentioned above you can change the thread's security environment. The idea is, you can log on as a user, change the impersonation to that environment, do your thing then revert back. Let's look at how to do this first before going over some caveats. Given that you have rights to use PInvoke calls to make these API calls (NETWORK SERVICE generally does have these rights) the following code can be used to accomplish this:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Runtime.InteropServices;
using System.Text;
namespace Westwind.WebStore.Demos
{
public class Test : System.Web.UI.Page
{
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NETWORK = 3;
const int LOGON32_LOGON_BATCH = 4;
const int LOGON32_LOGON_SERVICE = 5;
const int LOGON32_LOGON_UNLOCK = 7;
const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
const int LOGON32_PROVIDER_DEFAULT = 0;
[DllImport("advapi32.dll", SetLastError=true)]
public static extern int LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern int ImpersonateLoggedOnUser(
IntPtr hToken
);
[DllImport("advapi32.dll", SetLastError=true)]
static extern int RevertToSelf();
[DllImport("kernel32.dll", SetLastError=true)]
static extern int CloseHandle(IntPtr hObject);
private void Page_Load(object sender, System.EventArgs e)
{
Response.Write( Environment.UserName + "<hr>");
IntPtr lnToken;
int TResult = LogonUser("ricks",".","supersecret",
LOGON32_LOGON_NETWORK,LOGON32_PROVIDER_DEFAULT,
out lnToken);
if ( TResult > 0 )
{
ImpersonateLoggedOnUser(lnToken);
StringBuilder sb = new StringBuilder(80,80);
uint Size = 79;
Response.Write( Environment.UserName + " - " +
this.User.Identity.Name + "<hr>");
RevertToSelf();
Response.Write("<hr>" + Environment.UserName);
CloseHandle(lnToken);
}
else
{
Response.Write("Not logged on: " + Environment.UserName);
}
return;
}
}
To use this code change the username and password in the call to LogonUser to a valid local or domain account (see MSDN Docs for exact syntax to use for domain accounts and machine names).
When you run this code you should see:
NETWORK SERVICE
ricks
NETWORK SERVICE
which corresponds to the original account the page is running under, the Impersonation that I applied, and then after I reverted back to the original account.
Note that you should make sure to close the Token handle returned after you are done with your request, preferrably in a Finally section so you don't leak handles.
Actually you don't have to revert back - ASP.NET assigns security to the ASP.NET thread before your request starts, so Revert is not really required.
Caveats
Now is this a good idea to do this in your code? Not really, because it's obviously a potential security risk. There are two things that are a problem here.
First you need to run under an account that has rights to make PInvoke calls which is by no means guaranteed. Many multi-hosted ISPs will set up very low right accounts for their sites so that you may not be able to actually perform these tasks. OTOH, it's unlikely that in an ISP scenario you would actually need access to additional resources that require impersonation.
Second and maybe more importantly using LogonUser requires that you use a password and that password has to come from somewhere. This means somewhere in your system you have to store this password and retrieve that password which can be compromised and then potentially be used to further penetrate security. A fairly far fetched hack scenario that would require somebody pretty damn familiar with your architecture, but still a threat.
You can also impersonation system accounts like SYSTEM and NETWORK SERVICE which don't require passwords (pass "" for the password), but most likely these accounts are not what you need to get your job done - for example access another machine on the network.
Alternatives
Ultimately the solution to this problem is to set up your ASP.NET application with the right account with the exact rights it needs. While I think security is important it's always been my feeling that if someone can penetrate your network and compromise ASP.NET pages - they're in too far already to not be able to do the rest (like changing web.config and stepping down security), so I am usually not opposed to running ASP.NET in slightly elevated security modes to match my needs.
Another option for those one off requests is to use Impersonation and Windows Security in a separate directory. I do this in several of my applications where several admin requests require elevated rights. These pages sit off in a seperate directory with anonymous access off and Impersonation enabled in a separate web.config (or a Location section in the main web.config).
An example of this is my Configuration class manager which has the capability to write Config changes back into Web.Config. I don't want to give NETWORK SERVICE rights to write any files in my main application directory, so I have my Configuration page off in an Admin directory. Admin requires Windows Security and then uses Impersonation, which means it runs under my Admin account on the server once I've logged in with my username and password.
<location path="admin">
<system.web>
<identity impersonate="true" />
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
I can set this up in a couple of ways. Here I do it all through ASP.NET's impersonation and security settings which deny access to non-authenticated users to these Admin pages. I could also set this up with directory security on the Admin directory and simply remove IUSR_ to achieve the same effect although I would still need the impersonate setting in web.config.
This works well as long as the requests in question can be easily isolated and users are that are accessing these requests indeed can authenticate, which is not always the case.
Use with care... and don't impersonate GrandMa - it ain't nice!
Other Posts you might also like