Foursquare is getting beat down today; hot on the heals of the breaking news that Foursquare is sending your passwords in plaintext (something they are fixing very soon) Mayank Lahiri has decided to figure out how exactly one could game Foursquare checkins to become the mayor of almost anywhere in no more than 9 Perl statements.
It looks like Mayank likely sniffed the Foursquare HTTP POST traffic from his phone to the Foursquare servers on checking and is simply mocking the POST request to the server by replacing the GPS coordinates with the ones of his favorite places that he wants to check into multiple times a day; in this case, a random time between 1 and 10 mins.
Here’s how to use his script…
How to Get GPS Coordinates from Google Maps
This is the bread-and-butter of this trick, you have to search for the location you want to find, so Google Maps centers it on the map, for example here is JFK Airport in New York.
Then on the top right of the map there is the “Link” text, copy it and paste it anywhere you want, for JFK, it looks like this (newlines and color added for readability):
Notice the two blue numbers? Those are your latitude and longitude (in that order) that you need for the Perl script to work, so write those down for the location(s) that you want to “hack” the Foursquare mayorship to.
How to Base64 Encode your Email & Password
Now the next step is that you need to create a single Base64-encoded string that includes your email and password in the format “firstname.lastname@example.org:myPassword”; without the quotes.
Luckily The Buzz Media just launched our super-sexy free AJAX Base64 encoder/decoder utility that you can use. So in the example of the following data:
CLARIFICATION: The online AJAX Base 64 utility does not log/record or remember the values you put in or it generates in any way.
- Email: email@example.com
- Password: applerocks
We would create the string “firstname.lastname@example.org:applerocks” and plug that into the Base64 encoder utility, hit “Encode” and we get back the Base64-encoded string “c3RldmVAYXBwbGUuY29tOmFwcGxlcm9ja3M“, sweet!
How to Run the Perl Script
Ok so now you are ready for the script, Mayank provided the source code to his script and it is included here for longevity (and the sake of completeness for this guide).
You will want to take this code and paste it into a new .pl file to run it, here are Google search results for “How to run a perl script” (Windows and Unix included):
#!/usr/bin/perl -W use IO::Socket; srand; sleep(rand()*600); my $sock = IO::Socket::INET->new(PeerAddr=>'api.foursquare.com', PeerPort=>80, Proto =>'tcp', Type=>SOCK_STREAM) or die; $ARGV += rand() * 0.0001 - 0.00005; $ARGV += rand() * 0.0001 - 0.00005; my $str = "vid=$ARGV&private=0&geolat=$ARGV&geolong=$ARGV"; print $sock "POST /v1/checkin HTTP/1.1\r\nHost: api.foursquare.com\r\nUser-Agent:" ." Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ " ."(KHTML, like Gecko) Version/3.0 Mobile/1C10 Safari/419.3\r\nContent" ."-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic " ."XXXXXX\r\nContent-length: ", length($str)+2, "\r\n\r\n$str\r\n"; $_=<$sock>;
The key things to note are that this script takes 3 command line arguments:
- $ARGV – Foursquare Venue ID
- $ARGV – Latitude
- $ARGV – Longitude
You also need to replace the “XXXXXX” in the 2nd to last line, with that big Base64-encoded string we generated above that was the encoded version of your email and password (login) to Foursquare.
You would need to get the Venue ID from the Foursquare website in some fashion. If you just search for a place, the URL includes the Venue ID that you need (see screenshot above). Once you have that, running the script to start checking yourself into places in random intervals between 0 and 10 mins would look like this:
perl foursquare-mayor-hack.pl 123456 32.221743 -110.926479
Where the name of the .pl file would be based on whatever you named your script, and the first number is the Venue ID you would have to get from the Foursquare interface somewhere, and the 2nd and 3rd arguments are the Latitude and Longitude we grabbed from Google Maps in the first section of the article.
DISCLAIMER: Foursquare will likely get wise to check-in abuse pretty quickly to avoid degrading the integrity of the service, so if you start abusing this script, we imagine you’d probably get banned from the service. Use this at your own risk.
Mayank goes on to explain why Foursquare will not be able to immediately protect against this type of gaming of their check-in system and what potential methods they could take to stop the fake checkins, but also equally notes the downsides to each approach.
He expects Foursquare (and likely Facebook Places and Gowalla) to remain susceptible to these type of hacks for a while.
Update #1: Alright, so I wrote a GUI app to do this for you, but for anyone interested, here are the relevant Java portions of the code to do it:
public static final String HOSTNAME = "api.foursquare.com"; public static final String API_URL = "http://" + HOSTNAME + ":80/v1/checkin"; public static final String USER_AGENT = "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1C10 Safari/419.3"; public static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; ... SNIP ... Double lat = Double.parseDouble(latTextField.getText()) + (Math.random() * 0.0003); Double lng = Double.parseDouble(longTextField.getText()) + (Math.random() * 0.0003); String args = "vid=" + venueIDTextField.getText() + "&private=0&geolat=" + lat + "&geolong=" + lng; URLConnection connection = new URL(API_URL).openConnection(); connection.setDoInput(true); connection.setDoOutput(true); connection.setUseCaches(false); connection.setRequestProperty("Host", HOSTNAME); connection.setRequestProperty("User-Agent", USER_AGENT); connection.setRequestProperty("Content-Type", CONTENT_TYPE); connection.setRequestProperty("Authorization", "Basic " + encodedAuth); connection.setRequestProperty("Content-Length", Integer.toString(args.length() + 2)); DataOutputStream output = new DataOutputStream(connection.getOutputStream()); output.writeBytes(args); output.flush(); output.close();