CreateProcessAsUser user context - c#

I've already been searching long time but couldn't find a working solution yet :-(
I have created a window service that launches a client on every user logged on to a machine using CreateProcessAsUser (http://www.pinvoke.net/default.aspx/advapi32/createprocessasuser.html), WTSEnumerateSessions and so on...
This works fine already. The client starts in the user's session, shows its taskbar icon, and communication with the service is working fine.
The problem I have is that I need to have that client store temporary files in the user's profile. I tried starting with a small log file so that I can keep track of any errors that my user's could eventually experience. Unfortunately I can not save to the user's temp folder because the client somehow seems to be running in LocalSystem's context although WindowsIdentity shows the correct user: System.IO.Path.GetTempPath() always returns 'C:\Windows\Temp' but my user's don't have administrative rights so they are not able to write there... furthermore, I planned to store settings in the current user's registry which is not working, too. I think this is related to the wrong temp path in some way.
I also tried CreateEnvironmentBlock (http://www.pinvoke.net/default.aspx/userenv/CreateEnvironmentBlock.html) but I could not make it work and somewhere I found an article saying that this won't work any more on Vista or higher so I stopped researching on that one.
For testing I have created a small test form just doing this:
MessageBox.Show("Temp: " + System.IO.Path.GetTempPath() + Environment.NewLine + "User: " + WindowsIdentity.GetCurrent().Name, "Before impersonation");
WindowsIdentity currentUserId = WindowsIdentity.GetCurrent();
WindowsImpersonationContext impersonatedUser = currentUserId.Impersonate();
MessageBox.Show("Temp: " + System.IO.Path.GetTempPath() + Environment.NewLine + "User: " + WindowsIdentity.GetCurrent().Name, "After impersonation");
This one always shows the same results before and after impersonation: "Temp: C:\Windows\Temp User:testdomain\testuser" :-(
If it helps here's my function to start a process (user token is delivered by WTSEnumerateSessions) - of course this only works under LocalSystem's context:
public static Process StartProcessAsUser(IntPtr UserToken, string App, string AppPath, string AppParameters)
{
Process ResultProcess = null;
IntPtr hDupedToken = IntPtr.Zero;
NativeProcessAPI.PROCESS_INFORMATION oProcessInformation = new NativeProcessAPI.PROCESS_INFORMATION();
try
{
NativeProcessAPI.SECURITY_ATTRIBUTES oSecurityAttributes = new NativeProcessAPI.SECURITY_ATTRIBUTES();
oSecurityAttributes.Length = Marshal.SizeOf(oSecurityAttributes);
bool result = NativeProcessAPI.DuplicateTokenEx(
UserToken,
NativeProcessAPI.GENERIC_ALL_ACCESS,
ref oSecurityAttributes,
(int)NativeProcessAPI.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)NativeProcessAPI.TOKEN_TYPE.TokenPrimary,
ref hDupedToken
);
if (!result)
{
return null;
}
NativeProcessAPI.STARTUPINFO oStartupInfo = new NativeProcessAPI.STARTUPINFO();
oStartupInfo.cb = Marshal.SizeOf(oStartupInfo);
oStartupInfo.lpDesktop = String.Empty;
result = NativeProcessAPI.CreateProcessAsUser(
hDupedToken,
null,
App + " " + AppParameters,
ref oSecurityAttributes, ref oSecurityAttributes,
false, 0, IntPtr.Zero,
AppPath, ref oStartupInfo, ref oProcessInformation
);
if (result)
{
try
{
int ProcessID = oProcessInformation.dwProcessID;
try
{
ResultProcess = System.Diagnostics.Process.GetProcessById(ProcessID);
}
catch
{
ResultProcess = null;
}
}
catch (Exception ex)
{
ResultProcess = null;
}
}
}
catch
{
ResultProcess = null;
}
finally
{
if (oProcessInformation.hProcess != IntPtr.Zero)
NativeProcessAPI.CloseHandle(oProcessInformation.hProcess);
if (oProcessInformation.hThread != IntPtr.Zero)
NativeProcessAPI.CloseHandle(oProcessInformation.hThread);
if (hDupedToken != IntPtr.Zero)
NativeProcessAPI.CloseHandle(hDupedToken);
}
return ResultProcess;
}
Any ideas how I could start my processes in the user's contexts and not in the context of LocalSystem?
Thanks a lot!

Leaving this here for anyone else wondering how to do this: CreateEnvironmentBlock is what you need to use.
DuplicateTokenEx(userToken, MAXIMUM_ALLOWED | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, IntPtr.Zero, SecurityIdentification, TokenPrimary, out dupUserToken);
CreateEnvironmentBlock(out envBlock, dupUserToken, false);
CreateProcessAsUserW(dupUserToken, null, cmdLine, IntPtr.Zero, IntPtr.Zero, false,
(uint)(CreateProcessFlags.CREATE_NEW_CONSOLE | CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT),
envBlock, processDir, ref startupInfo, out procInfo);

Ok I have found a workaround: I switched to using the USERS hive instead of the CURRENT_USER hive by using the SID provided by WindowsIdentity:
Microsoft.Win32.Registry.Users.OpenSubKey(System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString() + ..., true)
This works perfectly although it feels a bit uncomfortable to get environment variables from the user's "environment" and "volatile environment" registry paths instead of just using .Net's built-in functions...
But thanks a lot for your help ;-)
EDIT:
I will not mark this as an answer because it is a) my own solution and b) just a workaround

