Impersonation and CurrentUser Registry Access - c#

Environment: Windows XP SP3, C#, .Net 4.0
Problem:
I'm attempting to add access to an impersonated users registry hive in an impersonation class and I'm running into issues based on the type of user being impersonated (or more accurately the limitation seems to be on the impersonating user).
I was originally following an impersonation example from CodeProject which showed a call to LoadUserProfile() taking place after impersonation was started using a duplicate token generated througha call to DuplcateToken() from the original token obtained from LogonUser(). I was not able to get this example working in my environment impersonating a limited user from an administrator account (From the screen shots included in the example it appears as though it was done on a windows Vista\7 system and no details were given about the account types involved).
The call to LoadUserProfile() was throwing an error of "Access Denied". Looking at userenv.log showed the line "LoadUserProfile: failed to enable the restore privilege. error c0000022". The LoadUserProfile documentation on MSDN shows that the calling process must posses the SE_RESTORE_NAME and SE_BACKUP_NAME privileges which by default only members of the Administrators and Backup Operators groups have. (As a side note when I attempted to add these two privileges later on to the Users group i still received Access Denied but the userenv.log showed "DropClientContext: Client [number] does not have enough permission. error 5" which I couldn't find any information on)
Given that the user I was impersonating did not have these privileges I moved the call to LoadUserProfile() up to before starting the impersonation and this time it loaded without a problem and I was able to read and write to it in this test. Thinking that I had discovered my answer I created a conditional check for the type of account so that LoadUserProfile() would be called before impersonation if the current user was a member of Administrators or wait until after impersonation if the member was not a member of Administrators (in the later instance I would be relying on the impersonated user having these privileges). Unfortunately I was wrong; I had not discovered my answer. When I tested the call with the role reversed (User > Administrator) The call to LoadUserProfile() still failed again with the Access Denied error and userenv.log showed the same "LoadUserProfile: failed to enable the restore privilege. error c0000061" but with a different error number this time.
Thinking that the privileges may not be enabled by default on the tokens returned from LogonUser() and\or DuplicateToken() I added two calls to AdjustTokenPrivilege() on the current users token (taking place after impersonation) obtained from WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token.
TokenAccessLevels.AdjustPrivileges and TokenAccessLevels.Query were specified because the documentation for AdjustTokenPrivilege on MSDN specifies that they are needed on the token being adjusted (I also tried obtaining the token through a call to OpenProcessToken() using a handle retrieved from System.Diagnostics.Process.GetCurrentProcess().Handle but that failed when called from the User both inside and outside of impersonation with GetCurrentProcess() being the function that threw access denied)
AdjustTokenPrivilege() returned successfully when used with the WindowsIdentity...Token but LoadUserProfile() still resulted in Access Denied (restore privilege).
At this point I was not convinced that AdjustTokenPrivilege() was doing it's job so I set out to determine what privileges were available and what state they were in for a particular token with GetTokenInformation() which resulted in it's own little bag of fun. After learning some new things I was able to call GetTokenInformation() and print out a list of privileges and their current status but the results were somewhat inconclusive since both Restore and Backup showed an attribute of 0 before and after calling AdjustTokenPrivilege() both as the administrator and while impersonating the administrator (Strangely three other privileges changed from 2 to 1 on the token when calling AdjustTokenPrivilege() but not the ones actually being adjusted which remained at a value of 0)
I removed the call to DuplicateToken() and replaced all of the places it was being used with the token returned from LogonUser() to see if that would help though in testing the privileges on the tokens the LogonUser() and DuplicateToken() tokens were identical. When I initially wrote the impersonation class I had been using the primary token in my call to WindowsImpersonationContext.Impersonate() without any problems and figured it was worth a try.
In the code example I've provided below I am able to impersonate and access the registry of a User when run as an Administrator but not the other way around. Any help would be greatly appreciated.
Pre-Post Edit:
I've also tried using the RegOpenCurrentUser() API in place of LoadUserProfile() and had success with Administrator > self and Administrator > User impersonation but when impersonating an Administrator from either another Administrator account or a User RegOpenCurrentUser() returns a pointer to HKEY_USERS\S-1-5-18 (what ever that is) instead of the actual account hive. I'm guessing because it is not actually loaded which brings me back to square one with needing to use LoadUserProfile()
From RegOpenCurrentUser documentation (MSDN):
RegOpenCurrentUser uses the thread's token to access the appropriate key, or the default if the profile is not loaded.
Code Snippet:
// Private variables used by class
private IntPtr tokenHandle;
private PROFILEINFO pInfo;
private WindowsImpersonationContext thisUser;
private string sDomain = string.Empty;
private string sUsername = string.Empty;
private string sPassword = string.Empty;
private bool bDisposed = false;
private RegistryKey rCurrentUser = null;
private SafeRegistryHandle safeHandle = null;
//Constants used for privilege adjustment
private const string SE_RESTORE_NAME = "SeRestorePrivilege";
private const string SE_BACKUP_NAME = "SeBackupPrivilege";
private const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001;
private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
private const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004;
private const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000;
[StructLayout(LayoutKind.Sequential)]
protected struct PROFILEINFO {
public int dwSize;
public int dwFlags;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpUserName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpProfilePath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpDefaultPath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpServerName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpPolicyPath;
public IntPtr hProfile;
}
protected struct TOKEN_PRIVILEGES {
public UInt32 PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID_AND_ATTRIBUTES {
public LUID Luid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID {
public uint LowPart;
public int HighPart;
}
// Private API calls used by class
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
protected static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile);
[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 Zero, IntPtr Null1, IntPtr Null2);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Start() {
tokenHandle = IntPtr.Zero; // set the pointer to nothing
if (!LogonUser(sUsername, sDomain, sPassword, 2, 0, ref tokenHandle)) {
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
} // end if !LogonUser returned false
try { //All of this is for loading the registry and is not required for impersonation to start
LUID LuidRestore = new LUID();
LUID LuidBackup = new LUID();
if(LookupPrivilegeValue(null, SE_RESTORE_NAME, ref LuidRestore) && LookupPrivilegeValue(null, SE_BACKUP_NAME, ref LuidBackup)) {
//Create the TokenPrivileges array to pass to AdjustTokenPrivileges
LUID_AND_ATTRIBUTES[] LuidAndAttributes = new LUID_AND_ATTRIBUTES[2];
LuidAndAttributes[0].Luid = LuidRestore;
LuidAndAttributes[0].Attributes = SE_PRIVILEGE_ENABLED;
LuidAndAttributes[1].Luid = LuidBackup;
LuidAndAttributes[1].Attributes = SE_PRIVILEGE_ENABLED;
TOKEN_PRIVILEGES TokenPrivileges = new TOKEN_PRIVILEGES();
TokenPrivileges.PrivilegeCount = 2;
TokenPrivileges.Privileges = LuidAndAttributes;
IntPtr procHandle = WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token;
if(AdjustTokenPrivileges(procHandle, false, ref TokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero)) {
pInfo = new PROFILEINFO();
pInfo.dwSize = Marshal.SizeOf(pInfo);
pInfo.lpUserName = sUsername;
pInfo.dwFlags = 1;
LoadUserProfile(tokenHandle, ref pInfo); //this is not required to take place
if(pInfo.hProfile != IntPtr.Zero) {
safeHandle = new SafeRegistryHandle(pInfo.hProfile, true);
rCurrentUser = RegistryKey.FromHandle(safeHandle);
}//end if pInfo.hProfile
}//end if AdjustTokenPrivileges
}//end if LookupPrivilegeValue 1 & 2
}catch{
//We don't really care that this didn't work but we don't want to throw any errors at this point as it would stop impersonation
}//end try
WindowsIdentity thisId = new WindowsIdentity(tokenHandle);
thisUser = thisId.Impersonate();
} // end function Start

From the LoadUserProfile docs:
Starting with Windows XP Service Pack 2 (SP2) and Windows Server 2003, the caller must be an administrator or the LocalSystem account. It is not sufficient for the caller to merely impersonate the administrator or LocalSystem account.
If your process starts as a regular user you're out of luck. You could possibly launch a new process (under the admin credentials) to load the profile.

I found that the logon type set in the call to LogonUser() can be a factor. Even when running as an administrator I couldn't get past the error unless I switched from LOGON32_LOGON_INTERACTIVE to LOGON32_LOGON_BATCH. You would need to be sure the "Log on as a batch job" group policy doesn't interfere though.

Related

LogonUser returns 0 and GetLastError also returns 0

I'm attempting to logon to a computer remotely so that I can copy a local directory locally. I'm attempting to use the LogonUser function call found in the advapi32 DLL. When I call the function, it returns 0 (false) but when I call Marshal.GetLastWin32Error() that also returns 0, indicating that there was no error. The username and password I'm trying to use I know works because I use it to log on to the computer. I've used both domain and local accounts and both return the same result. Below is my attempt to logon to the computer.
[DllImport("advapi32.DLL", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLongonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.Dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("kernel32.DLL", CharSet = CharSet.Auto)]
public static extern string GetLastError();
private static void CopyDirectory(string computerName, string localPath, string remotePath)
{
WindowsImpersonationContext impersonationContext = null;
IntPtr userHandle = IntPtr.Zero;
string username = #"testing";
string password = #"testing";
string fullRemotePath = string.Format("{0}\\{1}", computerName, remotePath);
try
{
bool Logon = LogonUser(username, computerName, password, 2, 0, ref userHandle);
if (!Logon)
{
Console.WriteLine("Error Logging in");
int errorCode = Marshal.GetLastWin32Error();
Console.WriteLine(string.Format("Error Code: {0}", errorCode));
Console.WriteLine(new Win32Exception(errorCode).Message);
}
else
{
WindowsIdentity user = new WindowsIdentity(userHandle);
impersonationContext = user.Impersonate();
System.IO.File.Copy(localPath, fullRemotePath, true);
}
}
catch (Exception ex)
{
int errorCode = Marshal.GetLastWin32Error();
Console.WriteLine(string.Format("Error Code: {0}", errorCode));
Console.WriteLine(ex.Message);
}
finally
{
if(impersonationContext != null)
impersonationContext.Undo();
if(userHandle != IntPtr.Zero)
CloseHandle(userHandle);
}
}
Could you please assist me in finding my flaw?
EDIT:
I found the following comment here
Even if an API supports SetLastError/GetLastError, the value returned by GetLastError is only meaningful if the API you just called actually fails. How that failure is indicated depends on the API. Except for a couple of braindead API/situation combinations, like Impersonate* with the new SeImpersonatePrivilege: the API can return a success code even if it fails due to lack of privilege, leaving you with only an identification token. Then you have to call GetLastError () to find out why the nominally-successful API call failed....
I have a feeling this is what is causing me problems. How do I get around this?
So i was over analyzing my problem and solution. Apparently i may not need to use UserLogon as This answer works in my test environment. Lets see if this works when I use it.
Edit:
Results
This all worked for me just had to create a user on my server with same credentials (username/password) and could see the other computers on the workgroup
I ended up solving this problem by using windows login credentials. so if "username/password" is your windows login then just use the same "username/password" in the "LogonUser" method. Hope it helps.

Is it possible to determine the file name of a certificate's private key on a remote Windows server?

I'm trying to determine the file name of a certificate's private key stored on a remote Windows (2k3/2k8) machine and am having some difficulty. I'm also not that familiar with Microsoft's CryptAPI, so I'm looking for any help you can provide.
The purpose of this exercise is to find certificates with private keys installed on a remote computer that meet a specific criteria and ensure the correct rights are assigned to their private key files. Although I could assign rights at the folder level, I'd prefer to only assign rights at the private key file level where it's necessary (for obvious reasons).
Here's the scenario, assume a service account with administrative-like permissions is accessing the certificate store:
I retreive the remote certificate store using the following call from C# using p/invoke:
[DllImport("CRYPT32", EntryPoint = "CertOpenStore", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CertOpenStore(int storeProvider, int encodingType, int hcryptProv, int flags, string pvPara);
IntPtr storeHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM,
0,
0,
CERT_SYSTEM_STORE_LOCAL_MACHINE,
string.Format(#"\{0}{1}", serverName, name));
I then use CertEnumCertificatesInStore to retreive certificates that I want to evaluate.
[DllImport("CRYPT32", EntryPoint = "CertEnumCertificatesInStore", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CertEnumCertificatesInStore(IntPtr storeProvider, IntPtr prevCertContext);
IntPtr certCtx = IntPtr.Zero;
certCtx = CertEnumCertificatesInStore(storeHandle, certCtx);
If a certificate matches my criteria, I create an X509Certificate2 instance from the IntPtr returned from the CertEnumCertificatesInStore call like:
X509Certificate2 current = new X509Certificate2(certCtx);
Once I have the X509Certificate2 instances for certificates I'm interested in, I call CryptAcquireCertificatePrivateKey to get the private key provider:
[DllImport("crypt32", CharSet = CharSet.Unicode, SetLastError = true)]
internal extern static bool CryptAcquireCertificatePrivateKey(IntPtr pCert, uint dwFlags, IntPtr pvReserved, ref IntPtr phCryptProv, ref int pdwKeySpec, ref bool pfCallerFreeProv);
//cert is an X509Certificate2
CryptAcquireCertificatePrivateKey(cert.Handle,
0,
IntPtr.Zero,
ref hProvider,
ref _keyNumber,
ref freeProvider);
To retreive the private key file name, I try to request the Unique Container Name from the hProvider as pData like:
[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
internal extern static bool CryptGetProvParam(IntPtr hCryptProv, CryptGetProvParamType dwParam, IntPtr pvData, ref int pcbData, uint dwFlags);
IntPtr pData = IntPtr.Zero;
CryptGetProvParam(hProvider, PP_UNIQUE_CONTAINER, pData, ref cbBytes, 0));
So far all of the above steps work great locally (servername == local machine name); however, the unique container name (private key filename) that's returned for a certificate stored in a remote computer's local machine certificate store doesn't render as the actual private key filename I'm seeing under:
w2k3: \Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys
ws08: \ProgramData\Microsoft\Crypto\RSA\MachineKeys
For example, if I run the above steps directly on the remote machine, I get a private key file name AAAAAAA-111111, but if I run them remotely, I get a private key BBBBBBBB-2222222. Also, if I install the remote certificate locally and run the steps against my local machine I get the same private key name BBBBBBBB-2222222.
Most likely I feel that I may be missing a caveat in step 4, calling CryptAcquireCertificatePrivateKey. It may be that this call relies on the local machine's identity to generate the name of the unique container that would be used to store the private key blob.
Updated
After some further research, I found a blog that details out exactly how the filenames for private key containers are created here.
Instead of using CryptAcquireCertificatePrivateKey, you can use the methods described on that blog to get the private key container name on any machine once you have the name of the container obtained by CertGetCertificateContextProperty. The code here shows how to get the private key container name so you can generate the private key filename. * disclaimer - I'm pretty sure this is subject to change and may not even be complete, but am posting it in case it helps someone else in the future *
Structs and P/Invoke:
[StructLayout(LayoutKind.Sequential)]
public struct CryptKeyProviderInfo
{
[MarshalAs(UnmanagedType.LPWStr)]
public String pwszContainerName;
[MarshalAs(UnmanagedType.LPWStr)]
public String pwszProvName;
public uint dwProvType;
public uint dwFlags;
public uint cProvParam;
public IntPtr rgProvParam;
public uint dwKeySpec;
}
public const uint CERT_KEY_PROV_INFO_PROP_ID = 0x00000002;
[DllImport("crypt32.dll", SetLastError = true)]
internal extern static bool CertGetCertificateContextProperty(IntPtr pCertContext, uint dwPropId, IntPtr pvData, ref uint pcbData);
IntPtr providerInfo = IntPtr.Zero;
string containerName = string.Empty;
try
{
//Win32 call w/IntPtr.Zero will get the size of our Cert_Key_Prov_Info_Prop_ID struct
uint pcbProviderInfo = 0;
if (!Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref pcbProviderInfo))
{
//if we can't get the certificate context, return string.empty
return string.Empty;
}
//Allocate heap for Cert_Key_Prov_Info_Prop_ID struct
providerInfo = Marshal.AllocHGlobal((int)pcbProviderInfo);
//Request actual Cert_Key_Prov_Info_Prop_ID struct with populated data using our allocated heap
if (Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, providerInfo, ref pcbProviderInfo))
{
//Cast returned pointer into managed structure so we can refer to it by it's structure layout
Win32.CryptKeyProviderInfo keyInfo = (Win32.CryptKeyProviderInfo)Marshal.PtrToStructure(providerInfo, typeof(Win32.CryptKeyProviderInfo));
//Get the container name
containerName = keyInfo.pwszContainerName;
}
//Do clean-up immediately if possible
if (providerInfo != IntPtr.Zero)
{
Marshal.FreeHGlobal(providerInfo);
providerInfo = IntPtr.Zero;
}
}
finally
{
//Do clean-up on finalizer if an exception cause early terminiation of try - after alloc, before cleanup
if (providerInfo != IntPtr.Zero)
Marshal.FreeHGlobal(providerInfo);
}
Using the CertGetCertificateContextProperty above, I was able to solve this question. So it is possible to determine the file name of a certificate's private key on a remote computer using the steps mentioned in the update.

How can I see which shared folders my program has access to?

My program needs to read and write to folders on other machines that might be in another domain. So I used the System.Runtime.InteropServices to add shared folders. This worked fine when it was hard coded in the main menu of my windows service. But since then something went wrong and I don't know if it is a coding error or configuration error.
What is the scope of a shared folder? If a thread in my program adds a shared folder, can the entire local machine see it?
Is there a way to view what shared folders has been added? Or is there a way to see when a folder is added?
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern System.UInt32 NetUseAdd(string UncServerName, int Level, ref USE_INFO_2 Buf, out uint ParmError);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct USE_INFO_2
{
internal LPWSTR ui2_local;
internal LPWSTR ui2_remote;
internal LPWSTR ui2_password;
internal DWORD ui2_status;
internal DWORD ui2_asg_type;
internal DWORD ui2_refcount;
internal DWORD ui2_usecount;
internal LPWSTR ui2_username;
internal LPWSTR ui2_domainname;
}
private void AddSharedFolder(string name, string domain, string username, string password)
{
if (name == null || domain == null || username == null || password == null)
return;
USE_INFO_2 useInfo = new USE_INFO_2();
useInfo.ui2_remote = name;
useInfo.ui2_password = password;
useInfo.ui2_asg_type = 0; //disk drive
useInfo.ui2_usecount = 1;
useInfo.ui2_username = username;
useInfo.ui2_domainname = domain;
uint paramErrorIndex;
uint returnCode = NetUseAdd(String.Empty, 2, ref useInfo, out paramErrorIndex);
if (returnCode != 0)
{
throw new Win32Exception((int)returnCode);
}
}
Each thread in a computer runs under a specific user account. Shared folder have security settings, i.e. they are subject to ACL-based access control, so that some users may have access permission and others may not. This means that the thread in your program may be able to "see" some shared folders, while other threads in the same computer (including the interactive user using the desktop) might be unable to "see" those folders.
In summary: you should assume very little.

Impersonating a user in wrong domain doesn't throw exception

I used the common impersonation code and it worked just fine, until I inserted random 'dggdgsdg' in domain - and it worked nonetheless...
if (LogonUser(Username, Domain, Password, Logon32LogonInteractive, Logon32ProviderDefault, ref existingTokenHandle) &&
DuplicateToken(existingTokenHandle, (int)SecurityImpersonationLevel.SecurityDelegation, ref duplicateTokenHandle))
{
Identity = new WindowsIdentity(duplicateTokenHandle);
ImpersonationContext = Identity.Impersonate();
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
I used some TestUser on my domain, and it worked. I then switched domain, to random nonsense 'werwerhrg', and it impersonated the TestUser on my domain! Why? I would expect an exception to be thrown, why on earth is it working?
private const int Logon32LogonInteractive = 2;
private const int Logon32ProviderDefault = 0;
public enum SecurityImpersonationLevel
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool DuplicateToken(IntPtr existingTokenHandle, int securityImpersonationLevel, ref IntPtr duplicateTokenHandle);
I believe the answer lies in how authentication is performed. LogonUser will attempt to log you on to the local computer it was executed on. If this computer is on a domain, then your password will be checked against an AD controller and be verified.
If you however provide a domain it cannot find it will authenticate against it's own local userbase as a fallback. Locally it will use NTLM (NTLM is used when client and server are the same machine). NTLM verifies password hashes and the username, and seems to not bother with the domain name (ref doc).
If you use UPN format instead and set domain to null then you will get an error if the domain is non-existant, and get your wanted result.
This is similar as if I create a user A with password B on two machines, then those users can access each others machines with the local rights without having to log on.
And all this is a good reason why you should stay away from local accounts in a domain world (at least with the same usernames, prefix them), and why non-domain machines should not be able to see each other on a network. And why you should use Kerberos all over if you can instead of NTLM.

Killing a Windows process from a C# console application: how do I set permissions?

Using Process.Kill() from an ASP.NET web application, I get a Win32Exception with text "Access is denied".
Searching the web has several times told me to set permissions. However I don't really understand the Windows XP User system well enough to know how to get started.
At the time the exception is thrown, Thread.CurrentPrincipal.Identity has the following visible properties:
AuthenticationType = ""
IsAuthenticated = "false"
Name = ""
WindowsIdentity.GetCurrent() shows me logged in as "NAME\ASPNET" but I don't think that this is relevant.
What do I need to do? Can I somehow get the thread to log in as some Windows user?
I think you're on the right way, the problem with "Access denied" is due to ASP.NET process running with ASPNET user which has limited rights and that's what you're getting an error. What you could do is to set up imersnation for your web application. You can it either by changing web.config or in code. More about impersonation you can read here
web.comfig is realtively easy, you need to add a following line into the system.web section of your web.config
<identity impersonate="true" userName="domain\user" password="password" />
user need to have admin rights on the server
if you would like to perform the impersonation in code below is an example of how you could do this:
...
WindowsImpersonationContext context = ImpersonateUser("domain", "user", "password");
// kill your process
context.Undo();
...
[DllImport("advapi32.dll")]
private static extern bool LogonUser(
String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll")]
private static extern bool DuplicateToken(
IntPtr ExistingTokenHandle, int ImpersonationLevel,
ref IntPtr DuplicateTokenHandle);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
private enum SecurityImpersonationLevel
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
private enum LogonTypes
{
LOGON32_PROVIDER_DEFAULT=0,
LOGON32_LOGON_INTERACTIVE=2,
LOGON32_LOGON_NETWORK=3,
LOGON32_LOGON_BATCH=4,
LOGON32_LOGON_SERVICE=5,
LOGON32_LOGON_UNLOCK=7,
LOGON32_LOGON_NETWORK_CLEARTEXT=8,
LOGON32_LOGON_NEW_CREDENTIALS=9
}
public static WindowsImpersonationContext ImpersonateUser(string domain, string username, string password)
{
WindowsImpersonationContext result = null;
IntPtr existingTokenHandle = IntPtr.Zero;
IntPtr duplicateTokenHandle = IntPtr.Zero;
try
{
if (LogonUser(username, domain, password,
(int)LogonTypes.LOGON32_LOGON_NETWORK_CLEARTEXT, (int)LogonTypes.LOGON32_PROVIDER_DEFAULT,
ref existingTokenHandle))
{
if (DuplicateToken(existingTokenHandle,
(int)SecurityImpersonationLevel.SecurityImpersonation,
ref duplicateTokenHandle))
{
WindowsIdentity newId = new WindowsIdentity(duplicateTokenHandle);
result = newId.Impersonate();
}
}
}
finally
{
if (existingTokenHandle != IntPtr.Zero)
CloseHandle(existingTokenHandle);
if (duplicateTokenHandle != IntPtr.Zero)
CloseHandle(duplicateTokenHandle);
}
return result;
}
hope this helps, regards
You need to run your C# application as an user with sufficient privileges.
If you cannot trust the ASP AppPool with such privileges (you shouldn't) you need to create a separate service that runs under an account with sufficent privileges and have protocol between the low privileged app and the higher privileged service to communicate with the purpose of killing a process.
Don't impersonate a high privileged user in the AppPool. You must present its password and by this you have effectively elevated the low privileged account to the high privileged one, by all effective means, in case of AppPool compromise, is just as if you run the AppPool under high privilege and did not accomplish any isolation.
Building on Serge Gubenko's answer, Here is the code to impersonate and kill a process (where in his answer he wrote "//kill your process"):
using (WindowsImpersonationContext context = ImpersonateUser("domain", "user", "password"))
{
Process[] proc = Process.GetProcessesByName("process name");
if (proc.Length > 0)
proc[0].Kill();
context.Undo();
}
You still need to use the rest of the code in his answer.
Note that if you are not part of a domain just enter an empty string

Categories