Why can I upload a file without IIS Write Permission?

A frequently misunderstood aspect of IIS is that disabling "Write" permissions from IIS Management UI actually prevents anyone from writing files to the server through IIS (such as upload files). This is clearly not the case, as the following question suggests... so is this a security hole in IIS6, or is there another explanation?

Question:

A customer have a IIS 6 web server and even with IIS Write property DISABLED, an ASPX form can upload files to the server.

The authentication is Anon (via IUSR_ user) and the IUSR_User have RWXD rights on the folder where the upload is stored.

In the properties of the IIS folder where upload is done, the Read permission is set, but Write, SourceAccerss and Browse are disabled.

Why the upload works???

Answer:

Yeah, we see lots of incredulous/frantic posts like this one in microsoft.public.inetserver.iis.security because the user thinks that it must be a security bug in IIS. After all, I just disabled "Write" from IIS, so why can a remote user still write to my server?

The short answer to this question is that everything the user observed is correct and by-design. The user just failed to configure what he thinks he configured, and IIS can do nothing to save you from your own misunderstanding...

The long answer

ASP.Net, IIS, and the NTFS FileSystem are all separate systems running on top of Windows. Since they can function independently from each other, they each have their own authentication, authorization, and access-control mechanisms, and they are all configured through different mechanisms in different configuration stores.

These mechanisms all have to be configured and interacting correctly in order for a logical concept, like "uploading a file", "browsing a directory", "running an ASPX page", etc to function. When they are not configured correctly, you see people asking questions about "why am I denied access", "why am I getting Forbidden", etc... but that's for another discussion another day.

Now, you may wonder why have all these different authentication/authorization/access-control mechanisms at all. After all, I just want to prevent users from writing files to my server or prevent them from reading a file - so just give me a simple "Allow/Deny" checkbox and do what I want! Isn't that what the IIS Manager UI means with the "Read" and "Write" check boxes?

Unfortunately, that is not the case. IIS, ASP.Net, and NTFS are all too configurable to give you this simplified view of life. The IIS Manager UI only configures settings at the IIS scope, so the the "Read", "Write", "Script source access", and "Directory browsing" check boxes only affect IIS-level behavior. What they translate into is:

  • "Read" access controls whether the HTTP GET verb is allowed. In other words, can you retrieve an HTML, GIF, or CSS static file from the web server. The astute reader should note that without Read access, you can still send the GET verb to scripts like an ASPX Page, and they will STILL execute and generate responses.

    Is this a bug? Not really... consider the following: for an URL whose resource extension matches an application mapping, does IIS actually read and execute the script file named by the URL, or does it execute a script handler binary which parses the URL to determine what script content to actually execute and then generate a response?

    Hint: you allowed "Scripts" execution permission, and GET is allowed by the Application Mapping, so IIS can execute the script handler. What the handler does with the URL is up to it; it can use the URL to look up content from a database and execute that content and have NOTHING to do with the filesystem.

  • "Write" access controls whether HTTP PUT verb is allowed. This verb allows the client to send entity-body and "put" it at the physical location corresponding to the URL being "put".

    i.e. If the URL "/" corresponds to the physical location C:\inetpub\wwwroot, then a PUT /file.txt request will create C:\inetpub\wwwroot\file.txt and its contents will be the entity body sent on the request.

  • "Script source access" controls whether to allow the DAV "Translate: f" command, which allows the client to retrieve the unprocessed source code of a script instead of the executed response generated by the script source code.

    i.e. If you have an Application Mapping of .asp to ASP.DLL and "Script source access" is enabled, then a GET /foobar.asp request with the "Translate:f" header will return the unprocessed source code of foobar.asp. Otherwise, GET /foobar.asp will return the response generated by executing code contained within foobar.asp.

  • "Directory browsing" controls whether IIS will send back a listing of a directory if you make a request to the "/" of a given virtual directory and no default document exists. Thus, if you always have a default document defined and existing, directory browsing will never happen.

As you can see, these access controls in IIS have nothing to do with allowing/denying uploads, so enabling "Read" while disabling "Write", "Script source access", and "Directory browsing" definitely do nothing as far as denying ASPX upload is concerned.

What about the Question?

What do I think is actually happening in your situation?

  • Since anonymous authentication is enabled, IIS will use the user token of the configured anonymous user identity in IIS for all remote, unauthenticated requests.

  • You configured ASP.Net to either impersonate (<identity impersonate="true" /> in an ASP.Net .config file), or you configured the ASP.Net worker process identity to be the configured anonymous user identity in IIS (<processModel userName="IUSR_UserName" password="IUSR_Password" /> in an ASP.Net .config file). This controls the user identity ASP.Net uses to execute the ASPX page that accepts the upload.

    I suspect you have the former configuration because the latter takes much more effort to misconfigure.

  • You ACLed the directory to give write access to the configured anonymous user identity in IIS, so the ASPX page could write there.

Now, since ASPX is arbitrary code, it can do whatever it wants when it runs, including writing something to some directory. IIS configuration can only control whether this code is executed at all (i.e. does IIS allow a handler to execute the URL that represents this ASPX page).

So, how would you disable uploads in your situation? Try changing any of the above three parameters that I mentioned so that remote requests do not run with a user identity that can write to the directory in question, and uploads should stop happening.

//David