HOWTO: Run Console Applications from IIS6 on Windows Server 2003, Part 2
I finally have enough blog entries about various portions of IIS6 request processing that I can stitch together this meta-blog-entry explaining how it all works together and then apply it towards an issue. You probably want to keep a link to this loaded entry. Anyhow, here goes...
Question:
We currently has IIS 6 installed on a Windows 2003 server, and we are attempting to run some executables through the browser. We have "Scripts and Executables" selected for the web application, and we are currently allowing "All Unknown CGI Extensions" and "All Unknown ISAPI Extensions" under Web Service Extensions during testing.
With this setup we are able to run some executables successfully through a browser, like:
Ping
Hostname
netstat
query session
However other executables are not working:
Net
Dirquota
Filescrn
Fsutil quota
We are getting no value for most of these, except "Fsutil quota" which is returning:
The FSUTIL utility requires that you have administrative privileges
I have confirmed that the browser is using an ID that has administrative privileges by returning the userid it is using. This administrative ID does not have any problem running Fsutil quota on the server directly.
What privileges am I missing? Does something else need to be set up, or is it possible the executables not working are not CGI or ISAPI?
Thanks for any help.
Answer:
Ok, this blog entry is unique in that it is the first time that I am aggregating information from all my prior posts and applying it to resolve a problem. I will NOT explain everything in detail - only the new stuff... for everything else, if I pulled the information from a blog entry, it will be linked in this blog entry somewhere appropriate.
Putting it all together
When it comes to request execution on IIS6, this is how I think about things:
HTTP.SYS parses HTTP requests in kernel mode and sends a digested form to user mode w3wp.exe. Interesting details about this process can be found in this blog entry.
IIS6 in user mode w3wp.exe receives this request and determines the URL namespace (and hence all applicable metadata from configuration that affect the execution of this request - which includes the authentication required, the ScriptMaps applicable for the request, etc)
Then, the authentication process runs to determine the user token used to execute a request.
One of the factors that affect a user token's abilities is the way the user logon was performed (i.e. logon type). In other words, Interactive logon from Remote Desktop/Local Console is different than the Network logon performed by IIS6. This blog entry describes the differences in more detail.
Furthermore, IIS6 can perform different logon types depending on authentication protocol as well as by configuration, though the defaults pretty much work and should not be fiddled with.
So, logon difference alone may affect how FSUTIL.EXE behaves.
In addition to authentication producing a user token, IIS has to determine the correct handler to execute the requested resource (i.e. should the resource be handled as a static file and sent back as-is? or should it go to a CGI/ISAPI to process? or should it produce a courtesy redirect? Etc). This blog entry and this blog entry describes that decision process.
After determining the specific type of handler to execute a request, IIS uses the user token to execute the handler for the request. This blog entry describes how IIS selects the user token to execute the handler.
Additional user code can be run by the handler, which have arbitrary behavior when it comes to user token, privileges, and generated response
The browser client can have automatic behavior which affects the number and sequence of requests made to IIS, as described in this blog entry.
Whew. If you got to here and read through all the associated blog entries, you have just gone through a LOT of details about how IIS6 functions to process requests as well as how it interacts with user identity. Time to put this info to good use and apply it. :-)
The Problem Restated...
Ok, the following details and conclusions all come from the aforementioned blog entries. If you get lost, I suggest you read 'em and read 'em again until you understand it. :-)
The first thing that needs to be clear is HOW you are "running executables through the browser". Since IIS only recognizes static files, CGI/ISAPI, or Scripts (ok, I am simplifying DAV away since it is not involved in this picture), there are two basic ways to run executables:
As a CGI/ISAPI
If you want IIS to directly run the executable as a CGI or ISAPI, then you need to configure "Scripts and Executables" execute permission as well as Web Service Extension for the binary. i.e.
/cgi-bin has "Scripts and Executable" execute permission enabled.
<full-path-to-FSUTIL.EXE>\FSUTIL.EXE is enabled as a Web Service Extension.
You make a request to https://localhost/cgi-bin/FSUTIL.EXE
IIS is going to execute FSUTIL.EXE as a CGI, so it checks it against Web Service Extension. It is allowed.
Thus, IIS will execute FSUTIL.EXE as a CGI EXE using the user token obtained through whatever authentication protocol is negotiated between the browser and server.
The EXE is executed using either CreateProcess() or CreateProcessAsUser() Win32 API call, depending on IIS configuration for CGI execution. If it is CreateProcess(), then FSUTIL.EXE runs with the Process Identity; if it is CreateProcessAsUser(), then FSUTIL.EXE runs with the IIS impersonated identity.
IIS parses the text output from FSUTIL.EXE according to CGI specification and then sends a response back to the client.
If you use the nph- prefix, IIS won't parse the text and just send it back as is. Of course, if you happen to send invalid HTTP response using NPH, the client can complain or behave weirdly...
Via a Scriptmapped CGI/ISAPI
If you want to run executables on IIS from a script (i.e. an ASP, ASP.Net, or PHP page is considered a script resource executed by ASP.DLL, ASPNET_ISAPI.DLL, or PHP-CGI.EXE / PHPISAPI.DLL Script Engine, respectively), then you need to configure "Scripts" execute permission as well as Web Service Extension for the appropriate Script Engine. i.e.
MyScript.asp contains the following content which executes FSUTIL.EXE:
<%
set objShell = Server.CreateObject( "WScript.Shell" )
objShell.Run( "FSUTIL.EXE" )
%>
/cgi-bin has "Scripts" execute permission enabled.
%systemroot%\System32\inetsrv\ASP.DLL is enabled as a Web Service Extension.
/cgi-bin has a ScriptMaps property which associates .asp extension to %systemroot%\System32\inetsrv\ASP.DLL as a Script Engine.
You make a request to https://localhost/cgi-bin/MyScript.asp
IIS identifies ASP.DLL as the ISAPI Script Engine to process the /cgi-bin/MyScript.asp resource and checks it against Web Service Extension. Since it is allowed, it executes ASP.DLL using the user token obtained through whatever authentication protocol is negotiated between the browser and server.
Note: even though the ASP page runs FSUTIL.EXE, FSUTIL.EXE does NOT need to be in Web Service Extension because IIS never runs nor knows about FSUTIL.EXE. IIS only knows it is running ASP.DLL so that is what needs to be enabled as a Web Service Extension.
ASP.DLL will keep the impersonated identity from IIS and parse/execute the script code in MyScript.asp using Windows Scripting Host. objShell.Run() translates into a CreateProcess() Win32 API call, and FSUTIL.EXE runs using the Process Identity (this is how CreateProcess is documented to work!)
FSUTIL output is unknown to ASP (and IIS) unless you capture the output of objShell.Run() somehow and then Response.Write() it so that IIS knows about it.
Ok, back to the Original Problem
At this point, a couple differences should jump out at you:
When you ran FSUTIL QUOTA directly, it was executed by a user token with Interactive logon type. When you ran it from IIS, the user token has Network logon type.
Depending on how you ran the executables, FSUTIL.EXE was either called via CreateProcess() or CreateProcessAsUser(). The critical difference between them is that CreateProcess() implicitly uses the Process Identity to create the new FSUTIL.EXE process, while CreateProcessAsUser() uses the impersonated identity obtained by IIS through authentication.
The Application Pool Identity of the Application Pool servicing the URL namespace that executed the request determines the Process Identity
The authentication protocol negotiated between the browser and server determines the impersonated identity, in a manner consistent with this blog entry.
I do not know if FSUTIL.EXE has special code that cares about the above details. But I do know that CMD.EXE does... so YMMV.
What about the Executables which appear to return Nothing
As for the question of whether the executables "not working" are not CGI/ISAPI... After executing a request handler, IIS expects a valid HTTP response to come back so that it can send it back to the requesting client.
CGI is nothing more than an executable which outputs text which conforms to the CGI specification (this blog entry describes an interest interaction between CGI and HTTP). If the output conforms to specification, IIS sends it back as-is; otherwise, IIS returns a 502 response.
Since IIS6 in WS03SP1 is lax about CGI conformance and no longer requires the status: and content-type headers, if the executable happens to output a CRLF before printing data, that data gets interpreted as response entity by IIS and sent back to the client as-is. The following are some illustrative examples with CRLF clearly marked as \r\n.
NET.EXE Output:
The syntax of this command is:\r\n
\r\n
\r\n
NET [ ACCOUNTS | COMPUTER | CONFIG | CONTINUE | FILE | GROUP | HELP |\r\n
HELPMSG | LOCALGROUP | NAME | PAUSE | PRINT | SEND | SESSION |\r\n
SHARE | START | STATISTICS | STOP | TIME | USE | USER | VIEW ]\r\n
PING.EXE Output:
\r\n
Active Connections\r\n
\r\n
Proto Local Address Foreign Address State\r\n
TCP TEST-MACHINE:80 0.0.0.0:80 ESTABLISHED\r\n
As you can see, NET.EXE fails because its CGI output starts off with what looks like an invalid header named "The syntax of this command is:" (it has spaces in it, and no header value) prior to the double CRLF, so it fails to parse correctly and IIS returns a 502 "Bad Gateway" response. Meanwhile, PING.EXE succeeds because it starts off with a CRLF that tells IIS to use a 200 OK response header ahead of the PING output.
Now, if you prefix the resources with NPH- (i.e. nph-netstat.exe or nph-ping.exe), then IIS will NOT process the CGI output and just send it as-is back to the client. That is what NPH means - Non-Parsed Header. Of course, this avoids the 502 response from IIS, but if the output is not proper HTTP, it will now confuse the client, which can have its own arbitrary behavior...
Basically, these console commands are not designed to produce output that conforms to CGI specification, so the fact that they work when invoked as CGI by IIS is purely random, mostly depending on whether the output begins with something that looks like broken HTTP headers or not.
Conclusion
I congratulate you on getting to the end of this marathon of a blog post. I finally got a juicy topic to explain and a lot of blog entries to logically stitch together. :-)
Hopefully, this clears up common confusions surrounding:
- IIS request execution logic
- User identity used to execute a request
- Executing an EXE via CreateProcess() is different than via CreateProcessAsUser(), regardless if you made IIS directly execute it as a CGI or indirectly through a Script
- Executing an ISAPI uses the impersonated identity by default, but can be the Process Identity, depending on the ISAPI
- How some EXEs magically work as "CGIs" but others fail
- How some programs, like CMD.EXE, simply fail depending on whether invoked via CreateProcess() or CreateProcessAsUser()... and maybe FSUTIL.EXE is in the same category
//David
Comments
Anonymous
April 28, 2006
That was teh best short explanation of how it all works that I have ever seen. I think, for the first time, I understand all of to pieces.
You should save this and post it somewhere obvious.Anonymous
April 28, 2006
jvierra - thanks for the suggestion. I am planning to do that with this post because it lays down the foundation for understanding how request processing works on IIS6 - it is rather straight forward once you see it all - and should help in understanding how to configure and troubleshoot it, too.
I will probably make additions to it as time goes on (there are still tons of interesting details missing), as new posts, etc...
//DavidAnonymous
April 28, 2006
IIS has always been somewhat poorly documented with MS seaming to go for web site and business apps and not providing in-depth technical information about how IIS works under the covers.
I have been using IIS since it first debuted. I have found that, once you get to know it it is really quite simple.
Except when I have to do something extra. esterday I needed to set up a scond SSL site on a WS2003 SP1 server and relaized that there is no way to specify a header value.
Looking at teh IIS manager page I thought that ther must be a way to do this. I though - maybe MetaDat Editor???
I also had just discovered the following webcast. Who knew?
TechNet Webcast: Using Host Headers with SSL-Enabled Web Sites in IIS 6
https://msevents.microsoft.com/cui/EventDetail.aspx?culture=en-US&EventID=1032280959&EventCategory=5.0 (Level 200)Anonymous
April 28, 2006
The comment has been removedAnonymous
April 28, 2006
Well, as long as we are going to use scripts how would using this work:
set objShell = Server.CreateObject( "WScript.Shell" )
objShell.Exec( "FSUTIL.EXE" )
while not input = "someline"
input = objShell.Readln()
wend
Do what you need with reformatting the lines and sending them to the browser.
This would avoid some issues but is still not the easiest way to set up.
I would try and build everyting in script embedded in the ASP web page. All of the commands listed can have script couterparts. You would need to run the web application at an elevated privilege or only allow admins to use it. All things like quotas and such are available from either AD or from WMI.
I would also consider using NET 2.0 as it has access to the system management objects and can be made to work in a web application.
The only issue I have found with this is that the web server cannot access other hosts under any circumstances without being allowed to delegate.Anonymous
April 28, 2006
The comment has been removedAnonymous
April 28, 2006
maurits - Details, details... :-)
Since .bat is not recognized by IIS as an executable extension, it is really the same as ASP from an IIS-perspective (i.e. it is a ScriptMapped scenario).
In particular, .bat extension has to be ScriptMapped to CMD.EXE to function, and CMD.EXE has extremely tight security checks and is a huge additional dependency that is not present in the simple "run NET.EXE as a CGI by IIS" use.
However, the nice thing is that the .bat file is executed under CreateProcessAsUser().
//DavidAnonymous
April 28, 2006
The comment has been removedAnonymous
April 29, 2006
David -
According to the Webcast wildcard certificates CAN be used with SP1. I am just getting ready to try this on a test server. Somtime next week I hope.
Part of the main theme of the webcast is "using wildcard certs" for cross site security.
Is it possible the the Webcast is wrong. The demo appears to work.Anonymous
April 29, 2006
The comment has been removedAnonymous
April 29, 2006
ADDENDUM to previous comment:
For clarity I want to add that this will only work WITHIN the domain and using "Integrated Security".
David - your blog on SSO/XAPI was excellent and should be helpful in settling issues of "how" and "why" authentication works and doesn't work.Anonymous
May 10, 2006
This is a frequently asked question about IIS6 extensibility - how to access the request entity body...Anonymous
May 13, 2006
The comment has been removedAnonymous
May 13, 2006
PL - I prefer ASP because it is simple, cleanly illustrates the issue, and allows me to explain what is going on without all the fluff. I am interested in explaining the underlying concepts and issues which stay valid through time, and not whatever is the latest and hotest technology.
ASP.Net can only obscure the issue because it involve the Managed Code layer as a system... when we ultimately only care about the Win32 function calls generated by that system
FYI:
1. What you just wrote in ASP.Net is much shorter in ASP.
2. Since the issue has nothing to do with ASP or ASP.Net but rather whether the code ultimately calls CreateProcess() or CreateProcessAsUser(), giving an ASP.Net-based solution does not seem fiiting.
3. My explanation can be equally applied to PHP, Perl, etc - while an ASP.Net-based solution without explaining the fundamentals only caters to the ASP.Net user.
//DavidAnonymous
May 14, 2006
Oh my good, you just wrote two pages about it but you claim it was shorter ?
I can make it shorter if you want me to:
<%
System.Diagnostics.Process.Start("whatever");
%>
I think you better buy a book about .NET, can't belive you work at MS.Anonymous
May 14, 2006
PL - yo, chill... we're both on the same side, just have different approaches, hear me out.
I like to explain the details of what is going on so that anyone can apply knowledge to make their own decisions - critical thinking and problem solving - i.e. teach a man to fish, he will never go hungry
You mention another popular approach - pattern matching - just give me a working solution to the problem. Fast and to the point.
I am not saying that either is good/bad; everyone learns and teaches differently - so please be tolerent.
RE: Book about .NET
Me, I never need to buy a book about .NET; I already studied its core - MSIL / CLR - as well as the C# Specification. The .Net Framework is all applied stuff on top of the core. I don't need to buy a book - I just need to reflect on the DLL for its APIs and I am ready to go.
With your approach, one has to buy a book about .NET and every new Namespace/API because that is how you obtain new information when you do not know how the core really works.
Yes, my approach takes way more time (and pages) than yours, but that does not make either less valid. The objectives are different.
//DavidAnonymous
May 14, 2006
The comment has been removedAnonymous
May 15, 2006
The comment has been removedAnonymous
August 08, 2006
The comment has been removedAnonymous
August 08, 2006
The comment has been removedAnonymous
August 09, 2006
The comment has been removedAnonymous
August 15, 2006
The comment has been removedAnonymous
August 22, 2006
The comment has been removedAnonymous
August 23, 2006
The comment has been removedAnonymous
September 04, 2006
Can we to this on linux aswell ?Anonymous
June 05, 2007
The comment has been removedAnonymous
December 19, 2007
how can i use the CreateProcessWithLogonW() API function from vc6.0 without installing any .net /asp/c# packege on my IDE / computer/ (I understand it is essential to load a dll with this api, but this is the only thing I can use). i need CreateProcessWithLogonW() to do silent log on (with my known username and password) to a remote desktop. (I am a win2000 professional client rdp). I wish to open the remote desktop programatically without the user password popup/window/input fields. thenks ori kovacsio@hotmail.comAnonymous
December 20, 2007
ori - I don't understand your question. VC6.0 can directly call CreateProcessWithLogonW() API. No .Net/ASP/C# required. For example, this is exactly what IIS6 does when it launches a w3wp.exe, and IIS6 has no .Net/ASP/C# code. //DavidAnonymous
December 20, 2007
The comment has been removedAnonymous
February 20, 2008
Hi All, I like all the discussions above, I used same way as following to load my application, ProcessStartInfo startInfo = new ProcessStartInfo(@"C:Program FilesMacromediaFlashPaper 2FlashPrinter.exe"); Process proc= new Process(); proc.StartInfo = startInfo; startInfo.UserName = "Administrator"; startInfo.Password = _password; startInfo.UseShellExecute = false; startInfo.Arguments = ...; proc.Start() ; My problem is, when I use "Administrator" the codes working OK, when I use other kind of users (except "Administrator") or remove username and password, it is not working. "Administrator" account is not allow to use in my application, can any one please tell me how to create a user which has a lower right as "Administrator", but still let the application works.Anonymous
February 20, 2008
Hi David, As my last post, my application is a traditional asp website, so I can not use web config or add <identity impersonate="true" userName="accountname" password="password" />. I made properties to receive usernamse and password, the application is working OK only when user name is administrator, but I am requested to use a non-admin account. Do you know how to fix it ? Thanks.Anonymous
February 21, 2008
I have a console application that aquires data from external equipment via ethernet packets and builds data files of the raw data then creates PCL/HPGL-2 print files from this data. I want to launch an external program, a PCL to PDF converter .exe, from within my console application. I am using cygwin to develop the app with and can't seem to locate the appropriate functions to allow me to launch the external .exe. Any suggestions for me. deg@vidcoinc.com Thank you.Anonymous
February 22, 2008
ron - You need to clarify whether FlashPrinter.exe requires the user to have administrative privileges. Also, your code snippet is in .Net, but you say your application is traditional ASP website, so that seems to conflict. And if you want to run an application as another user, just use RUNAS.EXE and no need to use .Net. As for how to run code using a specific user identity - the answer is linked off this blog post, so I shall leave that exercise to the reader. //DavidAnonymous
February 22, 2008
Darwin - There are many ways to launch external programs in Win32, but since you want cygwin, I won't be able to help. I suggest contacting cygwin support for your questions. This is a support forum for IIS/ISAPI related questions. //DavidAnonymous
February 26, 2008
The comment has been removedAnonymous
May 17, 2008
Thanks David for te detailed information, was very helpful to get my CGI's working under IIS 60 Thank youAnonymous
June 09, 2008
The comment has been removedAnonymous
June 10, 2008
BobManahon - CMD.EXE on Windows Server 2003 will not run when launched via Win32 CreateProcess() (that's what oShell.Run boils down to) from IIS6, unless the Application Pool Identity is LocalSystem or if the Application Pool's ProcessIdentity == Impersonated Identity from Authentication. This pretty much prevents launching remote command consoles by hackers, as well as IIS scripts launching legitimate administration batch files. //DavidAnonymous
September 05, 2008
The comment has been removedAnonymous
December 18, 2008
I am trying to run a batch file which in turn calls a vbs file from iis6 but its failing with permission error 800a0046 permission denied. I have given permission to iusr_computer to run the file within iis all the extensions are allowed as this is behind a firewall and on the intranet and only used to display results of tests, in an attempt to automate the tests so they automatically restart is what I am trying to do. my page sendvuserend.asp only contains the following <%@language=vbscript%> <% set wschell=Server.CreateObject("Wscript.shell") wshell.run "c:restart.bat" %> I don't understand why this doesn't workAnonymous
December 22, 2008
The comment has been removedAnonymous
April 07, 2009
The comment has been removedAnonymous
November 17, 2009
Great Article. Thank you. I can make this work for everything but shutdown -s -t 25 from xp pro 64 bit running php 64 bit with fastcgi under iis 64 bit. Any pointers?Anonymous
March 10, 2010
The comment has been removedAnonymous
March 31, 2010
Hi David, I have this similar issue. My problem is I am able to call the exe but it remains in the memory. Means when I see the task manager, I could the exe being launched but it does no work. I had already enabled "Allow to interact with Desktop" on both IIS Admin and World Web Publishing service. SAMPLE CODE I AM USING
ProcessStartInfo myProcess = new ProcessStartInfo("notepad.exe"); myProcess.UserName = "BLAH-Blah"; myProcess.Password = convertToSecureString("DGHFDH"); myProcess.Domain = "ABCD"; myProcess.UseShellExecute = false; Process.Start(myProcess); It runs fine on XP machine but not on Server 2003. Any pointers ??
- Anonymous
November 17, 2010
The comment has been removed