Gaming foursquare with 9 lines of Perl

by Mayank Lahiri, August 18th, 2010

Click here for updates.

Foursquare (and its competitors GoWalla and Facebook Places) is an online service that allows you to use you fancy modern smartphone to tell the whole world exactly where you are right now. The New York Times carried an impassioned article today about how these 'checkin services' are increasingly patronized by young people as they scramble to be awarded virtual 'mayorships' of businesses, buildings, and even lost alleyways.

On this page, I pose the following question: how many lines of code does it take to game Foursquare and become the virtual mayor of a place using nothing but Perl scripts?

This is merely for research purposes. As long as there are at least a few businesses that offer monetary rewards to virtual 'mayors', there exists the incentive to game the system. Learning how easy it is will hopefully offer some perspective on this trend.

I demonstrate how to do it in 9 Perl statements. This is not particularly surprising to anyone who understands TCP. Further contributions that satisfy the requirements below in fewer statements are welcomed, and will be appended to this page with attribution.


The idea is that the little script/binary can be used in conjunction with cron to schedule semi-regular checkins, and also that these checkins don't look obviously mechanistic.

In 9 Perl statements

Note that in the code below, I've replaced my user id and password with the string XXXXXX. To use this script, you'll have to replace it with the Base64 encoding of "email/phone:password".
#!/usr/bin/perl -W
use IO::Socket;
my $sock = IO::Socket::INET->new(PeerAddr=>'', PeerPort=>80, 
                                 Proto =>'tcp', Type=>SOCK_STREAM) or die;
$ARGV[1] += rand() * 0.0001 - 0.00005;
$ARGV[2] += rand() * 0.0001 - 0.00005;
my $str = "vid=$ARGV[0]&private=0&geolat=$ARGV[1]&geolong=$ARGV[2]";
print $sock "POST /v1/checkin HTTP/1.1\r\nHost:\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";
OK, so it's not the absolute smallest it could be. On startup, line #4 sleeps for a random amount of time up to 10 minutes. Then, we establish a connection to the foursquare API server, randomly perturb the base GPS coordinates of the venue by a little bit, and then send the check-in string. The last line is probably superfluous, and just waits for the server to finish sending its response before closing the connection and quitting. Note that in Perl the "." operator concatenates strings, and I've broken up the string literal so that it looks nice here.

NOTE: To get this script to work, you must replace XXXXXX with the Base64 encoded version of "email/phone:password", so base64(""). Here's Google's top ranked site for online Base64 encoding.

Staging a foursquare coup

Ever since I was a wee lad, my dream has always been to be the foursquare mayor of a number of places here in San Francisco, CA:
  1. The Revolution Cafe, a small bar in the Mission with live music
  2. Bourbon and Branch, a great speakeasy in the Tenderloin district
  3. The Civic Center BART stop, for no good reason
Using Google Maps, I looked up the GPS coordinates of the places. Using Foursquare, I looked up the "venue IDs" for the locations as well. Now all I have to do is install a bunch of cron jobs that check me into my favorite places at pre-determined times. Here's the schedule I chose (subject to the 10-minute random perturbation):

Revolution Cafe at 1pm every weekday, because I'm a regular.
Bourbon and Branch immediately afterwards at 3:30pm every weekday.
Civic Center BART at 4:00pm every weekday, while barhopping.

Revolution Cafe at 4pm.
Bourbon and Branch at 8pm.

Please don't game my locations! :)

Why checkins will be vulnerable for quite some time

Foursquare could enforce travel times based on physical distance between checkins.
OK, this just slows down our rate of checkins and doesn't fix the problem. Also, I own a Ferrari.

