ASP 101 - Active Server Pages 101 - Web06
The Place ASP Developers Go!

Please visit our partners


Windows Technology Windows Technology
15 Seconds
4GuysFromRolla.com
ASP 101
ASP Wire
VB Forums
VB Wire
WinDrivers.com
internet.commerce internet.commerce
Partners & Affiliates














ASP 101 is an
internet.com site
ASP 101 is an internet.com site
IT
Developer
Internet News
Small Business
Personal Technology

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

ASP 101 News Flash ASP 101 News Flash



 Top ASP 101 Stories Top ASP 101 Stories
Getting Scripts to Run on a Schedule
The Top 10 ASP Links @ Microsoft.com
What is Adovbs.inc and Why Do I Need It?

QUICK TIP:
Switch to Fewer Colors Before Capturing Images
Show All Tips >>
ASP 101 RSS Feed ASP 101 Updates


Securing ASP Data Access Credentials Using the IIS Metabase

Thomas C. Carpe

As an ASP programmer, I am always writing code that accesses databases. Many applications, such as Site Server, Commerce Server, SharePoint, and Content Management Server provide their own API that helps an ASP programmer tie into this data in a secure and efficient way. That's nice if you have access to these remarkably expensive platforms, but what about the rest of us? Well, you could roll up your sleeves and just whip off a couple COM objects; however unless you are a crewmember of the starship Voyager, such miracles are unlikely.

Usually, what it comes down to is something more like this:

Set ADOConn = Server.CreateObject ("ADODB.Connection")
ADOConn.Open "myDataSource", "sa", "ItsASecret"

We need less than a second glance to see why this is bad. Any hacker who manages to view the ASP code will now have full access to your database server as well.

You've probably whipped up something like this while developing a new database driven web application. If you're anything like me, you don't want to be bogged down in supporting code and procedures. You want to get connected and begin writing feature code, the stuff that actually does something. "First things first." we tell ourselves; we'll go back and secure the application after we get it working.

If you have a good memory, and if you are not seriously overworked, you might actually come back and do this. When you finally do return to secure your code, there are a couple of things that can typically be done to help secure passwords like this one. First, we don't use the sa, or system administrator, account; that much is obvious. Secondly, we typically define database connections and the strings that support them in global.asa or an include file. But is this really enough?

Using an account other than sa is very important. After all, if a hacker were to acquire the connection name and password, you would want to limit the amount of access they would have to other databases that might be supporting other services on your site or even other customers. However, I say that this is insufficient. Your web application is going to want read and write access to the database in order to perform its duties. So, if a hacker has gotten this far, they've already got enough information to sabotage your site, place false orders, or download your entire user list.

Moving your connection strings to global.asa may seem like a good idea at first, but consider the fact that most hackers are very likely to look here first for critical application information. There are known loopholes that allow hackers access to global.asa, just as there are security issues that allow them to view your ASP code. Include files aren't much better, since hackers can usually view these just as any other ASP file on your site.

Don't think you've done enough to prevent this either. I am generally very thorough about security, but I was very alarmed one day when I received an anonymous e-mail from someone handing me a scrap of code from my site that contained the Administrator password for my entire domain! Fortunately for me, it was a password I had long since changed. (One wonders how my code functioned without it, though.) This just illustrates my point; even if you are diligent now, there's no guarantee that your server has always been protected, particularly if you are sharing it with others.

SQL databases are not the only thing that is susceptible to this kind of attack either. The administrator account I mentioned above was being used to access an LDAP directory. Many applications and frameworks that tie into ASP will require secured access. This is to prevent anonymous web users from accessing the API directly. But in so doing, they also expose us to the serious threat of compromising our security credentials. These can be SQL Server or other database accounts, LDAP directory accounts, or even privileged Windows user accounts. Literally, anything that needs this kind of protection can be at risk in this way.

So, what's a responsible programmer to do? Robert Howard, author of Site Server 3.0 Personalization and Membership (available from Wrox Press) recommends storing this critical information in the registry. There's only one problem. While Site Server and other high-end systems built on ASP often include a means of accessing the registry, Microsoft has, some would say thoughtfully, not included a standardized means of manipulating the registry from ASP. To his credit, Robert also briefly mentions the alternative we will illustrate today, even calling it preferable to using the registry. That alternative is to store our access codes in the IIS metabase.

Did I say preferable? Yes. In fact, the metabase is where IIS stores the usernames and passwords it uses to support itself and ASP. Unlike the registry, it not only includes a means of securing this content, but also a means for hiding passwords from casual observation. And--here's the great news--it comes built into IIS from version four onward.

