Injection

Injection Guide for Go (Gin) [Sep 2026] [CVE-2026-31654]

[Updated September 2026] Updated CVE-2026-31654

Overview

Injection vulnerabilities in Go with Gin can allow attackers to alter queries or commands by manipulating user-supplied input. If an application builds SQL from user input without parameter binding, an attacker could retrieve or modify data, bypass authentication, or cause data loss. In worst cases, database compromises can pivot to other services and lead to full system exposure. This guide covers the root causes, impact, and fixes for this class of issues. In Gin-based services, the problem often appears when handlers read query parameters, body fields, or route parameters and interpolate them into SQL, shell commands, or template rendering without proper escaping or binding. Go's database/sql requires placeholders and parameter binding; failing to use them leaves the query vulnerable to injection through the input value. Impact examples for SQL injection include unauthorized data access, data modification, or deletion; in some configurations, attacker could escalate privileges or run arbitrary SQL statements. Other injection variants can lead to OS command execution if user input is used to build system commands. Treat any user-supplied data as untrusted. Remediation approach: adopt parameterized queries, validate and bind inputs using Gin's binding, use placeholders appropriate to your driver, prefer prepared statements, limit database permissions, and log safely. Add tests and run static analysis to prevent regressions.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

  "github.com/gin-gonic/gin"
  _ "github.com/go-sql-driver/mysql"
)

// Vulnerable pattern: string concatenation using user input
func vulnerableHandler(db *sql.DB) gin.HandlerFunc {
  return func(c *gin.Context) {
    id := c.Query("id")
    // Vulnerable: direct string concatenation of user input into SQL
    query := `SELECT id, username FROM users WHERE id = ` + id
    rows, err := db.Query(query)
    if err != nil {
      log.Printf(`query error: %v`, err)
      c.Status(http.StatusInternalServerError)
      return
    }
    defer rows.Close()
    c.String(http.StatusOK, `vulnerable`)
  }
}

// Fixed pattern: parameterized queries using placeholders
func safeHandler(db *sql.DB) gin.HandlerFunc {
  return func(c *gin.Context) {
    id := c.Query("id")
    // Safe: parameterized query using placeholders
    // MySQL/SQLite style
    rows, err := db.Query(`SELECT id, username FROM users WHERE id = ?`, id)
    if err != nil {
      log.Printf(`query error: %v`, err)
      c.Status(http.StatusInternalServerError)
      return
    }
    defer rows.Close()
    c.String(http.StatusOK, `safe`)
  }
}

// Note: If using PostgreSQL, you would use $1 instead of ?:
// rows, err := db.Query(`SELECT id, username FROM users WHERE id = $1`, id)

func main() {
  router := gin.Default()
  // db should be initialized with a proper DSN
  var db *sql.DB
  router.GET("/vuln", vulnerableHandler(db))
  router.GET("/safe", safeHandler(db))
  router.Run(":8080")
}

CVE References

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