Return to HomePage



Securely Executing a New Process from a .NET Application (C#)


Applies To

* .NET Framework 2.0
* C#

Summary

The purpose of this code snippet is to illustrate how to execute a new process under a different user account within a running .NET application.

Objectives

* Secure execution of a new process from within a running application
* Secure storage and retrieval of credentials needed to run a process encrypted in the DPAPI
* Deploying an application in "least privilege" mode and making use of another, privileged component to perform a critical task

Scenarios

* Application component needs to access functionality that only exists in a different runtime component
* Application occasionally needs access to privileged functionality but wants to use a low-privileged security context for enhanced overall security

Solution Example

// Execute a process under another user account, making
// use of credentials stored in the DPAPI
static public void ExecuteProcess(string fullProcessPath)
{
		    		// Retrieve the user and password from the registry and decrypt
		    		byte[] encryptedUser = Registry.GetValue(@"HKEY_CURRENT_USER\ProcessExecCreds", "User", null) as byte[];
		    		byte[] encryptedPass = Registry.GetValue(@"HKEY_CURRENT_USER\ProcessExecCreds", "Pass", null) as byte[];
	

		    		string user = [Encoding.ASCII.GetString(ProtectedData.Unprotect(encryptedUser,] null, [DataProtectionScope.CurrentUser));]
		    		string pass = [Encoding.ASCII.GetString(ProtectedData.Unprotect(encryptedPass,] null, [DataProtectionScope.CurrentUser));]
		    		[SecureString] securepass = new [SecureString();]
		    		foreach(char b in [pass.ToCharArray())] {
		        			[securepass.AppendChar(b);]
		    		}
	

		    	// Execute a process as our stored user account
		    	// This process will run a simple batch file that outputs the runtime account name
		    	Process process = new Process();
		    	[process.StartInfo.UseShellExecute] = false;
		    	[process.StartInfo.RedirectStandardOutput] = true;
		    	[process.StartInfo.RedirectStandardError] = true;
		    	[process.StartInfo.CreateNoWindow] = true;
	

		    	// Set the working directory to a value the user will be able to access
		    	[process.StartInfo.FileName] = [fullProcessPath;]
		    	[process.StartInfo.WorkingDirectory] = @"C:\";
		    	[process.StartInfo.UserName] = user;
		   	 [process.StartInfo.Password] = securepass;
	

		    	process.Start();
	

		    	string output = [process.StandardOutput.ReadToEnd();]
		    	[Console.WriteLine(output);]
	
}


Problem Example

The following example executes simple execution of a process within the user context defined by the running executable. An example

// Start my process with a given argument
Process.Start("MyProc.exe", @"MyArg");

* Parent application must run with the same level of privileges as the executed process
* Process path is not fully specified, rendering the Process.Start call more vulnerable to trojan execution

Other problem practices not shown in this example:
* Application makes use of a different user context to run the process, but stores the credentials in clear text in a file or database


Test Case

The following classes must be included in any project making use of the sample code provided above:

using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.AccessControl;
using System.Security.Cryptography;
using Microsoft.Win32;

Run the following code to store a set of credentials in the registry using the DPAPI and, in conjunction with the solution method, execute a command as a given user. The example test case executes the "whoami"
command.

static void Main(string[] args)
{
		    		// prompt for credentials and store in the registry using DPAPI
		    		[StoreUserCreds();]
	

		    		// retrieve the credentials from the DPAPI and run a process
		    		ExecuteProcess(@"whoami.exe");
	
}

// This method provides a mechanism for storing credentials
// for a runtime process in the registry using the DPAPI.
static public void StoreUserCreds()
{
		    		// Obtain username and password for the account
		    		Console.Write(@"Enter user name for process execution: ");
		    		string user = [Console.ReadLine();]
		    		Console.Write("Enter password for this user name: ");
		    		string pass = [Console.ReadLine();]
	

		    		// Convert the username and passwords to byte arrays and encrypt 
		    		// the data by using the DPAPI [ProtectedData] class. 
		    byte[] encryptedUser = [ProtectedData.Protect(]
		                    [UnicodeEncoding.ASCII.GetBytes(user),] 
		                    null, 
		                    [DataProtectionScope.CurrentUser);]
		    byte[] encryptedPass = [ProtectedData.Protect(]
		                    [UnicodeEncoding.ASCII.GetBytes(pass),] 
		                    null, 
		                    [DataProtectionScope.CurrentUser);]
	

		    		// Create a security context for a new key that we will use to store 
		    		// the credentials that will restrict access to only the application user.
		    		string userEnv = [Environment.UserDomainName] + "\\" + [Environment.UserName;]
		    		[RegistrySecurity] security = new [RegistrySecurity();]
		    		[RegistryAccessRule] rule = new [RegistryAccessRule(userEnv,]
		                    	[RegistryRights.FullControl,]
		                    	[InheritanceFlags.ContainerInherit,]
		                    	[PropagationFlags.None,]
		                    	[AccessControlType.Allow);]
		    		[security.AddAccessRule(rule);]
	

		    		// Create a new registry key and apply the security context 
		    		Registry.CurrentUser.CreateSubKey("ProcessExecCreds", 
		                    	[RegistryKeyPermissionCheck.ReadWriteSubTree,] 
		                    	security);
	

		    		// Write the encrypted credentials into the registry
		    		Registry.SetValue(@"HKEY_CURRENT_USER\ProcessExecCreds", "User", encryptedUser);
		    		Registry.SetValue(@"HKEY_CURRENT_USER\ProcessExecCreds", "Pass", encryptedPass);
	
}


Expected Result

The following output was obtaining by running the test case as "vm-win2003\Administrator"
Enter user name for process execution: joeuser
Enter password for this user name: joeuser1234
vm-win2003\joeuser

More Information

The code makes use of the CurrentUser scope instead of the MachineKey scope so that other, potentially malicious applications cannot access this key in the registry and decrypt. This provides additional security but sacrifices interoperability with other applications. Consider using the MachineKey scope if you have a need to share credential data between applications.


Additional Resources

* User Impersonation (.NET): http://msdn.microsoft.com/library/en-us/dnpag2/html/PAGHT000023.asp
* Process Class Reference: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdiagnosticsprocessclassstarttopic.asp

Attributes

* Applies To: .NET Framework 2.0, C#
* Category: I/O
* Author: Jonathan Bailey




Return to HomePage
Microsoft Communities