SQL Injection via String Formatting in Go

High Risk Injection
gosql-injectiondatabasefmtstring-formatting

What it is

SQL injection vulnerabilities occur when user input is concatenated or formatted into SQL queries using fmt.Sprintf() or string concatenation instead of parameterized queries. This allows attackers to inject malicious SQL code that can read, modify, or delete data, potentially compromising the entire database.

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/lib/pq"
)

func getUserByID(db *sql.DB, userID string) (*User, error) {
    // VULNERABLE: String concatenation enables SQL injection
    query := "SELECT id, username, email FROM users WHERE id = '" + userID + "'"
    
    row := db.QueryRow(query)
    var user User
    err := row.Scan(&user.ID, &user.Username, &user.Email)
    return &user, err
}

func searchUsers(db *sql.DB, search string) ([]User, error) {
    // VULNERABLE: fmt.Sprintf allows injection
    query := fmt.Sprintf("SELECT * FROM users WHERE username LIKE '%%%s%%'", search)
    
    rows, err := db.Query(query)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var users []User
    for rows.Next() {
        var u User
        rows.Scan(&u.ID, &u.Username, &u.Email)
        users = append(users, u)
    }
    return users, nil
}

// Attack: userID = "1' OR '1'='1"
// Attack: search = "'; DROP TABLE users; --"
package main

import (
    "database/sql"
    _ "github.com/lib/pq"
)

func getUserByID(db *sql.DB, userID string) (*User, error) {
    // SECURE: Parameterized query prevents SQL injection
    query := "SELECT id, username, email FROM users WHERE id = $1"
    
    row := db.QueryRow(query, userID)
    var user User
    err := row.Scan(&user.ID, &user.Username, &user.Email)
    return &user, err
}

func searchUsers(db *sql.DB, search string) ([]User, error) {
    // SECURE: Using parameterized query with placeholder
    query := "SELECT * FROM users WHERE username LIKE $1"
    searchPattern := "%" + search + "%"
    
    rows, err := db.Query(query, searchPattern)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var users []User
    for rows.Next() {
        var u User
        rows.Scan(&u.ID, &u.Username, &u.Email)
        users = append(users, u)
    }
    return users, nil
}

💡 Why This Fix Works

The vulnerable code uses string concatenation and fmt.Sprintf() to build SQL queries, allowing attackers to inject malicious SQL. The secure version uses parameterized queries with placeholders ($1, $2) that treat user input as data rather than executable SQL code.

Why it happens

Building SQL queries by concatenating strings with user input.

Root causes

Using String Concatenation for Queries

Building SQL queries by concatenating strings with user input.

Using fmt.Sprintf() for Query Construction

Using fmt.Sprintf() or similar formatting functions to build SQL queries.

Not Using Parameterized Queries

Not leveraging database/sql package's built-in parameterized query support.

Fixes

1

Use Parameterized Queries

Use placeholder syntax ($1, $2, etc.) with Query() or QueryRow() methods.

2

Use Prepared Statements

Create prepared statements with db.Prepare() for frequently executed queries.

3

Never Concatenate User Input

Avoid string concatenation or fmt.Sprintf() when building SQL queries.

Detect This Vulnerability in Your Code

Sourcery automatically identifies sql injection via string formatting in go and many other security issues in your codebase.