Impersonate local user on another machine - c#

I need to log on my controller to another machine and copy a file on it; I have to use a local user on the remote machine.
Currently I'm using this code:
private Impersonate(bool active, string domain, string username, string password, LogonType logonType)
{
if (active)
{
IntPtr handle;
var ok = NativeMethods.LogonUser(username, domain, password, (int)logonType, 0, out handle);
if (!ok)
{
var errorCode = Marshal.GetLastWin32Error();
throw new ApplicationException(string.Format("Could not impersonate the elevated user. LogonUser returned error code {0}.", errorCode));
}
_handle = new SafeTokenHandle(handle);
_context = WindowsIdentity.Impersonate(_handle.DangerousGetHandle());
}
}
passing these args:
using (Impersonate.LogonUser(true,
".",
"todev1.domain.com\admin",
"Test123_",
LogonType.Interactive))
{
}
and this win API:
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
I checked this Q/A Using advapi32.dll:LogonUserA() to impersonate a remote machine's local user but the solution provided is not working.
I tried to pass multiple values to the method as domain, user and so on, but I can't find the rigth solution. I tried using NewCredentials but it returns always ok even if not logged.

I finally solved this issue, without the need to add the user to each machine that will impersonate to the remote machine.
It's correct to use NewCredential, but using the WINNT50 LogonProvider.
So my impersonate method now is like:
private Impersonate(bool active, string domain, string username, string password, LogonType logonType, LogonProvider logonProvider)
{
if (active)
{
IntPtr handle;
var ok = NativeMethods.LogonUser(username, domain, password, (int)logonType, (int)logonProvider, out handle);
if (!ok)
{
var errorCode = Marshal.GetLastWin32Error();
throw new ApplicationException(string.Format("Could not impersonate the elevated user. LogonUser returned error code {0}.", errorCode));
}
_handle = new SafeTokenHandle(handle);
_context = WindowsIdentity.Impersonate(_handle.DangerousGetHandle());
}
}
Then I call the Impersonate method using the code:
using (Impersonate.LogonUser(true,
"todev1.domain.com",
"admin",
"Test123_",
LogonType.NewCredentials,
LogonProvider.WinNT50))
{
}

You can try to make a local user on your local machine with the same username and password as the local user on the remote server.

Related

.Net Core Impersonation not working with Process.Start

