This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
|
Security with Windows NT and IIS: A Primer
By Jeff Niblack |
Is setting up the proper level of security on your IIS installation proving to be a headache? This guide to your options can ease the pain. |
Chances are, it hasn't taken you long to encounter little security gremlins running amok in your Web applications built on Microsoft® Internet Information Server (IIS). Often, a few minutes spent refreshing your memory related to Windows NT® security and IIS fundamentals is all that's needed to resolve the issue. Occasionally, however, you'll fall headfirst into that deep, dark crevasse of impersonation. No matter how hard IIS tries, your Web apps fail and your users' screams grow louder.
For Web developers who must support line-of-business apps where files are scattered throughout the maze of corporate-networkdom, the problem only escalates. I know developers who have spent hour after hour trying to get IIS to allow access to secured network drives only to find out that it can't be done without compromising the security of the targeted resourceor worse yetthe entire Web site. With a little ingenuity, along with the help of the Windows® networking (WNet) API and Visual Basic®, there is hope.
One Step Backward
|
Figure 1: IIS Authentication Methods |
Anonymous requests are the most common requests handled by HTTP servers. In this mode the server knows nothing about the user short of an IP address, browser type, and a few other nuggets of general information. Since all tasks performed under Windows NT require a valid Windows NT account, IIS needs an account to use when handling these requests. Therefore, when IIS is installed, by default the installation process creates a local user account, known as the IUSR account, with IUSR_machinename as the user name. This account is then added to the Guests group on the machine. When IIS receives an anonymous HTTP request, it impersonates the IUSR account to perform the task. Because the IUSR account by default has rights similar to that of the Guest account, access is very limited. In most cases (such as simple HTTP PUTs of HTML files), this isn't a problem. However, many of today's Web applications require access to secured resources such as database servers or network drives. Authenticated requests require a valid Windows NT account. IIS receives the required user information in one of two ways: Basic Authentication and Windows NT Challenge/Response (otherwise known as NTLM). With Basic Authentication the user is prompted for a user name and password, which is then Base64-encoded and passed to IIS. Once IIS receives the user's information and verifies it against the Windows NT user database on the Web server or an available domain controller, IIS will impersonate the specified user. This is great for Web developers because the required user information is provided. Unfortunately, it's also great for hackers since Base64- encoded information is very easily broken. NTLM is similar to Basic Authentication in that user information is provided to IIS. However, NTLM doesn't actually pass the user name and password over the wire. Instead, a one-way encryption method is employed to authenticate the user with a hash value. This is clearly the most secure form of authentication that IIS supports. Unfortunately for Web developers, the user name and password aren't available to IIS. This effectively prevents IIS from impersonating a user when needed, and as a result requests to secured resources will fail.
Uh-Oh, the Crevasse
Here Comes the Sun
|
Figure 5: Project Properties Box |
Now that you have an ActiveX DLL to perform your task, test it in Visual Basic before moving on to the ASP page for the Web server. With MINDSample as the active project in Visual Basic, select the File | Add Project menu item and add a Standard EXE project. Visual Basic will create Project1 with Form1 as a form. Add a textbox to Form1 named Text1 that is the same size as the form. You'll use it to hold the contents of the ASCII files you get with the ActiveX DLL. Change the default properties of Text1 as follows: |
|
Now add a code module by selecting the Project | Add Module menu item and clicking the Open button. Copy the following code into the newly created Module1: |
|
In the Project Explorer box, right-click Project1, as shown in Figure 6 and select Set as Startup. This ensures that your test project (Project1) will be started when you run your Visual Basic program. Finally, open the properties box for Project1 by right-clicking Project1 in the Project Explorer and selecting Project1 Properties. Select Sub Main as the Startup Object and click the OK button.
After saving the entire project group and compiling Project1, press F5 to run the test program. The contents of the target text file should display in Text1 of Form1.
A là ASP
Dive into the Code
|
|
dwScope specifies the scope of the network resource that is being defined. In my solution this value is actually ignored, so I won't define a value for it.
The dwType argument is used to indicate the type of resource that is being defined. Since I'm dealing with network drives, I'll set this to RESOURCETYPE_DISK or &H1. dwDisplayType is used to indicate how the connection should be displayed in a user interface. Since this doesn't apply here, I won't do anything with it. Likewise, dwUsage is only used when dwScope is set to RESOURCE_GLOBALNET or &H2, so there's no need to set this either. lpLocalName and lpRemoteName, however, are key to the solution. lpLocalName is used to specify the local device name or drive identifier. You'll need this device name when you actually do something with the connection, so you'll need to set it. In my example, I used W:, but you can use any available drive identifier. lpRemoteName is used to define the remote network name. This would normally be a UNC value. The last two components of the NETRESOURCE type are lpComment and lpProvider. Neither of these are required in this example, so I'll just leave them undefined. WNetAddConnection2 is the function used to add or create the network connection. The WNet API actually defines three different functions to add connections: WNetAddConnection, WNetAddConnection2, and WNetAddConnection3. WNetAddConnection is included only to provide compatibility with previous versions of Windows. WNetAddConnection3 is functionally equivalent to WNetAddConnection2, with one exception: WNetAddConnection3 includes an additional parameter (hwndOwner) that is used to provide a handle to a window for any network provider dialog boxes. This means that WNetAddConnection2 provides the functionality you need. Here's the Visual Basic function declaration: |
|
The lpNetResource parameter should be the NETRESOURCE defined previously. lpPassword and lpUserName are used to specify the Windows NT account information, if necessary. dwFlags is used to specify various connection options. In this case, I want the connection to be remembered (persistent) so I'll set this value to CONNECT_UPDATE_
PROFILE or &H1.
If successful, a call to the WNetAddConnection2 function will return NO_ERROR or 0. Otherwise, a failure occurred. In the sample ActiveX DLL, I've limited the scope of the error routine to only identify that an error occurred, not specifics about the error. (I'll talk more about this later.) Here's an example of connecting to \\TEST1\TESTAREA using TestUser as the Windows NT account (the password is "test") and W: as the local drive. |
|
To cancel a connection, the WNetCancelConnection2 function is used. Once again, for backward compatibility reasons, the WNet API includes the WNetCancelConnection function for older versions of Windows. The Visual Basic declaration for the WNetCancelConnection2 function is: |
|
The lpName parameter contains the name of the local device that was added using a previous call to the WNetAddConnection2 function. dwFlags is used to specify connection options. In this case, since I added a persistent connection, I want to also update the profile to indicate that the connection was cancelled or terminated. Setting dwFlags to CONNECT_UPDATE_PROFILE or &H1 accomplishes just that. The fForce parameter is used to indicate whether a connection should terminate even if there are open files or jobs on the connection. This value is set to either &H1 (TRUE) or &H0 (FALSE).
If successful, a call to the WNetCancelConnection2 function will return NO_ERROR or 0. Otherwise, a failure occurred. Once again, I'm not concerned with handling the errors at this point, so I'll just let the user know than an error occurred. With all that in mind, here's an example of canceling the network connection I previously added: |
|
Now that you know the purpose of the WNet API functions and type and how to call them, let's use them to do something after you have a connection. In the sample ActiveX DLL, you simply wanted to read the file that was specified. Visual Basic can easily provide this functionality by opening the file for Input and then reading and saving each line until you hit the end of the file. |
|
Aside from a few basic statements to trap the Visual Basic errors and return the contents of the text file, that's about it for the sample ActiveX DLL.
The code for the Visual Basic test program is very straightforward and involves only a few lines: |
|
My sample defines two local variables, objTemp and strTemp. Use objTemp as the variable to hold the pointer to the GetFileTest function in the ActiveX DLL. Once you've initialized the objTemp variable using the Set statement, you can make the call to the sGetFile method. Use the return value of sGetFile to hold the contents of the file you read, which is then shown using the Form you created. Finally, release all the system and memory resources associated with objTemp.
The sample ASP code is nearly the same as the test Visual Basic program. The only differences involve displaying the contents that were returned from the call to the sGetFile method of the ActiveX DLL. Instead of showing the contents in a form, wrap the contents up with the <pre> block element so the displayed text uses the formatting included in the text file. |
|
Whoops!
|
|
This is used for each call to a WNet API function to verify if an error occurred. If the return code from the function call doesn't match NO_ERROR, then the ErrorUpd procedure is called with the LastDllError and Description from the Visual Basic Err object. |
|
The ErrorUpd procedure is used to update two module-level variables, mlngErrorNum and mstrErrorDesc. These variables are accessed by calls to two properties: |
|
A third property, sError, is included to return a string with mlngErrorNum and mstrErrorDesc concatenated. |
|
Once inside the ErrorUpd procedure, the error number is assigned to the mlngErrorNum variable and is compared against the error numbers that are returned by WNetAddConnection2 or WNetCancelConnection2. If a match occurs, the mstrErrorDesc variable is updated with a detailed error message. When a network provider-specific error occurs (indicated by an error number of ERROR_EXTENDED_ ERROR or 1208), the WNetGetLastError WNet API function is called. Here's the Visual Basic declaration for the WNetGetLastError function. |
|
Each of the five parameters defined above are used to return information from the network provider. lpError is the actual error number that occurred. lpErrorBuf is the actual error message, and nErrorBufSize is the length of the error message in lpErrorBuf. Finally, lpNameBuf is the name of the network provider with nNameBufSize holding the length of the network provider name in lpNameBuf.
Figure 8 shows some sample Visual Basic code that gets the network provider-specific errors.
Odds and Ends
|
http://msdn.microsoft.com/library/devprods/vs6/vstudio/ vsentpro/veconconfiguringsecurityforinternetinformationserver.htm and http://support.microsoft.com/support/kb/articles/q229/6/94.asp |
From the October 1999 issue of Microsoft Internet Developer.
|