Archive for the ‘PHP’ category

Securing Nginx and PHP

December 16th, 2011

Disclaimer
This write up is intended for single-user systems where you are the only user expected to log in via shell/terminal/sftp (or at least people you actually trust). This collection of tips does not cover situations where you may have multiple users home folders or a shared hosting situation utilizing nginx and php-fpm. Generally speaking if you have to read this post for security tips you probably should not be allowing access to any other user but yourself in the first place.

If you do not care to read this whole write up, just remember one thing: `777` is not a magical quick-fix; it’s an open invitation to having your system compromised. (Script-Kiddies… can you say jackpot?)

User/Groups

Generally speaking your server, which will most likely be a VPS running some fashion of linux will already have a web service user and group. This will sometimes be www, www-data, http, apache, (or even nginx if a package manager installed it for you). You can run the following command to get a list of users that are on your system.

cat /etc/passwd

Both Nginx and PHP-FPM should run as a web service, on a Debian squeeze this would be www-data:www-data, or on FreeBSD www:www.

If your server was set up with root being the main user you should create an unprivileged user for handling your web files. This will also make it easier to handle permissions when uploading your web files via SFTP. For example the following command on a debian system would create a user named kbeezie which has www-data as the primary group.

useradd -g 33 -m kbeezie

Group ID #33 is the id for www-data on Debian Squeeze (you can verify with id www-data). You may have to su into the new user and change the password (or usermod to change). This will also create a home folder in /home/kbeezie/ by default. You can log in via SFTP to this user and create a www folder if you wish. You’ll notice that the files will be owned by kbeezie:www-data, which will allow Nginx and PHP to read from, but also gives you group-level control over how the web services may treat those files.

This is ideal since you’re not giving nginx or php-fpm too much control over the user’s files and they can be controlled with the group flag. You could also create the user with it’s own group such as kbeezie:kbeezie and just change the group of the web files to www-data where appropriate.

SSH Options

It is usually advisable to disable Root access via /etc/ssh/sshd_config with the following line:

PermitRootLogin no

However make sure you can log in with your new unprivileged user via SSH, and also make sure that you can `su` into root permission. On a FreeBSD system only a user belonging to the wheel group can su into root, and only a user listed in the sudoers file can use the sudo command. However on Debian/etc the user could have www-data as it’s own group and still be able to su/sudo as long as the root password is valid. Your password should be at least 12 characters long and contain digits, symbols and at least one capital letter. Do not use the same password for root and your web user.

Once you’ve verified that you’re able to obtain root status with the new user you can safely disable root log in via sshd_config and restart the ssh deaemon.

You should also change your default SSH port, which is 22. While a port scanner could probably find the new SSH port it is usually best practice not to use the default port for any type of secure access. Like before make sure you can log into the new port (you can configure sshd_config to listen to both 22 and another port to test this out).

SSH – PubKey Authentication

If you are on OSX or another unix/linux operating system, like I am, setting up pub key authentication is fairly painless. Logged in as your web user on the server you can run the following command:

ssh-keygen

The above by default will ask for a passphrase for your private key as well as a destination to save both the id_rsa and id_rsa.pub files (which will normally be ~/.ssh/). You can then copy your own user’s public key to an authorized_key file with the following command.

cp ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys

Then on your own computer you can run the ssh-keygen command as well, copy your own computer’s public key from the id_rsa.pub file and add it as another line to your server’s authorized_keys file.

If you have `PubkeyAuthentication yes` listed in your sshd_config file with the authorized key path being that of .ssh in your home folder the server should allow you to log in without prompting you for a password. Just remember that if you chose not to use a passphrase for your private key then it is possible for anyone who obtains your id_rsa* files to log into your server without being prompted for a password.

You can even turn off password authentication completely and rely solely on public key authentication by setting `PasswordAuthentication no` in your sshd_config file. However keep in mind, unless you have another means of getting into your server you might get locked out if you lose your public/private key or access to the machine you use to log in (also not every SFTP or Terminal application supports pub key authentication).

I actually have public key authentication set up with my iPad using iSSH for quick server access on the go (and you do not need a jailbroken iPad for this).

On the next page are some Nginx and PHP specific configurations to hardening your installation.

Nginx Flood Protection with Limit_req

April 9th, 2011

The Test

I’ll show you a very simple demonstration of Nginx’s Limit Request module and how it may be helpful to you in keeping your website up if you are hit by excessive connections or HTTP based denial-of-service attacks.

For this test I’m using a copy of my site’s about page saved as about.html, and the Blitz.io service (which is free at the moment) to test the limit_req directive.

First I test the page with the following command in Blitz, which will essentially ramp the number of concurrent connections from 1 to 1,075 over a period of 1 minute. The timeout has been set to 2 minutes, and the region set to California. I also set it to consider any response code other than 200 to be an error, otherwise even a 503 response will be considered a hit.

-p 1-1075:60 --status 200 -T 2000 -r california http://kbeezie.com/about.html

Not bad right? But what if that were a php document. That many users frequently might cause your server to show 502/504 errors as the php processes behind it keep crashing or timing out. This is especially true if you’re using a VPS or an inexpensive dedicated server without any additional protection. (Though if you use either, iptables or pf might be a good resource to look into)

You can of course use caching and other tools to help improve your website, such as using a wordpress caching plugin, which you should be using anyways. But sometimes that one lone person might decide to hit one of your php scripts directly. For those type of people we can use the limit request module.

Let’s create a zone in our http { } block in Nginx, we’ll call it blitz and set a rate of 5 request per second, and the zone to hold up to 10MB of data. I’ve used the $binary_remote_addr as the session variable since you can pack a lot more of those into 10MB of space than the human readible $remote_addr.

limit_req_zone $binary_remote_addr zone=blitz:10m rate=5r/s;

Then inside my server block I set a limit_req for the file I was testing above:

location = /about.html {
	limit_req zone=blitz nodelay;
}

So I reload Nginx’s configuration and I give Blitz another try:

You’ll notice that now only about 285 hits made it to the server, thats roughly 4.75 requests per second, just shy of the 5r/sec we set for the limit. The rest of the requests were hit with a 503 error. If you check out the access log for this you’ll see that a majority of the requests will be the 503 response with the spurts of 200 responses mixed in there.

Using this can be quite helpful if you want to limit the request to certain weak locations on your website. It can also be applied to all php requests.

Applying Limit Request to PHP

If you would like to limit all access to PHP requests you can do so by inserting the limit_req directive into your php block like so:

location ~ \.php {
	limit_req   zone=flood;
	include php_params.conf;
	fastcgi_pass unix:/tmp/php5-fpm.sock;
}

It may help to play with some settings like increasing or decreasing the rate, as well as using the burst or nodelay options. Those configuration options are detailed here: HttpLimitReqModule.

Note about Blitz.io

You may have noticed from the graphs above that the test were performed for up to 1,075 users. That is a bit misleading. All of the hits from the California region came from a single IP (50.18.0.223).

This is hardly realistic compared to an actual rush of traffic, or a DDOS (Distributed Denial of Service) attack. This of course also explains why the hits are consistent with the limiting of a single user as opposed to a higher number that reflecting a higher number of actual users or IP sources. Load Impact actually uses separate IPs for their testing regions. However with the free version you are limited to a max of 50 users and number of times you may perform the test. You would have to spend about $49/day to perform a test consisting up to 1,000 users to your site.

Testing from a single IP can be easily done from your own server or personal computer if you got enough ram and bandwidth. Such tools to do this from your own machine include: siege, ab, openload and so forth. You just don’t get all the pretty graphs or various locations to test from.

Also if you were testing this yourself, you have to remember to use the –status flag as Blitz will consider 5xx responses as a successful hit.

Better Alternatives

I won’t go into too much details, but if you are serious about protecting your site from the likes of an actual DDOS or multi-service attack it would be best to look into other tools such as iptables (linux), pf (packet filter for BSD) on the software side, or a hardware firewall if your host provides one. The limit request module above will only work for floods against your site over the HTTP protocol, it will not protect you from ping floods or various other exploits. For those it helps to turn off any unnecessary services and to avoid any services listening on a public port that you do not need.

For example on my server, the only public ports being listened on are HTTP/HTTPS and SSH. Services such as MySQL should be bound to a local connection. It may also help to use alternate ports for common services such as SSH, but keep in mind it doesn’t take much for a port sniffer to find (thats where iptables/pf come in handy).

Nginx and Codeigniter The Easy Way

March 5th, 2011

I personally don’t use Codeigniter (was never much of a fan of PHP frameworks), however I had a client approach me with this issue so I decided to take a stab at it.

Now normally you would look for any existing .htaccess that comes with a script package and attempt to convert the rewrite rules. But seriously though, why do we really want to go thru all that fuss when we can simply use try files:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    root /path/to/your/website.com/;
 
    location / {
        try_files $uri $uri/ /index.php;
    }
 
    # For more information on the next two lines see http://kbeezie.com/view/nginx-configuration-examples/
    include php.conf;
    include drop.conf;
}

