From 86be99fc439c893d4542313e5a3df89093a3d7b3 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 21 May 2013 17:11:01 -0500 Subject: [PATCH] PHP as CGI paper --- papers/index.mdwn | 1 + papers/php-cgi.mdwn | 161 ++++++++++++++++++++++++++++++++++++++++++++ src/eris.mdwn | 2 +- 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 papers/php-cgi.mdwn diff --git a/papers/index.mdwn b/papers/index.mdwn index b0aa08a..509c6b5 100644 --- a/papers/index.mdwn +++ b/papers/index.mdwn @@ -16,6 +16,7 @@ Computer Nerd Stuff * [Runit on Arch Linux](arch-runit.html) * [Reply-To Munging Still Considered Harmful](reply-to-still-harmful.html) +* [Runnning PHP as a CGI](php-cgi.html) in anything other than Apache * [Converting .docx files to text using unzip and sed](docx.html) * [Introduction to TCP Sockets](sockets.html) * [3-Minute HTML Tutorial](html-tutorial.html) diff --git a/papers/php-cgi.mdwn b/papers/php-cgi.mdwn new file mode 100644 index 0000000..2a47802 --- /dev/null +++ b/papers/php-cgi.mdwn @@ -0,0 +1,161 @@ +Title: Running PHP as a CGI + +I'm the author of the +[eris HTTPd](http://woozle.org/~neale/src/eris.html), +a small web server intended for use on embedded Linux devices with low RAM and low storage. +I've used other web servers (boa, mathopd, thttpd, etc.) for years, +and this problem has been present for as long as I can remember. +A [recent gripe post about PHP](http://me.veekun.com/blog/2012/04/09/php-a-fractal-of-bad-design/) +inspired me to document it. + +The Situation +------------- + +Let's say I'm using something that isn't Apache, +and I need to run some PHP code. +PHP comes with a CGI variant, +so I'll use that: + + #! /usr/bin/php-cgi + + +When run from the command-line, this does exactly what I expect. +Superb! +When I try it from a browser, though, I get: + + Unable to load the webpage because the server sent no data. + +Well that's puzzling. +So I do my standard thing, +wrapping it with something to dump stderr to stdout. + + #! /bin/sh + echo "Content-type: text/plain" + echo + exec foo.php.cgi 2>&1 + +Now I get this (reformatted for narrow displays): + + Security Alert! The PHP CGI cannot + be accessed directly. + +

This PHP CGI binary was compiled with + force-cgi-redirect enabled. This means + that a page will only be served up if the + REDIRECT_STATUS CGI variable is set, e.g. + via an Apache Action directive.

+

For more information as to why + this behaviour exists, see the + manual + page for CGI security.

+

For more information about changing this + behaviour or re-enabling this webserver, + consult the installation file that came with + this distribution, or visit + the + manual page.

+ +It's writing out HTML, sort of: I mean, the first paragraph doesn't have P tags, but whatever. +So clearly they intend me to see it from a browser. +I mean, the interpreter binary is called `php-cgi`, after all. +And yet, they're not actually writing out a CGI header, +so the web server thinks it's a broken script and dies. +So maybe they meant me to view this from the command-line. +Except, this error only shows up when I run it as CGI. + +Anyway. + +PHP is asking me to set the REDIRECT_STATUS variable, +and telling me that this is somehow a security concern. +It's interesting how Python, Perl, and Lua don't think the lack of this variable is a security problem. + +A quick Google search tells me this is a non-standard CGI variable that Apache sets. +But I'm not using Apache, otherwise I'd just use mod_php. +Why are you making me set this, PHP? + +What I thought would fix it +--------------------------- + +Okay, whatever. + + #! /bin/sh + echo "Content-type: text/plain" + echo + REDIRECT_STATUS=1 export REDIRECT_STATUS + exec foo.php.cgi 2>&1 + +Now I get this: + + Status: 404 Not Found + X-Powered-By: PHP/5.3.3-7+squeeze15 + Content-type: text/html + + No input file specified. + +However, it continues to run just fine from the command-line. + +404 means "file not found". +Which is very strange, because clearly the file *was* found, +given that PHP has sent its "X-Powered-By" header. +If it couldn't find the file, it wouldn't have known enough to start php-cgi. +And, remember, this all works fine from the command line. +Not to mention Python, Perl, Lua, and even the lowly Awk and Bourne shell, +have no trouble finding their input file when run as a CGI with the shebang (#! /path/to/binary) +as the first line. + +What actually fixed it +---------------------- + +After nearly a full day trying to chase this cryptic message down in web searches, +I landed on a PHP bug open since 2004: +[PHP CGI depends on non-standard SCRIPT_FILENAME](https://bugs.php.net/bug.php?id=28227). +Included in the comments on this ancient but still unresolved bug is a link to +[a wrapper](http://pastebin.ca/1296199) +which proports to fix the problem. + +So the ultimate fix to make `php-cgi` actually run like a CGI is to wrap it +with a second CGI that convinces PHP that I really meant it: + + #! /bin/sh + REDIRECT_STATUS=1 export REDIRECT_STATUS + SCRIPT_FILENAME=$(pwd)/foo.php.cgi export SCRIPT_FILENAME + exec $SCRIPT_FILENAME 2>&1 + +If I have multiple PHP CGIs, +I must wrap each one. +Or just give up and install Apache, +which is, I'm sure, +the path taken by most system administrators who haven't written their own web server. + +Why does PHP do this? +--------------------- + +I have skimmed [the URL that they asked me to](http://us3.php.net/manual/en/security.cgi-bin.attacks.php). +They list two points: + +1. "Interpreters open and execute the file specified as the first argument on the command line." This is true, it's how shebangs work (a file "script.sh" beginning with "#!/bin/sh" is magically transformed to ["/bin/sh", "script.sh"]). It's how Python and Perl launch. I don't get the exploit path here, unless there's some horrible way to misconfigure Apache to do the wrong thing with scripts. + +2. If you put the php interpreter under your web server's document root (where files are served from), and then use it to load up php scripts using the `PATH_INFO` environment variable *which is specified by the end user*, then PHP will dutifully serve up every document on your machine. + + If you don't get it, (I didn't either), what they mean is that + some sysadmins honestly want the following URL to work: + + http://yourhost/cgi-bin/php-cgi/var/www/yourhost-root/scripts/mything.php + + This is such a terrible way to do things that it took me half an hour to even understand what they were describing. It's analagous to putting your shell in `/var/www/cgi-bin` and then asking the browser (which, remember, the end user can make do whatever they please) to pass the path to your shell script in as part of the URL. + + +These are both just awful, terrible, +hideous ways to set up a web server. +But apparently people do it anyway, +and apparently the PHP developers felt like it was their job +to allow this setup and still try to prevent +some of the security nightmares it entails. +Their solution: introduce dependencies on two Apache-specific +environment variables, +and one of them (`REDIRECT_STATUS`) +isn't even checked further than "is this set to anything at all". + +And that, dear reader, is why you must fake out PHP +in order to get it to behave like every other CGI ever written. + diff --git a/src/eris.mdwn b/src/eris.mdwn index 32abffc..4533171 100644 --- a/src/eris.mdwn +++ b/src/eris.mdwn @@ -38,6 +38,6 @@ Differences with fnord Download -------- -* [Version 3.1.2](http://woozle.org/~neale/g.cgi/eris/snapshot/eris-3.1.2.tar.gz) +* [Version 4.1](http://woozle.org/~neale/g.cgi/eris/snapshot/eris-4.1.tar.gz) * [Source Control](http://woozle.org/~neale/g.cgi/eris)