Learning Go with AppEngine

Sat 06 April 2013 by Peter Ward

I’ve been meaning to learn Go for a while, and I had a nice opportunity today to do that. But before I talk about Go, let me explain why I was writing a Go program in the first place.

Last night, I saw that Alex (a friend of mine) had written a little website for unicode faces called textfac.es. At the moment, you need to ask him to add new faces, but there’s a dynamic voting system: every time you click on a face, that’s counted as a vote.

Of course, with such a simple voting system, it’s pretty obvious that it’s going to be exploitable in various ways, so I set out to explore some of them. The first thing to do is to have a look at how clicks translate into votes in the browser. Looking at the JavaScript, I saw (essentially) this:

var faces = $("button.facebtn");

clip.on("mousedown", function(client, args) {
    var id = $(this).attr("face-id")
    $.ajax("/increment?id=" + id);
});

Which means that when you click it, it sends a GET request to a URL like http://textfac.es/increment?id=37.

The attack I didn’t do

The obvious thing to do would be to run a brute-force attack to increment the count as much as possible, like this:

$ while true; do wget -qO - http://textfac.es/increment?id=37 &>/dev/null; done

Bor-ing.

Not only that, it’s also very easy to block with a per-ip rate limit (which was put in place). I wanted to be more subtle about it, and make it more difficult to identify whether it was spam or not.

My first attack

Having voting happen by a GET request is clearly a mistake. According to RFC2616 (Section 9.1.1), GET requests should not change any state on the server:

In particular, the convention has been established that the GET and
HEAD methods SHOULD NOT have the significance of taking an action
other than retrieval. These methods ought to be considered "safe".

What this means is that it’s fairly easy to get a browser to send a GET request for you, since the browser "knows" it’s a harmless request.

And so, I added a little bit of HTML to the bottom of all the pages of my blog:

<img src="http://textfac.es/increment?id=37" style="display:none" />

Then I needed to generate some traffic, so I wrote a blog post (that I had been meaning to write for a while) and posted it to Google+, Twitter and Facebook.

So if you were one of the nice people who visited my blog since last night, your browser helpfully loaded that "image" with a GET request, thus casting a vote using your IP. Why thank you, random stranger!

My second attack, with Go!

Great, this generated some traffic, and I was sitting at #5 for a while. But I wanted to get higher up, and so I had the thought: you know, it would be really nice if I could use Google’s network to spam the voting system for me…

I knew that it was possible to make outbound web requests with AppEngine, and I’d been meaning to write something in Go for a while, so this seemed like a good excuse.

I just needed to write a simple application to take a url and a number, and send that many GET requests to the url. Easy, peasy.

So I wrote that, and then did some testing, and discovered that there was still only a fairly limited number of IPs which it would generate traffic from, though I could vary the IPs by changing where my request to AppEngine came from.

My final variation was to hard-code the url and number, and to use that in an img tag on my blog, so that the requests to AppEngine would be from a diverse IP range. I have no idea if that’s actually working or not!

Finally he gets onto actually talking about Go…

I only ran into two interesting things with Go. The first one was when I was doing authentication: I wanted to restrict it to a set of valid logins, which basically meant testing to see if a list contained a certain item.

I was very surprised that Go doesn’t have a builtin function for this, I don’t really expect to have to write linear search in 2013. I did find someone’s recipe for this on the golang-nuts mailing list, which I shamelessly stole^Wappropriated.

On this topic, it’s quite possible that the Go language does has this built in, and I just didn’t find it: while the documentation does appear to be complete, I didn’t find it anywhere near as readable or searchable as the Python documentation (hmmm, I think Python spoils me).

The other thing which I found interesting was that Go doesn’t have a map() primitive using goroutines. While it’s not overly difficult to express the same thing with channels, I felt that my code would have looked cleaner.

Here’s a contrived example using explicit goroutines and channels:

func square(x int, ch chan int) {
    ch <- x * x
}

func main() {
    n := 10

    ch := make(chan int)

    for i := 0; i < n; i++ {
        go square(i, ch)
    }

    sum := 0
    for i := 0; i < n; i++ {
        sum += <-ch
    }
}

And here’s what the equivalent code would look like with a concurrent map function:

func square(x int) int {
    return x * x
}

func main() {
    values := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    results := go_map(values, square)

    sum := 0
    for _, v := range results {
        sum += v
    }
}

Of course, I could have left that last example as pseudocode, but I didn’t like that, so I wrote out an implementation of go_map. Unfortunately, my go-fu isn’t at the point where I can write a generic version for any type. Perhaps someone will write one, and leave a note in the comments. Even better, perhaps someone will point out that this already exists in the standard library somewhere?

func go_map_wrap(x int, fn func(int) int, ch chan int) {
    ch <- fn(x)
}

func go_map(values []int, fn func(int) int) []int {
    results := make([]int, len(values))

    ch := make(chan int)

    for _, v := range values {
        go go_map_wrap(v, fn, ch)
    }

    for i := 0; i < len(values); i++ {
        results[i] = <-ch
    }

    return results
}

Epilogue

By the way, that last complaint wasn’t because I’m a functional programming nut or anything, it’s because I wanted to send off N concurrent web requests, and it seemed like a pain to have to manually manage the channel.

On the whole I do like Go, and in my book it’s up with Vala and Scheme as nice languages. Python is still vastly more usable, but it’s likely a language which I’ll use again in the future.

P.S: Alex is not an awful developer! He did realise the hole when he initially coded it, and just didn’t get around to fixing it until I started messing around. So don’t blame him, he’s awesome.


Comments