Related

c# search possible reasons for: impersonation fails with logon type LOGON32_LOGON_NEW_CREDENTIALS

We have in our domain an application where people can upload files to a dfs share. As information is sensitive, only a service account has read/write access to that share. That means, we have to impersonate the filestream to be able to up-(and down-) load the files.
the code we're using is quite standard
public Impersonation(string Username, string Password, string Domain, logonType LogonType = LogonType.LOGON32_LOGON_INTERACTIVE, ProviderType ProviderType = ProviderType.LOGON32_PROVIDER_DEFAULT)
{
int lastError;
try {
UndoImpersonation();
wic = WindowsIdentity.Impersonate(IntPtr.Zero);
if (!WindowsIdentity.GetCurrent().Name.ToLower().Contains(Username.ToLower())) {
if (LogonUser(Username, Domain, Password, (int)LogonType, (int)ProviderType, out token)) {
LogWriter.Debug("Token after LogonUser: " + token.ToString());
if (DuplicateToken(token, (int)ImpersonationLevel.SecurityImpersonation, out tokenDuplicate)) {
LogWriter.Debug("Duplicate Token: " + tokenDuplicate.ToString());
copyIdentity = new WindowsIdentity(tokenDuplicate);
wic = copyIdentity.Impersonate();
} else {
lastError = Marshal.GetLastWin32Error();
LogWriter.Error("DuplicateToken failed with error: " + lastError);
throw new Win32Exception(lastError);
}
} else {
lastError = Marshal.GetLastWin32Error();
LogWriter.Error("LogonUser failed with error: " + lastError);
throw new Win32Exception(lastError);
}
} else {
LogWriter.Error("Double impersonation detected: WindowsIdentity: " + WindowsIdentity.GetCurrent().Name.ToLower() + " //// Username: " + Username.ToLower());
}
} catch (Exception ex) {
LogWriter.Error(ex);
throw;
}
}
we are calling that procedure with LOGON32_LOGON_NEW_CREDENTIALS and LOGON32_PROVIDER_WINNT50.
That works for most people (>90%), and most of them have no admin rights on their machine.
But in some cases, we see a "part of the path not found" exception in our application log when creating the corresponding stream. In that case, we retry to create the streams with other logon_types (e.g. LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_WINNT40 and/or -50). We do not get any other error but "a part of the path could not be found".
but in our error log, we see in that case that:
impersonating with "new credentials" is logged with the original user's accound. that means, that impersonation didn't take place! (I repeat: it works for almost the whole company)
the other tries (using interactive) are logged as service account, that means the impersonation succeeded, but that kind of logon type has no rigths to access the share (because we still get that "part of the path not found").
My question is: What can be the reason or where can we look, why some people can't impersonate using "new credentials"?
Can there be a group policy, active directory settings, registry settings?

