Vulnerability in "Proxy Everything" (Wiki article Pitfalls)

Hi,

I discovered a vulnerability in your best practice wiki article:

The configuration which is promoted as “good” doesn’t work as intended:

<<<>>>
server {
server_name _;
root /var/www/site;
location / {
try_files $uri $uri/ @proxy;
}
location @proxy {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME
$document_root$fastcgi_script_name;
fastcgi_pass unix:/tmp/phpcgi.socket;
}
}
<<<>>>

try_files checks if a file exists and if it does, it is simply returned
to the browser. This is a good thing for static files, but with this
configuration PHP files are not parsed.

<<<>>>
$ curl -i http://foo.lan/info.php
HTTP/1.1 200 OK
Server: nginx/0.8.54
Date: Tue, 08 Mar 2011 14:08:07 GMT
Content-Type: application/octet-stream
Content-Length: 20
Last-Modified: Tue, 08 Mar 2011 13:05:32 GMT
Connection: keep-alive
Accept-Ranges: bytes

<?php phpinfo(); ?>

<<<>>>

The configuration works, i.e. the PHP files are parsed, only in one
case: If nginx believes the files does not exist (although it does) and
handles the request to the FastCGI instance.

<<<>>>
C:\Users\Lukas>curl -i http://foo.lan/info.php/asdf
HTTP/1.1 200 OK
Server: nginx/0.8.54
Date: Tue, 08 Mar 2011 14:21:17 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.3.5-1 <-------- This file was parsed by PHP as
intended.

[…]
<<<>>>

Posted at Nginx Forum:

It is not meant as a complete configuration. The examples are to
highlight specific cases where issues might occur and how to better
handle such cases.

I agree that it could be worded better but no one should ever use an
example from the pitfalls page as their full configuration.

Posted at Nginx Forum:

On 8 Mar 2011 14h33 WET, [email protected] wrote:

server_name _;
}
Date: Tue, 08 Mar 2011 14:08:07 GMT
case: If nginx believes the files does not exist (although it does)
X-Powered-By: PHP/5.3.5-1 <-------- This file was parsed by PHP as
intended.

[…]
<<<>>>

That’s a generic example. The pitfalls page is meant to warn you
against some inadvisable practices. It’s not meant to be a config
recipe. You should always adapt your config to your application.

As a rule all PHP (or whatever language file) scripts should be
enumerated in the config, if possible with exact matchings, or if
using PATHINFO with the correct pattern.

Otherwise you’re setting yourself up for getting p0wned.

— appa

My point is: The bad example does something, which is extremely
inefficient but it just works. It has no side effects concerning
security. All files are parsed by PHP, so no unparsed configuration
files can leek.

The “good example” only handles requests to the FastCGI instance if the
file or directory can not be found by nginx. This is clearly not the
same although the whole intention of the pitfall site is, in my eyes, to
offer exactly that: A naive, inefficient way to achieve things and a
professional, tested, reliable and secure way. It’s the first URL given
in Debian’s default config and possibly the first place a user will look
like searching for help.

Proxying everything is certainly a bad idea; proxying too less is
disastrous in terms of security. This should be pointed out in the wiki
in big fat letters. Or better, let’s come up with a better example of
how to proxy a bare minimum.

Posted at Nginx Forum:

On Tue, 2011-03-08 at 14:37 -0500, Lukas0907 wrote:

in Debian’s default config and possibly the first place a user will look
like searching for help.

Proxying everything is certainly a bad idea; proxying too less is
disastrous in terms of security. This should be pointed out in the wiki
in big fat letters. Or better, let’s come up with a better example of
how to proxy a bare minimum.

I agree, it’s not good to replace one bad example with another. OTOH,
cluttering up the “good” example might dilute the point (the particular
pitfall being discussed) with details.

Maybe continue that with a third example that introduces the pitfall of
accidentally serving up source code and provides the complete, correct
solution, or at the very least, put a big, fat warning next to the
“good” example.

Cliff

On Tue, 2011-03-08 at 19:09 +0000, António P.P.Almeida wrote:

That’s a generic example. The pitfalls page is meant to warn you
against some inadvisable practices. It’s not meant to be a config
recipe. You should always adapt your config to your application.

As a rule all PHP (or whatever language file) scripts should be
enumerated in the config, if possible with exact matchings, or if
using PATHINFO with the correct pattern.