In a nutshell this should normally work… but why does it not?

Quite simple really; CodeIgniter by default uses PATH_INFO which is a really antiquated method of parsing the uri. As a result we must tell CI to use REQUEST_URI instead. To do this open up your config.php under /system/application/config/ and find the $config['uri_protocol'] and set it to this:

$config['uri_protocol'] = "REQUEST_URI";

You could also choose to use AUTO, but since we know we’ll be dealing with request_uri, it is best to set it as such (though if you do run into problems, give AUTO a try).

If you have not already set the index page, you will want to blank it out in order for it to work with a rewrite method (request_uri, etc).

$config['index_page'] = "";

For known static files, take it a step further and capture common static requests so that you can cache accordingly:

	location ~* \.(ico|css|js|gif|jpe?g|png)$ {
		expires max;
		add_header Pragma public;
		add_header Cache-Control "public, must-revalidate, proxy-revalidate";
	}

Stop Bit.ly from Traversing your Redirects

November 12th, 2010

Bit.ly is one of my favorite URL Shortener, but it has one major flaw when it comes to link building; it tends to drill down to the final destination of a link and shortens the link at the end. This can make shortening a prosper redirect nearly impossible as the shortened url bypasses it completely.

To illustrate this observe the image below:

In a nutshell Bit.ly will keep following a redirect until it hits the end, and will use the final destination as the url that needs to be shortened. This also means even if you create several of your own redirects with different names, the shortened url will always be the same if they all point to the same destination.

The solution is rather simple, though it may be a tad inconvenient depending on your setup. If you use your own custom PHP redirect script you can simply place this at top:

if($_SERVER['HTTP_USER_AGENT'] == 'bitlybot') { echo 'Hi Bitly'; exit(); }

What this will do is halt the redirect if it detects bitlybot, so that the final destination happens to be the same one you pasted in the box, as such will leave your link logic safe (such as geo-targeting, IP blocks, etc)

Thats really all there is to it.

PS: I’ll be including that into the next version of KBLinker.

Mimic Apache mod_geoip in Nginx

November 12th, 2010

Maxmind makes a variety of APIs and tools to use their geolocation database and one such tool is the mod_geoip module for Apache. Using the GeoIP module at the apache level means that PHP can access the visitor’s country code simply by means of an environment variable such as this:

echo $_SERVER['GEOIP_COUNTRY_CODE'];

To setup Nginx with this capability we’ll need to recompile Nginx if you have not already used the –with-http_geoip_module compile option.

First we’ll need to install the GeoIP API system-wide:

CentOS (yum)

yum install GeoIP-devel

Debian/Ubuntu (aptitude)

apt-get install libgeoip-dev

Mac OS X 10.5+ (via HomeBrew)

brew install geoip
sudo brew link geoip

Once you have the GeoIP library installed you can then proceed to recompile Nginx, this is rather simple if you have installed Nginx from source (this assumes you already have the nginx source unpacked somewhere):

$ cd src/nginx-0.8.53
$ nginx -V 
nginx version: nginx/0.8.53
built by gcc 4.4.4 (Debian 4.4.4-6) 
TLS SNI support enabled
configure arguments: --prefix=/opt --with-pcre=/root/src/pcre-8.02 --with-md5=/usr/lib 
--with-sha1=/usr/lib --with-http_ssl_module --with-http_realip_module --with-http_gzip_static_module 
--with-openssl=/root/src/openssl-1.0.0a/ --without-mail_pop3_module
 --without-mail_imap_module --without-mail_smtp_module
$ ./configure --prefix=/opt --with-pcre=/root/src/pcre-8.02 --with-md5=/usr/lib --with-sha1=/usr/lib \
--with-http_ssl_module --with-http_realip_module --with-http_gzip_static_module \
--with-openssl=/root/src/openssl-1.0.0a/ --without-mail_pop3_module \
--without-mail_imap_module --without-mail_smtp_module --with-http_geoip_module
$ make && sudo make install
$ /etc/init.d/nginx restart

We’ll want to download the Maxmind Geolite Country Database some place Nginx can use it.

$ cd /opt/conf
$ wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
$ gunzip ./GeoIP.dat.gz

Then we need to tell Nginx where to find that file, so in your nginx.conf add this in your http { } block:

geoip_country  /opt/conf/GeoIP.dat;
#geoip_city     /opt/conf/GeoLiteCity.dat;
#Uncomment the above if you also wish to lookup cities

