Avoiding SQL Injection with Go

There’s a good chance if you’ve worked with Go you’ve interacted with a database of some sort and also accepted user input. Injection attacks always make it to the OWASP Top 10 (although it’s finally been dropping) – particularly SQL injection. SQL has kind of wild access control when you look at standards today; the same ‘method’ (see below) you use to execute a read-only query could also modify data, or even delete it! All it takes is a varied statement and the cleverness of the internet never fails to find a way to poison user input.

Note: I think I first heard it on Steve Gibson’s Security Now podcast – wouldn’t it be cool if SQL had different way to handle different kinds of statements. Like if you wanted to just query data and not change anything you could effectively send your query off with the equivalent of a GET request and the server would just reject any INSERT, UPDATE, or DROP commands. One day…

Back to Go. Go is a very well designed and new language built with knowledge of these attacks in mind. In fact, all documentation and convention points to using the package database/sql where you craft a query with placeholders so you can safely execute that query with user data and any type of injection is escaped away.

This works all well and good in most cases like your typical user login:

username := "joetats"

// all these examples use the postgres $1 placeholder
stmt:= `SELECT username, hashed_password FROM users WHERE username = $1;`

result, err := db.Query(stmt, username)
if err != nil {
   // blah, blah, blah...
}

The documentation uses these types of examples extensively to the point you’d think there is no other way to do it and this is great! The issue pops up when you start needing partial matches instead of exact matches like above – how you do handle the wildcards?

// this won't work
stmt := `SELECT username FROM users WHERE username LIKE '%$1%'`

// how about this?
stmt := `SELECT username FROM users WHERE username LIKE '%` + username + `%';`

// ahh! injection risk!
// building with fmt.Sprintf() won't save you either...

So what do you do when you run into this? I’m embarrassed it took me more than a couple stack overflow searches to find the solution, looking back I should have just seen it on my own:

username := "joetats"

stmt := `SELECT username FROM users WHERE username LIKE $1;`

username = "%" + username + "%"

result, err := db.Query(stmt, username)

Just wrap the lookup term with your needed wildcards and insert it like normal. Riddles are always easy when you know the answer…

Leave a comment