Otherwise you’re setting yourself up for getting p0wned.

So… maybe this pitfall should also be covered in the pitfalls page and
linked to from that example?

I agree with the OP that this example is bad, and given that people
usually read the minimal amount of documentation required to solve a
task, it’s likely people will be caught with this.

Cliff

On 8 Mar 2011 19h37 WET, [email protected] wrote:

My point is: The bad example does something, which is extremely
inefficient but it just works. It has no side effects concerning
security. All files are parsed by PHP, so no unparsed configuration
files can leek.

I beg to differ. If there are files that are not supposed to be
interpreted by the PHP interpreter and your config doesn’t prevent
that: it’s a bad thing. Efficiency, certainly being important, falls
behind security IMO.

The “good example” only handles requests to the FastCGI instance if
the file or directory can not be found by nginx. This is clearly not
the same although the whole intention of the pitfall site is, in my
eyes, to offer exactly that: A naive, inefficient way to achieve
things and a professional, tested, reliable and secure way. It’s the
first URL given in Debian’s default config and possibly the first
place a user will look like searching for help.

Proxying everything is certainly a bad idea; proxying too less is
disastrous in terms of security. This should be pointed out in the
wiki in big fat letters. Or better, let’s come up with a better
example of how to proxy a bare minimum.

The problem with that is that it varies from app to app. It’s quite
difficult to be “generic”. Hence probably that’s one of the reasons
why the example in the Pitfalls page is so “uncongenial”.

We should opt for a specific example and warn about the pitfalls
involved.

— appa

This was a discussion in this ML a few weeks ago labelled “nginx 0day”
or so. There were a few sample configs that don’t run into this
problem posted. I check for existence of the php file inside the proxy
location with
try_files $uri =403
for example.
And of course always be sure to have cgi.fix_pathinfo=0 if you don’t
need it.

bb, proud owner of a hacked pathinfo box.

I created this conf file following a couple of how to’s on the internet.
I use this template for creating wordpress sites (hence the total cache
references)

Servidor: name.server.tld

Cliente: template / Dominio: www.domain.tld

Revisión: 19/3/2011

server {
server_name www.domain.tld domain.tld;
root /home/clientes/tamplate/domain.tld;
index index.php;

listen 1.1.1.1:80;
#listen 1.1.1.1:443;

#ssl on;
#ssl_certificate /usr/local/nginx/conf/certs/domain_bundle.crt;
#ssl_certificate_key /usr/local/nginx/conf/certs/domain.key;

error_page 404 = @wordpress;
log_not_found off;

location ^~ /files/ {
rewrite /files/(.+) /wp-includes/ms-files.php?file=$1 last;
}

Rewrite minified CSS and JS files

rewrite
^/wp-content/w3tc/min/([a-f0-9]+)/(.+).(include(-(footer|body))?(-nb)?).[0-9]+.(css|js)$
/wp-content/w3tc/min/index.php?tt=$1&gg=$2&g=$3&t=$7 last;

Set a variable to work around the lack of nested conditionals

set $cache_uri $request_uri;

POST requests and urls with a query string should always go to PHP

if ($request_method = POST) {
set $cache_uri ‘no cache’;
}
if ($query_string != “”) {
set $cache_uri ‘no cache’;
}

Don’t cache uris containing the following segments

if ($request_uri ~*
"(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|index.php|/cuenta/|/compra/|wp-comments-popup.php|wp-l${
set $cache_uri “no cache”;
}

Don’t use the cache for logged in users or recent commenters

if ($http_cookie ~*
“comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in”)
{
set $cache_uri ‘no cache’;
}

Use cached or actual file if they exists, otherwise pass request to

WordPress
location / {
try_files /wp-content/w3tc/pgcache/$cache_uri/_index.html $uri $uri/
/index.php;
}

Cache static files for as long as possible

location ~*
.(xml|ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)${
try_files $uri =404;
expires max;
access_log off;
}

Deny access to hidden files

location ~* /.ht {
deny all;
access_log off;
log_not_found off;
}
location @wordpress {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
include fastcgi_params;
fastcgi_param SCRIPT_NAME /index.php;
}
location ~ .php$ {
try_files $uri @wordpress;
fastcgi_index index.php;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ^~ /blogs.dir/ {
internal;
root /home/clientes/template/name/wp-content;
}
}

Posted at Nginx Forum: