Archive for the ‘Security’ category

Allowing secure WordPress Updates with SSH2

April 2nd, 2013

The Set Up

For the purpose of this guide:

  • PHP-FPM runs as an unprivileged user such as www-data or www (FreeBSD).
  • The owner of the web files is non-root such as “WebUser” belonging to the www/www-data group.
  • PECL-ssh2 has been installed for PHP
  • You currently use paired key authentication (How-To)

The Guild

If you are like myself, you may be running your wordpress-driven site on a VPS without a control panel, or even without a typical FTP server (ie: SSH/SCP only). I’ll show you how to set up wordpress to update itself via SSH, while only doing so when you allow it.

For those of you using Apache’s SuExec, this guide will not be of much use, as SuExec executes PHP and other processes as the same user that owns the files. In which case the permission setting at the very bottom of this page may be of more help to you, or you can use ‘direct’ mode instead of ssh2.

PECL-ssh2

First thing we need to do is make sure PHP has the SSH2 extension installed. This can be installed via PECL, or in the case of FreeBSD ports:

cd /usr/ports/www/security/pecl-ssh2
make install clean
service php-fpm restart

Once installed SSH2 will be visible on a php_info() output.

Paired Key Authentication for PHP

Now we need to provide PHP with a public/private key, for this purpose let us create a folder to store these files in. Because PHP runs as www, and the files are owned by WebUser (or whichever username you’ve given to manage your web files), PHP will not have free reign to any existing paired keys that user may already exist. Besides it is not advisable to use the same keys for your main Web User.

For my purposes, websites are stored in a path under the Web user’s home folder.
Example: /home/WebUser/www/domain_name.com/public_html

We can create a folder in “www” (outside of any of the website’s public documents), named .ssh:

mkdir /home/WebUser/www/.ssh
cd /home/WebUser/www/.ssh
ssh-keygen -b 4096 -C php-key -t rsa
** When asked use the path /home/WebUser/www/.ssh/id_rsa instead of the default
** Provide a passphrase, DO NOT leave it blank.

You do not need to create such a strong key using 4096 bits for local communication, nor do you need to store it in a folder named “.ssh”. The keys do not even need to be named id_rsa, so feel free to mix it up a bit, just remember your public key will have a pub extension. You can even create separate keys for each website so as long as you do not place them inside the publicly accessible root.

The “php-key” portion of the command is the comment, if you are using multiple keys, you can edit this comment to help keep organized.

Authorizing the new keys

As mentioned before, this guide assumes you are already using paired key authenication. As a result there should be an authorized_keys file placed in your User’s .ssh folder (typically /home/UserName/.ssh/authorized_keys).

In order for the new keys to be given permission to use the web user’s credentials, we need to add the content of id_rsa.pub to authorized_keys. You may do this either in an editor such as WinSCP, or via the command line, such as using the ‘cat’ command:

cat /home/WebUser/www/.ssh/id_rsa.pub >> /home/WebUser/.ssh/authorized_keys

Make sure there is a double arrow in the command above and not a single one, or you risk replacing the entire content of authorized_keys with just that one public key.

The purpose of the passphrase, which is not required but STRONGLY encouraged is to make it so that PHP cannot utilize this key unless you explicitly provide the passphrase. This would of course prevent a malicious script from acting as the main Web User, when you are not in the process of performing an update or installation (since your passphrase would only be provided at those times).

While you can also store the passphrase in the wp-config.php as the FTP_PASS, it is also strongly discouraged.

Setting Key Ownership and Permission

Because PHP in this configuration runs as www-data or www, it will not be able to access the newly created keys unless we change their ownership and permission. With the commands below we’re setting the ownership of the .ssh folder to www:www (or www-data:www-data) and changing the permissions so that only the owner of the file can read the private key, and owner+group can read the public key; Though only the owner really ever needs to see it, as the public key provided in the authorized_keys, but normally you will not be logged in as www, and may need to read the content of the file.

chown -R www:www /home/WebUser/www/.ssh
chmod 0400 /home/WebUser/www/.ssh/id_rsa
chmod 0440 /home/WebUser/www/.ssh/id_rsa.pub

Modifying wp-config.php

Below is the code segment that needs to be added to the wp-config.php file. First the method is defined as ssh2, then the absolute folder paths, then the paths to the public and private key, and finally the username and host SSH will attempt to connect to.

