Run a process as administrator from non admin user - local admin credentials - windows

I have a Window 10 vb.net main process (tester) that needs to create a process to clean up certain registry area. The products we test have a single FTDI serial port adapter, but over time these instances build up and slow the registry to a crawl.
As the main process starts, the desire is to use an off the shelf utility, DeviceCleanupCmd.exe, to search and remove the driver instance build up. This requires administrator privileges for that sub process. I've been able to do this on XP with script, but the system is running with full administrator privileges. Since it's not on the network, there's very little risk.
Moving forward, we are trying to replace the XP system with a Windows 10 Enterprise box that is network connected. I wanted to incorporate the equivalent of the script into the vb.net process and I modified the vb.net application to use
Process() with StartInfo.Verb = "runas"
Local user credentials (not a domain user) the application provides to start the sub process.
Using this method, my sub process executes, but not with administrator level privileges. The Stackoverflow reference below explains that I can't use this method and have to use CreateProcessWithLogonW. I understand Ian Boyd's concerns for security in the post.
Run process as administrator from a non-admin application
I have followed the Microsoft implementation that follows and am still having issues.
https://learn.microsoft.com/en-us/troubleshoot/dotnet/visual-basic/start-process-as-another-user
Here's the essential part of my code. You will see that I truncated the MS example to use the W2K portion, as we won't be going back that far!
wUser = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(UserName + Chr(0)))
wDomain = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(DomainName + Chr(0)))
wPassword = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(Password + Chr(0)))
wCommandLine = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(CommandLine + Chr(0)))
wCurrentDir = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(CurrentDirectory + Chr(0)))
Result = CreateProcessWithLogonW(wUser, wDomain, wPassword, CREATE_DEFAULT_ERROR_MODE, 0&, wCommandLine, CREATE_NEW_CONSOLE, 0&, wCurrentDir, si, pi)
If Result <> 0 Then
CloseHandle(pi.hThread)
CloseHandle(pi.hProcess)
W2KRunAsUser = 0
Else
W2KRunAsUser = Err.LastDllError
Status = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, CType(0&, IntPtr), W2KRunAsUser, 0, strMessage, Len(strMessage), 0)
MsgBox("CreateProcessWithLogonW() failed with error: " & W2KRunAsUser & " " & strMessage, vbExclamation)
End If
Please remember that my target administrator user is only resident on the PC I'm running the application on - not on the domain. I've verified that I can login with the credentials and run DeviceCleanupCmd.exe with success. In order to login locally, I must use a \ before the user. When I run the code, I've attempted the following, with the error message that Windows 10 supplies after the colon (:):
Providing "user", "password", and "domain" (I know it's not required): The directory name is invalid.
Providing "user", "password", and domain as "": The directory name is invalid.
Providing "\user", "password", and domain as "": The directory name is invalid.
Providing ".\user", "password", and domain as "": The stub received bad data.
Providing "user", "password", and domain as "computer name": The directory name is invalid.
Providing "non-admin user", "password", and "domain of user": DeviceCleanupCmd.exe runs but complains 'No admin privileges available'.
Because the application is complaining about an invalid directory name, I've purposely placed the DeviceCleanupCmd.exe in a C:\sub-directory that is not protected by Windows (Like Program Files). Attempt 6 above would seem to prove that there's not rights/access violations.
I have also tried CREATE_NEW_CONSOLE in place of CREATE_DEFAULT_ERROR_MODE and same results as above. I've proved the user is being decoded, as when I provide an incorrect password, I get "The user name or password is incorrect".
#Hurshey provided the insight to look at the Windows Task Scheduler and the following links support full configuring the task + incorporating the exported XML into NSIS - my target deployment method.
https://www.windowscentral.com/how-create-automated-task-using-task-scheduler-windows-10
https://nsis.sourceforge.io/Talk:Scheduled_Tasks

Related

Is there an API call to prompt user for UAC elevation?

My app needs to write to a file in \ProgramData that could be protected. This only happens once after installation.
Is there an API function that would take ACL info and prompt the user to authorize the app to access the file? In other words the app would ask Windows to prompt the user for confirmation and authorization. This should happen interactively, and allow the app to resume execution after access to the file has been authorized. The app runs as Standard User, does not require Admin privilege.
The file gets opened with CreateFile().
Edit: There is a subtle difference between my query and the others said to be duplicates. I am asking for permission to access one specific object, a file. The others are asking to elevate the privileges of the whole process. Anyway, I am grateful for all responses which include a solution.
If you don't want to elevate your entire app, you have a few options:
spawn a separate elevated process just to access the file. Use ShellExecute/Ex() with the runas verb, or CreateProcessElevated(), to run a second copy of your app, or another helper app, with command-line parameters to tell it what to do. The main process can wait for the second process to exit, if needed.
create a COM object to access the file, and then use the COM Elevation Moniker to run the COM object in an elevated state.
prompt the user for credentials using CredUIPromptForCredentials() or CredUIPromptForWindowsCredentials() (see Asking the User for Credentials for more details), then logon to the specified account using LogonUser() to get a token, impersonate that token using ImpersonateLoggedOnUser(), access the file as needed, and then stop impersonating using RevertToSelf() and close the token with CloseHandle().
Thanks to #Remy for the ShellExecuteEx suggestion, here are the sordid details. Note the use of 'cmd' and the double-command, so the user only has to reply once. Also, [1] must wait for process completion otherwise you could find yourself creating the file before it was deleted, and [2] don't wait for the process if it failed.
// delete file with Admin privilege
// 'file_name' is path of file to be deleted
SHELLEXECUTEINFO shex;
char param[512];
char *cmd = "/C \"attrib -H \"%s\" && del /F /Q \"%s\"\""; // double command
_snprintf(param, sizeof(param), cmd, file_name, file_name);
ZeroMemory(&shex, sizeof(shex));
shex.cbSize = sizeof(shex);
shex.lpVerb = "runas"; // runas, open
shex.lpFile = "cmd"; // not 'del'
shex.lpParameters = param;
shex.nShow = SW_HIDE;
shex.fMask = SEE_MASK_NOCLOSEPROCESS;
BOOL retshx = ShellExecuteEx(&shex);
// wait otherwise could return before completed
if(retshx)
{ time_t st = clock();
DWORD exitCode;
do
{ if(!GetExitCodeProcess(shex.hProcess, &exitCode))
break;
if(clock() - st > CLOCKS_PER_SEC * 5) // max 5 seconds
break;
} while(exitCode != STATUS_WAIT_0); // STILL_ACTIVE
CloseHandle(shex.hProcess);
}
Processes can only be launched with an elevated token, they can't gain it after the fact. So you can either re-launch your app elevated with a command line argument telling it what to do (simple solution), or implement an out-of-proc COM server that you can create elevated and pass instructions to it (harder).
A third solution is to leverage the built-in UAC support of the IFileOperation interface, but this doesn't let you read/write, only copy. So you could make a copy of the file you need to modify, modify the copy and then use IFileOperation to copy the temporary over the original.

How to elevate .net application permissions?

I have an application that would check for updates upon start and, if updates are found, it would copy some files over the network to the program files folder. Obviously such task can't be performed by Standard Users under normal scenarios.
I tried creating a service to do the update process but I had some security issues and I asked this question about it in superusers.
Now, considering the fact that most applications require elevated privileges to perform such task I think that might be the right approach. But how do I request elevation for the updater under all Windows version as of XP, included. I've found many topics about a manifest file, but since I need this to work with XP I can't create a solution specifically for UAC.
Privileges can only be elevated at startup for a process; a running process' privileges cannot be elevated. In order to elevate an existing application, a new instance of the application process must be created, with the verb “runas”:
private static string ElevatedExecute(NameValueCollection parameters)
{
string tempFile = Path.GetTempFileName();
File.WriteAllText(tempFile, ConstructQueryString(parameters));
try
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.UseShellExecute = true;
startInfo.WorkingDirectory = Environment.CurrentDirectory;
Uri uri = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase);
startInfo.FileName = uri.LocalPath;
startInfo.Arguments = "\"" + tempFile + "\"";
startInfo.Verb = "runas";
Process p = Process.Start(startInfo);
p.WaitForExit();
return File.ReadAllText(tempFile);
}
catch (Win32Exception exception)
{
return exception.Message;
}
finally
{
File.Delete(tempFile);
}
}
After the user confirms the execution of the program as administrator, another instance of the same application is executed without a UI; one can display a UI running without elevated privileges, and another one running in the background with elevated privileges. The first process waits until the second finishes its execution. For more information and a working example you can check out the MSDN archive.
To prevent all this dialog shenanigans in the middle of some lengthy process, you'll need to run your entire host process with elevated permissions by embedding the appropriate manifest in your application to require the 'highestAvailable' execution level: this will cause the UAC prompt to appear as soon as your app is started, and cause all child processes to run with elevated permissions without additional prompting.

How does logging in interactively to a Windows host effect WMI queries to that host?

I have an application that performs WMI queries on remote hosts. I'm attempting to define the minimal amount of security permissions required for the user used to do the queries.
I've defined a user with minimal permissions. I've found that this user has enough permissions to do the needed queries, but only if the same user is logged in interactively on the remote host.
For example, if I log in to the remote host with the nonadmin user (i.e. open a desktop on the remote host), the query select * from Win32_Environment returns 2 additional entries than if I'm not logged in. The 2 entries are:
instance of Win32_Environment
{
Caption = "COMP-BAF3244E\\nonadmin\\TEMP";
Description = "COMP-BAF3244E\\nonadmin\\TEMP";
Name = "TEMP";
Status = "OK";
SystemVariable = FALSE;
UserName = "COMP-BAF3244E\\nonadmin";
VariableValue = "%USERPROFILE%\\Local Settings\\Temp";
};
and
instance of Win32_Environment
{
Caption = "COMP-BAF3244E\\nonadmin\\TMP";
Description = "COMP-BAF3244E\\nonadmin\\TMP";
Name = "TMP";
Status = "OK";
SystemVariable = FALSE;
UserName = "COMP-BAF3244E\\nonadmin";
VariableValue = "%USERPROFILE%\\Local Settings\\Temp";
};
as seen in MOF representation in the wbemtest tool. The wbemtest connection is to \\remotehost\root\cimv2 as user nonadmin.
How does logging in to the remote host effect the results of the WMI query?
What permissions do I need to add to my user so that the additional information is available even when the remote user is not logged in?
The problem has to do with the user profile not being loaded unless the user has the correct rights.
The user profile not being loaded is also an issue for calling Win32_Process.Create. If the profile is not loaded then this will fail with UNKNOWN FAILURE (8).
Thanks to this blog post for pointing out the cause of the errors!
In order to have the user profile loaded automatically while during remote WMI access, give the user "Backup files and directories" and "Restore files and directories" user rights.
You can find the these rights policies at:
"Run..." --> secpol.msc --> Local Policies --> User Rights Assignment

Using Pgp.exe from a MVC application

I've been tasked with converting a legacy application to mvc. The app used pgp.exe to pgp sign user input and send it as an email. The application works locally and on a test server but won't run on a live server. I've had to jump though hoops such as running a specified user in the application pool so that we can set the keys in the users profile BUT it worked.
For some reason on the live server which is windows 2003 IIS 6 and identical to the testing server it fails. The problem is pgp.exe just wont seem to sign and create files the message I get from the console out put is. "Signature Error"?? When I put the command into a shell window logged in as the app pool user it runs no problem (after a fight with some permissions) but when running through the mvc application/IIS server it fails. The code used to call the process is below.
var startInfo = new ProcessStartInfo();
startInfo.FileName = _pgpexeLocation;
//startInfo.FileName = "pgp.exe";
startInfo.Arguments = string.Format("-sta \"{0}\" -u keyuser-z keypass +COMPATIBLE +FORCE", _tempFilePath);
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.LoadUserProfile = true;
using (Process exeProcess = Process.Start(startInfo))
{
// TODO: set limit to wait for and deal with exit
exeProcess.WaitForExit();
//var stringItem = exeProcess.StandardOutput.ReadToEnd();
//Logger.Info(stringItem);
}
I'm clutching at straws here hoping somebody has done something similar before and can help. I'm guessing it's key location or file location not being picked up somewhere but not sure what else to try?
Turns out that even though the app pool was using a specific user and I'd set the keys up in that users appdata folder when I checked the underlying process call it was actually trying to pick the keys up from the Default User profile. Not sure if this was an IIS config or something similar but moving the keys and pgp folder to this appdata instead worked?

What default credential Directory Entry binds to if it is running as a service on Windows

If I bind to a DirectoryEntry ( without any arguments) on the application mode to search all the users at my current domain, I could see the logged in in user is the default credential for DirectoryEntry since it finds all the users for my domain. With that, I would expect that this won't work if the application is run at service mode since account for service is SYSTEM which is not a user of my domain. However the code still works and it could get all the users of my logged in user domain.
I did some search on internet, I didn't find any answer on this when running as a service. Any idea? Any way I could get that default credential at power shell?
At service mode, the default credential for directory entry would be the credential which computer is used to login into the domain (called computer login). See this link: https://serverfault.com/questions/61191/what-happens-when-a-computer-joins-an-active-directory-domain.
I first tried to use System.Net.CredentialCache::DefaultNetworkCredentials
to try to get the default credential, however I found out the user and domain are all blank. It is confirmed from MSDN that we could not view those info with the returned NetworkCredentials.
So I added the following vb script (got the base code from this link: http://blogs.technet.com/b/heyscriptingguy/archive/2008/05/12/how-can-i-determine-the-distinguished-name-of-the-ou-where-the-logged-on-user-s-user-account-resides.aspx) to be called from power shell to get the dn of the current user
and save it to a file.
Set objSysInfo = CreateObject("ADSystemInfo")
strUserName = objSysInfo.UserName
Set objUser = GetObject("LDAP://" & strUserName)
Wscript.Echo objUser.distinguishedName
Set objFS = CreateObject("Scripting.FileSystemObject")
strTemp = "c:\temp\dn.txt"
Set objOutFile = objFS.CreateTextFile(strTemp,True)
objOutFile.Write(objUser.distinguishedName)
objOutFile.Close
Then when I ran it in service context, I found out that user is my computer itself, like this:
CN=my computer name,OU=Domain computer accounts,DC=domain,DC=local

Resources