C# Nested impersonation - impersonate a user whilst already impersonating another

I have a fairly odd requirement to be able to impersonate a user, when I'm already impersonating another, using C#.
I'm writing an app to allow the management of Active Directory users. This app will provide the ability for anyone in the company to view and maintain certain details about themselves (some of which will not actually be saved to Active Directory, but some of which will), for managers to be able to view and maintain details about their team, and for HR to be able to view and maintain details about anyone.
For obvious reasons I don't want to develop or test this against the live domain. We have recently ported all users over to this domain from another domain, which means I can actually test against the old domain without affecting anything. However, to enable me to do this I have to impersonate my old account on the old domain, which I do on loading the application.
Although for me everything will work fine as I'm setup as a domain admin, going forward obviously not all users will be domain admins, and won't be able to write to AD under their own account, and therefore we have another domain admin user setup specifically for this application, whenever data needs to be saved to AD that user is impersonated. This was working great before when I was testing against an Active Directory I'd setup on a virtual machine because I was logging onto the local domain, however that didn't allow me to step through the code in Visual Studio so debugging was slow, and hence I've stopped using that virtual machine and am using this old domain. Now I'm already impersonating another user (i.e. my old domain account), when it then tries to impersonate the domain admin user it fails with an "System.Security.SecurityException: Access is denied." exception. The line this fails on is just writing out some debugging information using "WindowsIdentity.GetCurrent().Name".
If I change my code so I'm actually logging in using the new domain admin rather than my old account, the first time it goes through it logs in successfully (so the credentials are correct), however when it then goes through and tries to do the same again to write to AD it fails with the above exception. Therefore I think it must be a problem with trying to do a nested impersonate.
Is it possible to do a nested impersonate?
Below is the code I'm using:
private static WindowsImpersonationContext ImpersonateUser(out string result, string sUsername,
string sDomain, string sPassword)
{
// initialize tokens
var pExistingTokenHandle = new IntPtr(0);
var pDuplicateTokenHandle = new IntPtr(0);
// if domain name was blank, assume local machine
if (sDomain == "")
{
sDomain = Environment.MachineName;
}
try
{
result = null;
const int logon32ProviderDefault = 0;
// create token
const int logon32LogonInteractive = 2;
// get handle to token
var bImpersonated = LogonUser(sUsername, sDomain, sPassword,
logon32LogonInteractive,
logon32ProviderDefault,
ref pExistingTokenHandle);
// did impersonation fail?
if (!bImpersonated)
{
var nErrorCode = Marshal.GetLastWin32Error();
result = "LogonUser() failed with error code: " + nErrorCode + "\r\n";
}
// Get identity before impersonation
result += string.Format("Before impersonation: {0}\r\n", WindowsIdentity.GetCurrent().Name);
var bRetVal = DuplicateToken(pExistingTokenHandle, (int)SecurityImpersonationLevel.SecurityImpersonation,
ref pDuplicateTokenHandle);
// did DuplicateToken fail?
if (bRetVal)
{
// create new identity using new primary token
var newId = new WindowsIdentity(pDuplicateTokenHandle);
var impersonatedUser = newId.Impersonate();
// check the identity after impersonation
result += "After impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n";
return impersonatedUser;
}
else
{
var nErrorCode = Marshal.GetLastWin32Error();
CloseHandle(pExistingTokenHandle); // close existing handle
result += "DuplicateToken() failed with error code: " + nErrorCode + "\r\n";
return null;
}
}
finally
{
// close handle(s)
if (pExistingTokenHandle != IntPtr.Zero)
{
CloseHandle(pExistingTokenHandle);
}
if (pDuplicateTokenHandle != IntPtr.Zero)
{
CloseHandle(pDuplicateTokenHandle);
}
}
}
When this is called for the nested impersonation which fails, "bImpersonated" is actually "true", as is bRetVal, which suggests its worked, however when it gets to "WindowsIdentity.GetCurrent().Name" it fails with the exception above.
I hope this makes sense, and would appreciate any assistance.

