Secure your scripts
Rather than reinventing the wheel each time they create a script, scripters often adapt existing code. However, there are many situations in which you might have a script but need to adapt it to run under a specific security context. For example, suppose you need a script to monitor Windows services locally and remotely and you find a script called ServiceMonitor.wsf. This script performs the tasks you want to automate. It uses Windows Management Instrumentation (WMI) to monitor services, restart a monitored service when it stops, and send an email alert if the service doesn't restart. However, you notice that to run ServiceMonitor.wsf on remote machines, users need to enter an ID and password when they launch the script. Because this information is entered on the command line in plaintext, this practice isn't secure. In addition, because the script provides alerts by sending email messages through a specified secured SMTP relay, the security credentials to establish an SMTP connection are hard-coded in the script, which is another insecure practice. Although you could configure the SMTP relay to accept anonymous connections, doing so is undesirable because it introduces other security risks.
Fortunately, there are several ways to work around these insecure practices. Three common solutions for Windows Script Host (WSH) scripts are to prompt users for passwords, encode the script, and run the script as a Windows service. Before I explain how to implement these solutions, though, you need to know a little bit about the ServiceMonitor.wsf script that we'll be adapting.
Listing 1 shows an excerpt from ServiceMonitor.wsf. You can download the entire script from the Windows Scripting Solutions Web site. Go to http://www.windowsitpro.com/windowsscripting, enter InstantDoc ID 44580 in the InstantDoc ID text box, then click the 44580.zip hotlink.
To launch this script, you use the command
ServiceMonitor.wsf ServiceName \[ServiceName\] \[/Machine:value\] \[/User:value\] \[/Password:value\]
(Although this command appears on several lines here, you would enter it on one line. The same holds true for the other multiline commands in this article.) In this command, each ServiceName parameter specifies the name of a Windows service to monitor. You can specify one or more services. The /Machine, /User, and /Password parameters are optional. If you don't include them, the script will connect to the WMI system on the local host by using the security credentials of the user running the script. If you want to connect to the WMI system on a remote machine, you use the /Machine parameter's value to specify the name of the remote machine, the /User parameter's value to provide the user ID for the remote connection, and the /Password parameter's value to specify the password for the remote connection.
The overall structure of ServiceMonitor.wsf is similar to the structure of the GenericEventAsyncConsumer .wsf script, which I documented in the Exchange & Outlook Administrator article "Exchange 2000 SP2 WMI Updates," January 2003, InstantDoc ID 27211. Therefore, I won't dig into the details of the script here.
Prompting for Passwords
One way to secure passwords is to have the script prompt users for the required passwords when they launch the script. Windows Server 2003 and Windows XP expose a new COM object called ScriptPW.Password. This object works in conjunction with the standard input stream to read any text input. This object doesn't display the input text on the screen, which is perfect for a password prompt.
To modify ServiceMonitor.wsf to prompt for passwords, follow these steps:
1. Delete the code that callout A in Listing 1 shows. This code isn't needed because the script will prompt users for passwords.
2. Add the <object> tag
<object progid="ScriptPW.Password" id="objPasswordPrompt" />
after the code that callout B in Listing 1 shows. This tag defines the ScriptPW.Password object so that the script can use it.
3. Replace the code at callout D in Listing 1 with the code that Listing 2 shows. The new code prompts the user for the password for the remote WMI connection, then the password for the SMTP connection. Note that the password for the WMI connection is prompted only when the /User parameter is specified. If no /User parameter is specified, the script uses the security context of the user running the script for the WMI connection.
In the 44580.zip file, you'll find a script called ServiceMonitorWithPrompt.wsf. This script contains the modifications I just provided. To launch this script, you use the command
ServiceMonitorWithPrompt.wsf ServiceName \[ServiceName\] \[/Machine:value\] \[/User:value\]
Encoding the Script
Although ServiceMonitorWithPrompt.wsf secures the passwords used during the script's execution, the script requires user intervention at start-up. Thus, ServiceMonitorWithPrompt.wsf isn't appropriate for unattended runs. However, hard-coding passwords in plaintext the way ServiceMonitor.wsf stores the SMTP password presents an obvious security risk. Fortunately, the Script Encoder (scr10enc.exe) lets you encode VBScript and JScript files so that you can hard-code passwords with minimal risk. You can download this free utility from the Microsoft Developer Network (MSDN) at http://msdn.microsoft.com/library/default.asp?url=/downloads/list/webdev.asp.
When you use the Script Encoder, the script's extension changes to .vbe and .jse for VBScript and JScript files, respectively. This extension change is important because it informs the WSH scripting engines that the script is encoded.
Unfortunately, the Script Encoder doesn't work for .wsf files because it isn't aware of the XML tags that WSH uses. However, there's a workaround you can use. Follow these steps:
1. In the ServiceMonitor.wsf file, cut the VBScript code in the <!\[CDATA\[ \]\]> section and save the code in a .vbs file.
2. Use the Script Encoder to encode the .vbs file.
3. Cut the encoded code from the .vbe file and paste it back in the ServiceMonitor.wsf file's <!\[CDATA\[ \]\]> section.
In the 44580.zip file, you'll find a script called ServiceMonitorEncoded.wsf that contains the encoded section. Figure 1 shows an excerpt from this script. Note the VBScript.Encode statement in the <script language> tag. A language tag with VBScript.Encode or JScript.Encode informs the script engine that an encoded version of the script is being used. The script engine then turns off its debugging features to ensure that people don't load the script into the debugger in order to decipher the passwords.
Before you use the Script Encoder, though, you need to realize that storing credentials in an encoded script isn't the most secure approach. An encoded script still represents a security risk if someone has access to the script. A more secure approach is to encrypt the script. Encoding uses a generic reversible algorithm, whereas encryption uses a key-based algorithm. However, WSH doesn't currently support encryption. (The only cryptography-based mechanism supported by WSH is the digital signature. For more information about using digital signatures in scripts, see "Secure Script Execution with WSH 5.6," August 2002, InstantDoc ID 25644.) Given that encryption isn't possible, encoding can be a reasonable compromise in a well-secured environment. It lets you execute scripts under the correct security context, yet lets you run scripts as scheduled tasks.
Running the Script as a Service
Although prompting for passwords and encoding scripts are viable solutions in some situations, the ideal solution is to run the script as a service. WSH doesn't expose the Windows service manager APIs, so you can't write a script that stands alone as a Windows service. However, srvany.exe, which you can find in any Windows resource kit, can be very helpful. Srvany.exe is a Windows service designed to start another application. The application then runs within the security context that the service defines.
You can run cscript.exe under srvany.exe, so you can run ServiceMonitor.wsf as a Windows service. To do so, follow these steps:
1.Install srvany.exe. To install srvany.exe, you must use another resource-kit tool: instsrv.exe. In a command-shell window, run the command
Instsrv ServiceMonitor "C:\Program Files\Windows Resource Kit Tools\srvany.exe" -a VMLISSWARENET\#SMTP -p Password1
The Instsrv command's first parameter is the display name you want to use for the service. For this example, name the service ServiceMonitor. The second parameter is the path to srvany.exe. The -a and -p parameters specify the service account name and password, respectively, under which the service will run. (You can also specify the credentials through the Microsoft Management Console—MMC—Services snap-in.) If no credentials are specified, the service will run under the LocalSystem account. Running the script in this account prevents any network access. Therefore, although the script can restart any stopped Windows service under the LocalSystem account, it can't send an email alert to the secured SMTP relay. For this reason, the script needs to run under a custom account. A good solution is to use the #SMTP service account. This account provides permission to send email through the SMTP relay service.
2.Assign the necessary permissions to the #SMTP service account. The #SMTP service account will require permission for starting Windows services. The best way to assign this permission is to make the account a member of the Server Operators group. In addition, if the script will be monitoring remote services, the #SMTP service account must also be a member of the Administrators group because only Administrators group members have rights to make remote WMI connections by default. (Note that the security settings described here are based on the default Windows security settings. Another option is to change the default Windows security settings to match these requirements.)
3.Adapt ServiceMonitor.wsf. You need to modify ServiceMonitor.wsf so that it leverages Windows integrated authentication (i.e., NT LAN Manager—NTLM—authentication) for its SMTP security settings. To do so, replace the lines at callout C in Listing 1 with the following code:
Const cSMTPAuthenticate = 2 Const cSMTPUserName = ""
The first line of code above sets the authentication method to NTLM authentication (which the value of 2 represents). Previously, the authentication method was set to Basic authentication (value of 1). The second line sets the SMTP username to an empty string so that the account running the script is used to authenticate—in this case, the #SMTP service account. Note that the line in callout D that sets the password is omitted entirely in the replacement code.
In the 44580.zip file, you'll find a script called ServiceMonitorSvc.wsf. This script contains the modifications I just described.
4.Let srvany.exe know which application to start. You can tell srvany.exe this information one of two ways: through the ServiceMonitor Properties dialog box or the registry. To provide the necessary information through the ServiceMonitor Properties dialog box, which Figure 2 shows, you need to start the MMC Services snap-in, right-click ServiceMonitor, and select Properties. In the Start parameters text box on the General tab, enter a command such as
/D "C:\\Script Samples" C:\\Windows\\System32\\cscript.exe ServiceMonitorSvc.wsf WMIWatcher.cfg /Machine:RemoteSystem.LissWare.Net
As this command shows, you use the /D switch to define the default directory. You then specify the full pathname of the application you want srvany.exe to run (in this case, the pathname to cscript.exe). Next, you specify the script to start (in this case, ServiceMonitorSvc.wsf). Finally, you provide the script parameters. In this case, the script reads its monitoring settings from a file called WMIWatcher.cfg to monitor a remote system called RemoteSystem .LissWare.Net, which the /Machine switch specifies.
Note the use of double backslashes (\\). The backslash character has a special meaning within the application UI code, so you need to escape (i.e., flag) the backslash with another backslash. Also note that the startup parameters you specify here aren't saved with the service settings, so the next time the service starts, you have to specify this command line again. Therefore, this approach is good only for testing purposes because it requires user intervention to start the service.
To avoid this inconvenience, you must create registry keys that contain the service settings. You can use the code that Listing 3 shows with the reg.exe utility to define the required registry key. (In the 44580.zip file, you'll find the Srvany.txt file, which contains the code in Listing 3 as well as the Instsrv command given earlier.) Windows 2003 and XP include reg.exe by default.
After you run reg.exe, your registry will contain a registry key similar to the one that Figure 3 shows. When you can start the ServiceMonitor service, the ServiceMonitorSvc.wsf script will behave exactly like the ServiceMonitor.wsf script but within the context of the #SMTP service account.
An Important Step
Writing and adapting production-quality code requires consideration for security. This article demonstrates several ways you can protect passwords and run scripts under the correct security context. All things considered, addressing security during the development phase is a worthwhile investment of time because it contributes to an organization's overall security.