Once that is done you can restart Nginx, you’ll be able to use variables such as $geoip_country_code to obtain the visitor’s country code. Full details of this module can be found at HttpGeoIPModule.

We’re not done yet, now we need to make it so that PHP see’s these results in the same fashion it would with the apache module. In your fastcgi_params or where ever you are passing fastcgi_param values to PHP you’ll wish to add at least these two lines:

	fastcgi_param GEOIP_COUNTRY_CODE $geoip_country_code; 
	fastcgi_param GEOIP_COUNTRY_NAME $geoip_country_name;

Once that is done, restart Nginx. Now you’ll be able to access the visitor’s country code in PHP via $_SERVER['GEOIP_COUNTRY_CODE].

You can instead use the module to redirect visitors straight from nginx in the following fashion:

    server {
        server_name  mysite.com www.mysite.com;
	root /opt/html/mysite.com;
 
        location / {
                try_files /index_$geoip_country_code.html index.html;
        }
    }

If a visitor from Russia visits your site it will try to load index_RU.html, and if that is not found will fall back to index.html, likewise if you created a index_US.html a visitor from the United States will see that page’s content.

Generating Ioncube Licenses

September 28th, 2010

Ioncube Encoder Pro or Cerberus is required to generate license files that can be distributed to your customers. Most of the nitty-gritty involved with the make_license executable can be found in the user guide (a pdf document) distributed with the encoder in section 4. The entry level version of Ioncube Pro cannot generate licenses, however much of the same restrictions can be applied to an encoded project (section 3 of the user guide) on a per-customer basis, pro/cerberus eliminates the hassle with having to re-encode and redistribute the project for each customer.

In this article we’ll talk about two of the most popular restrictions that can be applied to a license. But first we need to look at how to prepare a project to be used with a license. Since I am using Mac OSX there is no graphical user interface, as such these instructions will be based on the command line options.

Preparing Project for License Management

Let us say that we have our project in a folder called myproject, the simplest way to encode such would be to use the following command (ioncube_encoder5 is for PHP 5.x, ioncube_encoder is for PHP 4.x, version 7 of Ioncube will likely have a separate binary for PHP 5.3 encoding)

ioncube_encoder5 /projects/myproject --into /encoded-projects

The above will encode myproject into a new folder located under /encoded-projects, no licensing or restrictions has yet been applied. To encode the project so that a license file is required you add

ioncube_encoder5 --with-license key.php --passphrase yourpassphrasehere /projects/myproject --into /encoded-projects

Note there is a passphrase used, you must use a passphrase when specifying a license file to be searched, you will use the same passphrase when generating a license with make_license. With the options above the encoded files will search for a file called key.php, and will check the parent folder recursively until it finds it. If it is a valid license the script will execute normally. If it is not, you’ll get a simple error stating that the code has an invalid license.

However I’ve always preferred to show errors a bit more gracefully and more explanatory and to do so the script needs to be able to handle the validation. To do this we add the –license-check option like so.

ioncube_encoder5 --with-license key.php --passphrase yourpassphrasehere --license-check script \
/projects/myproject --into /encoded-projects

What this does is disables the automatic checking done by the ionCube loader, and allows you to handle the script’s response to an license issue using the Loader API. Below is a simple example how to check if the license is expired and respond accordingly.

<?php
 
if(ioncube_file_is_encoded() === false) { 
	/* if this is in a function you can return true to bypass license check if the file is not encoded,
	makes it easier to debug when you don't have to keep turning on and off the license check during
	coding and encoding. */
} else { 
	//Obtains the license properties
	$ic_prop = ioncube_license_properties();
 
	/*
	Always use === when matching boolean, since a non-boolean response could be interpreted as true 
	or false when it would in fact be an array or other value. For example the function above will return an 
	array of license properties, but if the file is not encoded or does not have a valid license it will 
	return FALSE. As such will never return TRUE but an array response comared with == could be
	interpreted as a FALSE response.
	*/
 
	if($ic_prop === FALSE)
	{
		//ioncube_license_properties returns false if a license file is not found or invalid/corupted
		echo "License File Not Found or Not Valid.";
		exit();
	}
 
	/* The two functions below will return FALSE if the file is 1) encoded, 2) requires a license and
	3) the server/time does not meet the license restrictions. */
 
	if(ioncube_license_matches_server() === FALSE)
	{
		//This will check to see if the current domain matches the license restriction
		echo "License is Not Valid for This Domain.";
		exit();
	}
 
	$expiry = ioncube_license_has_expired();
 
	if(ioncube_license_has_expired() === TRUE)
	{
		//The above function will return true if the license is expired
		echo "License Has Expired.";
		exit();
	}	
 
	/*
	Other checks such as property values, like encoding the user's transaction ID or feature restrictions 
	from the license file. License properties such as UserName can be grabed like so:
	$ic_prop['UserName']['value'] 
	*/
}

So there you have it a very brief explanation of how to encode a project to use a license, and how to check for the license yourself so that you may control the output (such as making your own branded page with the error as opposed to a simple text on white background) or alter the project’s features based on the license properties.

Generating a License with make_license

The ionCube encoder ships with a make_license binary, the Windows and Linux version of the encoder both ship with a linux version of the binary (plus a windows executable for Windows). The Mac OSX version of the encoder only ships with an OSX binary of make_license, which annoyed me quite a bit as I tried to use it on my linux-based hosting provider. For OSX users as of version 6.5 you have to request a linux binary of the make_license file from ionCube support.

In a nutshell this is how utilize the make_license binary manually on a linux server:

./make_license --passphrase yourpassphrasehere --header-line '<?php exit(0); ?>' --property "UserName='Chuck Norris'"

Note that you have to use the same passphrase used to encode your project. The above license has no restrictions yet, rather just a few header lines and encoding a property called UserName with the value of Chuck Norris within. The results of above will generate something like this:

<?php exit(0); ?>
 
 
------ LICENSE FILE DATA -------
9TS21X45EIPmmmjcWh+ZLlelwqJyMLD8
R2SpnyDbbMtdlpgO5bKYCwVI8wM1oqH1
SCLOc/tJ93duEBVt1BFX6//GL+UkLtXI
ZxYecENH0KR6sAoR5iEDSMcgicXpxcto
NR0VDOJETrkmWGHkc+KhpELoB4iF+RjB
ALizJ3gzQjL9HTfDZS+zGF0JVzy7IBrg
fNJ5pvxUJ+p/15hrhnYfWhOlkB5lhr/x
B+ommB3rAwVAxhRVf0nHGisRo+TFEKi=
--------------------------------

The reason for the header lines is because I like to generate my licenses as php files, such as key.php, this way the license in a public location cannot be viewed via the web since the PHP portion would exist the script before it reached the license data, and its easier than telling your customers to place the license data outside of the public_html folder, especially if you’re not using domain or hardware restrictions on the license file.

To add a domain restriction such as example.com (and www.example.com) we would add –allowed-server option like so.

/make_license --passphrase yourpassphrasehere --header-line '<?php exit(0); ?>' \
--property "UserName='Chuck Norris'" --allowed-server example.com,www.example.com

The above will make it so that the license is only valid on example.com and www.example.com, wildcards can also be used such as *.domain.com, or for a single character api?.domain.com (where ? can be no more than a single character), you can also use brackets to match a defined set such as [123].domain.com would match 1.domain.com 2.domain.com and 3.domain.com, likewise [!123].domain.com would match any domain as long as it wasn’t 1., 2. or 3. IP ranges can also be defined (Section 3.6.3 of the user manual for more details).

Time-based restrictions can be applied with –expire-in and –expire-on.

--expire-in 7d
--expire-in 8h
--expire-on 2012-12-21

The first two would expire in 7 days, or 8 hours, the last one would expire on December 21st 2012 (along with the rest of us).

On Page 2: Automating License Generation with PHP or Python

Path_Info & PHP_SELF woes [NginX]

December 12th, 2009

3/31/2011 This has been updated to reflect a better configuration to be used with Nginx 0.8/0.9.

Over the last couple of years I’ve been constantly researching for a way to get the PHP environment variables to show up correctly. My latest pains were with PATH_INFO and PHP_SELF, which are now finally solved.

My current configuration are PHP-FPM (5.2.10) and NginX (0.8.29) on a CentOS 5.4 x64 VPS. (As of 2011, I’m now using PHP 5.3.6, Nginx 0.9.6 and FreeBSD 8.2)

Traditionally you would use a PHP configuration such as this:

	server {
		server_name  your-domain.com www.your-domain.com;
 
		location / {
			root html/default;
		}
 
		location ~ \.php$ {
			include fastcgi_params;
			fastcgi_param  SCRIPT_FILENAME  /usr/local/nginx/html/default$fastcgi_script_name;
			fastcgi_pass  127.0.0.1:9000;
		}
	}

Within fastcgi_params would be something like this:

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;
 
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
 
fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
 
fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;
 
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

While the above method may seem to work at first, you’ll quickly notice problems when it comes to using $_SERVER['PATH_INFO'] and $_PATH['PATH_TRANSLATED'], and often enough $_SERVER['PHP_SELF'] ends up being set incorrectly when you try to adjust for the two environment variables.

Here is a setup I’ve come to prefer, especially when it comes to having multiple virtual hosts that use PHP. Notable tips are commented below the line.

Simple Nginx configuration file with a single virtual host

worker_processes  1;
pid        logs/nginx.pid;
events { worker_connections  1024; }
http {
	include       mime.types;
	default_type  application/octet-stream;
	sendfile        on;
	keepalive_timeout  65;
 
	index index.html index.htm index.php;
	# Identical to Apache's DirectoryIndex, setting it in
	# the http block set it as a default for all server blocks within
 
	server {
		listen	80; 
		# since port 80 is set by default, you do not actually need
		# to set this, unless of course you are binding to a specific
		# address such as listen server-ip-address:80 or alternate port; 
 
		server_name  example.com www.example.com;
 
		root   html/example.com/;
		# You will want to set your root here, since otherwise
		# $document_root within the php block will not work
		# if you set it in the location block you would also have 
		# to set the php block within that location as well
 
		location / {
			# This would replace the typical mod_rewrite rules for wordpress
			# it can also be try_files $uri $uri/ @rewrites; where it goes to a 
			# location @rewrites { ... } where you can place rewrite rules if a file
			# or folder is not found.
 
			try_files $uri $uri/ /index.php;
		}
 
		location = /favicon.ico { access_log off; log_not_found off; }
		# If you haven't created a favicon for your site, you can keep
		# your access and error logs clean by turning off the logs
		# when a browser requests the fav icon (its also a good way
		# to keep your logs from filling with useless information)
 
		location ~ /\. { access_log off; log_not_found off; deny all; }
		# You want to make sure that Nginx does not serve any .hidden files
 
		include php.conf;
		# I prefer to keep my php settings in one file, so I can simply
		# paste this single line for each of my virtual hosts
	}
}

Now the php.conf file (which I’ve created in the /conf folder with nginx.conf)

fastcgi_intercept_errors on;
# this will allow Nginx to intercept 4xx/5xx error codes
# Nginx will only intercept if there are error page rules defined
# -- This is better placed in the http {} block as a default
# -- so that in the case of wordpress, you can turn it off specifically
# -- in that virtual host's server block
 
location ~ \.php {
	fastcgi_split_path_info ^(.+\.php)(/.+)$;
	# A handy function that became available in 0.7.31 that breaks down 
	# The path information based on the provided regex expression
	# This is handy for requests such as file.php/some/paths/here/ 
 
	fastcgi_param  PATH_INFO          $fastcgi_path_info;
	fastcgi_param  PATH_TRANSLATED    $document_root$fastcgi_path_info;
 
        fastcgi_param  QUERY_STRING       $query_string;
        fastcgi_param  REQUEST_METHOD     $request_method;
        fastcgi_param  CONTENT_TYPE       $content_type;
        fastcgi_param  CONTENT_LENGTH     $content_length;
 
        fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param  REQUEST_URI        $request_uri;
        fastcgi_param  DOCUMENT_URI       $document_uri;
        fastcgi_param  DOCUMENT_ROOT      $document_root;
        fastcgi_param  SERVER_PROTOCOL    $server_protocol;
 
        fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
        fastcgi_param  SERVER_SOFTWARE    nginx;
 
        fastcgi_param  REMOTE_ADDR        $remote_addr;
        fastcgi_param  REMOTE_PORT        $remote_port;
        fastcgi_param  SERVER_ADDR        $server_addr;
        fastcgi_param  SERVER_PORT        $server_port;
        fastcgi_param  SERVER_NAME        $server_name;
 
	fastcgi_pass   127.0.0.1:9000;
	fastcgi_index  index.php;
}

There you have it, PHP should now have correct environment variables. For example http://www.example.com/php.php/a/path/string/?var=foo would render the following results:

$_SERVER["QUERY_STRING"] -> var=foo
$_SERVER["SCRIPT_NAME"] -> /php.php
$_SERVER["SCRIPT_FILENAME"] -> /usr/local/nginx/html/default/php.php
$_SERVER["REQUEST_URI"] -> /php.php/a/path/string/?var=foo
$_SERVER["DOCUMENT_URI"] -> /php.php/a/path/string/
$_SERVER["DOCUMENT_ROOT"] -> /usr/local/nginx/html/default
$_SERVER["PATH_INFO"] -> /a/path/string/
$_SERVER["PATH_TRANSLATED"] -> /usr/local/nginx/html/default/a/path/string
$_SERVER["PHP_SELF"] -> /php.php/a/path/string/

So there you have it. A simple php block that will correctly assign the path environment variables, without having to use multiple blocks and patterns. And quite easy to simply assign to a new virtual host by simply pasting the include php; line.

Cloaking and Faking the Referrer

November 23rd, 2009

Cloaking the Referrer

This portion is the more useful portion out of the two if you are trying to drive traffic from a site that is not exactly ‘kosher’ (ie: blackhat). You’ll need a whitehat domain and landing page to make this method effective.

For this article I wrote a simple php script, it may take a second to wrap your head around. I’m not using short tags here because as of PHP 5.3 they are no longer supported by default.

At the very least there is three components to Cloaking the referrer:

  • The Blackhat Domain
  • The Whitehat Domain
  • The ‘Fake’ Landing Page

The first part is simply direct linking from the blackhat domain to the whitehat one.

The second part is the PHP code below, for my example I’m treating it as the base file on the domain (index.php) which will start the redirect process when the blackhat domain is detected as the referrer.

<?php
/* Setup your sites here, blackhat, whitehat and advertiser */
$bh = "http://blackhat.kbeezie.com/";
$wh = "http://whitehat.kbeezie.com/";
$ad = "http://advertiser.kbeezie.com/";
 
/* Grabs the querystring, referer, and initialized some variables */
$pg = (isset($_SERVER['QUERY_STRING']))?$_SERVER['QUERY_STRING']:'';
$rf = (isset($_SERVER['HTTP_REFERER']))?$_SERVER['HTTP_REFERER']:'';
$meta=$js=$ie=$lp="";
 
/* if the referrer is the blackhat domain start a meta redirect
and assign a temporary cookie for 5 seconds */
if(substr($rf, 0, strlen($bh)) == $bh) { 
	$meta = $wh.'?r'; 
	setcookie("r", "1", time()+5); 
}
/* The first meta refresh has completed, and 
we're checking for the 'r' cookie, if set
set a meta refresh back to the domain and set
a new 'rr' cookie while killing the old 'r' one. */
elseif(isset($_COOKIE['r'])) {
	$meta = $wh; 
	setcookie("r", "", time()-5); 
	setcookie("rr", "1", time()+5);
}
/* If we're back to the base of the domain, and the 'rr' cookie is set
then we need to prepare for the final redirect to the advertiser
using javascript */
elseif(($_SERVER['REQUEST_URI'] == '/') && (isset($_COOKIE['rr'])))
{
	if (isset($_SERVER['HTTP_USER_AGENT']) && (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false))
	{
		$js = "document.forms[0].submit();";
		$ie = true; 
	} else {
		$js = 'window.location = "'.$ad.'";';
	}
	setcookie("rr", "", time()-5);
}
 
/* It is much faster to send meta data via the HTTP headers
its also a good way to hide the meta data from the HTML 
source itself */
header("Content-Type: text/html; charset=UTF-8");
if($meta != "") header("refresh: 0;url=".$meta);
if(($meta == "") && ($js == "")) $lp = true;
echo '<?xml version="1.0" encoding="UTF-8"?>'; 
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
	<head profile="http://gmpg.org/xfn/11">
		<title>Whitehat Domain</title>
		<script type="text/javascript" defer="defer">
		<!--
			<?php echo $js; ?>
		//-->
		</script>
	</head>
	<body>
		<?php if($lp === true)
			//Redirect to your lander, or load it here.
			echo "<b>There would be a landing page here if the destination owner attempted to visit the referral url.</b>";
		?>
		<?php if($ie === true) { ?>
			<form action="<?=$ad?>" method="get"></form>
		<? } ?>
		&nbsp;
	</body>
</html>

Some Explanations

You’ll notice the first two redirects are to blank out the referrer using a Double Meta Refresh (DMR). Course with Firefox and Internet Explorer using a quick meta refresh already blanks out the referrer by the time the second page loads, as such why cookies are being used. Safari appears to keep the previous page as the referrer during each zero second refresh, but that configuration wasn’t compatible with all three browsers.

After the DMR has completed, we want to refresh via Javascript. Safari and Firefox will carry the referrer over from the whitehat domain when the window.location method is used, but Internet Explorer will instead blank the referrer. Because of this, we have to resort to using a form submission to force the referrer to be sent.

The result of the above script will cause the advertiser to see http://whitehat.kbeezie.com as the referrer instead of http://blackhat.kbeezie.com. Also any attempt of the advertiser to visit the referring URL will result in the landing page loading, instead of a redirection.

On the next page I’ll show you how to fake a referrer, even if you don’t own that domain.

WordPress Automatic Update with SSH

November 22nd, 2009

If you’re like me, you don’t even want the insecure FTP protocol running on your server, but by default wordpress doesn’t even give you the option of using SSH to automatically upgrade your plugins, or wordpress itself.

I’m using a barebone CentOS server for this site, running on the Nginx webserver. I do not have a FTP server installed, and would very much prefer not to have one. Right now the only way to get into the server is via SSH. Below is a working configuration added to the wp-config.php file.

define('FS_METHOD', 'direct'); // 'ssh' is also an option, but did not work for my setup
define('FTP_BASE', '/opt/local/nginx/html/domain.com/');
define('FTP_CONTENT_DIR', '/opt/local/nginx/html/domain.com/wp-content/');
define('FTP_PLUGIN_DIR ', '/opt/local/nginx/html/domain.com/wp-content/plugins/');
define('FTP_PUBKEY', '/home/username/.ssh/id_rsa.pub');
define('FTP_PRIKEY', '/home/username/.ssh/id_rsa');
define('FTP_USER', 'username');
define('FTP_HOST', 'your-domain.com:22');

You can generate a public/private key by executing the following:

$ ssh-keygen

It will ask you where you wish to save the key (the default path usually /home/username/.ssh/id_rsa should be fine), followed by a passphrase which you can just leave blank for this purpose, then the location of the public key which is fine at its default location (usually /home/username/.ssh/id_rsa.pub)

You’ll also want to create an authorized key file by copying the public key into authorized_keys. We also need to make sure to set the proper permissions.

$ cd ~/.ssh
$ cp id_rsa.pub authorized_keys
$ cd ~/
$ chmod 755 .ssh
$ chmod 644 .ssh/*

By using the key pair shown in the configuration above you only have to supply the SSH username, otherwise if you don’t want to use key pairs, you can instead provide your SSH password with the following line:

define('FTP_PASS', 'password');

Make sure that the folders where updates (such as plugins) will need to be performed are writable by wordpress. From there when you click “upgrade automatically” it should just simply happen.

Handling wildcard subdomains with PHP

November 5th, 2009

Subdomains can be a very handy way to make your urls more friendly looking. They can also be incredibly useful for membership driven websites to allow members to have their own custom subdomain. But how do make manage dynamic subdomains with PHP?

There is two parts to making dynamic subdomains work in this case. The first is updating your DNS and webserver to expect wildcard requests. On the DNS side its a simple matter of adding * as an ‘A’ record, or cname pointing to the primary domain or IP. In most cases if you are using shared hosting, you’ll need to contact your hosting provider to enable wildcard DNS on your account. Otherwise if you are on a dedicated server or virtual private server, this can be most likely handled by yourself, such as editing the DNS zone in WHM.

Once you have enabled wildcard DNS, you’ll need to configure your webserver to expect a *.domain.com request. Otherwise it will ignore all requests other than the base domain and pre-defined subdomains.

In Nginx simply modify the server_name directive:

server_name yourdomain.com *.yourdomain.com;

In Apache you would add a ServerAlias directive to the appropriate virtual host entry.

ServerAlias *.yourdomain.com

On most Cpanel driven servers the http.conf file for Apache can be found in /usr/local/apache/conf.

Once you have configured both the name server and web server for wildcard requests you can now use php to not only detect the provide subdomain, but also generate them. The code below will obtain the subdomain being visited.

$sub = str_replace(".yourdomain.com", "", $_SERVER["HTTP_HOST"]);

The above will simply take the hostname stored in the HTTP_HOST enviroment variable, and attempt to replace the base domain with nothing (if no subdomain was provided all that remains is the base domain).

Once the subdomain is determined, you can filter out requests either with an if/then block or in a switch/case statement. In the event of using memeber names as subdomains you can use a switch statement to make sure that the subdomain doesn’t match the homepage (www / yourdomain.com) or a reserved page name such as register.yourdomain.com and assume anything else must be a member name. With that information you can pull the members information from a file or database.

Using PHP to handle the subdomain is certainly easier for most novice as opposed to modifying an htaccess to do rewrite rules passing the subdomain off as a query (which takes more resources than str_replace) or having to modify the webservers configuration file, especially if you do not have direct access to the webserver’s configuration file or do not have override permission.

If your current hosting provider does not support wildcard subdomain, and you are running a memeber based site, consider upgrading to a Virtual Private Server or use a shared hosting provider such as HostGator that allows you to turn on such capability via a support ticket.