SetupDiChangeState throws Access Denied

My user is an Administrator (I see it in the configuration panel), the below code throws a Win32Exception in which it says Access Denied, how can I change this (Win7 32 bits) ?
static Guid VideoGuid = new Guid("4d36e968-e325-11ce-bfc1-08002be10318");
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
static void Main(string[] args)
{
SafeDeviceHandle handle = null;
try
{
handle = NativeMethods.SetupDiGetClassDevs(ref VideoGuid, IntPtr.Zero, IntPtr.Zero, NativeMethods.DIGCF.PRESENT);
var data = new NativeMethods.SP_DEVINFO_DATA().Initialize();
var param = new NativeMethods.SP_PROPCHANGE_PARAMS().Initialize();
param.ClassInstallHeader.InstallFunction = 0x12;
param.StateChange = NativeMethods.DICS.ENABLE; // 0x01
param.Scope = NativeMethods.DICS_GLOBAL.GLOBAL; // 0x01
param.HwProfile = 0;
RunWin32Method(() => NativeMethods.SetupDiEnumDeviceInfo(handle, 0u, out data));
RunWin32Method(() => NativeMethods.SetupDiSetClassInstallParams(handle, ref data, ref param, (UInt32)Marshal.SizeOf(param)));
RunWin32Method(() => NativeMethods.SetupDiChangeState(handle, ref data));
}
catch
{
var w = new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
if (handle != null && (!handle.IsInvalid))
handle.Close();
}
}
static void RunWin32Method(Func<bool> f)
{
if (!f())
{
Debug.WriteLine(new Win32Exception(Marshal.GetLastWin32Error()).Message);
}
}
If you want more code, just ask :-)
Thanks
Recapping the comment trail, a user in the Administrator group doesn't have admin rights on Vista/Server 2008 and later unless the process runs elevated. A manifest is required to get Windows to display the UAC elevation prompt.
This cannot work for programs that are started at login by the Run registry key or the Startup folder. Windows refuses to display the elevation prompt because the user cannot accurately guess exactly what program asked for the elevation. Code-signing the program with a certificate may fix this since that permits Windows to verify and display the program owner, never actually tried that.
Workarounds for such programs are activating it as a service or a scheduled task. Neither of which requires the manifest. The theory behind this seeming oddity is that it already requires elevation to get a service or scheduled task installed.

Find free space available for each individual network share on a server that physically exist within the same directory

