Compiled Haskell programs all include special RTS (Run Time System) options, that change things like the number of cores that it runs on, various internal things relating to how often garbage collection runs, etc. They’re specified by invoking the program like ./foo +RTS -m10 -k2000 -RTS to run the GHC-compiled program ‘foo’, reserving 10% of the heap for allocation and setting each thread’s stack size to a maximum of 2000 bytes. In the current build of GHC, there is no way to disable these options from working (although the option –RTS will make all further options be interpreted as normal, non-RTS options). The problem is that the option -tout will write profiling data to the file out. So, if your program is setuid root, anybody who runs it can write the profiling data to, say /etc/passwd and render the system unusable. They don’t get to pick what gets written, so they can’t add a backdoor for themselves, but they can essentially scribble over whatever files they want. This is bug #3910, and the fix (disabling RTS by default) has been uploaded.
Now, one of the more little-known features of CGI is that if you pass a query string that does not contain any = signs to a CGI script, the httpd may pass the string along as command-line arguments. This is specified in section 4.4 of RFC 3875, and it specifies how the query string SHOULD be turned into arguments (although it does not say anything about whether the httpd should behave this way, only that some do). This is an example script that only outputs its arguments in a comma-separated list; the link gives it some sample arguments. Note that by URL-escaping, you can send arbitrary strings through… including +RTS. So if that were, say, a Haskell script, I could pass the query string ?%2BRTS+-tindex.html+-RTS and overwrite index.html.
There are three ways to get around this: first, GHC 6.12.2 has the -no-rtsopts option, which will obviously disable RTS options. So if you just recompile your script with that, it’ll be safe. Note that 6.14 will disable the RTS options by default; the 6.12.2 patch didn’t for backwards-compatibility reasons. Second, if you don’t want to use 6.12.2 for whatever reason, you can wrap it in a shell script that calls it with no options. For example, replace the Haskell script with a shell script called, say, hscript.cgi (if your Haskell program is called hscript) that calls it with no arguments, e.g.
./hscript.real
and rename the Haskell script to hscript.real, so that it doesn’t get run as CGI (I’m assuming that .real files don’t get run as CGI on your machine!) Another thing you can do is to add the following to your .htaccess, which will give 403 Forbidden errors to anybody passing RTS arguments in the URL:
RewriteCond %{QUERY_STRING} ^(?:[^=]*\+)?(?:%2[bB]|(?:-|%2[dD]){1,2})(?:%52|R)(?:%54|T)(?:%53|S)(?:\+[^=]*)?$
RewriteRule ^ - [F]
This will solve it for every Haskell script you use, but relies on the regex being correct, which isn’t something I can guarantee.

Kevin Reid
/ April 24, 2010I strongly recommend not using the third option as it’s more error-prone: the regexp is not obviously correct, whereas the first or second option simply removes this data path entirely and so can’t “miss something”.
zygoloid
/ April 27, 2010One nice “fix” is:
#! /bin/bash
GHCRTS= real-program –RTS “$@”
The –RTS switch disables recognition of all further +RTS flags. I’d recommend this even once GHC 6.14 ships (it’ll still produce a warning if you specify +RTS).
21st Century Man
/ April 27, 2010CGI? 1998 called, it wants its insecure remote executable-launching protocol back.
Hailey
/ June 15, 2010One nice “fix” is:
#! /bin/bash
GHCRTS= real-program –RTS “$@”
The –RTS switch disables recognition of all further +RTS flags. I’d recommend this even once GHC 6.14 ships (it’ll still produce a warning if you specify +RTS).
Longpoke
/ July 18, 2010Agreed with 21st Century Man. Next thing bash is vulnerable because it takes ‘-c ‘…