Don't trust user supplied data - Real World Example

Suppose I have a notion of a User Account in my application, and I want to allow 3rd parties to tie that user account to one or more objects in an external directory/authentication system. 

Let's say, for the sake of argument, that I'm going to use System.DirectoryServices.DirectoryEntry to represent links to external directory objects. 

I need to persist the relationship between a DE and my concept of a User. 

The way I've chosen to do this is by taking the Path Property  of the DirectoryEntry and saving that, as a string, into the database.

When I need to re-hydrate my user Account object, I'll back the DirectoryEntry property by using the DirectoryEntry constructor which takes the Path as its argument.

Now, this is all well and good, except what it means is that anytime anyone loads an instance of my User Account, and looks at the DirectoryEntry property (say, a user management application), they're going to be running code that I supplied data to.  Namely, the constructor code for the DE class.

We're now in a situation where we're running code, and passing it an argument from an untrusted source. 

DE is currently a shim on top of the COM based ADSI architecture.  ADSI is a scheme with multiple, pluggable providers.  The DE Path specifies the provider to use.

This is nice from a programming perspective - if you want to get an Active Directory object corresponding to a user, you might try:

de = new DirectoryEntry(”LDAP://CN=mattev,DC=microsoft,DC=com”);

alternatively, if you had an NT4 provider:

de = new DirectoryEntry(”WinNT://localhost/administrator”);

You've got the same .NET DirectoryEntry object, and the same ADSI COM interface, but underneath you've got an LDAP provider handling one LDAP object, and a WinNT provider handling the other.

Now, it turns out that there's a 3rd “standard” provider -the IIS:// provider.  This provider lets you use ADSI to script commands against the IIS metabase.  The IIS ADSI interface is extensive - you can create virtual directories, delete them, delete metabase backup copies, and so on.  Essentially any IIS admin task can be performed via the IIS ADSI interface.

Note that any of these IIS:// type paths are perfectly valid DirectoryEntry paths - Infact, only the IIS provider can say if an IIS:// path isn't valid. 

So I think you can see where this is going.  Suppose hypothetically, user “Evil” is running our user management system.  Evil, for whatever reason, can update the DirectoryEntry property of one of our user objects.  Say that Evil does the following:

user.DirectoryEntry = new DirectoryEntry(”IIS://importantserver/W3SVC/1/ROOT/Important/deleteApplication”);

Note that I'm not familiar enough with the IIS provider to actually know what the IIS path would be to perform this action, so play along :)

Now, because Evil doesn't have any sort of permissions to administer https://importantserver , this property assignment may fail or it may not - it depends on the ADSI provider, frankly.

If the property assignment silently fails, then we persist the above ADSI path into our database.

Now user “Admin” comes along and runs the user management application.  If the user admin app displays the directoryentries associated with a given user, the DirectoryEntry constructor will be run on the path saved in the database.

Admin does have permission to administrate https://importantserver.  And just the process of the application running the DirectoryEntry constructor is enough to to destroy the IIS application that Evil was targeting. 

In reality, there was at least one thing that kept this attack from working.  When Evil tried to set the DirectoryEntryPath property, we checked the Schema type of the object indicated by the path.  There's nothing in the IIS ADSI space that looks anything like a user or group schema.  This marginal validation was just enough to keep the IIS:// path from being a valid DirectoryEntry  in the eyes of the application, so it was not persisted. 

Secondly, it seems likely that the IIS provider, given a path with a verb, would return a failure code if the verb failed, and thus the DirectoryEntry constructor would throw an exception if its underlying COM call returned the error. 

Finally, it's not clear that you can actually construct IIS:// paths that are verbs.