Foursquare could use GeoIPs to make sure that reported locations are approximately correct.
Two ways around this: phone apps that exist solely to send these bogus checkins from mobile towers with presumably different IPs, or for the determined coup planner, a set of proxy servers (or friends' computers) around the city. The granularity of GeoIP isn't very fine, so this makes the job much easier.

Foursquare could block checkins for geographically different locations from the same IP
Still susceptible to localized proxies (or web hosting), using Tor, or to using a laptop and moving around a bit. This would also exclude at least some people who tunnel through VPNs.

Foursquare could attempt to group IPs that are geographically "near" a checkin spot.
Impossible to do this without denying service to a brand new IP that appears in the area (say due to an ISP reconfiguration).

In other words, it's very difficult, if not impossible, to tell a bogus 'checkin' from a real one. My prediction is that the incentive offered by businesses to virtual 'mayors' will never increase beyond an insignificant amount, because the incentive to game the 'mayor' system would become too high.

Disclaimer: This was done on my own time, for research purposes only, and is meant as an academic curiosity.


August 21, 2010: This got posted on Slashdot. Here's the
story. The Slashdot effect sent at least 13,141 unique visitors over this weekend, which bumped up the traffic to my tiny homepage by 9,025%. And that's not even counting users who have disabled Google Analytics.

August 22, 2010: Eli Foley sent in this 2-line Ruby script that exploits the OO nature of Ruby (I'm presuming) to chain functions into one long logical statement. Since this isn't a serious "contest" anyway, it's allowed!

Eli writes: "would have been prettier with Net/http, but since i don't use four-square i wanted to keep it as close to yours as possible."

require 'socket'

puts'', 80).print("POST /v1/checkin HTTP/1.1\r\nHost:\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: #{(m = "vid=#{ARGV[0]}&private=0&geolat=#{ARGV[1].to_i + rand(100)*0.01 - 0.00005}&geolong=#{ARGV[2].to_i + rand(100)*0.01 - 0.00005}").length+2 }\r\n\r\n#{m}\r\n").read if sleep(rand(100)*6)

August 22, 2010: Chris Stith notes quite correctly that I "didn't really even try" to make the script as short as possible. This is partly because I slapped this together quite quickly to not miss my bus, and partly because the purpose here is really to point out how ghastly it is that businesses assign a monetary value to "checkins" in their current form. His comment shortens the hack to 5 logical lines of Perl (found in the linked comment), which is still surprisingly readable.

August 23, 2010: Kevin LeCureux writes in with this interesting idea:

"Each venue (or at least those concerned with "gaming") could display a random QR code on screens that would only be valid for a certain time period (probably anywhere from an hour to a day). This code would be known to Foursquare et. al., of course, in order to validate a check-in."
The question, of course, is whether such a system would be commercially viable, and if business owners would be more inclined to favor the better known brands (the obvious example is Facebook), putting smaller operations like GoWalla at a serious disadvantage.

August 23, 2010: Some interesting comments about this on Hacker News, Martin Kou's blog, and Reddit.

August 23, 2010: The Buzz Media has a nice writeup explaining how to use this hack with lots more detail. Thanks for the attribution, Riyad! And his tutorial seems to have made it to dzone, where they say: "Not typically a DZone story (social media hype) but the hack (mock HTTP POST) is too entertaining to not share." Indeed! :)

August 25, 2010: Alex Ford sends in this two logical line Perl nugget, in the truest Perl readability style (it's three lines if you count the MIME::Base64 import). And we wonder why people complain about Perl! :)

!/usr/bin/perl -w
use MIME::Base64;
use IO::Socket;

readline do {
    $sock if $sock = IO::Socket::INET->new(PeerAddr => '',
       PeerPort => 80, Proto => 'tcp', Type => SOCK_STREAM)
    and printf($sock join(v13.10,
            "POST /v1/checkin HTTP/1.1", "Host:",
            "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",
            "Content-Type: application/x-www-form-urlencoded",
            "Authorization: Basic ${\encode_base64('YOUR_USERNAME_HERE' . ':'
            . 'YOUR_PASSWORD_HERE')}", 'Content-length: %2$s', '', '%1$s'
        ), local $_ = sprintf('vid=%s&private=0&geolat=%s&geolong=%s',
            $ARGV[0], map { $_ + rand() * 1e-4 - 5e-5 } @ARGV[1, 2]),
        2 + length)
} if sleep rand() * 600;

August 26, 2010: Thaddeus Bogner suggests using a CAPTCHA for each checking, but we both agree that it would probably be ineffective or ruin the user experience.

August 29, 2010: Rodrigo from Madrid, Spain sends in this cool little meta-hack. "It's a little pun on the concept of gaming Foursquare: this time,it's your site that has been gamed, which in turn games Foursquare."

use IO::Socket;
$sock0 = IO::Socket::INET->new(PeerAddr=>'', 
         PeerPort=>80, Proto =>'tcp', Type=>SOCK_STREAM) or die;
print $sock0 "GET /~mayank/4sq.html HTTP/1.0\n\n";
($d) = <$sock0> =~ m[<pre>(.*?)</pre>]gs;
$d =~ s/XXXXXX/YYYYYY/g;
eval $d;