Understanding SQL injection vulnerabilities

In this article, we will explore how SQL injection vulnerabilities work and how to prevent them
Wednesday, September 10, 2025

📘 What is SQL injection vulnerability?

SQL injection vulnerabilities occur when untrusted data is sent to an interpreter (such as a database engine, command shell, or LDAP server) as part of a command or query. If the data isn’t properly validated or sanitized, attackers can manipulate the interpreter into executing unintended commands or accessing unauthorized data.

According to the OWASP organization, 94% of tested applications were found vulnerable to some form of injection !

🧪 Demonstration

Let’s shows how an injection vulnerability can occur when user input is improperly handled in a SQL query.

This example uses the database/sql package with a SQLite driver (github.com/mattn/go-sqlite3) to simulate a vulnerable login system.

package main
import (
"database/sql"
"fmt"
"log"
"net/http"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./test.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Create a simple users table
db.Exec("CREATE TABLE IF NOT EXISTS users (username TEXT, password TEXT)")
db.Exec("INSERT INTO users (username, password) VALUES ('admin', 'admin123')")
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
username := r.URL.Query().Get("username")
password := r.URL.Query().Get("password")
// ❌ Vulnerable to SQL Injection
query := fmt.Sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", username, password)
rows, err := db.Query(query)
if err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
defer rows.Close()
if rows.Next() {
fmt.Fprintf(w, "Login successful!")
} else {
fmt.Fprintf(w, "Invalid credentials.")
}
})
fmt.Println("Server running at http://localhost:8080")
http.ListenAndServe(":8080", nil)
}

We expect that opening these URLs will attempt to log in the user :

http://localhost:8080/login?username=admin&password=wrong_password
http://localhost:8080/login?username=admin&password=admin123

💣 Exploiting the vulnerability

We can exploit this vulnerability by visiting this URL:

http://localhost:8080/login?username=admin&password=' OR '1'='1

This will bypass authentication because the SQL query becomes:

SELECT * FROM users WHERE username='admin' AND password='' OR '1'='1'

Even if the password is incorrect, the condition '1'='1' causes the entire WHERE clause to evaluate as true, resulting in the admin user being returned.

We can imagine more destructive queries, like :

http://localhost:8080/login?username=admin&password=%27%3BDROP%20TABLE%20users%3B--

Now, we can imagine that the attacker could access sensitive data, modify or delete it, execute administrative operations (such as dropping tables or creating users), or even take control of the server in some cases !

🛡️ How to prevent injection attacks

To fix the vulnerability, we can use prepared statements with parameterized queries. It prevents injection by separating SQL logic from data.

// Define the query and use placeholders (?)
stmt, err := db.Prepare("SELECT * FROM users WHERE username=? AND password=?")
if err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
// Execute the query with user-provided values safely
rows, err := stmt.Query(username, password)

🐝 Recommendations

To mitigate injection risks, OWASP recommends:

  • Prefer parameterized queries or ORM (Object Relational Mapping tools).
  • Apply strict server-side validation and allow only expected formats.
  • When dynamic queries are unavoidable, escape user input using interpreter-specific syntax.
  • Incorporate static (SAST), dynamic (DAST), and interactive (IAST) testing into your CI/CD pipeline to catch injection flaws early.

Recommended articles