How to open a website URL in browser without of Process.start(...) :
System.Diagnostics.Process.Start(#"http://www.google.com");
I can not use Process.Start() in Windows Service , I do not no know why.
See the answer to the question "How can a Windows service execute a GUI application?":
use WTSEnumerateSessions to find the right desktop, then CreateProcessAsUser to start the application on that desktop
Note also the opinion that you shouldn't do this :)
If all you are doing is launching a URL, the command might be
cmd.exe /c start http://example.com/
If you want to suppress the briefly-displayed Command Prompt window, you can set the wShowWindow field of the STARTUPINFO structure to SW_HIDE, or in .NET set the ProcessStartInfo.WindowStyle property to ProcessWindowStyle.Hidden.
Services run in an isolated session. That session has its own desktop, much like the login screen. A user however can never look at it. This is a very basic security measure, services typically run with a very privileged account. You can use Process.Start(), the user just will never be able to see the UI of the program.
This is not a real problem, it makes zero sense to start a browser in a service.
Services don't run using the Desktop, so I would not recommend trying to open a browser.
If you just need information from the website in question, in order to download and parse information, you might want to consider using a WebClient instead of a browser. This will allow you to download from any Uri, and parse the results in a service.
Services on Windows 7 CAN'T interact with desktop in any way.
So, what you need is a small process that will use some method of communication with the service, start it for the user that is logged on and wait for service message. You can start it in Startup group or any other means for that purpose, just make it very small and unnoticeable. Of course, if you want it to bi noticed, you can use tray icon for it or even some status window.
Service message could be something as simple as writing a file with an URL in the directory that is shared between service and the user process. That way you'll get what you need and stay compatible with most Windows versions.
Fixed code from here: http://18and5.blogspot.com/2008/01/i-hope-my-frustration-can-help-someone.html
I had to fix the parameter handling that the example below will actually work.
using System;
using System.Reflection;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Common.Utilities.Processes
{
public class ProcessUtilities
{
/*** Imports ***/
#region Imports
[DllImport("advapi32.dll", EntryPoint = "AdjustTokenPrivileges", SetLastError = true)]
public static extern bool AdjustTokenPrivileges(IntPtr in_hToken, [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 BufferLength, IntPtr PreviousState, IntPtr ReturnLength);
[DllImport("advapi32.dll", EntryPoint = "OpenProcessToken", SetLastError = true)]
public static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);
[DllImport("advapi32.dll", EntryPoint = "LookupPrivilegeValue", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);
[DllImport("userenv.dll", EntryPoint = "CreateEnvironmentBlock", SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out IntPtr out_ptrEnvironmentBlock, IntPtr in_ptrTokenHandle, bool in_bInheritProcessEnvironment);
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
[DllImport("wtsapi32.dll", EntryPoint = "WTSQueryUserToken", SetLastError = true)]
public static extern bool WTSQueryUserToken(UInt32 in_nSessionID, out IntPtr out_ptrTokenHandle);
[DllImport("kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId", SetLastError = true)]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll", EntryPoint = "WTSQuerySessionInformation", SetLastError = true)]
public static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr ppBuffer, out uint pBytesReturned);
[DllImport("wtsapi32.dll", EntryPoint = "WTSFreeMemory", SetLastError = false)]
public static extern void WTSFreeMemory(IntPtr memory);
[DllImport("userenv.dll", EntryPoint = "LoadUserProfile", SetLastError = true)]
public static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool CreateProcessAsUser(IntPtr in_ptrUserTokenHandle, string in_strApplicationName, string in_strCommandLine, ref SECURITY_ATTRIBUTES in_oProcessAttributes, ref SECURITY_ATTRIBUTES in_oThreadAttributes, bool in_bInheritHandles, CreationFlags in_eCreationFlags, IntPtr in_ptrEnvironmentBlock, string in_strCurrentDirectory, ref STARTUPINFO in_oStartupInfo, ref PROCESS_INFORMATION in_oProcessInformation);
#endregion //Imports
/*** Delegates ***/
/*** Structs ***/
#region Structs
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public uint m_nLowPart;
public uint m_nHighPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public int m_nPrivilegeCount;
public LUID m_oLUID;
public int m_nAttributes;
}
[StructLayout(LayoutKind.Sequential)]
public 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;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessID;
public Int32 dwThreadID;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public Int32 Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
#endregion //Structs
/*** Classes ***/
/*** Enums ***/
#region Enums
public enum CreationFlags
{
CREATE_SUSPENDED = 0x00000004,
CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
}
public enum WTS_INFO_CLASS
{
WTSInitialProgram,
WTSApplicationName,
WTSWorkingDirectory,
WTSOEMId,
WTSSessionId,
WTSUserName,
WTSWinStationName,
WTSDomainName,
WTSConnectState,
WTSClientBuildNumber,
WTSClientName,
WTSClientDirectory,
WTSClientProductId,
WTSClientHardwareId,
WTSClientAddress,
WTSClientDisplay,
WTSClientProtocolType
}
#endregion //Enums
/*** Defines ***/
#region Defines
private const int TOKEN_QUERY = 0x08;
private const int TOKEN_ADJUST_PRIVILEGES = 0x20;
private const int SE_PRIVILEGE_ENABLED = 0x02;
public const int ERROR_NO_TOKEN = 1008;
public const int RPC_S_INVALID_BINDING = 1702;
#endregion //Defines
/*** Methods ***/
#region Methods
/*
If you need to give yourself permissions to inspect processes for their modules,
and create tokens without worrying about what account you're running under,
this is the method for you :) (such as the token privilege "SeDebugPrivilege")
*/
static public bool AdjustProcessTokenPrivileges(IntPtr in_ptrProcessHandle, string in_strTokenToEnable)
{
IntPtr l_hProcess = IntPtr.Zero;
IntPtr l_hToken = IntPtr.Zero;
LUID l_oRestoreLUID;
TOKEN_PRIVILEGES l_oTokenPrivileges;
Debug.Assert(in_ptrProcessHandle != IntPtr.Zero);
//Get the process security token
if (false == OpenProcessToken(in_ptrProcessHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out l_hToken))
{
return false;
}
//Lookup the LUID for the privilege we need
if (false == LookupPrivilegeValue(String.Empty, in_strTokenToEnable, out l_oRestoreLUID))
{
return false;
}
//Adjust the privileges of the current process to include the new privilege
l_oTokenPrivileges.m_nPrivilegeCount = 1;
l_oTokenPrivileges.m_oLUID = l_oRestoreLUID;
l_oTokenPrivileges.m_nAttributes = SE_PRIVILEGE_ENABLED;
if (false == AdjustTokenPrivileges(l_hToken, false, ref l_oTokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero))
{
return false;
}
return true;
}
/*
Start a process the simplest way you can imagine
*/
static public int SimpleProcessStart(string in_strTarget, string in_strArguments)
{
Process l_oProcess = new Process();
Debug.Assert(l_oProcess != null);
l_oProcess.StartInfo.FileName = in_strTarget;
l_oProcess.StartInfo.Arguments = in_strArguments;
if (true == l_oProcess.Start())
{
return l_oProcess.Id;
}
return -1;
}
/*
All the magic is in the call to WTSQueryUserToken, it saves you changing DACLs,
process tokens, pulling the SID, manipulating the Windows Station and Desktop
(and its DACLs) - if you don't know what those things are, you're lucky and should
be on your knees thanking God at this moment.
DEV NOTE: This method currently ASSumes that it should impersonate the user
who is logged into session 1 (if more than one user is logged in, each
user will have a session of their own which means that if user switching
is going on, this method could start a process whose UI shows up in
the session of the user who is not actually using the machine at this
moment.)
DEV NOTE 2: If the process being started is a binary which decides, based upon
the user whose session it is being created in, to relaunch with a
different integrity level (such as Internet Explorer), the process
id will change immediately and the Process Manager will think
that the process has died (because in actuality the process it
launched DID in fact die only that it was due to self-termination)
This means beware of using this service to startup such applications
although it can connect to them to alarm in case of failure, just
make sure you don't configure it to restart it or you'll get non
stop process creation ;)
*/
static public int CreateUIProcessForServiceRunningAsLocalSystem(string in_strTarget, string in_strArguments)
{
PROCESS_INFORMATION l_oProcessInformation = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES l_oSecurityAttributes = new SECURITY_ATTRIBUTES();
STARTUPINFO l_oStartupInfo = new STARTUPINFO();
PROFILEINFO l_oProfileInfo = new PROFILEINFO();
IntPtr l_ptrUserToken = new IntPtr(0);
uint l_nActiveUserSessionId = 0xFFFFFFFF;
string l_strActiveUserName = "";
int l_nProcessID = -1;
IntPtr l_ptrBuffer = IntPtr.Zero;
uint l_nBytes = 0;
try
{
//The currently active user is running what session?
l_nActiveUserSessionId = WTSGetActiveConsoleSessionId();
if (l_nActiveUserSessionId == 0xFFFFFFFF)
{
throw new Exception("ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSGetActiveConsoleSessionId failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString());
}
if (false == WTSQuerySessionInformation(IntPtr.Zero, (int)l_nActiveUserSessionId, WTS_INFO_CLASS.WTSUserName, out l_ptrBuffer, out l_nBytes))
{
int l_nLastError = Marshal.GetLastWin32Error();
//On earlier operating systems from Vista, when no one is logged in, you get RPC_S_INVALID_BINDING which is ok, we just won't impersonate
if (l_nLastError != RPC_S_INVALID_BINDING)
{
throw new Exception("ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSQuerySessionInformation failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString());
}
//No one logged in so let's just do this the simple way
return SimpleProcessStart(in_strTarget, in_strArguments);
}
l_strActiveUserName = Marshal.PtrToStringAnsi(l_ptrBuffer);
WTSFreeMemory(l_ptrBuffer);
//We are supposedly running as a service so we're going to be running in session 0 so get a user token from the active user session
if (false == WTSQueryUserToken((uint)l_nActiveUserSessionId, out l_ptrUserToken))
{
int l_nLastError = Marshal.GetLastWin32Error();
//Remember, sometimes nobody is logged in (especially when we're set to Automatically startup) you should get error code 1008 (no user token available)
if (ERROR_NO_TOKEN != l_nLastError)
{
//Ensure we're running under the local system account
WindowsIdentity l_oIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
if ("NT AUTHORITY\\SYSTEM" != l_oIdentity.Name)
{
throw new Exception("ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSQueryUserToken failed and querying the process' account identity results in an identity which does not match 'NT AUTHORITY\\SYSTEM' but instead returns the name:" + l_oIdentity.Name + " GetLastError returns: " + l_nLastError.ToString());
}
throw new Exception("ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSQueryUserToken failed, GetLastError returns: " + l_nLastError.ToString());
}
//No one logged in so let's just do this the simple way
return SimpleProcessStart(in_strTarget, in_strArguments);
}
//Create an appropriate environment block for this user token (if we have one)
IntPtr l_ptrEnvironment = IntPtr.Zero;
Debug.Assert(l_ptrUserToken != IntPtr.Zero);
if (false == CreateEnvironmentBlock(out l_ptrEnvironment, l_ptrUserToken, false))
{
throw new Exception("ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to CreateEnvironmentBlock failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString());
}
l_oSecurityAttributes.Length = Marshal.SizeOf(l_oSecurityAttributes);
l_oStartupInfo.cb = Marshal.SizeOf(l_oStartupInfo);
//DO NOT set this to "winsta0\\default" (even though many online resources say to do so)
l_oStartupInfo.lpDesktop = String.Empty;
l_oProfileInfo.dwSize = Marshal.SizeOf(l_oProfileInfo);
l_oProfileInfo.lpUserName = l_strActiveUserName;
//Remember, sometimes nobody is logged in (especially when we're set to Automatically startup)
if (false == LoadUserProfile(l_ptrUserToken, ref l_oProfileInfo))
{
throw new Exception("ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to LoadUserProfile failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString());
}
if (false == CreateProcessAsUser(l_ptrUserToken, in_strTarget, in_strTarget + " " + in_strArguments, ref l_oSecurityAttributes, ref l_oSecurityAttributes, false, CreationFlags.CREATE_UNICODE_ENVIRONMENT, l_ptrEnvironment, null, ref l_oStartupInfo, ref l_oProcessInformation))
{
//System.Diagnostics.EventLog.WriteEntry( "CreateProcessAsUser FAILED", Marshal.GetLastWin32Error().ToString() );
throw new Exception("ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to CreateProcessAsUser failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString());
}
l_nProcessID = l_oProcessInformation.dwProcessID;
}
catch (Exception l_oException)
{
throw new Exception("ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "An unhandled exception was caught spawning the process, the exception was: " + l_oException.Message);
}
finally
{
if (l_oProcessInformation.hProcess != IntPtr.Zero)
{
CloseHandle(l_oProcessInformation.hProcess);
}
if (l_oProcessInformation.hThread != IntPtr.Zero)
{
CloseHandle(l_oProcessInformation.hThread);
}
}
return l_nProcessID;
}
#endregion //Methods
}
}
And this is the call you have to do:
Common.Utilities.Processes.ProcessUtilities.CreateUIProcessForServiceRunningAsLocalSystem(
#"C:\Windows\System32\cmd.exe",
" /c \"start http://www.google.com\""
);
I have tested it on my own system and it worked like a charm (Windows 7 x64 with enabled UAC)
However I recommend to create a tiny stub application which will not make the cmd window flash. And which will accept the url as parameter.
Plus you should not use the hardcoded path to cmd.exe like I did in the example. However the code works, the rest should be clear I hope :-)
HTH
It is a design problem if a service has to interact with the user (as example, services are started before logon).
I usally solve this problem by making a small program that starts with the user's session. If I have to interact with the user in the service, it will first look if the user level program is running and if it is, it will send commands to it.
If you are interested in the response try:
string url = #"http://www.google.com";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
response = (HttpWebResponse)request.GetResponse();
Stream receiveStream = response.GetResponseStream();
StreamReader reader = new StreamReader(receiveStream);
Why not try to Impersonate a user who has logon privilages on the machine only for that particular piece of code that starts the web browser using System.Diagnostics.Process.Start.
What exactly do you intend to do with browser once the web page gets loaded?
You can try to run your service with "interact with desktop" set. This should fix your problem, but may cause other issues like your service will stop when someone logs out of the main console.
Related
I have Zebra printer connected via USB and I'm trying to read printer's memory using command ^XA^HWR:^XZ. The command works on TCP/IP.
I'm not even sure if I have method header right.
[DllImport("winspool.Drv", EntryPoint = "ReadPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
static extern bool ReadPrinter(IntPtr hPrinter, IntPtr pBuf, int cbBuf, out int pNoBytesRead);
Method ReadPrinter always returns false and 0 read bytes. When I'm trying to get LastWin32Error, I'm getting a variety of 0, 6 or 63 (ERROR_SUCCESS - although it returns false and no data, ERROR_INVALID_HANDLE or ERROR_PRINT_CANCELLED), depending on that I'm trying. I've tried several method headers and different approaches but none of them lead to the success data read. I have 'bidirectional support' enabled and printer drivers installed. It may seem as duplicates of other threads, but I've already got through them and none of them was helpful.
Code snippet:
private static void SendBytesToPrinter(string printerName, IntPtr pointerBytes, int bytesCount)
{
int written = 0;
PrintResult printResult = new PrintResult();
IntPtr pointerPrinter = new IntPtr(0);
DOCINFOA docInfo = new DOCINFOA();
bool success = false;
docInfo.DocName = "RAW Document";
docInfo.DataType = "RAW";
try
{
if (OpenPrinter(printerName.Normalize(), out pointerPrinter, IntPtr.Zero))
{
if (StartDocPrinter(pointerPrinter, 1, docInfo))
{
if (StartPagePrinter(pointerPrinter))
{
success = WritePrinter(pointerPrinter, pointerBytes, bytesCount, out written);
EndPagePrinter(pointerPrinter);
}
EndDocPrinter(pointerPrinter);
}
// READ HERE
Int32 bufLen = 32;
IntPtr pStatus = Marshal.AllocCoTaskMem(bufLen);
Int32 statusLen = 0;
bool bSuccess = ReadPrinter(hPrintJob, pStatus, bufLen, out statusLen);
ClosePrinter(pointerPrinter);
}
} catch (Exception ex) { }
}
I solved it by using Visual Basic example, using FileStreams and StreamReaders. Thanks to #kunif for pointing out the sample code.
kernel32.dll is way to go instead of winspool.Drv
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern SafeFileHandle CreateFile(string lpFileName, EFileAccess dwDesiredAccess,
EFileShare dwShareMode, IntPtr lpSecurityAttributes,
ECreationDisposition dwCreationDisposition,
EFileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
private static string ReadUsbPort(StreamReader sr)
{
string readResult = string.Empty;
if (sr != null && sr.BaseStream != null)
{
do
{
readResult += sr.ReadLine();
readResult += "\r";
}
while (sr.EndOfStream == false);
}
return readResult;
}
Very useful is combination of
GUID_DEVINTERFACE_USB_DEVICE and WinUSBNet
Computer A is on Domain A
Computer B is on Domain B
Domain A and Domain B have a trust allowing Accounts to connect to the other domain's computers
Account A is Admin on Computer A and has Backup&Restore Privileges
Account B is Admin on Computer B and has Backup&Restore Privileges
Account A IS NOT Admin on Computer B and does not have Backup&Restore Privileges
Account B IS NOT Admin on Computer A and does not have Backup&Restore Privileges
A person owns both Account A and Account B.
They want to copy a file from Computer A to Computer B.
They wish to copy this file from and into a folder they do not have permissions for. Therefore, they must both enable Backup&Restore Privileges on their Access Token they got from LogonUser
It is not allowed to change the DACL to grant these permissions on any folders or files and instead both Account A and Account B must enable B&R privileges which is recognized by the Computer, only then will they be able to copy the file without Access Denied.
The problem is I have tried using NETWORK, INTERACTIVE, and NEW_CREDENTIALS Access Tokens and enabling privileges on them, but only NETWORK contains the privileges and they are already enabled by default. Then when I try to WindowsIdentity.Impersonate the NETWORK token and then call "CreateFile" to open the file with privileges, it fails and returns an invalid file handle of -1. I can use INTERACTIVE to read an unrestricted file but it didn't have Access Token Privileges needed ( SeBackupPrivilege & SeRestorePrivilege ) when it was returned from LogonUser. I assume once Impersonate happens it generates a Token that "might" have those privileges, but I assume that's based on the machine the code is running on.
Is there a way to Impersonate Access Token -> Enable Access Token B&R Privileges on the remote computers they would have normally when at that machine running as administrator which could be enabled.
OR
Is there a way to use the NETWORK Token with Impersonation to successfully copy the file from Computer on Domain A to Computer on Domain B. If I run the program as Account B who isn't admin trying to impersonate Account A with network credential, it appears to not work on impersonation
Below is demo code in a Console Application that emulates the situation. You must change parts of it to test it accordingly:
You must create the file paths and the file to be read.
You must edit permissions on "DENYTHEIMPERSONATINGUSERHERE" folder so the impersonating user account is denied and must use privileges.
You must enter an actual account credentials to get an AccessToken.
class Program
{
static void Main(string[] args)
{
//Credentials of Account A
string usernameA = "AccountA";
string DomainA = "DomainA.com";
SecureString passwordA = new SecureString();
passwordA.AppendChar('P');
passwordA.AppendChar('W');
passwordA.AppendChar('D');
passwordA.AppendChar('A');
IntPtr AccessToken = IntPtr.Zero;
//Getting Network Access Token. (Network is an Impersonation Token, most other types are returned as Primary Tokens)
AccessTokenHelper.LOGON32_LOGONERROR lgnCode = AccessTokenHelper.GetAccessToken(DomainA, usernameA, passwordA, AccessTokenHelper.LOGON32_LOGONTYPE.LOGON32_LOGON_NETWORK, AccessTokenHelper.LOGON32_LOGONPROVIDER.LOGON32_PROVIDER_WINNT50, out AccessToken);
/*
//Getting INTERACTIVE Access Token. (returns Primary Token)
//Impersonation will work but won't have enabled privileges so will error when trying to write file.
AccessTokenHelper.LOGON32_LOGONERROR lgnCode = AccessTokenHelper.GetAccessToken(DomainA, usernameA, passwordA, AccessTokenHelper.LOGON32_LOGONTYPE.LOGON32_LOGON_INTERACTIVE, AccessTokenHelper.LOGON32_LOGONPROVIDER.LOGON32_PROVIDER_WINNT50, out AccessToken);
*/
if(AccessToken == IntPtr.Zero || AccessToken.ToInt32() == -1)
{
//Not valid creds
System.Diagnostics.Debug.WriteLine(lgnCode.ToString());
return;
}
//Enable Token Security Privileges
AccessTokenHelper.ElevateSecurityPrivileges(AccessToken);
//List Enabled Token Privileges
List<string> tokenEnabledPrivileges = AccessTokenHelper.ListEnabledPrivileges(AccessToken);
foreach(string s in tokenEnabledPrivileges)
{
System.Diagnostics.Debug.WriteLine(s);
}
string sourceFile = #"C:\Temp\Test1\TestData.txt";
string destFile = #"C:\Temp\Test1\DENYTHEIMPERSONATINGUSERHERE\PrivCopy.txt";
bool bCopied = PrivilegedEnterpriseCopyFile(sourceFile, destFile, AccessToken, AccessToken);
System.Diagnostics.Debug.WriteLine("DID THE COPY WORK? " + bCopied.ToString().ToUpper());
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateFile(
string lpFileName,
uint dwDesiredAccess,
OPENFILE_SHAREACCESS dwShareMode, //If you are reading folders you better have OPENFILE_SHAREACCESS.FILE_SHARE_READ
IntPtr SecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
private enum OPENFILE_SHAREACCESS : uint
{
NO_SHARING = 0x00000000,
FILE_SHARE_READ = 0x00000001,
FILE_SHARE_WRITE = 0x00000002,
FILE_SHARE_DELETE = 0x00000004
};
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetFileSizeEx(IntPtr hFile, out long lpFileSize);
/// <summary>
/// Goal: Enterprise Level Copy.... can copy file data between remote machines between domains.
/// </summary>
public static bool PrivilegedEnterpriseCopyFile(string sourceAbsoluteFileName, string destAbsoluteFileName, IntPtr sourceAccessToken, IntPtr destAccessToken, bool bOverwrite = false)
{
const uint GENERIC_READ = 0x80000000;
const uint GENERIC_WRITE = 0x40000000; //If you are writing to a file, best allow both READ and WRITE because Windows Performance suffers without.
const uint CREATE_ALWAYS = 2; //Create and overwrite if necesarry
const uint OPEN_EXISTING = 3; //Open only if exists
const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; //This makes it take enabled Backup&Restore privileges into account
const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; //This helps speed up reading.
const uint FILE_FLAG_WRITE_THROUGH = 0x80000000; //This helps speed up writing!
//We pick a value that is the largest multiple of 4096 that is still smaller than the large object heap threshold (85K).
// The CopyTo/CopyToAsync buffer is short-lived and is likely to be collected at Gen0, and it offers a significant
// improvement in Copy performance.
const int DefaultCopyBufferSize = 81920; //80kb
bool bSuccess = false;
//Get File Ptr Handles. DO NOT ALLOW DELETE ON SOURCE OR DEST WHILE THIS IS HAPPENING.
//Get the file pointer by using an access token with access.
IntPtr pSource = IntPtr.Zero;
IntPtr pDest = IntPtr.Zero;
//As source user, connect for optimized READING
using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(sourceAccessToken))
{
pSource = CreateFile(sourceAbsoluteFileName, GENERIC_READ, OPENFILE_SHAREACCESS.FILE_SHARE_READ,
IntPtr.Zero, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, IntPtr.Zero);
}
if (pSource == IntPtr.Zero || pSource.ToInt32() == -1)
{
//Failed to get Source, return false
return bSuccess;
}
//Get Dest Path
string DestPath = Path.GetDirectoryName(destAbsoluteFileName);
string DestFileName = Path.GetFileName(destAbsoluteFileName);
//As dest user
using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(destAccessToken))
{
try
{
bool bProceed = true;
if (!bOverwrite)
{
//We don't want to overwrite existing file, ensure it doesn't exist.
List<string> files = Directory.EnumerateFiles(DestPath).ToList();
if (files.Any(s => s.Equals(DestFileName, StringComparison.OrdinalIgnoreCase)))
{
//File exists, do not proceed
bProceed = false;
}
}
//Do we proceed?
if (bProceed)
{
//Create/Overwrite existing File
pDest = CreateFile(destAbsoluteFileName, GENERIC_READ | GENERIC_WRITE, OPENFILE_SHAREACCESS.NO_SHARING,
IntPtr.Zero, CREATE_ALWAYS,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_WRITE_THROUGH, IntPtr.Zero);
}
}
catch(Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
//If we successfully have both File Handles, we can proceed with Reading from source and writing to Dest.
//If valid file pointers!!!
if (pSource != IntPtr.Zero && pSource.ToInt32() != -1 &&
pDest != IntPtr.Zero && pDest.ToInt32() != -1)
{
//Put Handle into more supported SafeFileHandle Ptr.
SafeFileHandle safeSourcePtr = new SafeFileHandle(pSource, false); //We will close handle manually
SafeFileHandle safeDestPtr = new SafeFileHandle(pDest, false); //We will close handle manually
try
{
using (FileStream fsSource = new FileStream(safeSourcePtr, FileAccess.Read, DefaultCopyBufferSize))
{
using (FileStream fsDest = new FileStream(safeDestPtr, FileAccess.ReadWrite, DefaultCopyBufferSize))
{
//Here we read X bytes up to limit from fsSource and write to fsDest, until no more bytes!
// Read the source file into a byte array.
byte[] buffer = new byte[DefaultCopyBufferSize];
int bytesRead = -1;
while ((bytesRead = fsSource.Read(buffer, 0, buffer.Length)) > 0)
{
fsDest.Write(buffer, 0, bytesRead);
}
//Force data to be flushed to harddisk.
fsDest.Flush(true);
}
}
}
catch { }
//Compare File Size to know if we successfully wrote bytes to destination!
//We'll assume it'd error out if it didn't.
GetFileSizeEx(pSource, out long sourceSize);
GetFileSizeEx(pDest, out long destSize);
if (sourceSize == destSize)
{
//consider it a success
bSuccess = true;
}
}
if (pSource != IntPtr.Zero && pSource.ToInt32() != -1)
{
//Close file handle manually
CloseHandle(pSource);
}
if (pDest != IntPtr.Zero && pDest.ToInt32() != -1)
{
//Close file handle manually
CloseHandle(pDest);
}
return bSuccess;
}
}
public class AccessTokenHelper
{
public enum LOGON32_LOGONTYPE : int
{
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 enum LOGON32_LOGONPROVIDER : int
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3,
};
public enum LOGON32_LOGONERROR : int
{
ERROR_SUCCESS = 0,
ERROR_NO_LOGON_SERVERS = 1311,
ERROR_INVALID_ACCOUNT_NAME = 1315,
ERROR_LOGON_FAILURE = 1326,
ERROR_ACCOUNT_RESTRICTION = 1327,
ERROR_INVALID_LOGON_HOURS = 1328,
ERROR_INVALID_WORKSTATION_LOGONDENIED = 1329,
ERROR_PASSWORD_EXPIRED = 1330,
ERROR_ACCOUNT_DISABLED = 1331,
ERROR_INVALID_LOGON_TYPE = 1367,
ERROR_LOGON_NOT_GRANTED = 1380,
ERROR_NETLOGON_NOT_STARTED = 1792,
ERROR_ACCOUNT_EXPIRED = 1793,
ERROR_PASSWORD_MUST_CHANGE = 1907,
ERROR_ACCOUNT_LOCKED_OUT = 1909,
};
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private extern static bool LogonUser(string username, string domain, IntPtr password, LOGON32_LOGONTYPE logonType, LOGON32_LOGONPROVIDER logonProvider, out IntPtr token);
/// <summary>
/// Attempts to create Access token from information given.
/// </summary>
public static LOGON32_LOGONERROR GetAccessToken(string domain, string username, SecureString securepassword, LOGON32_LOGONTYPE eLOGONTYPE, LOGON32_LOGONPROVIDER eLOGONPROVIDER, out IntPtr token)
{
token = IntPtr.Zero;
// Marshal the SecureString to unmanaged memory. I hate doing this but it's currently most secure way offered by Microsoft to get Access Token from Credentials.
IntPtr passwordPtr = Marshal.SecureStringToGlobalAllocUnicode(securepassword);
bool bSuccess = LogonUser(username,
domain,
passwordPtr,
eLOGONTYPE,
eLOGONPROVIDER,
out token);
//return the error code, useful if not successful.
int errResult = Marshal.GetLastWin32Error();
// Zero-out and free the unmanaged string reference.
Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr);
return (LOGON32_LOGONERROR)errResult;
}
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeName(string lpSystemName, IntPtr lpLuid, StringBuilder lpName, ref int cchName);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeDisplayName(string systemName, string privilegeName, StringBuilder displayName, ref int cchDisplayName, out uint languageId);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out long lpLuid);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
[MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
uint prevStateBuffer,
IntPtr prevStateNA,
IntPtr prevBufferNA);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetTokenInformation(
IntPtr hToken,
uint TokenInformationClass,
IntPtr TokenInformation,
int TokenInformationLength,
out int ReturnLength);
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public uint LowPart;
public uint HighPart;
};
[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public int PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
};
[Flags]
public enum SE_PRIVILEGE_STATE : uint
{
SE_PRIVILEGE_DISABLED = 0x00,
SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x01,
SE_PRIVILEGE_ENABLED = 0x02,
SE_PRIVILEGE_REMOVED = 0x04,
SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000
};
/// <summary>
/// Will elevate Backup and Restore Privileges of Access Token (be it Primary Type or Impersonation Type) if Access Token has Privilege
/// 1) SeBackupPrivilege -- this grants read access regardless of DACL entries.
/// 2) SeRestorePrivilege -- this grants write access regardless of DACL entries.
/// 3) SeSecurityPrivilege -- this grants access to SACL for audits/security log.
/// </summary>
public static void ElevateSecurityPrivileges(IntPtr hToken, bool bEnabled = true)
{
SE_PRIVILEGE_STATE privSTATE = bEnabled ? SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED : SE_PRIVILEGE_STATE.SE_PRIVILEGE_DISABLED;
List<string> SecurityPrivNames = new List<string>() { "SeBackupPrivilege", "SeRestorePrivilege", "SeSecurityPrivilege" };
if (hToken != IntPtr.Zero)
{
AdjustAccessTokenPrivileges(hToken, SecurityPrivNames, privSTATE);
}
}
public static void AdjustAccessTokenPrivileges(IntPtr TokenHandle, List<string> PrivilegeNames, SE_PRIVILEGE_STATE eNewPrivilegeState, string remoteMachineName = null)
{
if (TokenHandle != IntPtr.Zero && PrivilegeNames != null && PrivilegeNames.Count > 0)
{
DataTable privDT = GetAccessTokenPrivilegesAsDataTable(TokenHandle, remoteMachineName);
if (privDT != null && privDT.Rows.Count > 0)
{
//If we have privileges, try to set state!
foreach (string privName in PrivilegeNames)
{
DataRow row = privDT.Select(string.Format("[{0}]='{1}'", privDT.Columns[1].ColumnName, privName)).FirstOrDefault();
if (row != null)
{
UpdateExistingTokenPrivilege(TokenHandle, row.Field<LUID>(0), eNewPrivilegeState);
}
}
}
}
}
private static bool UpdateExistingTokenPrivilege(IntPtr tokenHandle, LUID privLuid, SE_PRIVILEGE_STATE eNewPrivilegeState)
{
//Create our updated tokenPriv to send what privs we want updated.
TOKEN_PRIVILEGES tokenPrivs = new TOKEN_PRIVILEGES();
tokenPrivs.PrivilegeCount = 1;
tokenPrivs.Privileges = new LUID_AND_ATTRIBUTES[] { new LUID_AND_ATTRIBUTES() { Luid = privLuid, Attributes = (uint)eNewPrivilegeState } };
//Adjust Token Privilege!
bool bSuccess = AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivs, 0, IntPtr.Zero, IntPtr.Zero);
//Return result of trying to adjust token privilege.
return bSuccess;
}
public static DataTable GetAccessTokenPrivilegesAsDataTable(IntPtr TokenHandle, string remoteMachineName = null)
{
TOKEN_PRIVILEGES tokenPrivData = GetAccessTokenPrivileges(TokenHandle);
DataTable privDT = new DataTable();
privDT.Columns.Add("PrivilegeLUID", typeof(LUID));
privDT.Columns.Add("PrivilegeName");
privDT.Columns.Add("PrivilegeState", typeof(SE_PRIVILEGE_STATE));
foreach (LUID_AND_ATTRIBUTES privData in tokenPrivData.Privileges)
{
string PrivilegeName = LookupPrivilegeName(privData.Luid, remoteMachineName);
if (!string.IsNullOrEmpty(PrivilegeName))
{
DataRow row = privDT.NewRow();
row[0] = privData.Luid;
row[1] = PrivilegeName;
row[2] = (SE_PRIVILEGE_STATE)privData.Attributes;
//Add Row
privDT.Rows.Add(row);
}
}
return privDT;
}
private static TOKEN_PRIVILEGES GetAccessTokenPrivileges(IntPtr TokenHandle)
{
uint TOKEN_INFORMATION_CLASS_TokenPrivileges = 3;
if (TokenHandle != IntPtr.Zero)
{
int nBufferSize = 0;
bool TokenInfoResult;
//First call is to get buffer size!
TokenInfoResult = GetTokenInformation(TokenHandle, TOKEN_INFORMATION_CLASS_TokenPrivileges, IntPtr.Zero, nBufferSize, out nBufferSize);
//Allocate Token Info correctly.
IntPtr pTokenInfo = Marshal.AllocHGlobal(nBufferSize);
//Get our Token Info Data
TokenInfoResult = GetTokenInformation(TokenHandle, TOKEN_INFORMATION_CLASS_TokenPrivileges, pTokenInfo, nBufferSize, out nBufferSize);
if (TokenInfoResult)
{
TOKEN_PRIVILEGES returnedPrivilegeSet = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(pTokenInfo, typeof(TOKEN_PRIVILEGES));
int PrivilegeCount = returnedPrivilegeSet.PrivilegeCount;
//lets create the array we should have had returned in the first place
LUID_AND_ATTRIBUTES[] AllPrivs = new LUID_AND_ATTRIBUTES[PrivilegeCount]; //initialize an array to the right size, including 0!
if (PrivilegeCount > 0)
{
LUID_AND_ATTRIBUTES currentPriv = new LUID_AND_ATTRIBUTES();
//pPrivileges will hold our new location to read from by taking the last pointer plus the size of the last structure read
IntPtr pPrivilege = new IntPtr(pTokenInfo.ToInt32() + sizeof(int)); //pointer math, we point to the first element of Privileges array in the struct.
currentPriv = (LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(pPrivilege, typeof(LUID_AND_ATTRIBUTES)); //Get Privilege from pointer
AllPrivs[0] = currentPriv; //We'll add the first element to our array.
//After getting our first structure we can loop through the rest since they will all be the same
for (int i = 1; i < PrivilegeCount; ++i)
{
pPrivilege = new IntPtr(pPrivilege.ToInt32() + Marshal.SizeOf(currentPriv)); //This will point to the next Privilege element in the array
currentPriv = (LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(pPrivilege, typeof(LUID_AND_ATTRIBUTES)); //Get Privilege from pointer
AllPrivs[i] = currentPriv; //Add element to the array
}
}
//Create our complete struct of TOKEN_PRIVILEGES
TOKEN_PRIVILEGES completePrivilegeSet = new TOKEN_PRIVILEGES();
completePrivilegeSet.PrivilegeCount = PrivilegeCount;
completePrivilegeSet.Privileges = AllPrivs;
//We can get release all the pointers now, we got what we wanted!
Marshal.FreeHGlobal(pTokenInfo); //Free up the reserved space in unmanaged memory (Should be done any time AllocHGlobal is used)
//Return our completePrivilegeSet!
return completePrivilegeSet;
}
}
return new TOKEN_PRIVILEGES() { PrivilegeCount = 0, Privileges = new LUID_AND_ATTRIBUTES[] { } };
}
private static string LookupPrivilegeName(LUID privLuid, string remoteMachineName = null)
{
string PrivilegeName = null;
StringBuilder sb = new StringBuilder();
int cchName = 0; //Holds the length of structure we will be receiving LookupPrivilagename
IntPtr ipLuid = Marshal.AllocHGlobal(Marshal.SizeOf(privLuid)); //Allocate a block of memory large enough to hold the structure
Marshal.StructureToPtr(privLuid, ipLuid, true); //Write the structure into the reserved space in unmanaged memory
LookupPrivilegeName(remoteMachineName, ipLuid, null, ref cchName); // call once to get the name length we will be receiving
sb.Capacity = cchName; //Our string builder is buffered for the name!
if (LookupPrivilegeName(remoteMachineName, ipLuid, sb, ref cchName))
{
// Successfully retrieved name!
PrivilegeName = sb.ToString();
}
Marshal.FreeHGlobal(ipLuid); //Free up the reserved space in unmanaged memory
return PrivilegeName;
}
public static List<string> ListEnabledPrivileges(IntPtr TokenHandle, string remoteMachineName = null)
{
List<string> enabledPrivs = null;
DataTable dt = GetAccessTokenPrivilegesAsDataTable(TokenHandle, remoteMachineName);
if (dt != null && dt.Rows.Count > 0)
{
uint nEnabled = (uint)SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED;
uint nEnabledAndDefault = (uint)(SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED);
string query = string.Format("[{0}] IN ( {1}, {2} )"
, dt.Columns[2].ColumnName
, nEnabled
, nEnabledAndDefault);
IEnumerable<DataRow> rows = dt.Select(query);
if (rows != null && rows.Count() > 0)
{
enabledPrivs = dt.Select(query).Select(r => r.Field<string>(1)).ToList();
}
}
return enabledPrivs;
}
}
So after much research it turns out this is locked down in Windows. Unless the current process token has SeTcbPrivilege (Act as Part of Operating System) and it's enabled, you cannot get an unrestricted/elevated token when deciding to impersonate from a non-elevated token.
Also if you then try to get the LinkedToken/Unrestricted Token of an Access Token but do not have the SeTcbPrivilege enabled, you will end up with a Token that has a SECURITY_IMPERSONATION_LEVEL of SecurityIdentification which cannot be used for impersonation. Without SECURITY_IMPERSONATION_LEVEL being SecurityImpersonation (Local Access) or SecurityDelegation (Local+Remote Access), you cannot impersonate the Unrestricted Access Token and will result in failure with LastError being ERROR_BAD_IMPERSONATION_LEVEL (Either a required impersonation level was not provided, or the provided impersonation level is invalid).
This means if you do not have SeTcbPrivilege and it is not enable on the token, it is required to have an elevated process token (ex. Run As Admin was used to start the program manually or forcefully via requestedExecutionLevel level="requireAdministrator") then Windows will not strip the privileges on Access Token when you decide to impersonate the Access Token.
By using a local administrator account that runs the program as administrator, and then using domain account credentials that are an admin on another machine to get an access token using the params of LOGON32_LOGONTYPE.LOGON32_LOGON_NEW_CREDENTIALS and LOGON32_LOGONPROVIDER.LOGON32_PROVIDER_WINNT50. It is then possible to elevate privileges on the NEWCRED token, impersonate the NEWCRED token which will not strip away the privileges, and have it successfully connect to another computer and read from a file and write to a file on another computer to which it has no permissions or is even explicitly denied, proving it was using the token backup and restore privileges!
Helpful Sources:
how do i convert restricted user token to unrestricted one?
How to call LogonUser() to get a non-restricted full token inside a Windows Service with UAC enabled?
Self service password reset site. For certain operations, I am impersonating a technical user that has account operator privileges in the domain. It works perfectly on my laptop, I can change a user's password, unlock the account or query the domain for all the locked accounts.
It even worked on the server until some 2 weeks ago. I tried to investigate for changes in our environment bot no one is aware of any change that could have had an effect on this.
The best part is that I have no error messages at all. Marshal.GetLastWin32Error() is returning zero, which is basically "everything went OK".
Here's the code where the impersonation happens:
#region accountManagement
[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
WindowsImpersonationContext impersonationContext;
private bool impersonateValidUser(String userName, String domain, String password)
{
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NETWORK = 3;
const int LOGON32_LOGON_BATCH = 4;
const int LOGON32_LOGON_SERVICE = 5;
const int LOGON32_LOGON_UNLOCK = 7;
const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
const int LOGON32_PROVIDER_DEFAULT = 0;
WindowsIdentity tempWindowsIdentity;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
// Int32 result = LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token);
// Response.Write(">>> " + result.ToString());
if (LogonUserA(userName, domain, password, LOGON32_LOGON_UNLOCK, LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
if (impersonationContext != null)
{
CloseHandle(token);
CloseHandle(tokenDuplicate);
return true;
}
}
}
}
if (token != IntPtr.Zero)
CloseHandle(token);
if (tokenDuplicate != IntPtr.Zero)
CloseHandle(tokenDuplicate);
return false;
}
private void undoImpersonation()
{
impersonationContext.Undo();
}
#endregion
And here is the piece that calls it (irrelevant parts removed):
String iuUser = System.Configuration.ConfigurationManager.AppSettings["domain.accountop.user"];
String iuPass = System.Configuration.ConfigurationManager.AppSettings["domain.accountop.pass"];
String iuDomn = System.Configuration.ConfigurationManager.AppSettings["domain.name"];
if (impersonateValidUser(iuUser, iuDomn, iuPass))
{
try
{
email = user.Properties["mail"].Value.ToString();
user.Invoke("SetPassword", new object[] { pw1 });
user.Properties["LockOutTime"].Value = 0; //unlock account
user.CommitChanges();
user.Close();
undoImpersonation();
// clear form and redirect
pPopupSuccess.Visible = true;
hfSuccess.Value = "The account is unlocked now and the password has been reset successfully.";
}
catch (Exception ex)
{
Exception innerException = ex.InnerException;
DirectoryServicesCOMException exds = (DirectoryServicesCOMException)innerException;
String errorMessage = "<p><strong>Your password probably did not meet the requirements.</strong></p>";
errorMessage += "<p>" + System.Configuration.ConfigurationManager.AppSettings["domain.pwreqmessage"] + "</p>";
errorMessage += "<strong>Detailed error message:</strong><br />";
errorMessage += ex.Message;
errorMessage += "<br />";
errorMessage += ex.StackTrace;
if (innerException != null) {
errorMessage = errorMessage + innerException.Message;
}
pPopupError.Visible = true;
hfErrorMessage.Value = errorMessage;
}
}
else
{
// The impersonation failed. Include a fail-safe mechanism here.
pPopupError.Visible = true;
hfErrorMessage.Value = "<p>Impersonation error. Failed to elevate the rights to account operator. Please report this error to the Help Desk.</p>";
hfErrorMessage.Value += "<p>" + Marshal.GetLastWin32Error().ToString() + "</p>";
}
What I keep getting is NOT an exception at the middle, but my own message at the very end of the second code piece saying the impersonation was not successful. And Marshal.GetLastWin32Error() just returs zero.
What can go wrong here and how can I get more information on what's happening? What can be different on the server that makes this code fail, while it's running OK on my dev PC?
Sorry, it was a #PEBKAC. Windows security log suggested that
The user has not been granted the requested logon type at this
machine.
So the answer is "check your windows events!"
This is strange because I had been using this server with this technical account. So I double-checked the user and put it back to the Administrators group of the server. I think it is more than I really need, also a security risk, but I will do some experiments soon to see what is the minimum amount of rights locally to keep it working.
I'm trying to return a Sytem.IntPtr from a service call so that the client can use impersonation to call some code. My imersonation code works properly if not passing the token back from a WCF service. I'm not sure why this is not working. I get the following error:
"Invalid token for impersonation - it cannot be duplicated."
Here is my code that does work except when I try to pass the token back from a service to a WinForm C# client to then impersonate.
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType, int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);
private IntPtr tokenHandle = new IntPtr(0);
private IntPtr dupeTokenHandle = new IntPtr(0);
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
public enum SecurityImpersonationLevel
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3
}
public enum TokenType
{
TokenPrimary = 1,
TokenImpersonation = 2
}
private const int MAXIMUM_ALLOWED = 0x2000000;
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public System.IntPtr GetWindowsUserToken(string UserName, string Password, string DomainName)
{
IntPtr tokenHandle = new IntPtr(0);
IntPtr dupTokenHandle = new IntPtr(0);
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
//Initialize the token handle
tokenHandle = IntPtr.Zero;
//Call LogonUser to obtain a handle to an access token for credentials supplied.
bool returnValue = LogonUser(UserName, DomainName, Password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle);
//Make sure a token was returned; if no populate the ResultCode and throw an exception:
int ResultCode = 0;
if (false == returnValue)
{
ResultCode = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ResultCode, "API call to LogonUser failed with error code : " + ResultCode);
}
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.bInheritHandle = true;
sa.Length = Marshal.SizeOf(sa);
sa.lpSecurityDescriptor = (IntPtr)0;
bool dupReturnValue = DuplicateTokenEx(tokenHandle, MAXIMUM_ALLOWED, ref sa, (int)SecurityImpersonationLevel.SecurityDelegation, (int)TokenType.TokenImpersonation, ref dupTokenHandle);
int ResultCodeDup = 0;
if (false == dupReturnValue)
{
ResultCodeDup = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ResultCode, "API call to DuplicateToken failed with error code : " + ResultCode);
}
//Return the user token
return dupTokenHandle;
}
Any idea if I'm not using the call to DuplicateTokenEx correctly? According to the MSDN documentation I read here I should be able to create a token valid for delegation and use across the context on remote systems. When 'SecurityDelegation' is used, the server process can impersonate the client's security context on remote systems.
Thanks!
You are using the value TokenAccessLevels.MaximumAllowed as if it was the maximum privileges allowed, that means: as if it would give you all permissions...
...but that is not the case.
TokanAccesslevels.MaximumAllowed has a value of 0x02000000, which is the maximum value that thae enumeration can grow to at any time, a reference for future implementations.
Please, see this page for the actual implementation of the enum.
For your code to work you need to set the desired access levels one by one via bitwise operations.
The code will certainly run if you substitute MAXIMUM_ALLOWED for
(uint)(TokenAccessLevels.Query | TokenAccessLevels.Duplicate | TokenAccessLevels.Impersonate)
in the PInvoke call for the DuplicateTokenEx function.
I know that you don't have the problem anymore, but that can help other people and, after all, you may run into the same problem again... ;)
I'm working on an application which is made of two modules. These modules communicate through named pipes in the following environment:
Windows 7 Home Premium x64
Visual Studio 2008
C# / .Net 3.5
The server runs with administrator rights (high integrity level). The client runs in low integrity level. So that the client can connect to the server, I need to create the pipe in low integrity level. I manage to do this only when the server runs in medium integrity level.
I tested the following setups :
server : high, client : low => access refused
server : high, client : medium => access refused
server : high, client : high => OK
server : medium, client : low => OK
server : medium, client : medium => OK
server : low, client : low => OK
Setup #4 shows that the named pipe gets created with different integrity level than the one of the process, which is good. However, the setup I am interested in is the first one.
I have a sample which makes it easy to test. If the connection is successful, the clients writes "Connected" and the server writes "Received connection". If the connection fails, the client writes "Failed" and the server stays on "Waiting".
Here is how I execute the client program (for the server, simply replace NamePipeClient with NamedPipeServer):
medium integrity level:
open the command prompt
icacls NamedPipeClient.exe /setintegritylevel Medium
NamedPipeClient.exe
low integrity level:
open the command prompt
icacls NamedPipeClient.exe /setintegritylevel Low
NamedPipeClient.exe
high integrity level:
open the command prompt in admin mode
icacls NamedPipeClient.exe /setintegritylevel High
NamedPipeClient.exe
Any help will be greatly appreciated!
Server code
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.IO.Pipes;
namespace NamedPipeServer
{
class Program
{
static void Main(string[] args)
{
SafePipeHandle handle = LowIntegrityPipeFactory.CreateLowIntegrityNamedPipe("NamedPipe/Test");
NamedPipeServerStream pipeServer = new NamedPipeServerStream(PipeDirection.InOut, true, false, handle);
pipeServer.BeginWaitForConnection(HandleConnection, pipeServer);
Console.WriteLine("Waiting...");
Console.ReadLine();
}
private static void HandleConnection(IAsyncResult ar)
{
Console.WriteLine("Received connection");
}
}
}
LowIntegrityPipeFactory.cs
using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.IO.Pipes;
using System.ComponentModel;
using System.IO;
using System.Security.Principal;
using System.Security.AccessControl;
namespace NamedPipeServer
{
static class LowIntegrityPipeFactory
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern SafePipeHandle CreateNamedPipe(string pipeName, int openMode,
int pipeMode, int maxInstances, int outBufferSize, int inBufferSize, int defaultTimeout,
SECURITY_ATTRIBUTES securityAttributes);
[DllImport("Advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = false)]
private static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(
[In] string StringSecurityDescriptor,
[In] uint StringSDRevision,
[Out] out IntPtr SecurityDescriptor,
[Out] out int SecurityDescriptorSize
);
[StructLayout(LayoutKind.Sequential)]
private struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
private const string LOW_INTEGRITY_SSL_SACL = "S:(ML;;NW;;;LW)";
public static SafePipeHandle CreateLowIntegrityNamedPipe(string pipeName)
{
// convert the security descriptor
IntPtr securityDescriptorPtr = IntPtr.Zero;
int securityDescriptorSize = 0;
bool result = ConvertStringSecurityDescriptorToSecurityDescriptor(
LOW_INTEGRITY_SSL_SACL, 1, out securityDescriptorPtr, out securityDescriptorSize);
if (!result)
throw new Win32Exception(Marshal.GetLastWin32Error());
SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();
securityAttributes.nLength = Marshal.SizeOf(securityAttributes);
securityAttributes.bInheritHandle = 1;
securityAttributes.lpSecurityDescriptor = securityDescriptorPtr;
SafePipeHandle handle = CreateNamedPipe(#"\\.\pipe\" + pipeName,
PipeDirection.InOut, 100, PipeTransmissionMode.Byte, PipeOptions.Asynchronous,
0, 0, PipeAccessRights.ReadWrite, securityAttributes);
if (handle.IsInvalid)
throw new Win32Exception(Marshal.GetLastWin32Error());
return handle;
}
private static SafePipeHandle CreateNamedPipe(string fullPipeName, PipeDirection direction,
int maxNumberOfServerInstances, PipeTransmissionMode transmissionMode, PipeOptions options,
int inBufferSize, int outBufferSize, PipeAccessRights rights, SECURITY_ATTRIBUTES secAttrs)
{
int openMode = (int)direction | (int)options;
int pipeMode = 0;
if (maxNumberOfServerInstances == -1)
maxNumberOfServerInstances = 0xff;
SafePipeHandle handle = CreateNamedPipe(fullPipeName, openMode, pipeMode,
maxNumberOfServerInstances, outBufferSize, inBufferSize, 0, secAttrs);
if (handle.IsInvalid)
throw new Win32Exception(Marshal.GetLastWin32Error());
return handle;
}
}
}
Client code
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Pipes;
namespace NamedPipeClient
{
class Program
{
static void Main(string[] args)
{
try
{
var pipeClient = new NamedPipeClientStream(".", "NamedPipe/Test",
PipeDirection.InOut,
PipeOptions.None);
pipeClient.Connect(100);
}
catch (Exception ex)
{
Console.WriteLine("Failed: " + ex);
return;
}
Console.WriteLine("Connected");
Console.ReadLine();
}
}
}
Works for me on Windows 7 SP1
public static class NativeMethods
{
public const string LOW_INTEGRITY_SSL_SACL = "S:(ML;;NW;;;LW)";
public static int ERROR_SUCCESS = 0x0;
public const int LABEL_SECURITY_INFORMATION = 0x00000010;
public enum SE_OBJECT_TYPE
{
SE_UNKNOWN_OBJECT_TYPE = 0,
SE_FILE_OBJECT,
SE_SERVICE,
SE_PRINTER,
SE_REGISTRY_KEY,
SE_LMSHARE,
SE_KERNEL_OBJECT,
SE_WINDOW_OBJECT,
SE_DS_OBJECT,
SE_DS_OBJECT_ALL,
SE_PROVIDER_DEFINED_OBJECT,
SE_WMIGUID_OBJECT,
SE_REGISTRY_WOW64_32KEY
}
[DllImport("advapi32.dll", EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean ConvertStringSecurityDescriptorToSecurityDescriptor(
[MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
UInt32 sDRevision,
ref IntPtr securityDescriptor,
ref UInt32 securityDescriptorSize);
[DllImport("kernel32.dll", EntryPoint = "LocalFree")]
public static extern UInt32 LocalFree(IntPtr hMem);
[DllImport("Advapi32.dll", EntryPoint = "SetSecurityInfo")]
public static extern int SetSecurityInfo(SafeHandle hFileMappingObject,
SE_OBJECT_TYPE objectType,
Int32 securityInfo,
IntPtr psidOwner,
IntPtr psidGroup,
IntPtr pDacl,
IntPtr pSacl);
[DllImport("advapi32.dll", EntryPoint = "GetSecurityDescriptorSacl")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean GetSecurityDescriptorSacl(
IntPtr pSecurityDescriptor,
out IntPtr lpbSaclPresent,
out IntPtr pSacl,
out IntPtr lpbSaclDefaulted);
}
public class InterProcessSecurity
{
public static void SetLowIntegrityLevel(SafeHandle hObject)
{
IntPtr pSD = IntPtr.Zero;
IntPtr pSacl;
IntPtr lpbSaclPresent;
IntPtr lpbSaclDefaulted;
uint securityDescriptorSize = 0;
if (NativeMethods.ConvertStringSecurityDescriptorToSecurityDescriptor(NativeMethods.LOW_INTEGRITY_SSL_SACL, 1, ref pSD, ref securityDescriptorSize))
{
if (NativeMethods.GetSecurityDescriptorSacl(pSD, out lpbSaclPresent, out pSacl, out lpbSaclDefaulted))
{
var err = NativeMethods.SetSecurityInfo(hObject,
NativeMethods.SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
NativeMethods.LABEL_SECURITY_INFORMATION,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
pSacl);
if (err != NativeMethods.ERROR_SUCCESS)
{
throw new Win32Exception(err);
}
}
NativeMethods.LocalFree(pSD);
}
}
}
Setup of server side
InterProcessSecurity.SetLowIntegrityLevel(pipeServer.SafePipeHandle);
Your code for setting the mandatory integrity label on the pipe achieves this successfully. However, because your security descriptor doesn't define a DACL, your pipe is being created with the default one.
It is the DACL which is causing the low integrity client to fail when trying to connect to the pipe created by your high integrity server.
You need to fix the DACL in the server before opening the listener. Rather than trying to construct the full descriptor using P/Invoke code before creating the pipe, which is pretty hard to get right, I'd suggest leveraging the System.IO.Pipes classes to do it in managed code as a separate step after the pipe is created, like this:
// Fix up the DACL on the pipe before opening the listener instance
// This won't disturb the SACL containing the mandatory integrity label
NamedPipeServerStream handleForSecurity = null;
try
{
handleForSecurity = new NamedPipeServerStream("NamedPipe/Test", PipeDirection.InOut, -1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, null, System.IO.HandleInheritability.None, PipeAccessRights.ChangePermissions);
PipeSecurity ps = handleForSecurity.GetAccessControl();
PipeAccessRule aceClients = new PipeAccessRule(
new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null), // or some other group defining the allowed clients
PipeAccessRights.ReadWrite,
AccessControlType.Allow);
PipeAccessRule aceOwner = new PipeAccessRule(
WindowsIdentity.GetCurrent().Owner,
PipeAccessRights.FullControl,
AccessControlType.Allow);
ps.AddAccessRule(aceClients);
ps.AddAccessRule(aceOwner);
handleForSecurity.SetAccessControl(ps);
}
finally
{
if (null != handleForSecurity) handleForSecurity.Close();
handleForSecurity = null;
}
This works for me, with the rest of your code unchanged.
The answer I posted in December does work, despite the anonymous drive-by voting down which someone indulged themselves in. (At least, it does on Vista SP2 and I don't think there are any differences between Vista and Windows 7 which would affect this issue).
Here is a different approach which also works, specifying the DACL within the SDDL string used inside the pipe factory class:
Change the line in the CreateLowIntegrityNamedPipe(string pipeName) method which calls ConvertStringSecurityDescriptorToSecurityDescriptor, thus:
bool result = ConvertStringSecurityDescriptorToSecurityDescriptor(
CreateSddlForPipeSecurity(), 1, out securityDescriptorPtr,
out securityDescriptorSize);
and provide an additional private static method, something like:
private static string CreateSddlForPipeSecurity()
{
const string LOW_INTEGRITY_LABEL_SACL = "S:(ML;;NW;;;LW)";
const string EVERYONE_CLIENT_ACE = "(A;;0x12019b;;;WD)";
const string CALLER_ACE_TEMPLATE = "(A;;0x12019f;;;{0})";
StringBuilder sb = new StringBuilder();
sb.Append(LOW_INTEGRITY_LABEL_SACL);
sb.Append("D:");
sb.Append(EVERYONE_CLIENT_ACE);
sb.AppendFormat(CALLER_ACE_TEMPLATE, WindowsIdentity.GetCurrent().Owner.Value);
return sb.ToString();
}
My version sets the pipe access to allow any authenticated user to be a pipe client. You could add additional features to the pipe factory class to specify a list of allowed client SIDs or such like.