DEV Community

Cover image for Preventing SQL Injection with Raw SQL and ORM in Golang
Dzung Nguyen
Dzung Nguyen

Posted on

Preventing SQL Injection with Raw SQL and ORM in Golang

In modern development, secure coding is crucial. Golang applications can also be prone to SQL injection when interacting with databases, unless proper precautions are taken.

This article covers how to prevent SQL injection in Golang using raw SQL and ORM frameworks.


🛑 What is SQL Injection?

SQL Injection (SQLi) is a web security vulnerability that allows an attacker to INJECT malicious SQL code into the queries an application makes to its database.

Example of a vulnerable SQL query:

query := "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"
rows, err := db.Query(query)
Enter fullscreen mode Exit fullscreen mode

If username or password contains malicious input, attackers can manipulate the query logic.

SQL Injection

You can check out my another post on SQL injection to know more about it.


🔐 How to Prevent SQL Injection in Raw SQL

When writing raw SQL queries in Go, adhere to best practices for security.

1. Use Prepared Statements

Go’s database/sql package provides prepared statements that safely handle user input.

Vulnerable Example:

query := "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"
rows, err := db.Query(query) // Potential SQL injection
Enter fullscreen mode Exit fullscreen mode

With Prepared Statements:

query := "SELECT * FROM users WHERE username = ? AND password = ?"
rows, err := db.Query(query, username, password)

if err != nil {
    log.Fatal(err)
}
Enter fullscreen mode Exit fullscreen mode

Prepared statements automatically escape user input to prevent injection.

2. Use Parameterized Queries

In Go, parameterized queries can be written using db.Query or db.Exec with placeholders:

query := "INSERT INTO products (name, price) VALUES (?, ?)"
_, err := db.Exec(query, productName, productPrice)

if err != nil {
    log.Fatal(err)
}
Enter fullscreen mode Exit fullscreen mode

Avoid string concatenation or formatting with fmt.Sprintf for dynamic queries.

3. Use QueryRow for Single Row Queries

If fetching a single record, use QueryRow to avoid risks:

query := "SELECT id, name FROM users WHERE email = ?"

var id int
var name string
err := db.QueryRow(query, email).Scan(&id, &name)

if err != nil {
    log.Fatal(err)
}
Enter fullscreen mode Exit fullscreen mode

4. Input Sanitization and Validation

Even when using prepared statements, input sanitization and validation remain crucial in preventing injection attacks and maintaining data integrity.

  • Sanitization: Cleans input data by removing unwanted characters to ensure it meets the application's expectations.

  • Validation: Ensures that input conforms to a specific format, type, or length before processing.

Example of Input Validation in Go:

func isValidUsername(username string) bool {
    re := regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
    return re.MatchString(username)
}

if len(username) > 50 || !isValidUsername(username) {
    log.Fatal("Invalid input")
}
Enter fullscreen mode Exit fullscreen mode

5. Use Stored Procedures

Stored procedures encapsulate query logic on the database side and limit direct input handling in code:

CREATE PROCEDURE AuthenticateUser(IN username VARCHAR(50), IN password VARCHAR(50))
BEGIN
    SELECT * FROM users WHERE username = username AND password = password;
END;
Enter fullscreen mode Exit fullscreen mode

Call stored procedures from Go using:

_, err := db.Exec("CALL AuthenticateUser(?, ?)", username, password)

if err != nil {
    log.Fatal(err)
}
Enter fullscreen mode Exit fullscreen mode

🔄 Preventing SQL Injection with ORMs

Many Golang applications use ORM libraries such as GORM or XORM to simplify database interactions. While these tools offer built-in protection against SQL injection, you must follow best practices.

1. GORM

GORM is one of the most popular ORMs in Go.

Vulnerable Example (Dynamic Query Construction):

db.Raw("SELECT * FROM users WHERE name = '" + userName + "'").Scan(&user)
Enter fullscreen mode Exit fullscreen mode

This can lead to injection if userName contains malicious input.

Safe Example:

db.Raw("SELECT * FROM users WHERE name = ? AND email = ?", userName, email).Scan(&user)

Enter fullscreen mode Exit fullscreen mode

GORM’s Raw method supports parameterized queries.

Using Struct-Based Queries (safer default):

db.Where("email = ? AND status = ?", email, status).First(&user)
Enter fullscreen mode Exit fullscreen mode

Using GORM’s query methods like Where is the safer default and should be preferred for database interactions.

2. Avoid Raw Strings for Complex Queries

When using raw SQL queries for complex operations, use placeholders:

result := db.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, accountId)

if result.Error != nil {
    log.Println(result.Error)
}
Enter fullscreen mode Exit fullscreen mode

3. Use Struct Tags for Safe ORM Mapping

When mapping Go structs to database tables, use tags to define field names and constraints:

type User struct {
    ID       uint   `gorm:"primaryKey"`
    Name     string `gorm:"size:100"`
    Email    string `gorm:"uniqueIndex"`
    Password string
}
Enter fullscreen mode Exit fullscreen mode

This helps ensure safe ORM mappings by defining constraints, data types, and relationships directly within the Go struct.

⚠️ Common Mistakes to Avoid

1. Avoid Building Queries with String Concatenation

Always use placeholders or prepared statements instead of concatenating variables into queries.

2. Avoid ORM Functions that Bypass Safety Checks

Some ORM functions allow executing raw SQL with direct string input. Use parameterized alternatives whenever possible.

3. Do Not Trust User Input

Always carefully validate and sanitize inputs before processing.


🛡️ Conclusion

Golang provides powerful tools for handling database queries securely. By implementing best practices—such as leveraging prepared statements, parameterized queries, and ORM frameworks while also sanitizing and validating inputs — you create a robust defense against SQL injection vulnerabilities.

Follow me to stay updated with my future posts:

Top comments (0)