Impersonate for entire application lifecycle - c#

I have been using this link to setup impersonation. I have many methods and i need to impersonate for all of them.
Do i have to add the using code around every method call or is there
a way to set impersonation for the entire lifecycle of the
application ?
I am impersonating a local administrator below - are
there any flaws or security implications with my code ?
is the LOGON32_LOGON_NEW_CREDENTIALS value of 2 correct below (it is working as expected)
c# Code
private void buttonUsingImpersonation_Click(object sender, EventArgs e)
{
try
{
using (new Impersonation("LocalHost", "test", "test"))
{
// do whatever you want
string fileName = System.IO.Path.GetRandomFileName();
String pathString = System.IO.Path.Combine(FolderPath, fileName);
.
if (!System.IO.File.Exists(pathString))
{
using (System.IO.FileStream fs = System.IO.File.Create(pathString))
{
for (byte i = 0; i < 100; i++)
{
fs.WriteByte(i);
}
}
}
else
{
MessageBox.Show("File already exists " + fileName);
return;
}
}
}
catch (Exception ex)
{
// If exception happens, it will be returned here
MessageBox.Show("Error " + MethodBase.GetCurrentMethod().Name + " " + ex.ToString());
}
}
Class
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
namespace ImpersonationDemo
{
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class Impersonation : IDisposable
{
private readonly SafeTokenHandle _handle;
private readonly WindowsImpersonationContext _context;
const int LOGON32_LOGON_NEW_CREDENTIALS = 2;
public Impersonation(string domain, string username, string password)
{
var ok = LogonUser(username, domain, password,
LOGON32_LOGON_NEW_CREDENTIALS, 0, out this._handle);
if (!ok)
{
var errorCode = Marshal.GetLastWin32Error();
throw new ApplicationException(string.Format("Could not impersonate the elevated user. LogonUser returned error code {0}.", errorCode));
}
this._context = WindowsIdentity.Impersonate(this._handle.DangerousGetHandle());
}
public void Dispose()
{
this._context.Dispose();
this._handle.Dispose();
}
[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);
public 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);
}
}
}
}

There are many ways of doing this, not really a single correct way. One way could be that you pass the methods you want impersonated as a delegate, to some kind of ImpersonatedContext class, that will wrap the invocation in your Impersonator scope. This will also move all the logic away from the button click event handler, which is also architecturally not the best way.
Regarding your question about security, then as of now you are specifying the username and password directly. If Eve was to decompile your code, she would be able to see the password. Maybe you should let the user specify administrators password?
I have been using the example code as well, and it has worked fine for me. I would think that LOGON32_LOGON_NEW_CREDENTIALS is fine. You can read more about the different modes here (LogonUser)
Update
I guess a quick sample of what that would look like, is something like this. The delegate you pass accepts an object, and returns a success value as a boolean. You could modify that to suit your needs.
public class ImpersonationContext {
public delegate bool ImpersonationDel(object obj);
public bool Invoke(ImpersonationDel del){
using (new Impersonation("LocalHost", "test", "test")){
return del.Invoke();
}
}
}

Related

C# Windows Service can't access to network drive [duplicate]

I need to be able to programmatically authenticate when trying to read and write files on a remote computer in a non-domain environment.
When you type a command into the Windows RUN prompt that is similar to \\targetComputer\C$\targetFolder or \\targetComputer\admin$, where the targetComputer is NOT on a domain, you will be prompted to enter a username and password. Once you enter the username and password, you have full access to the remote folder.
How can I accomplish this authentication programmatically in C#?
I've tried..
--Impersonation, but it appears to only work in a domain environment.
--CMDKEY.exe, but it also seems to only work in a domain environment.
There must be a way to do this, but I have searched high and low with no luck so far. Maybe I'm just looking for the wrong thing? I'm sure I'm not the first to have this question. Any help would be greatly appreciated.
Thanks!
EDIT :
I think I just found a different SO posting that answers my question: Accessing a Shared File (UNC) From a Remote, Non-Trusted Domain With Credentials
I will work with that for now and see where it gets me.
Thanks!
Impersonation works with Peer/LAN network as well. I got your typical home network with some machines on default "Workgroup" and some on a named one if I remembered doing it on the install.
Here is the code I use from my IIS server app to access files on my other computer (without having to have the same user and password on both machines involved, copied from somewhere and modified for my use):
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.ComponentModel;
/// <summary>
/// Class to impersonate another user. Requires user, pass and domain/computername
/// All code run after impersonationuser has been run will run as this user.
/// Remember to Dispose() afterwards.
/// </summary>
public class ImpersonateUser:IDisposable {
private WindowsImpersonationContext LastContext = null;
private IntPtr LastUserHandle = IntPtr.Zero;
#region User Impersonation api
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ImpersonateLoggedOnUser(int Token);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DuplicateToken(IntPtr token, int impersonationLevel, ref IntPtr duplication);
[DllImport("kernel32.dll")]
public static extern Boolean CloseHandle(IntPtr hObject);
public const int LOGON32_PROVIDER_DEFAULT = 0;
public const int LOGON32_PROVIDER_WINNT35 = 1;
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_LOGON_NETWORK = 3;
public const int LOGON32_LOGON_BATCH = 4;
public const int LOGON32_LOGON_SERVICE = 5;
public const int LOGON32_LOGON_UNLOCK = 7;
public const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;// Win2K or higher
public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;// Win2K or higher
#endregion
public ImpersonateUser(string username, string domainOrComputerName, string password, int nm = LOGON32_LOGON_NETWORK) {
IntPtr userToken = IntPtr.Zero;
IntPtr userTokenDuplication = IntPtr.Zero;
bool loggedOn = false;
if (domainOrComputerName == null) domainOrComputerName = Environment.UserDomainName;
if (domainOrComputerName.ToLower() == "nt authority") {
loggedOn = LogonUser(username, domainOrComputerName, password, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, out userToken);
} else {
loggedOn = LogonUser(username, domainOrComputerName, password, nm, LOGON32_PROVIDER_DEFAULT, out userToken);
}
WindowsImpersonationContext _impersonationContext = null;
if (loggedOn) {
try {
// Create a duplication of the usertoken, this is a solution
// for the known bug that is published under KB article Q319615.
if (DuplicateToken(userToken, 2, ref userTokenDuplication)) {
// Create windows identity from the token and impersonate the user.
WindowsIdentity identity = new WindowsIdentity(userTokenDuplication);
_impersonationContext = identity.Impersonate();
} else {
// Token duplication failed!
// Use the default ctor overload
// that will use Mashal.GetLastWin32Error();
// to create the exceptions details.
throw new Win32Exception();
}
} finally {
// Close usertoken handle duplication when created.
if (!userTokenDuplication.Equals(IntPtr.Zero)) {
// Closes the handle of the user.
CloseHandle(userTokenDuplication);
userTokenDuplication = IntPtr.Zero;
}
// Close usertoken handle when created.
if (!userToken.Equals(IntPtr.Zero)) {
// Closes the handle of the user.
CloseHandle(userToken);
userToken = IntPtr.Zero;
}
}
} else {
// Logon failed!
// Use the default ctor overload that
// will use Mashal.GetLastWin32Error();
// to create the exceptions details.
throw new Win32Exception();
}
if (LastContext == null) LastContext = _impersonationContext;
}
public void Dispose() {
LastContext.Undo();
LastContext.Dispose();
}
}
The specific code I found out worked after a bit of trying was this:
using (var impersonation = new ImpersonateUser("OtherMachineUser", "OtherMachineName", "Password", LOGON32_LOGON_NEW_CREDENTIALS))
{
var files = System.IO.Directory.GetFiles("\\OtherMachineName\fileshare");
}

How to print to network printer through application hosted in IIS