define('FS_METHOD', 'ssh2');
define('FTP_BASE', '/home/WebUser/www/YourDomain.com/public_html/WordPressRoot/');
define('FTP_CONTENT_DIR', '/home/WebUser/www/YourDomain.com/public_html/WordPressRoot/wp-content/');
define('FTP_PLUGIN_DIR ', '/home/WebUser/www/YourDomain.com/public_html/WordPressRoot/wp-content/plugins/');
define('FTP_PUBKEY', '/home/WebUser/www/.ssh/id_rsa.pub');
define('FTP_PRIKEY', '/home/WebUser/www/.ssh/id_rsa');
define('FTP_USER', 'WebUser');
define('FTP_HOST', 'YourDomain.com');
// If you are not using port 22, append :Port# to the domain, such as YourDomain:1212

With the following in place, you’ll be able to:

  • Install a Theme or Plugin from WordPress itself
  • Update a Theme or Plugin automatically in WordPress
  • Delete a Theme or Plugin from within WordPress
  • Upgrade WordPress Itself from the automatic update utility

Each time you perform any of the above tasks, you’ll be informed that the public and private keys are invalid, this error is only shown because without the passphrase it cannot continue. So provide the passphrase via the password field each time to perform the tasks. Make sure the “ssh2” radio button has been selected when you do this.

If you are uploading a zip file to install a theme/plugin

While the setting above will work for the most part with the automatic fetch-n-install, such as searching for plugin and then clicking install. It may not work when providing a zip file from your local storage.

If this becomes the case we just need to adjust the permissions of the upload directory. Assuming your files are owned by WebUser:www and PHP runs as www:www, we need to set the permissions of the /wp-content/uploads folder to 775 (read/write/execute for both the WebUser owner, and www group, but keep read/execute on ‘other’).

chmod 0775 /home/WebUser/www/YourDomain.com/public_html/wp-content/uploads/

If you have content already in there you may need to add on the recursive flag with -R before 0775.

chmod -R 0775 /home/WebUser/www/YourDomain.com/public_html/wp-content/uploads/

For the purpose of installations, this is only required in order for PHP to move the uploaded zip file to the uploads folder where it will be unpacked. From there the familiar SSH dialog will appear to continue the rest of the installation. After which the uploaded zip file will be removed from the uploads folder.

Securing your Uploads folder on Nginx

Because PHP is now capable of writing to the uploads folder, there is a chance that someone may attempt to upload a script into it and as such execute from it. The uploads folder should not host any executable scripts so to fix this we need to add some rules into the configuration for Nginx.

This location block should go before the PHP location block.

location ~* ^/wp-content/uploads/.*\.php$ {
    return 403;
}

Any attempts to call a PHP script from the uploads folder will now be met with a forbidden response code.

For further information on securing PHP and Nginx please refer to Securing Nginx & PHP

GeoIPLookup: Geolocation from the CLI

November 29th, 2012

In order to do a geolookup from the command line, we have to get the GeoIP binary and database installed.

FreeBSD

cd /usr/ports/net/GeoIP
make install clean

Debian/Ubuntu

apt-get install geoip-bin geoip-database

CentOS/RHEL

yum install GeoIP GeoIP-data

By default these install the free version of the GeoLite Country binary database (GeoIP.dat) usually in the /usr/local/share or /usr/share directory. You can either copy in a custom version of the database or edit your GeoIP.conf file to enter in your license information.

Performing a Lookup

To perform a lookup you would simply type geoiplookup followed by an IP address, for example let’s look up one of google’s IPs.

geoiplookup 74.125.225.33
GeoIP Country Edition: US, United States

Some Tricks with cat, grep, sed, and xargs

If you have a text file with a list of IP addresses you can instead pass it thru with xargs

cat ./ip_list.txt | xargs -n 1 geoiplookup { }
GeoIP Country Edition: US, United States
GeoIP Country Edition: CN, China
GeoIP Country Edition: JP, Japan
GeoIP Country Edition: BR, Brazil
GeoIP Country Edition: SG, Singapore
GeoIP Country Edition: SG, Singapore
GeoIP Country Edition: SG, Singapore

If you have duplicate IPs you can filter them out with sort and uniq:

cat ./ip_list.txt | sort | uniq | xargs -n 1 geoiplookup { }

But that by itself may not be too useful to you. Perhaps you want to get the total number of IPs in a file sorted by the least instances to the most instances of a single country.