Enter The Metabase

To do what I am suggesting, you are going to need some handy tools. One of these is the Metabase Editor, or MetaEdit for short. This tool is generously provided by Microsoft, and comes included in the IIS Resource Kit. You can also download it from Microsoft at http://support.microsoft.com/support/kb/articles/Q232/0/68.ASP.

Do yourself a favor and read the knowledge base article if you haven't already. As the name implies, MetaEdit functions with the metabase much like our old friend RegEdit did with the registry. It also shares the same caveat, that you can do a considerable amount of damage with it. Before you face that risk, back up your metabase from the IIS management console, preferably several times. It is extremely important to do this when you are writing code that manipulates the metabase itself, because you will want to be able to undo any potentially bad changes it makes.

Once you have downloaded and installed MetaEdit on your web server, open it and take a look around. You'll see that the metabase has a tree structure, very similar to the registry, or even Active Directory. In fact, like Active Directory (or any LDAP database for that matter) the metabase has a schema. The schema defines all the data types that can be defined within the metabase, in which containers they are valid, and other vital information.

So, this is where we'll begin. You need to define data types that will store the username, password, and connection string for our database. If you were connecting to LDAP or Active Directory, you'd also need to create data types for these connections. There are three paths in which your new data type will be defined. These are each listed under the /Schema/Properties path, and are Defaults, Names, and Types. If you take a direct look at the values under these paths, you can see that they are almost impossible to understand, because much of the information is stored in binary. Fortunately, you can extend the schema via the ADSI, or Active Directory Services Interface, a COM object API that allows us to interact with the metabase, as well as other directory structures. Through ADSI, we can use VBScript or ASP to bind to the metabase and define our values.

Older versions of Windows NT 4.0 may not have the ADSI installed. If this is the case on your server, you can download it from Microsoft from the following URL:

http://www.microsoft.com/NTWorkstation/downloads/Other/ADSI25.asp

Schema: Preparing The Metabase

We want to create three data types, which I have chosen to call ODBCDataSource, ODBCUserName, and ODBCPassword. The data stored in these values will be used to replace the text strings in that awful ADODB command at the beginning of this article. If we wanted to use DSN-less connections, you can extend this list further to include a server and database name as well. You can do the same kind of thing to add other types of connection information for WinNT, Active Directory, LDAP, or whatever you like.

What you don't want to do is take forever to get this part done. After all, we're not even at the useful bits yet. So, I've included a VBScript file called MetaSchema.vbs that you can use to extend the metabase schema, so that it includes these data types. Simply put the script on the desired server, open our command prompt, navigate to it, and then type its name to execute it. You'll need to run it using an account with Administrator level access.

Our sample script does four things. First it creates a class for the new data types. I chose to call this DataAccessMethods. Next, it creates the three data types we described, then adds the data types to the class. Finally, it creates a class for the container that will hold each of our DataAccessMethods instances, called DataAccessStorage.

In this example, all the data types are strings with default settings for inheritance and security. Also, be aware that the error detection is very rudimentary. If the script detects an error, it will simply stop working. In many cases it will skip the remaining code without even reporting the error. As an advanced exercise you can add these features later. However, for the purpose of illustrating our point, this script will run fine as it is.

Now, here's a little about what is going on in this script. If you've done programming using ADSI before, this code will seem very basic to you. You may have been exposed to this through Windows 2000 or Microsoft Site Server. Regardless of whether you are familiar with ADSI or not, this code should be reasonably self-explanatory, and you should be able to familiarize yourself with the syntax by comparing the path names you see in the code to the paths visible when using MetaEdit.

The first thing it does after defining some constants is bind to the IIS metabase schema.

' Bind to the Schema container object.
Set SchemaObj = GetObject ("IIS://" & MachineName & "/Schema")

This is done by using the machine name, in this case "localhost", to create the metabase path. This path is then passed to the GetObject function, which is part of the ADSI component model. The remainder of the script uses the schema object to perform various functions.

Next, the script calls CreateClass, passing the name of the new class, "DataAccessMethods". CreateClass is a simple function, which attempts to create the new class and returns TRUE if it succeeds. The functional part of the code is:

Set NewClassObj = SchemaObj.Create ("Class", ClassName)
NewClassObj.SetInfo

If the class was created successfully, then the script will create the properties themselves, adding each one to the class only if it has also been successfully created. In each case, the script will call CreateProperty and AddToClass for each new property. It does this through a subroutine called CreateProperty_Plus_AddToClass, the purpose of which is basically to save typing and prevent potential spelling errors that would bomb the script.