I have scenario of printing pdf (generated from stream) to network printer through application hosted in IIS. I tried with PrintDocument.Print() and problem I'm facing is: 1. Document is getting queued to the print job queue with size 0 bytes. 2. Document is getting queued to the print job queue with owner name as machine_name.
Here is the code which i tried using PdfiumViewer (to generate PrintDocument from bytearray) and System.Drawing.Printing.PrintDocument:
public void SendPdfToPrinter(byte[] byteArray, string fileName, string printerNetworkPath)
{
using (Stream fileStream = new MemoryStream(byteArray)) //byte array for the file content
{
var printerSettings = new System.Drawing.Printing.PrinterSettings
{
PrinterName = printerNetworkPath, //this is the printer full name. i.e. \\10.10.0.12\ABC-XEROX-01
PrintFileName = fileName, //file name. i.e. abc.pdf
PrintRange = System.Drawing.Printing.PrintRange.AllPages,
};
printerSettings.DefaultPageSettings.Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0);
// Now print the PDF document
using (PdfiumViewer.PdfDocument document = PdfiumViewer.PdfDocument.Load(fileStream))
{
using (System.Drawing.Printing.PrintDocument printDocument = document.CreatePrintDocument())
{
printDocument.DocumentName = fileName;
printDocument.PrinterSettings = printerSettings;
printDocument.PrintController = new System.Drawing.Printing.StandardPrintController();
printDocument.Print();
}
}
For both the problems, answer is impersonating the user to do the printing.
In my case app pool is running under LocalSystem account which is obviously is not a domain user and printer is exposed to the domain user only.
Note: Application pool is 64bit, if you use 32bit you will face another set of challenges which is well described here:
https://blogs.msdn.microsoft.com/winsdk/2015/05/19/printing-successfully-using-impersonation-from-a-32-bit-application-on-a-64-bit-system/
Below is code which required to do the impersonation for domain user:
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class Impersonation : IDisposable
{
private readonly SafeTokenHandle _handle;
private readonly WindowsImpersonationContext _context;
bool disposed = false;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
public Impersonation(ImpersonateUserDetails user) : this(user.Domain, user.UserName, user.Password)
{ }
public Impersonation(string domain, string username, string password)
{
var ok = LogonUser(username, domain, password,
LOGON32_LOGON_INTERACTIVE, 0, out this._handle);
if (!ok)
{
var errorCode = Marshal.GetLastWin32Error();
throw new ApplicationException(string.Format("Could not impersonate the elevated user. LogonUser returned error code {0}.", errorCode));
}
this._context = WindowsIdentity.Impersonate(this._handle.DangerousGetHandle());
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
this._context.Dispose();
this._handle.Dispose();
}
disposed = true;
}
~Impersonation()
{
Dispose(false);
}
[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);
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);
}
}
}
public class ImpersonateUserDetails
{
public string UserName { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
}
Another possible & easy solution is to configure your Application Pool Identity to a custom/domain user which has access/permission to print in network printers.

Read messages from queue from remote computer

We're trying to implement MSMQ in our applications and we face the next problem: Our application(console application) starts under a local machine (machine1) user account in the domain X.
On the same domain there is another machine (machine2) and on this machine is the queue. On the domain X there is an user account with admin rights and this user has full control on the queue, but when our application starts, because it runs under the local account, it doesn't have the rights to read messagess.
Is there any solution on how to fix this issue only from the code? We can't change the user account that our console application is using. I'm thinking to use impersonation as the last solution.
Do you have any solution for this problem?
Both private and public queues should allow you to do this: https://technet.microsoft.com/en-us/library/cc772532.aspx
Have a look at the section under Public and Private queues.
Public queues should be available to any domain account, including machine accounts.
Private queues allow anyone to write, but need particular permission to read.
I've been able to read messages only by impersonating. Here is my code:
Impersonating context wrapper:
public class WrapperImpersonationContext
{
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain,
String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
private const int LOGON32_PROVIDER_DEFAULT = 0;
private const int LOGON32_LOGON_INTERACTIVE = 2;
private string m_Domain;
private string m_Password;
private string m_Username;
private IntPtr m_Token;
private WindowsImpersonationContext m_Context = null;
protected bool IsInContext
{
get { return m_Context != null; }
}
public WrapperImpersonationContext(string domain, string username, string password)
{
m_Domain = domain;
m_Username = username;
m_Password = password;
}
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Enter(out string result)
{
if (this.IsInContext)
{
result = "not in context";
return;
}
m_Token = new IntPtr(0);
try
{
m_Token = IntPtr.Zero;
bool logonSuccessfull = LogonUser(
m_Username,
m_Domain,
m_Password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref m_Token);
if (logonSuccessfull == false)
{
result = "logon failed";
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error);
}
else
{
result = "logon succseeded";
}
WindowsIdentity identity = new WindowsIdentity(m_Token);
m_Context = identity.Impersonate();
}
catch (Exception exception)
{
result = "exception: " + exception.Message;
// Catch exceptions here
}
}
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Leave()
{
if (this.IsInContext == false) return;
m_Context.Undo();
if (m_Token != IntPtr.Zero) CloseHandle(m_Token);
m_Context = null;
}
}
Reading messages:
MessageQueue queue = new MessageQueue(#"FormatName:DIRECT=OS:servername\PRIVATE$\queue_name");
WrapperImpersonationContext context = new WrapperImpersonationContext("domain", "username", "password");
context.Enter(out result);
Message msg = queue.Receive();
context.Leave();
If you can't impersonate a valid domain account, you would have to allocate the 'anonymous logon' special account permissions on the queue. The 'everyone' special group won't work as that only covers accounts recognised by the domain. Accounts local to your machine are foreign to the domain and not included.

connection one time to network Drive when Creating files with Impersonator

I have to create many files in Network drive with specified user.
I used this answer to connect different user
I use Impersonator Class :
public class Impersonator : IDisposable
{
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
private IntPtr token = IntPtr.Zero;
private WindowsImpersonationContext impersonated;
private readonly string _ErrMsg = "";
public bool IsImpersonating
{
get { return (token != IntPtr.Zero) && (impersonated != null); }
}
public string ErrMsg
{
get { return _ErrMsg; }
}
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public Impersonator(string userName, string password, string domain)
{
StopImpersonating();
bool loggedOn = LogonUser(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token);
if (!loggedOn)
{
_ErrMsg = new System.ComponentModel.Win32Exception().Message;
return;
}
WindowsIdentity identity = new WindowsIdentity(token);
impersonated = identity.Impersonate();
}
private void StopImpersonating()
{
if (impersonated != null)
{
impersonated.Undo();
impersonated = null;
}
if (token != IntPtr.Zero)
{
CloseHandle(token);
token = IntPtr.Zero;
}
}
public void Dispose()
{
StopImpersonating();
}
}
and the code :
using (Impersonator impersonator = new Impersonator("UserName", "UserPwd", "UserDomaine"))
{
if (!Directory.Exists("Z:\\")) // check if Network drive exist
{
NetworkDrive drive = new NetworkDrive
{
ShareName = #"\\IP\Partage",
LocalDrive = "Z",
Force = true
};
drive.MapDrive(#"UserDomaine\UserName", "UserPwd");
}
File.Create(#"Z:\Log\FileName.txt");
}
But in this case I found that the code Map the drive every time that I have to create a file or update it!! And I have a lot of work with this function.
There’s a solution to not map it every time?
I tried to Map the driver in with this user in opening of application but same problem.
I think you don't need to map the drive. After impersonating you can just create the file directly using the network drive and it will create the file as impersonated user.
using (Impersonator impersonator = new Impersonator("UserName", "UserPwd", "UserDomaine"))
{
File.Create(#"\\IP\Partage\Log\FileName.txt");
}
Try not to use Using block. declare Impersonator as global static variable.

How to make the ActiveDirectoryMembershipProvider accept an empty password?

We are developing a web application that uses forms authentication and the ActiveDirectoryMembershipProvider to authenticate users against the Active Directory. We soon found out that the provider does not allow a blank/empty password to be specified, even though this is perfectly legal in the Active Directory (provided a preventative password policy is not in place).
Courtesy of reflector:
private void CheckPassword(string password, int maxSize, string paramName)
{
if (password == null)
{
throw new ArgumentNullException(paramName);
}
if (password.Trim().Length < 1)
{
throw new ArgumentException(SR.GetString("Parameter_can_not_be_empty", new object[] { paramName }), paramName);
}
if ((maxSize > 0) && (password.Length > maxSize))
{
throw new ArgumentException(SR.GetString("Parameter_too_long", new object[] { paramName, maxSize.ToString(CultureInfo.InvariantCulture) }), paramName);
}
}
Short of writing our own custom Provider, is there any way to override this functionality using the magic of .NET?
I don't beleive you could change this behaviour without creating a derived class and overiding every method that calls the private CheckPassword method. I would not recomend this option however, i would recomend that you review your design and question whether it is approriate to allow blank passwords in your application. Whilst they are valid in AD it is unusual for this to be allowed in practice and it does impact other things in a windows network, e.g. i think the default settings for network file shares disallow any user with a blank password from connecting to the share.
You could perhaps look at using impersonation but i don't know if you will have the same issue. If it's to authorise a user, then you could use impersonation to try and "impersonate" the user on the machine. I don't know if it helps but I was doing something similar to this the other week. Have put the code below if any of this helps.. :)
using System;
using System.Runtime.InteropServices;
public partial class Test_Index : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e)
{
IntPtr ptr = IntPtr.Zero;
if (LogonUser("USERNAME", "", "LEAVE-THIS-BLANK", LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, ref ptr))
{
using (System.Security.Principal.WindowsImpersonationContext context = new System.Security.Principal.WindowsIdentity(ptr).Impersonate())
{
try
{
// Do do something
}
catch (UnauthorizedAccessException ex)
{
// failed to do something
}
// un-impersonate user out
context.Undo();
}
}
else
{
Response.Write("login fail");
}
}
#region imports
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern 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);
#endregion
#region logon consts
// logon types
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NETWORK = 3;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
// logon providers
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_PROVIDER_WINNT50 = 3;
const int LOGON32_PROVIDER_WINNT40 = 2;
const int LOGON32_PROVIDER_WINNT35 = 1;
#endregion }

Categories