cat ./ip_test.txt | sort | uniq | xargs -n 1 geoiplookup { } | sort | uniq -c | sort
   2 GeoIP Country Edition: JP, Japan
   3 GeoIP Country Edition: FR, France
   3 GeoIP Country Edition: GB, United Kingdom
   3 GeoIP Country Edition: IP Address not found
   3 GeoIP Country Edition: RU, Russian Federation
   3 GeoIP Country Edition: SG, Singapore
   3 GeoIP Country Edition: UA, Ukraine
   4 GeoIP Country Edition: TH, Thailand
   5 GeoIP Country Edition: KR, Korea, Republic of
   6 GeoIP Country Edition: DE, Germany
   7 GeoIP Country Edition: TR, Turkey
  27 GeoIP Country Edition: CN, China
  28 GeoIP Country Edition: US, United States

If you’re using pf (OpenBSD’s Packet Filter) with pflog, you can even get an output of the most countries that were blocked in your packet filter log with the following command:

tcpdump -ntr /var/log/pflog | awk '{print }' | sed -r 's/ \>.*//g' | \
grep -oE '[0-9]{1,}.[0-9]{1,}.[0-9]{1,}.[0-9]{1,}' | grep -v "0.0.0.0" | \
sort -n | uniq | xargs -n 1 geoiplookup { } | sort | uniq -c | sort -n | sed -r 's/ GeoIP Country Edition://g'
 
   1 AR, Argentina
   1 BB, Barbados
   1 BG, Bulgaria
   1 CA, Canada
   1 CZ, Czech Republic
   1 EU, Europe
   1 HR, Croatia
   1 ID, Indonesia
   1 IE, Ireland
   1 IS, Iceland
   1 IT, Italy
   1 LA, Lao People`s Democratic Republic
   1 LB, Lebanon
   1 PT, Portugal
   1 RO, Romania
   1 UY, Uruguay
   1 VN, Vietnam
   2 BR, Brazil
   2 ES, Spain
   2 IN, India
   2 JP, Japan
   3 FR, France
   3 GB, United Kingdom
   3 IP Address not found
   3 RU, Russian Federation
   3 SG, Singapore
   3 UA, Ukraine
   4 TH, Thailand
   6 DE, Germany
   6 KR, Korea, Republic of
   7 TR, Turkey
  27 CN, China
  29 US, United States

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).

Protecting Folders with Nginx

February 5th, 2011

This question comes up every so often, and its actually fairly easy besides the fact you do not use an .htaccess file. To do so we’ll need the auth_basic module which comes included with Nginx.

First we’ll need to create a password file, you can create this in the folder you wish to protect (though the file can reside anywhere Nginx has access to).

Creating .htpasswd

If you previously had apache/httpd installed on your machine you may still have some of the utilities that came with it. In this case we’ll want to use the htpasswd command:

htpasswd -c /var/www/domain.com/admin/.htpasswd username

When you run the above command, it will prompt you for a password for the provided username, and then create the file .htpasswd in the folder you specified. If you already have a pre-existing password file, you can omit the -c flag. You can use the -D flag to remove the specified user from a password file.

If you do not have htpasswd on your system, you can create the file yourself which each line in the format of:

username:encrypted-password:comment

To generate the encrypted password you can use Perl which is installed on most systems.

perl -le 'print crypt("your-password", "salt-hash")'

Or you can use Ruby via the interactive console command ‘irb’:

"your-password".crypt("salt-hash")

Setting up Nginx

Add protection for the /admin folder.

server {
    listen 80;
    server_name domain.com www.domain.com;
 
    location / { 
        # your normal content
    }
 
    location /admin {
        auth_basic "Administrator Login";
        auth_basic_user_file /var/www/domain.com/admin/.htpasswd;
    }
 
    #!!! IMPORTANT !!! We need to hide the password file from prying eyes
    # This will deny access to any hidden file (beginning with a .period)
    location ~ /\. { deny  all; }
}

With the above access to the admin directory will prompt the user with a basic authentication dialog, and will be challenged against the password file provided.

Additional Security Considerations

The password file itself does not have to be named .htpasswd, but if you do store it a web-accessible location make sure to protect it. With the last location block above using any name with a period in front should be protected from web-access.

The safest place to store a password file is outside of the web-accessible location even if you take measures to deny access. If you wish to do so, you can create a protected folder in your nginx/conf directory to store your password files (such as conf/domain.com/folder-name/password) and load the user file from that location in your configuration.

Securing a Thumb Drive with TrueCrypt

January 15th, 2011

Thumb drives (aka flash drives) are extremely useful storage devices; they’re portable and easy to use, and with growing capacity used by more people every day. However they are more easily lost or stolen. Most thumbdrives offer no prevention against exposing the data within to unauthorized access.

Using Truecrypt 7 you can encrypt an entire thumbdrive (or create an encrypted container within for more novice users). The software works on Windows, Linux and Mac OS X. For this article we’ll look at using an entire thumb drive as an encrypted device, so that without decoding it’ll simply look like an empty or corrupted drive when plugged in by an unauthorized persons. The best thing about TrueCrypt is that it’s free and powerful, so effective in fact that FBI Technicians have been unable to crack a Truecrypt 5.1 volume used by an alleged suspect since 2008.

Preparing the Thumb Drive

It goes without saying that if you already have information on your drive, that you’ll want to back up it’s content as this procedure will erase all the existing data.

First thing we need to do is remove any existing partitions on the device, since I’m a Mac user I’ll illustrate this with Disk Utility. For windows users you can use these instructions to remove any partition on the device. I’m assuming most linux users know how to do this with their own preferred utility such as gpartd.

In Disk Utility select the device on the left, then the partition tab, select “1 Partition” with a format of “Free Space”, then click Apply.

Once you have done this, the device will be completely empty and be ready to be used completely by TrueCrypt.

Encrypting the Thumb Drive

First we’ll want to install and launch the TrueCrypt software on your system, and click the Create Volume button. You’ll be prompted with this screen, from here select that you would like to create a volume within a drive or partition.

You’ll then be asked to select your device, you may be prompted for your system password at this point. From the list select your now-empty device from the list.

From here select the Volume Type. For this article we’ll use a standard volume. The hidden type is useful if you wish to have an extra layer of protection. The hidden volume will only open with the use of an alternate password, this gives you a level of plausible deniability in case for legal or extortion reasons you’re forced to give up the password to the device, in which case you can simply provide the password for the normal volume.

Then you can select your encryption options. AES is the default and typically the fastest, especially with processors that now have hardware acceleration for the AES algorithm. You can click the benchmark button to see how fast your current system can encrypt/decrypt the various algorithm methods. With a very strong password the AES method is usually just fine. I usually stick with the RIPEMD-160 hash algorithm, but you can choose to use one of the larger 512bit options. If you went with the Hidden Volume option above, you’ll have the chance later to select a different method for the hidden volume.

Now select a password, I recommend a strong password, something over 20 digits, and uses a mix of letters, numbers, and symbols. The longer the password, and the more variety of passwords you use the harder it will be for even brute force method to crack. For example a 6 letter password using letters could theoretically be cracked in 5 minutes by an old Pentium 100mhz computer, mixing upper and lower case characters would increase that time to 5 hours on the same machine.

Now days a typical quad core desktop could crack the above scenario using brute force in as little as 3 minutes, but an 8 character password using both upper and lower case letters, plus numbers and symbols would likely take a 100 years to crack. So adding some variety to your password such as symbols and using something longer than 8 characters goes quite a long way to prevent a simple brute force attack from succeeding. For more information on possible scenarios and how long they may take to crack refer to this link, for the most part most multi-core computers now days fall under “Class E”.

A keyfile option will give you an extra level of password protection, but keep in mind if you lose or change the keyfile on your computer, the encrypted volume may become unrecoverable. It’s also impractical if you wish to use the thumbdrive from a number of different computers.

If you wish to use the thumbdrive with multiple operating systems, FAT format will be your best bet. If you choose to use the Mac OS Extended option, you’ll be asked later if you wish to use the drive on other operating systems as well as if you wish to store files larger than 4GB.

If you had previously sensitive information on your device, do not choose the Quick Format option. Otherwise when someone attempts to use recovery software they may be able to see the data left behind prior to the encrypting.

On the volume format window you will be asked to move the mouse randomly within the window for a while. This will help seed the hashing algorithm for stronger cryptographic strength. Once you’ve sufficiently moved the mouse about you can click the format button. Depending on the speed of your device this can take a few minutes.

And now your volume has been created:

If you remove, then reinsert your thumb drive you may be prompted with a message like below, you can saftely ignore this message and proceed back to the TrueCrypt application.

Within TrueCrypt you can select a device and mount it, again selecting the seemingly-empty device and then provide it with your password.

Once mounted it’ll appear on your computer as just another volume. Encryption and Decryption occurs in the memory, so even if your computer were to suddenly shut off, or the device were to be pulled from the computer, the data on the device is still encrypted. On OS X you normally want to eject the volume from finder before dismounting it from TrueCrypt.

Downsides

The most obvious down-side is that you will need to have TrueCrypt installed onto any system you wish to use the thumbdrive on. However if you choose to do a container volume, you could simply use the thumbdrive as a regular device and store your important stuff as a file container on your thumbdrive. The encrypted device would no longer simply be plug-n-use, you would have to open truecrypt, select your device and provide a password in order to mount it. But on the plus side, to anyone who may steal your drive it’ll just look like a corrupted/empty device to them.

The other impact is speed, encrypted data takes longer to read than normal data straight off the device. However even on my 3 year old macbook, and a couple old thumbdrives I have not noticed much of a difference accessing and using files than when unencrypted.

I’ve tried this on an 8GB PNY Micro Attache Slide (one of my favorites, very cheap, tiny, and has a high read speed) and 16GB Sandisk Cruzer as well as a 16GB PNY Attache Mini (from the above screenshots) without a problem. The write speeds are usually much slower than the read speeds on the above devices. My Micro Attache has very slow write speeds, but a very nice read speed so I tend to use that a lot.

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

SSL: Untrusted Connection in Firefox

June 8th, 2010

This is primarily for PositiveSSL Certificates

If you’ve ever received an “Untrusted Connection” from Firefox, specifically with the error “Error code: sec_error_unknown_issuer”, then one possible fix is to append your issuer’s own certificates to yours.

For example if you have a domain.crt file, then download the PositiveSSL CA Bundle, once downloaded append it’s content to the end of your own certificate and then restart the webserver.

For example if your certificate looks something like this:

—–BEGIN CERTIFICATE—–
AwEAAaOCAcowggHGMB8GA1UdIwQYMBaAFLjKEekGMXnbw5TG6BkqvLs1FjGkMB0G
A1UdDgQWBBTH5gLAPZa1SyJ9NU/bIB2Ci+q0YjAOBgNVHQ8BAf8EBAMCBaAwDAYD
VR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwRgYDVR0g
BD8wPTA7BgsrBgEEAbIxAQICBzAsMCoGCCsGAQUFBwIBFh5odHRwOi8vd3d3LnBv
c2l0aXZlc3NsLmNvbS9DUFMwaQYDVR0fBGIwYDAvoC2gK4YpaHR0cDovL2NybC5j
b21vZG9jYS5jb20vUG9zaXRpdmVTU0xDQS5jcmwwLaAroCmGJ2h0dHA6Ly9jcmwu
MIIEkjCCA3qgAwIBAgIQPV6rece1lR6IHiQhSIJSZTANBgkqhkiG9w0BAQUFADBx
MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
VQQHEwdTYWxmb3JkMRowGAYDVQQKExFDb21vZG8gQ0EgTGltaXRlZDEXMBUGA1UE
AxMOUG9zaXRpdmVTU0wgQ0EwHhcNMTAwNjA4MDAwMDAwWhcNMTEwNjA4MjM1OTU5
C1Bvc2l0aXZlU1NMMRQwEgYDVQQDEwtrYmVlemllLm5ldDCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAtjW+zyGQTevv6oo/Y8Oh1vSq4Xl0wkP4XJBA7Gf6qXjh
AxIh/+KBC2LjOI5N2drQvnFFSFX9D+t35GRpiXtzZauqJYKSFuR2a3Ie2gZyVTUC
Y29tb2RvLm5ldC9Qb3NpdGl2ZVNTTENBLmNybDBrBggrBgEFBQcBAQRfMF0wNQYI
KwYBBQUHMAKGKWh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL1Bvc2l0aXZlU1NMQ0Eu
Y3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wJwYDVR0R
BCAwHoILa2JlZXppZS5uZXSCD3d3dy5rYmVlemllLm5ldDANBgkqhkiG9w0BAQUF
AAOCAQEAW/M7T5oUvp+JB4XNQpxHzf5e/0MN0x/+nUBdWPbY1hW58ccUEqyh/g82
PSvK7bYAsVrJ7mkt3h53L4941SjhcMftGDGov8o3Awfs8CLfTBymtbUKVd/h41m9
BGH9Y9jkdw+PSnIRldg84KjVukfzdw==
—–END CERTIFICATE—–

It would become something like this:

—–BEGIN CERTIFICATE—–
AwEAAaOCAcowggHGMB8GA1UdIwQYMBaAFLjKEekGMXnbw5TG6BkqvLs1FjGkMB0G
A1UdDgQWBBTH5gLAPZa1SyJ9NU/bIB2Ci+q0YjAOBgNVHQ8BAf8EBAMCBaAwDAYD
VR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwRgYDVR0g
BD8wPTA7BgsrBgEEAbIxAQICBzAsMCoGCCsGAQUFBwIBFh5odHRwOi8vd3d3LnBv
c2l0aXZlc3NsLmNvbS9DUFMwaQYDVR0fBGIwYDAvoC2gK4YpaHR0cDovL2NybC5j
b21vZG9jYS5jb20vUG9zaXRpdmVTU0xDQS5jcmwwLaAroCmGJ2h0dHA6Ly9jcmwu
MIIEkjCCA3qgAwIBAgIQPV6rece1lR6IHiQhSIJSZTANBgkqhkiG9w0BAQUFADBx
MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
VQQHEwdTYWxmb3JkMRowGAYDVQQKExFDb21vZG8gQ0EgTGltaXRlZDEXMBUGA1UE
AxMOUG9zaXRpdmVTU0wgQ0EwHhcNMTAwNjA4MDAwMDAwWhcNMTEwNjA4MjM1OTU5
C1Bvc2l0aXZlU1NMMRQwEgYDVQQDEwtrYmVlemllLm5ldDCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAtjW+zyGQTevv6oo/Y8Oh1vSq4Xl0wkP4XJBA7Gf6qXjh
AxIh/+KBC2LjOI5N2drQvnFFSFX9D+t35GRpiXtzZauqJYKSFuR2a3Ie2gZyVTUC
Y29tb2RvLm5ldC9Qb3NpdGl2ZVNTTENBLmNybDBrBggrBgEFBQcBAQRfMF0wNQYI
KwYBBQUHMAKGKWh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL1Bvc2l0aXZlU1NMQ0Eu
Y3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wJwYDVR0R
BCAwHoILa2JlZXppZS5uZXSCD3d3dy5rYmVlemllLm5ldDANBgkqhkiG9w0BAQUF
AAOCAQEAW/M7T5oUvp+JB4XNQpxHzf5e/0MN0x/+nUBdWPbY1hW58ccUEqyh/g82
PSvK7bYAsVrJ7mkt3h53L4941SjhcMftGDGov8o3Awfs8CLfTBymtbUKVd/h41m9
BGH9Y9jkdw+PSnIRldg84KjVukfzdw==
—–END CERTIFICATE—–
—–BEGIN CERTIFICATE—–
MIIFAzCCA+ugAwIBAgIQTM1KmltFEyGMz5AviytRcTANBgkqhkiG9w0BAQUFADCB
lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
SGFyZHdhcmUwHhcNMDYwOTE4MDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBxMQswCQYD
VQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdT
YWxmb3JkMRowGAYDVQQKExFDb21vZG8gQ0EgTGltaXRlZDEXMBUGA1UEAxMOUG9z
aXRpdmVTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9T3lY
IpPJKD5SEQAvwKkgitctVR4Q57h/4oYqpOxe6eSSWJZUDfMXukGeFZFV78LuACAY
RYMm3yDMPbOhEzEKIVx5g3mrJBVcVvC0lZih2tIb6ha1y7ewwVP5pEba8C4kuGKe
joteK1qWoOpQ6Yj7KCpNmpxIT4O2h65Pxci12f2+P9GnncYsEw3AAcezcPOPabuw
PBDf6wkAhD9u7/zjLbTHXRHM9/Lx9uLjAH4SDt6NfQDKOj32cuh5JaYIFveriP9W
XgkXwFqCBWI0KyhIMpfQhAysExjbnmbHqhSLEWlN8QnTul2piDdi2L8Dm53X5gV+
wmpSqo0HgOqODvMdAgMBAAGjggFuMIIBajAfBgNVHSMEGDAWgBShcl8mGyiYQ5Vd
BzfVhZadS9LDRTAdBgNVHQ4EFgQUuMoR6QYxedvDlMboGSq8uzUWMaQwDgYDVR0P
AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwewYDVR0fBHQwcjA4oDagNIYy
aHR0cDovL2NybC5jb21vZG9jYS5jb20vVVROLVVTRVJGaXJzdC1IYXJkd2FyZS5j
cmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9VVE4tVVNFUkZpcnN0LUhh
cmR3YXJlLmNybDCBhgYIKwYBBQUHAQEEejB4MDsGCCsGAQUFBzAChi9odHRwOi8v
Y3J0LmNvbW9kb2NhLmNvbS9VVE5BZGRUcnVzdFNlcnZlckNBLmNydDA5BggrBgEF
BQcwAoYtaHR0cDovL2NydC5jb21vZG8ubmV0L1VUTkFkZFRydXN0U2VydmVyQ0Eu
Y3J0MA0GCSqGSIb3DQEBBQUAA4IBAQAdtOf5GEhd7fpawx3jt++GFclsE0kWDTGM
MVzn2odkjq8SFqRaLZIaOz4hZaoXw5V+QBz9FGkGGM2sMexq8RaeiSY9WyGN6Oj5
qz2qPMuZ8oZfiFMVBRflqNKFp05Jfdbdx4/OiL9lBeAUtTF37r0qhujop2ot2mUZ
jGfibfZKhWaDtjJNn0IjF9dFQWp2BNStuY9u3MI+6VHyntjzf/tQKvCL/W8NIjYu
zg5G8t6P2jt9HpOs/PQyKw+rAR+lQI/jJJkfXbKqDLnioeeSDJBLU30fKO5WPa8Y
Z0nf1R7CqJgrTEeDgUwuRMLvyGPui3tbMfYmYb95HLCpTqnJUHvi
—–END CERTIFICATE—–
—–BEGIN CERTIFICATE—–
MIIEhjCCA26gAwIBAgIQUkIGSk83/kNpSHqWZ/9dJzANBgkqhkiG9w0BAQUFADBv
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow
gZcxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl
IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY
aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR8wHQYDVQQDExZVVE4tVVNFUkZpcnN0
LUhhcmR3YXJlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsffDOD+0
qH/POYJRZ9Btn9L/WPPnnyvsDYlUmbk4mRb34CF5SMK7YXQSlh08anLVPBBnOjnt
KxPNZuuVCTOkbJex6MbswXV5nEZejavQav25KlUXEFSzGfCa9vGxXbanbfvgcRdr
ooj7AN/+GjF3DJoBerEy4ysBBzhuw6VeI7xFm3tQwckwj9vlK3rTW/szQB6g1ZgX
vIuHw4nTXaCOsqqq9o5piAbF+okh8widaS4JM5spDUYPjMxJNLBpUb35Bs1orWZM
vD6sYb0KiA7I3z3ufARMnQpea5HW7sftKI2rTYeJc9BupNAeFosU4XZEA39jrOTN
SZzFkvSrMqFIWwIDAQABo4H0MIHxMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8D
veAky1QaMB0GA1UdDgQWBBShcl8mGyiYQ5VdBzfVhZadS9LDRTAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAIMAYGBFUdIAAwewYDVR0f
BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQWRkVHJ1c3RFeHRl
cm5hbENBUm9vdC5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BZGRU
cnVzdEV4dGVybmFsQ0FSb290LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAYGQ5WaJD
ZS79+R/WrjO76FMTxIjuIxpszthkWVNTkOg239T88055L9XmjwzvKkFtcb2beDgj
03BLhgz9EqciYhLYzOBR7y3lzQxFoura7X7s9zKa5wU1Xm7CLGhonf+M8cpVh8Qv
sUAG3IQiXG2zzdGbGgozKGYWDL0zwvYH8eOheZTg+NDQ099Shj+p4ckdPoaEsdtf
7uRJQ8E5fc8vlqd1XX5nZ4TlWSBAvzcivwdDtDDhQ4rNA11tuSnZhKf1YmOEhtY3
vm9nu/9iVzmdDE2yKmE9HZzvmncgoC/uGnKdsJ2/eBMnBwpgEZP1Dy7J72skg/6b
kLRLaIHQwvrgPw==
—–END CERTIFICATE—–

Upon restarting it should cause newer strict browsers to lookup and identify the issuer correctly.

How to Steal an Android Market App

April 10th, 2010

One of the biggest fear plaguing any freelance application developer is piracy. All their hours and hours of work to bring you the next useful little app that they hope you’ll enjoy. So why shouldn’t they be compensated for their hard work. Sometimes however this fear can hurt a new platform more than it can help. That is why in this article I will show you how easy it is to steal even a protected android market app.

Step One – Rooting your phone

Rooting your android device is similar to jailbreaking your iPod Touch or iPhone. Basically gives you the ability to perform tasks normally reserved only for the ‘root’ user of the device. The method of rooting vary greatly depending on the device. I personally own a T-Mobile MyTouch 3G Limited Edition (fender) which is identical to the new T-Mobile MyTouch 3G v1.2 with the headphone jack on top, the only difference is design, and the size of the miniSDHC card that comes with it. I rooted my phone by following the instructions found in this XDA Developer forum thread. If you have one of the older MyTouches you can easily find how-to guides on the same forum corresponding to the Magic 32A and 32B.

Warning: Like most things in gadget life, performing an action such as above not only has the possibility of voiding your warranty, but can also brick your device, that is to say it’ll be no better than a brick that won’t even turn on. So proceed at caution. It is very important to not only carefully read any instructions (such as provided by the link above) but also to verify that your device actually matches the instruction’s requirements. Most people who do this tend to have no problems, but those who fail to actually follow instructions have a very good chance at flashing the wrong recovery or radio image.

Once you are rooted, you can download the Android Software Developer’s Kit, or find the ConnectBot App in the market place. Sometimes protected applications (like the free Paypal app) may not show up after rooting, usually installing the Market Enabler fixes this.

Step Two – Purchase

For this demonstration to work we need to make a purchase. For this article I’ve decided to purchase Retro Defense by Larva Labs. I’ve always enjoyed the games from Larva Labs.

Ok, so we have purchased the game, now what?

Step Three – Back it up

With your rooted phone you can now either open up ConnectBot, or in your terminal run “adb shell” (from the SDK).

Once you are in the terminal as root (you may have to type su if you see $ instead of # to elevate yourself as the root user), you can then follow the following commands, to make a directory and copy the game to that directory.

# cd /sdcard
# mkdir /backapk
# cd /data/app
# ls (you'll see a screen showing you the file content, find the file you want)
# cp com.larvalabs.retrodefense.apk /sdcard/backapk

You may be prompted by SUuser application to allow Connectbot root access (if you used adb shell instead you won’t see this prompt)




At this point all you have done was make a backup of your app/game onto the SD card. Protected application usually exist in /data/app-private as an apk (leaving a small .zip in the /app location)

Configuring SNI with NginX

December 28th, 2009

Traditionally for every SSL certificate issued, you needed a separate and unique IP address. However if you compile OpenSSL and NginX with TLS SNI (Server Name Identification) support you can install multiple SSL certificates without having to bind a domain name to a specific IP address or require each certificate to have its own unique IP.

First thing we need to do is actually compile OpenSSL with TLS SNI support. We’ll start by downloading the latest source and unpacking it into a temporary directory. For this article I keep all sources in ~/src.

$ cd ~/src
$ wget http://www.openssl.org/source/openssl-0.9.8l.tar.gz
$ tar zxvf ./openssl-0.9.8l.tar.gz
$ cd ./openssl-0.9.8l

We’ll then configure with TLS support:

$ ./config enable-tlsext
$ make
$ make install
$ cd ..

Assuming the Nginx source also resides in ~/src you can add the following option to your configure statement when compiling Nginx from source (you can also instead use an absolute path such as /root/src/openssl-0.9.8l/).

--with-openssl=../openssl-0.9.8l/

Once compiled and installed you can check to see if TLS SNI is enabled:

[root@host src]# nginx -V
nginx version: nginx/0.8.31
built by gcc 4.1.2 20080704 (Red Hat 4.1.2-46)
TLS SNI support enabled
configure arguments: --prefix=/usr/local/nginx 
--add-module=/usr/local/lib/ruby/gems/1.9.1/gems/passenger-2.2.5/ext/nginx 
--pid-path=/usr/local/nginx/logs/nginx.pid --sbin-path=/usr/local/sbin/nginx 
--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-0.9.8l/

From there you will no longer be required to have an SSL enabled server block on it’s own unique IP. You won’t even have to have the block listening to a specific IP either since now OpenSSL will handle the certificate validation based on the server name.

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
/* 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="";
 
/* Setup your sites here, blackhat, whitehat and advertiser */
$bh = "http://blackhat.kbeezie.com/";
$wh = "http://whitehat.kbeezie.com/";
$ad = "http://advertiser.kbeezie.com/";
 
/* 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) { 
	/* appends querystring to wh destination */
	$meta = $wh.'?'.$pg; 
	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."?".$pg; 
	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(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.'?'.$pg.'";';
	}
	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.'?'.$pg?>" 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.