I can't seem to start a process as another user when using impersonation under .Net Core.
I'm running this script in Linqpad running as User1 and trying to launch a program as User2.
At first, the impersonation seems to work (the Console.Writeline()s on the current user change correctly from User1 to User2 in the RunImpersonated() Method). However, the process always runs as User1.
This is one of many tests I'm doing to validate that RunImpersonated() works (this originally stems from issues in an ASP.Net Core App trying to impersonate the current user). This is the simplest reproducible example I could find.
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);
void Main()
{
string domainName = "myDomain";
string userName = "User2";
string passWord = "User2Password";
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
// Call LogonUser to obtain a handle to an access token.
SafeAccessTokenHandle safeAccessTokenHandle;
bool returnValue = LogonUser(userName, domainName, passWord,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeAccessTokenHandle);
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
Console.WriteLine("LogonUser failed with error code : {0}", ret);
throw new System.ComponentModel.Win32Exception(ret);
}
Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
// Check the identity.
Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);
// Note: if you want to run as unimpersonated, pass
// 'SafeAccessTokenHandle.InvalidHandle' instead of variable 'safeAccessTokenHandle'
WindowsIdentity.RunImpersonated(
safeAccessTokenHandle,
// User action
() =>
{
// Check the identity.
Console.WriteLine("During impersonation: " + WindowsIdentity.GetCurrent().Name);
Directory.GetFiles(#"C:\TMP\").Dump();
var pi = new ProcessStartInfo
{
WorkingDirectory = #"C:\TMP\",
FileName = #"C:\TMP\TestUser.exe"
};
var proc = Process.Start(pi);
proc.WaitForExit();
}
);
// Check the identity again.
Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);
}
If you don't specify username and password, Process.Start will uses the token for the calling process, not the impersonation token.
Looking into source code of Process.Start:
If you pass user name and password it uses CreateProcess
If you don't specify username and password, it calls CreateProcessWithLogon
If the calling process is impersonating another user, the new process
uses the token for the calling process, not the impersonation token.
To run the new process in the security context of the user represented
by the impersonation token, use the CreateProcessAsUser or
CreateProcessWithLogonW function.
Without passing username and password the process always runs on security context of the original process owner. If you want to run the process under the context of another user:
If you have username and password: use CreateProcessWithLogon
If you have a to the primary token that represents a user use CreateProcessAsUser
There are multiple impersonation levels. Documentation states that you cannot use thread impersonation token to start the process - primary token will always be used:
The system uses the primary token of the process rather than the
impersonation token of the calling thread in the following situations:
If an impersonating thread calls the CreateProcess function, the new
process always inherits the primary token of the process.
Given you don't have user's password and you want to use impersonation token instead to start a process, unfortunately, the answer is - you can't do that.
You can impersonate caller for other operations though (RPC, COM, FS...).
A while back I used the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
using System.Text;
using System.Windows;
using Microsoft.Win32.SafeHandles;
namespace ZZZ
{
partial class User : IDisposable
{
private string m_domain;
private string m_user;
private string m_pass;
private WindowsIdentity user;
private SafeTokenHandle safeTokenHandle;
private WindowsImpersonationContext impContext;
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out SafeTokenHandle phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool RevertToSelf();
//For the current user
public User()
{
user = WindowsIdentity.GetCurrent();
}
// For custom user
public User(string domain, string user, string password, bool doImepsonate = false)
{
m_domain = domain;
m_user = user;
m_pass = password;
if (doImepsonate) this.Impersonate();
}
// If it's intended to incorporate this code into a DLL, then demand FullTrust.
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public void Impersonate()
{
if (impContext != null) throw new ImpersonationException();
try
{
// Get the user token for the specified user, domain, and password using the unmanaged LogonUser method.
// The local machine name can be used for the domain name to impersonate a user on this machine.
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(
m_user,
m_domain,
m_pass,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
if (returnValue == false)
{
int ret = Marshal.GetLastWin32Error();
throw new Win32Exception(ret);
}
using (safeTokenHandle)
{
// Use the token handle returned by LogonUser.
user = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
impContext = user.Impersonate();
}
}
catch (Exception ex)
{
MessageBox.Show("Exception occurred:\n" + ex.Message);
}
}
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
private void Quit()
{
if (impContext == null) return;
impContext.Undo();
safeTokenHandle.Dispose();
}
#endregion
internal IEnumerable<string> Groups
{
get
{
return user.Groups.Select(p =>
{
IdentityReference ir = null;
try { ir = p.Translate(typeof(NTAccount)); }
catch { }
return ir == null ? null : ir.Value;
});
}
}
}
// Win32 API part
internal sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle() : base(true) { }
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
}
internal sealed class ImpersonationException : Exception
{
public ImpersonationException() : base("The user is already impersonated.") { }
}
}
This works great for me. Check the user who is running the process. Sometime the user is not administrator or cannot impersonate.
Processinfo creates new process. Try process.start, Or you can convert exe to util dll and run inside code like utli.testuser code. Use dll call method from main program and not exe.
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
// Call LogonUser to obtain a handle to an access token.
SafeAccessTokenHandle safeAccessTokenHandle;
bool returnValue = LogonUser(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeAccessTokenHandle);
WindowsIdentity.RunImpersonated(safeAccessTokenHandle, () => {
Var impersonatedUser = WindowsIdentity.GetCurrent().Name;
//--- Call your Method here…….
});

Selenium C# - exception when run selenium driver after impersonate

I am using windows functionality, impersonate, to change the active user before starting Chrome driver. Now, starting the driver without the impersonate code works fine. Also the impersonate code works fine; I see the user is changed. But when this change happens and after that I run IWebDriver driver=new ChromeDriver then the exception is triggered on that exact code and the test stops. Any ideas why this happens?
Here is main part of the code used (the code is just little modified code from another post here at stackoverflow)
namespace localSeleniumTest.Impersonation
{
class Program
{
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(stringpszUsername, string pszDomain, string pszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
// closes open handes returned by LogonUser
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
public void Impersonation()
{
WindowsImpersonationContext impersonationContext = null;
IntPtr userHandle = IntPtr.Zero;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
string domain = Config.domain;
string user = Config.username;
string password = Config.password;
try
{
String currentName = WindowsIdentity.GetCurrent().Name;
// if domain name was blank, assume local machine
if (domain == "")
domain = System.Environment.MachineName;
// Call LogonUser to get a token for the user
bool loggedOn = LogonUser(
user,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref userHandle
);
if (!loggedOn)
{
return;
}
// Begin impersonating the user
impersonationContext = WindowsIdentity.Impersonate(userHandle);
String afterImpersonationName = WindowsIdentity.GetCurrent().Name;
/*this few lines below does not work after impersonation but
work perfectly without the code above.*/
IWebDriver driver = new ChromeDriver();
driver.Navigate().GoToUrl("www.google.com");
System.Threading.Thread.Sleep(6000);
driver.Quit();
Found the issue. D user did not have permission to chrome driver i Bin folder

Waiting for prompt from Process.StandardOutput [duplicate]

We have a scenario where we need our users to be able to launch SQLServer and authenticate into it using a different domain than they are currently logged into. So to clarify the way this is setup:
User arrives at the office and logs in to the corporate domain (lets call it LOCALDOMAIN for simplicity)
They wish to connect to our remote database on a different domain (lets call it REMOTEDOMAIN)
First they launch the VPN tool which establishes the VPN tunnel to REMOTEDOMAIN (this is all tested and works great)
But if they launch SSMS by default it will only allow Windows Auth via LOCALDOMAIN, the option to select REMOTEDOMAIN is not even available
What we discovered is that running this from the command line will work:
RUNAS /user:REMOTEDOMAIN\AUserName /netonly "C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe
it will prompt with the message "Enter the password for REMOTEDOMAIN\AUserName:" and if you supply the correct password, SSMS will be launched and can connect to the remote dbs. However, when I try to do the same thing in C# with a nicer interface around it, I get "Logon failure: unknown user name or bad password", here is my code:
System.Security.SecureString password = new System.Security.SecureString();
foreach(char c in txtPassword.Text.ToCharArray()){
password.AppendChar(c);
}
System.Diagnostics.ProcessStartInfo procInfo = new System.Diagnostics.ProcessStartInfo();
procInfo.Arguments = "/netonly";
procInfo.FileName = #"C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe"; ;
procInfo.Domain = "REMOTEDOMAIN";
procInfo.Verb = "runas";
procInfo.UserName = txtUsername.Text;
procInfo.Password = password;
procInfo.UseShellExecute = false;
System.Diagnostics.Process.Start(procInfo);
I tried the username with and without the domain pre-pended but neither works. Anyone ever tried to do something similar? thanks
You should remove the following lines:
// Not passing /netonly to SMSS, it was passed to RunAs originally.
procInfo.Arguments = "/netonly";
// Again, SMSS is not getting the verb, it's being run
procInfo.Verb = "runas";
Basically, you're passing the /netonly parameter to SMSS, whereas on the command line, you're running runas not SMSS. Same with the verb, you're not running runas.
The call to Start should succeed at that point, as you'll be pointing to the correct executable with the correct credentials.
I've done something that may be related. I login to one domain and try to get a directory listing of a shared folder on a different domain. To do this, I use LogonUser and Impersonate. The code looks like the following (sorry, I don't have an SQL server to try your exact scenario)...
public class Login : IDisposable
{
public Login(string userName, string domainName)
{
_userName = userName;
_domainName = domainName;
}
string _userName = null;
string _domainName = null;
IntPtr tokenHandle = new IntPtr(0);
IntPtr dupeTokenHandle = new IntPtr(0);
WindowsImpersonationContext impersonatedUser = null;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "LogonUser")]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "LogonUser")]
public static extern bool LogonUserPrompt(String lpszUsername, String lpszDomain, IntPtr lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
public void AccessShare(string password)
{
tokenHandle = IntPtr.Zero;
bool returnValue = LogonUser(_userName, _domainName, password,
LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
ref tokenHandle);
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
// Use the token handle returned by LogonUser.
WindowsIdentity newId = new WindowsIdentity(tokenHandle);
impersonatedUser = newId.Impersonate();
}
#region IDisposable Members
public void Dispose()
{
impersonatedUser.Undo();
// Free the tokens.
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
}
#endregion
}
I've used this with Directory.GetDirectories(UNCPath) where the path leads to a machine on another domain and it works there. I have not yet tried it for implementing a "runas".
I call it like so...
using(var login = new Login("myname","mydomain))
{
login.AccessShare("mypassword");
// do stuff
}
Maybe you can adapt it to your problem. LMK
I tried all the various user impersonation code samples I could find. None of them worked.
Finally, I came up with the following code. It executes cmd.exe with the /C argument, which Carries out the command specified by string and then terminates . The command that I execute is runas /netonly ...
Caveats
Unfortunately, the password has to be typed manually. My next step is to investigate sending key stokes to the process. I tried redirecting standard input and writing to it, but it didn't work. I read somewhere on SO that most password prompts only accept input directly from the keyboard.
Also, when SSMS opens, the Connect to Server dialog will show your current domain\username, but it will authenticate using the one you gave to runas.
Finally, if your AD account is locked, you won't get an error until you try to connect to SQL Server. I neglected to copy down the error message that I received, but it did not mention the account was locked.
Code
public static void RunAsNetonly(string username, string domain, string exePath)
{
var psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.Arguments = $"/C runas /netonly /user:{domain}\\{username} \"{exePath}\"";
psi.UseShellExecute = false;
var process = Process.Start(psi);
// not sure if this is required
process.WaitForExit();
}
// usage example
public static void RunSSMS()
{
RunAsNetonly("walter", "domain123", #"C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\ssms.exe");
}

System.Diagnostics.Process.Start a process against a different domain

We have a scenario where we need our users to be able to launch SQLServer and authenticate into it using a different domain than they are currently logged into. So to clarify the way this is setup:
User arrives at the office and logs in to the corporate domain (lets call it LOCALDOMAIN for simplicity)
They wish to connect to our remote database on a different domain (lets call it REMOTEDOMAIN)
First they launch the VPN tool which establishes the VPN tunnel to REMOTEDOMAIN (this is all tested and works great)
But if they launch SSMS by default it will only allow Windows Auth via LOCALDOMAIN, the option to select REMOTEDOMAIN is not even available
What we discovered is that running this from the command line will work:
RUNAS /user:REMOTEDOMAIN\AUserName /netonly "C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe
it will prompt with the message "Enter the password for REMOTEDOMAIN\AUserName:" and if you supply the correct password, SSMS will be launched and can connect to the remote dbs. However, when I try to do the same thing in C# with a nicer interface around it, I get "Logon failure: unknown user name or bad password", here is my code:
System.Security.SecureString password = new System.Security.SecureString();
foreach(char c in txtPassword.Text.ToCharArray()){
password.AppendChar(c);
}
System.Diagnostics.ProcessStartInfo procInfo = new System.Diagnostics.ProcessStartInfo();
procInfo.Arguments = "/netonly";
procInfo.FileName = #"C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe"; ;
procInfo.Domain = "REMOTEDOMAIN";
procInfo.Verb = "runas";
procInfo.UserName = txtUsername.Text;
procInfo.Password = password;
procInfo.UseShellExecute = false;
System.Diagnostics.Process.Start(procInfo);
I tried the username with and without the domain pre-pended but neither works. Anyone ever tried to do something similar? thanks
You should remove the following lines:
// Not passing /netonly to SMSS, it was passed to RunAs originally.
procInfo.Arguments = "/netonly";
// Again, SMSS is not getting the verb, it's being run
procInfo.Verb = "runas";
Basically, you're passing the /netonly parameter to SMSS, whereas on the command line, you're running runas not SMSS. Same with the verb, you're not running runas.
The call to Start should succeed at that point, as you'll be pointing to the correct executable with the correct credentials.
I've done something that may be related. I login to one domain and try to get a directory listing of a shared folder on a different domain. To do this, I use LogonUser and Impersonate. The code looks like the following (sorry, I don't have an SQL server to try your exact scenario)...
public class Login : IDisposable
{
public Login(string userName, string domainName)
{
_userName = userName;
_domainName = domainName;
}
string _userName = null;
string _domainName = null;
IntPtr tokenHandle = new IntPtr(0);
IntPtr dupeTokenHandle = new IntPtr(0);
WindowsImpersonationContext impersonatedUser = null;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "LogonUser")]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "LogonUser")]
public static extern bool LogonUserPrompt(String lpszUsername, String lpszDomain, IntPtr lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
public void AccessShare(string password)
{
tokenHandle = IntPtr.Zero;
bool returnValue = LogonUser(_userName, _domainName, password,
LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
ref tokenHandle);
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
// Use the token handle returned by LogonUser.
WindowsIdentity newId = new WindowsIdentity(tokenHandle);
impersonatedUser = newId.Impersonate();
}
#region IDisposable Members
public void Dispose()
{
impersonatedUser.Undo();
// Free the tokens.
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
}
#endregion
}
I've used this with Directory.GetDirectories(UNCPath) where the path leads to a machine on another domain and it works there. I have not yet tried it for implementing a "runas".
I call it like so...
using(var login = new Login("myname","mydomain))
{
login.AccessShare("mypassword");
// do stuff
}
Maybe you can adapt it to your problem. LMK
I tried all the various user impersonation code samples I could find. None of them worked.
Finally, I came up with the following code. It executes cmd.exe with the /C argument, which Carries out the command specified by string and then terminates . The command that I execute is runas /netonly ...
Caveats
Unfortunately, the password has to be typed manually. My next step is to investigate sending key stokes to the process. I tried redirecting standard input and writing to it, but it didn't work. I read somewhere on SO that most password prompts only accept input directly from the keyboard.
Also, when SSMS opens, the Connect to Server dialog will show your current domain\username, but it will authenticate using the one you gave to runas.
Finally, if your AD account is locked, you won't get an error until you try to connect to SQL Server. I neglected to copy down the error message that I received, but it did not mention the account was locked.
Code
public static void RunAsNetonly(string username, string domain, string exePath)
{
var psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.Arguments = $"/C runas /netonly /user:{domain}\\{username} \"{exePath}\"";
psi.UseShellExecute = false;
var process = Process.Start(psi);
// not sure if this is required
process.WaitForExit();
}
// usage example
public static void RunSSMS()
{
RunAsNetonly("walter", "domain123", #"C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\ssms.exe");
}

windows service gets UnauthorizedAccessException on reading file from remote share while impersonating

I have a windows service that runs under the local machine system account. Inside this service, it tries to read a remote .ini file that is available on a remote shared folder. The code trying to read this file is wrapped in impersonation using LogonUser (a simplified version of the code is below for an idea of what it is doing). The impersonation successfully starts impersonating the user configured, however the instant it attempts to read the remote ini file found on the remote network share, an UnauthorizedAccessException is thrown. This happens even though the configured user has read/write permissions on the remote machine. When I modify the code to remove all impersonation, and instead run the windows service as the configured user, all access to the remote .ini file are successful. I would prefer to use impersonation to get access to this file rather than a hack such as running the service as the user. Can anyone see errors with the impersonation code in the example? Is there something I need to do on my Vista 64 bit box to enable this? Are there specific active directory permissions my IT co-workers need to enable?
private WindowsImpersonationContext _impersonatedUser;
private IntPtr _token;
// Declare signatures for Win32 LogonUser and CloseHandle APIs
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LogonUser(
string principal,
string authority,
string password,
LogonSessionType logonType,
LogonProvider logonProvider,
out IntPtr token);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr handle);
enum LogonSessionType : uint
{
Interactive = 2,
Network,
Batch,
Service,
NetworkCleartext = 8,
NewCredentials
}
enum LogonProvider : uint
{
Default = 0, // default for platform (use this!)
WinNT35, // sends smoke signals to authority
WinNT40, // uses NTLM
WinNT50 // negotiates Kerb or NTLM
}
....
var result = LogonUser(exchangeUserId, exchangeDomain,
password,
LogonSessionType.Network,
LogonProvider.Default,
out _token);
var id = new WindowsIdentity(_token);
_impersonatedUser = id.Impersonate();
try
{
//Validate access to the file on the remote computer/share
File.GetAccessControl(remoteFileAvailableInSharedFolder);
}
catch (UnauthorizedAccessException unauthorized)
{
...
}
....
// Stop impersonation and revert to the process identity
if (_impersonatedUser != null)
{
_impersonatedUser.Undo();
_impersonatedUser = null;
}
if (_token != IntPtr.Zero)
{
CloseHandle(_token);
_token = IntPtr.Zero;
}
Doing further research I discovered the core issue to be caused by the impersonation code. I had to add the use of the api DuplicateToken and added usage of the api RevertToSelf too. On making these changes (see class below) the impersonation worked as well as the code did when the whole service was ran under my domain user. Hope the code helps others with this same issue.
public class Impersonation : IDisposable
{
private WindowsImpersonationContext _impersonatedUserContext;
// Declare signatures for Win32 LogonUser and CloseHandle APIs
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LogonUser(
string principal,
string authority,
string password,
LogonSessionType logonType,
LogonProvider logonProvider,
out IntPtr token);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool RevertToSelf();
// ReSharper disable UnusedMember.Local
enum LogonSessionType : uint
{
Interactive = 2,
Network,
Batch,
Service,
NetworkCleartext = 8,
NewCredentials
}
// ReSharper disable InconsistentNaming
enum LogonProvider : uint
{
Default = 0, // default for platform (use this!)
WinNT35, // sends smoke signals to authority
WinNT40, // uses NTLM
WinNT50 // negotiates Kerb or NTLM
}
// ReSharper restore InconsistentNaming
// ReSharper restore UnusedMember.Local
/// <summary>
/// Class to allow running a segment of code under a given user login context
/// </summary>
/// <param name="user">domain\user</param>
/// <param name="password">user's domain password</param>
public Impersonation(string user, string password)
{
var token = ValidateParametersAndGetFirstLoginToken(user, password);
var duplicateToken = IntPtr.Zero;
try
{
if (DuplicateToken(token, 2, ref duplicateToken) == 0)
{
throw new Exception("DuplicateToken call to reset permissions for this token failed");
}
var identityForLoggedOnUser = new WindowsIdentity(duplicateToken);
_impersonatedUserContext = identityForLoggedOnUser.Impersonate();
if (_impersonatedUserContext == null)
{
throw new Exception("WindowsIdentity.Impersonate() failed");
}
}
finally
{
if (token != IntPtr.Zero)
CloseHandle(token);
if (duplicateToken != IntPtr.Zero)
CloseHandle(duplicateToken);
}
}
private static IntPtr ValidateParametersAndGetFirstLoginToken(string user, string password)
{
if (string.IsNullOrEmpty(user))
{
throw new ConfigurationErrorsException("No user passed into impersonation class");
}
var userHaves = user.Split('\\');
if (userHaves.Length != 2)
{
throw new ConfigurationErrorsException("User must be formatted as follows: domain\\user");
}
if (!RevertToSelf())
{
throw new Exception("RevertToSelf call to remove any prior impersonations failed");
}
IntPtr token;
var result = LogonUser(userHaves[1], userHaves[0],
password,
LogonSessionType.Interactive,
LogonProvider.Default,
out token);
if (!result)
{
throw new ConfigurationErrorsException("Logon for user " + user + " failed.");
}
return token;
}
public void Dispose()
{
// Stop impersonation and revert to the process identity
if (_impersonatedUserContext != null)
{
_impersonatedUserContext.Undo();
_impersonatedUserContext = null;
}
}
}

Categories

Resources