We have a server that has a network share for each user in our organization to back up their files to. All of the shares sit physically on the server in the same folder. ie D:\UserArchives\user1$, d:\UserArchives\user2$. All shares are suffixed with a dollar sign to be hidden.
I am trying to pull the amount of free space that is available to each user in their respective share using c#.
I am enumerating the shares using the chosen answer from here: Enumerating Network Shares with C#.
I have been trying to pull the free space using GetDiskFreeSpaceEx from here: http://social.msdn.microsoft.com/Forums/ar-SA/csharpgeneral/thread/b7db7ec7-34a5-4ca6-89e7-947190c4e043
If I run my app as user1 it pulls the right amount of free space for their share, and I get a security exception because they cannot access user2's share, and vice versa. This is expected. When I run my app as an administration account that has access to both share, I get back the amount of free space for user1 + user2.
This too makes sense as the documentation for GetDiskFreeSpaceEx states:
"Retrieves information about the amount of space that is available on a disk volume, which is the total amount of space, the total amount
of free space, and the total amount of free space available to the
user that is associated with the calling thread."
My question is how do I do achieve this in C# across the network. I do
not want a solution that is local to the server.
My code so far:
static void Main(string[] args)
{
string server = "filesvr1";
try
{
//Impersonator is a class for impersonating a different windows account
using (new Impersonator("UserName", "Domain", "Password"))
{
GetUserShareInfo(server);
}
}
catch (Exception ex)
{
Console.WriteLine("ERROR: " + ex.Message);
}
}
static void GetUserShareInfo(string server)
{
ShareCollection shi;
if (server != null && server.Trim().Length > 0)
{
Console.WriteLine("\nShares on {0}:", server);
shi = ShareCollection.GetShares(server);
if (shi != null)
{
foreach(Share si in shi)
{
// If you don't have permissions to the share you will get security exceptions.
if (si.IsFileSystem)
{
string userName = si.NetName.Substring(0, si.NetName.Length - 1);
try
{
//Network Share Size
Console.WriteLine(FreeSpace("\\\\" + server + "\\" + si.Root.Name));
}
catch (Exception ex)
{
Console.WriteLine(userName + " - " + ex.Message);
}
}
}
}
else
Console.WriteLine("Unable to enumerate the shares on {0}.\n"
+ "Make sure the machine exists, and that you have permission to access it.", server);
}
}
public static long FreeSpace(string folderName)
{
if (string.IsNullOrEmpty(folderName))
{
throw new ArgumentNullException("folderName");
}
if (!folderName.EndsWith("\\"))
{
folderName += '\\';
}
long free = 0, dummy1 = 0, dummy2 = 0;
if (GetDiskFreeSpaceEx(folderName, ref free, ref dummy1, ref dummy2))
{
return free;
}
else
{
return -1;
}
}
[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("Kernel32", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetDiskFreeSpaceEx
(
string lpszPath, // Must name a folder, must end with '\'.
ref long lpFreeBytesAvailable,
ref long lpTotalNumberOfBytes,
ref long lpTotalNumberOfFreeBytes
);

How to clear System.Windows.Forms.WebBrowser session data?

How can I clear current session data (cookies, cached data, auth sessions, etc) without restarting the application?
Update: I'm talking about WebBrowser control in Windows.Forms, not the ASP.Net session.
To clear session (such as HttpOnly cookies), you can use InternetSetOption() from wininet.dll.
private const int INTERNET_OPTION_END_BROWSER_SESSION = 42;
[DllImport("wininet.dll", SetLastError = true)]
private static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int lpdwBufferLength);
and use this method whenever need to clear session.
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_END_BROWSER_SESSION, IntPtr.Zero, 0);
webBrowser1.Document.Window.Navigate(url);
I tried everything to clear the form data so the next user would not see the previous email address, etc. I ended up doing this to clear the cookies...
string[] theCookies = System.IO.Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.Cookies));
foreach (string currentFile in theCookies)
{
try
{
System.IO.File.Delete(currentFile);
}
catch (Exception ex)
{
}
}
If you have javascript enabled you can just use this code snippet to clear to clear the cookies for the site the webbrowser is currently on (I haven't yet found a way to clear session cookies other than this).
webBrowser.Navigate("javascript:void((function(){var a,b,c,e,f;f=0;a=document.cookie.split('; ');for(e=0;e<a.length&&a[e];e++){f++;for(b='.'+location.host;b;b=b.replace(/^(?:%5C.|[^%5C.]+)/,'')){for(c=location.pathname;c;c=c.replace(/.$/,'')){document.cookie=(a[e]+'; domain='+b+'; path='+c+'; expires='+new Date((new Date()).getTime()-1e11).toGMTString());}}}})())")
It's derived from this bookmarklet for clearing cookies.
In addition to this, you can delete the contents of the "C:\Documents and Settings\username\Cookies" folder (minus the index.dat, which is usually locked).
As for the cached data, it should be sufficient to just delete all of the files in "C:\Documents and Settings\username\Local Settings\Temporary Internet Files".
If you really need to be able to clear the cookies for all sites, you're probably better off using something like the axWebBrowser control in the long run.
Private Const INTERNET_OPTION_END_BROWSER_SESSION As Integer = 42
<DllImport("wininet.dll", SetLastError:=True)>
Public Shared Function InternetSetOption(hInternet As IntPtr, dwOption As Integer, lpBuffer As IntPtr, lpdwBufferLength As Integer) As Boolean
End Function
Private Sub WebBrowserFormName_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Closed
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_END_BROWSER_SESSION, IntPtr.Zero, 0)
End Sub
Just posting for someone looking for this answer in VB.
Happy coding!!!
webBrowser1.Document.Cookies = "" won't work. This call will not clear the cookie. webBrowser1.Document.Cookies = just works as document.cookie in javascript.
You should find the cookie you want to clear, sa 'Session', use
webBrowser1.Document.Cookies = "Session = ''";
It will just set the cookie to '', as you want.
Following solution worked for me -
webBrowser1.Document.ExecCommand("ClearAuthenticationCache", false, null);
This was suggested in following post for deleting cookies - https://stackoverflow.com/a/21512662/6291511
You can find more info regarding this here - https://msdn.microsoft.com/en-us/library/system.windows.forms.htmldocument.execcommand(v=vs.110).aspx
Hope it helps!!!
You have to realise that the way "session state" is tracked, from the point of view of a web server, is by giving the client browser a cookie with a session-id in it. When the browser posts back to the server, that cookie allows the server to associate the request with a stored session state.
So the solution is to clear the cookies of the webBrowser control. Eg webBrowser1.Document.Cookies = "", that should work I think.
ASP.NET also has what it calls "cookieless sessions", which work by adding the session id to the url. So if that's the mechanism used by the server, you could try to filter that out of the url. But you won't see that much, it's mostly the cookie based session state.
Windows 7 uses index.dat files to store cookies and history so that Bill and his freinds at CIA central can snoop on you and have done all they can to ensure you can not delete these files and that after taking copies because 'Special Folders' are used and the .Dat files remain locked whilst windows is running.
This is not a perfect solution but it works to some degree with the full file names being in a List.
int DeletedCount = 0;
int CouldNotDelete = 0;
KillExplorer();
foreach (string DatFile in DatFiles)
{//Do not put break point or step into the code else explorer will start and the file will become locked again
DirectoryInfo DInfo=new DirectoryInfo(DatFile.Replace("index.dat",""));
FileAttributes OldDirAttrib = DInfo.Attributes;
DInfo.Attributes = FileAttributes.Normal;//Set to normal else can not delete
FileInfo FInfo = new FileInfo(DatFile);
FileAttributes OldFileAttrib = FInfo.Attributes;
SetAttr(FInfo, FileAttributes.Normal);
TryDelete(FInfo);
SetAttr(FInfo, OldFileAttrib);//Sets back to Hidden,system,directory,notcontentindexed
if (File.Exists(DatFile))
CouldNotDelete++;
else
DeletedCount++;
}
if (DatFiles.Count>0)//Lets get explorer running again
System.Diagnostics.Process.Start(DatFiles[DatFiles.Count - 1].Replace("index.dat", ""));
else
System.Diagnostics.Process.Start("explorer");
System.Windows.Forms.MessageBox.Show("Deleted " + DeletedCount + " Index.dat files with " + CouldNotDelete + " Errors");
return "Deleted " + DeleteFileCount + " Files ";
}
private void KillExplorer()
{
foreach (Process P in Process.GetProcesses())
{//Kill both these process because these are the ones locking the files
if (P.ProcessName.ToLower() == "explorer")
P.Kill();
if (P.ProcessName.ToLower() == "iexplore")
P.Kill();
}
}
private bool TryDelete(FileInfo Info)
{
try
{
Info.Delete();
return true;
}
catch
{return false;}
}
private void SetAttr(FileInfo Info,FileAttributes Attr)
{
try
{
Info.Attributes = Attr;
}
catch { }
}

Categories

Resources