The script also adds two predefined properties to the DataAccessMethods property. The first is KeyType, which the metabase uses to determine the class of a key within the metabase; generally speaking, all classes make use of this key. The second is AdminACL, which determines the security permissions that will be applied to a given instance of the class.

Finally, the script creates class DataAccessStorage, a class specially created to hold the key folder that contains each of our DataAccessMethod keys. The only properties of this class are KeyType and AdminACL, which will allow us to set the security permissions for the root container.

Here is the condensed code from CreateProperty.

Set NewPropertyObj = SchemaObj.Create ("Property", PropertyName)
If Trim(Syntax) = "" Then Syntax = "string" ' default is String
NewPropertyObj.Syntax = Syntax ' Set the syntax; must do pre-save
NewPropertyObj.SetInfo ' save to the metabase
NewPropertyObj.Inherit = True ' Set attributes by inheritance
NewPropertyObj.SetInfo ' save to the metabase

First, CreateProperty defines NewPropertyObj, the new property object, by calling the Create method of SchemaObj, which we defined earlier. At this point, nothing has changed in the metabase. Before the new property can be saved, its syntax must be defined. While there are many syntax types, "string" is sufficient for our purposes here, and so we use it as the default syntax. After setting the syntax, the script then performs a SetInfo on the object. This stores the item in the metabase. After the property has been saved once, changes can be made to its other settings, such as inheritance. Don't forget to use SetInfo again to save any changes you make at this point.

Now, let's move on to the code in AddToClass.

Set NewClassObj = GetObject ("IIS://" & MachineName _
	& "/Schema/" & ClassName) 'Get the class object
'Get the optional properties list
OptPropList = NewClassObj.OptionalProperties
cnt = UBound(OptProplist)
'Add the new property to the array
ReDim Preserve OptPropList(cnt+1)
OptPropList(cnt+1) = PropertyName
'Write the values to the metabase
NewClassObj.OptionalProperties = OptPropList
NewClassObj.SetInfo

As before, GetObject will retrieve the class object we want to add our properties to. Next, OptPropList is set with the value of the class object's OptionalProperties property.

All this talk of properties, property lists, and optional properties has probably got your tongue tied in knots. Just think of OptionalProperties as an array that contains the list of properties that are part of the class, which happens to be a property of another object altogether. "I love properties! I'll have the properties, properties, properties, properties, invoked methods, and properties!" (I refuse to take credit for this odd naming convention; you can blame the nice folks at Microsoft for it, but it certainly gives us an interesting insight into why they call it a meta-base.)

Now that we've got that little confusion out of the way, the next step is to set cnt to the upper bound of the OptPropList array. We do this so that we can extend the array by one in the following code. ReDim Preserve adds an additional element to the array, leaving existing values intact. This gives us just enough room to add the new property name to the list. Once that's done, the script reassigns OptPropList to the OptionalProperties property. (Here we go again!) And, finally, there is one more call to SetInfo to save the whole sordid mess to the metabase.

If that didn't confuse you as much as it did me the first time around, you can take a look at RemoveFromClass, another function I included that didn't get used here. There is also a wealth of information about ADSI and the IIS metabase on the Microsoft Developer's Network web site. To learn more about extending the IIS Schema, visit the Microsoft web site at:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/iisref/html/psdk/asp/adse3g1f.asp

The result of the preceding section and script is that the IIS metabase has now been successfully prepared to store the special data types that will be used to hold the connection string, user name, and password for the database. Next, we'll write those values, first using MetaEdit, then using ASP.

Storing Values in the Metabase

Now let's have some fun. Open MetaEdit. If you left it open while you ran MetaSchema.vbs, then close it and re-open it to make sure it reflects the changes the script made to the schema. Once you have the Metabase Editor console open, expand the Schema key, and then expand Classes. You should see a key folder named DataAccessMethods. (Mine appeared at the top of the list.) There's nothing of interest in it for us at the moment, but it's important to note (and verify) that it is there. Now, expand the /Schema/Properties/Names key. If you sort the list in the right hand pane by Id, you should see three items in the 13000 range that correspond to the three values we defined using the MetaSchema.vbs script. Each will start with "ODBC". The actual ID values may vary depending on your server configuration.

Once you've verified that the new property types exist, minimize the Schema key and expand the LM key. LM stands for "local machine", by the way. Under this key you will see all kinds of keys for different kinds of services running under IIS on the server. You will want to create a new key to use for credential storage. Create a new key by right clicking LM and selecting New | Key. I chose to name my key ASP101, but you can call it whatever you like, as long as you also change any code that references this path.

MetaEdit Screen Capture

