Securing ASP Data Access Credentials Using the IIS Metabase - The Article (Page 2 of 4 )
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:
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.
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.