
SQL injection!… Is it really a thing?
SQL injection is a code injection technique that is capable of destroying your database. It is a code injection technique, used to attack data-driven applications, in which malicious SQL statements are inserted into an entry field for execution. (e.g. to dump the database contents to the attacker)
How SQL Injection Works?
SQL injection is a hacking technique that’s been around since at least 1998. It takes advantage of two factors for success: First, web applications often ask users for data; Second, those applications tend to take the user-supplied data and pass it to the database as part of an instruction. Put them together with no code-based guardrails, and a criminal can run the application far off into the weeds.
Like in above case, Bobby’s school lose all their student records when Bobby’s father decided to name their son as “Robert’); DROP TABLE students;
” and used that as an input parameter in online school’s application form.
How can we avoid it in Golang?
In this post, we’ll learn how to avoid SQL Injection attack in our Golang code by sanitizing database inputs.
To be able to achieve our objective i.e. Avoid SQL Injection in Golang application, there are some prerequisites. Sticking to the core subject of this blog, I won’t cover them, however, I’m providing with some references
Install PostgreSQL, setup an instance and create a test database – https://www.postgresqltutorial.com/install-postgresql/
Install Go and configure workspace – https://www.callicoder.com/golang-installation-setup-gopath-workspace/
For this tutorial, I’m using Postgres 12 and Go 1.13.xx
Step 1. SQL Table definition
We’ll use test table EMP with some test data
-- create a sample table EMP CREATE TABLE emp ( empno SERIAL PRIMARY KEY, ename TEXT, sal INT CHECK (sal>0), email TEXT UNIQUE NOT NULL ); -- insert test data INSERT INTO public.emp (ename, sal, email) values ('Smith', 1400, 'smith@acme.com'), ('Allen', 2000, 'allen@acme.com'), ('Jones', 3000, 'jones@acme.com'), ('Blake', 4000, 'blake@acme.com');
Step 2. Basic select statement
Let’s just assume that we want to SELECT
employee record from table EMP by passing EMPNO column filter in WHERE
clause.
q := fmt.Sprintf("SELECT ename FROM emp where empno=%s;", "7") row := db.QueryRow(q)
The empno WHERE
clause is substituted with value 7 and all rows relating to that employee number are returned. No big deal. But what happens if someone like Bobby’s Father inputs 7); TRUNCATE TABLE emp;--sql_injection
as variable input value.
q := fmt.Sprintf("SELECT ename FROM emp where empno=%s;", "7; Truncate Table emp;--sql_injection!") row := db.QueryRow(q)
Here:
- Since empno contain ); the VALUES argument is closed
- And postgres continues executing the next line i.e. the text that follows TRUNCATE TABLE emp and truncates the whole table emp
- Finally the — at the end comments out the remaining SQL, essentially ignoring the rest of the original code and making sure no error occurs.
Step 3. How to deal with this in Golang
The database/sql
package from the standard library provides methods for executing parameterized queries either as prepared statements or as one-off queries. For example, you might want to have code that looks roughly like this:
// this is for Postgres driver // querying for a single record using Go's database/sql package sqlStatement := `SELECT ename FROM emp WHERE empno=$1;` row := db.QueryRow(sqlStatement, 7)
The key distinction here is that we aren’t trying to construct the SQL statement ourselves, but are instead providing arguments that can be easily escaped for us. The underlying driver for database/sql
will ultimately be aware of what special characters it needs to handle and will escape them for us, preventing any dangerous SQL from running.
Step 4. Putting this all together
package main // querying for a single record using Go's database/sql package import ( "database/sql" "fmt" _ "github.com/lib/pq" ) const ( host = "localhost" port = 5432 user = "postgres" password = "****" dbname = "connect" ) func main() { psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+ "password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) db, err := sql.Open("postgres", psqlInfo) if err != nil { panic(err) } defer db.Close() var ename string sqlStatement := `SELECT ename FROM emp WHERE empno=$1;` row := db.QueryRow(sqlStatement, 46) switch err := row.Scan(&ename); err { case sql.ErrNoRows: fmt.Println("No rows were returned!") case nil: fmt.Println(ename) default: panic(err) } }
Conclusion
You should always use the database/sql
package to construct prepared statements. These prepared statements have parameters that will be passed while executing the statement. This is much better than concatenating strings (Avoiding SQL injection attack). In PostgreSQL, the parameter placeholder is $N
, where N
is a number. In MySQL it is ?
. SQLite accepts either of these. For more best practices on preventing SQL Injection, please refer https://bobby-tables.com/go
If you find this post helpful, I’d be very grateful if you’d help it spread by emailing it to a friend, or sharing it on Twitter or Facebook. Thank you