Injection

Injection in Go (Gin): Secure Coding Guide [March 2026] [CVE-2026-29953]

[Updated March 2026] Updated CVE-2026-29953

Overview

Injection vulnerabilities in Go (Gin) apps often stem from building SQL or OS commands by concatenating untrusted input directly into strings. Real-world attackers can manipulate queries to reveal data, bypass authentication, or modify records. This guide outlines why these patterns are risky, how they manifest in Gin-based services, and the typical impact when databases or system commands are involved. While no CVEs are provided in this request, the guidance reflects widely observed risk patterns in Go web services using Gin and raw SQL APIs. It also highlights how standard Go database tools and careful coding practices can mitigate these issues. In Gin, the vulnerability typically arises when handlers read user input and interpolate it into SQL strings or shell commands rather than binding values as parameters. This is common when teams migrate from simple examples to production code and forget to use placeholders. The framework itself does not automatically sanitize inputs or protect against injection; developers must explicitly opt into safe patterns. Without parameter binding, an attacker can craft payloads that alter query logic, potentially exposing sensitive data or allowing unintended data modifications. To minimize risk, adopt robust input validation and strictly use parameterized queries or ORM abstractions that enforce binding. Combine this with disciplined error handling, least privilege database accounts, and periodic security testing. Even in a strongly-typed language like Go, overlooking unsafe string construction in a web framework can reintroduce classic injection flaws. This guide presents a concrete, side-by-side pattern to help you see both the vulnerability and the fix in a single Go/Gin sample.

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
	"database/sql"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	_ "modernc.org/sqlite"
)

func main() {
	// initialize in-memory database for demonstration
	db, err := sql.Open("sqlite", ":memory:")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// setup schema
	_, err = db.Exec("CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT)")
	if err != nil {
		log.Fatal(err)
	}
	_, err = db.Exec("INSERT INTO users (id, name) VALUES ('1','Alice'), ('2','Bob')")
	if err != nil {
		log.Fatal(err)
	}

	r := gin.Default()

	// Vulnerable endpoint: builds SQL with user input (injection prone)
	r.GET("/vulnerable", func(c *gin.Context) {
		id := c.Query("id")
		if id == "" {
			id = "1"
		}
		// vulnerable: direct string concatenation with user input
		row := db.QueryRow("SELECT name FROM users WHERE id = " + id)
		var name string
		if err := row.Scan(&name); err != nil {
			c.String(http.StatusInternalServerError, "error")
			return
		}
		c.String(http.StatusOK, "Name: "+name)
	})

	// Safe endpoint: parameterized query
	r.GET("/fixed", func(c *gin.Context) {
		id := c.Query("id")
		if id == "" {
			id = "1"
		}
		row := db.QueryRow("SELECT name FROM users WHERE id = ?", id)
		var name string
		if err := row.Scan(&name); err != nil {
			c.String(http.StatusInternalServerError, "error")
			return
		}
		c.String(http.StatusOK, "Name: "+name)
	})

	if err := r.Run(":8080"); err != nil {
		log.Fatal(err)
	}
}

CVE References

Choose which optional cookies to allow. You can change this any time.