Create a new string value in the ASP101 key folder. In the Id dropdown, choose KeyType, and type "DataAccessStorage" in the data field. This will help ADSI figure out what properties and objects are supported by these containers, including the AdminACL object, which will be essential, as we'll see soon enough.

Next, we need to separate the credentials for this particular web application from any others that we might potentially need for other programs. To do this, create another key under ASP101. I will call mine TestCred. If you want, you can use a friendlier name like an IIS site name or DNS name. Remember that the code in the upcoming samples is based on our naming convention; you'll have to modify it to reflect whatever names you decide to use. You'll need to create a new key for each new set of credentials you require; expect at least one per web site, but possibly more if your site has multiple levels of access.

Now open the TestCred key. Add a new value by right clicking it and selecting New | String. A dialog box will open. In the Id field, use the dropdown to find the ODBCDataSource item and select it. Change the User Type option to ASP App; this will relax security a little so that your web application can read the data. Check the inherit checkbox, if it is not already selected. Finally, type your data source name into the Data field and click OK.

Perform the same operations for ODBCUserName and ODBCPassword. As an extra step, when you create your ODBCPassword property, check the box marked Secure. This will prevent people from casually browsing with MetaEdit to determine the database password. You will get a warning about this step, telling you that it cannot be undone; once a data property has been secured, it can not be unsecured using MetaEdit.

MetaEdit Screen Capture

We're almost done now. Create another string value in the TestCred key and choose KeyType for the Id. Put "DataAccessMethods" in the data field and click OK. You don't need to create the AdminACL keys for either ASP101 or TestCred; we'll explain why when we talk about metabase security.

So, now there are values stored for the TestCred applications DSN, user name, and password. This is everything we need to access the database from ASP, and maybe even more than we needed to secure in this way. That's fine for one application, but how can we automate the setting of these values, so that we can administer multiple sites easily? The answer is to use the same ADSI functions to read and write to this part of the metabase as we'd use to access the schema.

If you want to write ASP scripts that set or change the values stored in the metabase, you will need to force the user to authenticate for those scripts. The user will need to log in to an account belonging to the server's Administrators group. Otherwise your scripts will fail in a profound way. In fact, this is going to be a problem for our scripts when we read the settings, as we'll explain in the next section.

Security and the Metabase

Before you can use ASP code to write to the database, or even read from it, you need to consider metabase security and how it will affect attempts to access this data. By default, only server administrators can access the metabase information. Even MetaEdit doesn't provide a means of changing the access control lists within the metabase. Microsoft provides a sample showing how to change security ACLs using VBScript though, and you can download it here:

http://support.microsoft.com/support/kb/articles/Q267/9/04.ASP

However, the error checking within the script is not very good, and it has some bugs, too. Because of this, I have included a revised version of the script, MetaACL.vbs that fixes a few shortcomings and provides better error checking.

If you want you can grab parts of this code and append them to MetaSchema.vbs to create one script that will set your schema and also set security for you. In my opinion, it is a better idea, to create a separate automated script for this purpose, because you will only need to configure the schema once, but you may reuse this security code to help you configure many, many instances of our DataAccessMethods class.

To change the permissions to suit our purposes, Everyone must be granted the ability to Read entries, and Enumerate objects. In order to do this, you'll have to remove the existing ACL for Everyone first. From the command line, go to the directory in which you have placed the revised MetaACL.vbs script, then type the following, hitting enter after each command:

METAACL "IIS://localhost/ASP101" Everyone -d
METAACL "IIS://localhost/ASP101" Everyone RE
METAACL "IIS://localhost/ASP101/CredTest" Everyone -d
METAACL "IIS://localhost/ASP101/CredTest" Everyone RE

This will remove Everyone from each part of the tree, and recreate it with the correct permissions. The second set of commands is important because, in this example, we have not set AdminACL to inherit settings from its parent. This is an improvement that can certainly be made later.

True, these settings won't make your keys hack-proof. But storing credentials here will help obfuscate your data, so that it won't fall victim to some script-kiddy who just happens to have learned to exploit CodeView.asp. In time, you can do more to secure the metabase even further, enhancing its ability to protect your data.

For example, you could easily make this part of an ASP based web administration script that could both create the necessary keys and properties, and set their security. I think it's a fine idea, but I only have so much space in this article. Also, it's more important for you to understand what is going on in the metabase regarding creation of data and setting security. Trust me, this will help you later when you do begin writing code, and if you do that, then I've done my job here.

Reading The Credentials from the MetaBase

Now that all this preparatory work is done, we arrive at our anticlimactic ending. In fact you're probably going to wonder how something so difficult to set up and configure could possibly be this easy to use. Let's take a look at the code for MetaRead.asp:

There is a lot of code there, but what it does is very simple. First, it defines the names for the machine, root key, and specific data access key that will be uses for this web application. It uses the same names we defined when we created keys earlier. If you created different keys, remember to check your code here.

Const ComputerName = "localhost"
Const StorageKey = "ASP101"
Const DataAccessKey = "CredTest"

We turn on error trapping, because we know that the upcoming commands could easily fail, and we want to test for that.

On Error Resume Next

Then we use GetObject to open a connection to the metabase.

' Get the data access container
MetaBasePath = "IIS://" & ComputerName _
	& "/" & StorageKey & "/" & DataAccessKey
Set ConfigKey = GetObject(MetaBasePath)

Once we have a connection to the container we want, we read the three properties for the data connection using the Get method.

' Read data access values
DataSource = ConfigKey.Get("ODBCDataSource")
UserName = ConfigKey.Get("ODBCUserName")
Password = ConfigKey.Get("ODBCPassword")

If this fails, we generate an error report and set the Boolean MetaSuccess to FALSE. Otherwise, we set MetaSuccess to TRUE. Once we're past our trap, we turn errors back on.

If Err.number <> 0 Then
	Response.Write "<P>ERROR Reading data access " _
		& "credentials from metabase<BR/>"
	Response.Write Err.Number & ": " & Err.Description
	MetaSuccess = FALSE
Else
	MetaSuccess = TRUE
End If
On Error Goto 0

At this point we take a brief moment to indulge in a little optional code. The next section just displays the values we've read in HTML. Obviously, this serves no purpose but to help inflate our egos a bit after a hard day of wrestling the metabase beast, and it should be removed from any real applications for this code.

The last section of the code checks for success by testing MetaSuccess. If TRUE, it will create an ADODB Connection object, then call the Open method to connect itself to an ODBC data source.

If MetaSuccess Then
	ADOConn = Server.CreateObject("ADODB.Connection")
	ADOConn.Open DataSource, UserName, Password
End If

If you are still using my sample data in the metabase, these commands will probably error out, unless of course you've created a DSN called myDSN and set your system-admin password to "ItsASecret". But why go to all that trouble? Put your own DSN data into the metabase and run the code again.

If you have trouble getting the code to access the metabase, check the spelling of your container names. If you don't see any mistakes there and you are still having trouble, or if you are getting messages that say "Permission Denied", then recheck the steps we discussed in the security section. You can use MetaACL.vbs to display the access rights for specific users or all the ACLs of a key. View the source code for details on how to use MetaACL.vbs.

Where to Go From Here

You've learned how to configure the metabase, set up security, add keys and data to it, and finally read that data into a web application. These abilities serve as a foundation that will allow you to accomplish a great deal using the metabase. Now that you've completed this project, there are a lot of things you can do to take this code even further and make it truly useful. With a little extra work, you can craft this lesson into a customized code library or component that will help you manage your usernames and passwords that might otherwise have been vulnerable. You could even build this up to the point where you have a tool that could be sold commercially. At the very least, you can use this code to retool the database driven ASP applications that you have now or might create someday.

Why not use code similar to MetaRead.asp in your global.asa file instead? You could put your metabase read function into an include file, and call it from global.asa using a single parameter to specify the key to read from. Of course, for performance reasons you shouldn't actually create your ADO connections at the application level, however, you can put these commands in an include file and have them read the credentials from application level objects created in global.asa.

Let's not forget that we could do a lot of work to automate the process of creating and populating the keys in the metabase. This goes for securing them as well. Why not build a web application to manage all of this? Just remember that you need to force the user to log in as someone for whom you've assigned write permissions to the DataAccessStorage key. Because you'd be creating new keys, you would want to be sure the user has administrator level access before allowing them to do so.

There really is a lot of uncharted territory here. Custom settings for inheritance and security can be configured on your metabase data to make administration easier on you. And, everything we've done today used only one of the many metabase data types. If you think it over for a while, you might be able to come up with a specialized use for this storage system that I haven't considered. As you play with the metabase some more, you'll find that it has other advantages as well, like replication for example.

So what are you waiting for? Get out there and make good use out of what you've learned.

Code Download

You can download a ZIP file of the code mentioned in this article from here.


Home |  News |  Samples |  Articles |  Lessons |  Resources |  Forum |  Links |  Search |  Feedback

Internet.com
The Network for Technology Professionals

Search:

About Internet.com

Legal Notices, Licensing, Permissions, Privacy Policy.
Advertise | Newsletters | E-mail Offers