2010-05-26

Nginx & PHP via FastCGI important security issue

A critical security issue has recently been pointed out on servers that run Nginx and PHP via FastCGI. The issue allows anyone to execute their own PHP code on the system, I don't think I have to remind you of the consequences this could have. I will attempt to provide a simple explanation of the issue and more importantly how to fix it.

What is the issue?
I would like to begin by discussing the nature of the problem: it is not caused by Nginx itself - it is not a bug or a security breach in itself. Actually, it is the way that people usually configure Nginx FastCGI options to work with PHP, and how PHP reacts to that configuration. Pretty much everyone adopts the same configuration without being aware of the issue.

The issue itself can be understood simply, then I will explain why PHP behaves that way. Most dynamic websites allow for a reason or another uploading of files. Say, I'm running a forum-based community, users can upload images to use as personal photo or avatar. The photo gets uploaded and you get the following URL:
http://myforum.com/uploads/photo1234.jpg
The breach consists in appending an additional path element to the URL, making it end in .php:
http://myforum.com/uploads/photo1234.jpg/anything.php

Under certain conditions (and unfortunately with default settings), your photo1234.jpg gets processed as PHP file. So you could upload a PHP script renamed as .jpg, upload the image, then execute the script on the server.

If you want to know instantly if your server is vulnerable to this attack, there is a simple way to know. Find a regular file on your server, such as http://myforum.com/robots.txt. Examine the HTTP headers of the response:
HTTP/1.1 200 OK
Server: nginx/0.7.64
Date: Wed, 26 May 2010 10:56:01 GMT
Content-Type: text/plain
Content-Length: 43
(...)

Now add /test.php after the URL: http://myforum.com/robots.txt/test.php:
HTTP/1.1 200 OK
Server: nginx/0.7.64
Date: Wed, 26 May 2010 10:56:01 GMT
Content-Type: text/plain
Content-Length: 43
(...)
X-Powered-By: PHP/5.2.3

The X-Powered-By header was added by PHP which shows that the file was processed by PHP. Now visit that URL http://myforum.com/robots.txt/test.php in your web browser. What do you see:
- do you see the robots.txt file ? if so, your server is vulnerable.
- do you see an error page (403, 404, 500, 502...) or just a simple message "No input file specified" ? if so, your server is not affected by the problem.

Why does this happen?
There are two main reasons why this happens. First let's have a look at the data Nginx transmits to PHP.
A regular FastCGI/PHP configuration would be as follows:
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/vhosts/myforum.com/httpdocs$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_script_name;

When requesting an URL like http://myforum.com/uploads/photo1234.jpg/anything.php to Nginx, here is the data that gets sent:
fastcgi_param SCRIPT_FILENAME /var/www/vhosts/myforum.com/httpdocs/uploads/photo1234.jpg/anything.php;
fastcgi_param PATH_INFO /robots.txt/test.php;

So far, no problem. PHP is supposed to load a file anything.php, in the directory /var/www/vhosts/myforum.com/httpdocs/uploads/photo1234.jpg/. Naturally, this directory should not exist, and anything.php shouldn't exist either, so we should be getting a 404 error.
However, that's where the problem comes in. The PHP option cgi.fix_pathinfo, when enabled (and it is usually enabled by default) will transform these two parameters. The SCRIPT_FILENAME becomes /var/www/vhosts/myforum.com/httpdocs/uploads/photo1234.jpg, which means the .jpg file actually becomes the request filename, and it gets treated as PHP. And PATH_INFO becomes /anything.php. The original purpose of this option was to allow such kind of URLs: index.php/param1/param2/...
But when combined with Nginx, this turns into a major issue.


How do I fix it?
Well, the simplest thing you can do is open up your php.ini configuration file, and insert this directive in the main section:
cgi.fix_pathinfo=0
Then restart PHP-FPM or whatever FastCGI manager you're using.

Unfortunately in some cases that is not possible a solution, since perhaps other scripts on your server make the most of this option. So you could do mainly employ two different solutions on the Nginx side.

First, you could check that the requested URI actually exists, before passing the request via FastCGI:
location \.php$ {
    if (!-f $request_filename) {
        return 404;
    }
    fastcgi_pass 127.0.0.1:9000;
    [...]
}

This solution is efficient and a few of us Nginx+PHP have retained it.
Otherwise, if you think it's too consuming in terms of resources, you could check the URI to meet the following requirements:
- if the URI contains a dot, then a slash (example: image.jpg/...)
- if the URI ends with ".php" (example: image.jpg/test.php)
- then return a 403 error.
location ~ \..*/.*\.php$ {
    return 403;
}
location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
    ...
}

Alternatively, you could make sure that PHP is only enabled in certain directories, where file uploads are not allowed:
location ~ ^/(scripts|sources|src)/.*\.php$ {
    fastcgi_pass 127.0.0.1:9000;
    ...
}

Thanks for reading. And if you find this vulnerability on servers that do not belong to you, contact the server administrator immediately to report the problem!

The problem was discovered here: http://www.80sec.com/nginx-securit.html
And discussed here: http://www.pubbs.net/201005/nginx/39767-nginx-0day-exploit-for-nginx-fastcgi-php.html

Thanks to Martin F. for reporting the issue!

2010-05-10

Dealing with Nginx 400 Bad Request HTTP errors

Today I'll write about something I experienced personally, on my websites.
Some visitors reported that they were getting a "400 Bad Request" Nginx error randomly when visiting pages. And when they start getting that error, they can't access the site anymore: it'll output the same error no matter the page, until you "clear your cache and cookies".

The error is easily understandable and is likely to be caused by... too much cookie data.
Every time a visitor loads *any* page/content/file of your website, it sends the cookie data to the server.
Cookie data is sent under the form of 1 header line starting with "Cookie: ".

Basically, Nginx by default is configured to accept header lines of a maximum size of 4 kilobytes.
When a line in the headers exceeds 4 kilobytes, Nginx returns the '400 Bad Request' error.
Cookie data sometimes gets big, so it causes the error. It particularly happens on forums like vBulletin, Invision and others.
So why does it happen only for some web browsers (Firefox, Chrome...) and not others? Because those browsers do not limit the amount of data a cookie may store. Or maybe they do, but the limit is higher than the default 4k of Nginx. Other browsers limit the amount of cookie data so they do not have the issue.

There is a simple fix for that. The large_client_header_buffers directive of Nginx allows you to define size of buffers that will contain large headers like those big fat cookies.

The directive specifies: the amount of buffers, and the size of buffers. You basically need to increase the size.
In your http block (or your server block, if you want to apply the setting at the virtual host level), insert this directive with a size larger than 4k (actually the default size can be 8k depending on your system, so let's make it... 16k):

http {
   [...]
   large_client_header_buffers 4 16k;
   [...]
}

Save your configuration, reload nginx by running /usr/local/nginx/sbin/nginx -s reload and it should now be fine. If you ever get the "400 Bad Request" again, you could either increase this value once more or look into the code and see why cookies get so big.

2010-05-03

Downloading MMS streams in Linux (CentOS, Ubuntu, Debian, Fedora...) with mmsclient

Hello!

So I was looking into solutions for saving a MMS stream on my server. My connection at home isn't fast enough so I cant watch most of the streams, I'd rather have those downloaded by my dedicated server and then I download the file off my server.

I first looked into mimms: http://savannah.nongnu.org/download/mimms/
Unfortunately it was written in Python and I was missing Python 2.5. After struggling with my system I couldn't get it to work (missing dependencies one after the other).

So I kept looking and I found mmsclient.
The official website: http://ole.tange.dk/projekter/kontroversielt/www.geocities.com/majormms/ (actually a copy of the site, which was hosted on Geocities previously).
Scroll down to the bottom of the page where it says "mmsclient". You will find a link to download it. I've mirrored the link here just in case: http://gbatemp.net/up/mms_client-0.0.3.tar.gz

To install mmsclient, follow these simple steps:
1) Download the package:
wget http://gbatemp.net/up/mms_client-0.0.3.tar.gz
2) Extract it:
tar xzvf mms_client-0.0.3.tar.gz
3) Configure, make, make install:
cd mms_client-0.0.3
./configure
make && make install

That's it! Now you can download mms streams. Just use the "mmsclient" command, followed by the URL of the stream you want to download:
mmsclient mms://example.com/stream.wmv
Additionally, you will notice that it outputs a lot of data to the command line window. To make it quiet, add >/dev/null at the end:

mmsclient mms://example.com/stream.wmv >/dev/null

Enjoy your downloaded streams!

Search This Blog

Loading...