Security of Go Applications: Top 10 Vulnerabilities
Go (Golang) is a compiled, statically typed language created at Google. Thanks to built-in concurrency support, high performance, and a simple memory model, it has become popular for building microservices, API gateways, cloud applications, and DevOps tools. However, like any other language, Go is not immune to security errors. Developers often rely on "security by default," which leads to serious vulnerabilities. In this article, we will break down the top 10 most critical security issues in Go applications and show how to avoid them.
1. SQL Injection
Despite Go providing powerful database drivers (database/sql), developers often make the classic mistake of string concatenation when forming queries.
Vulnerable code:
package main
import ( "database/sql" "fmt" _ "github.com/lib/pq")
func getUser(db *sql.DB, username string) { // Never do this! query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username) rows, _ := db.Query(query) // ...}Solution: Use parameterized queries (prepared statements).
func getUserSafe(db *sql.DB, username string) { query := "SELECT * FROM users WHERE username = $1" rows, err := db.Query(query, username) if err != nil { // Handle error } // ...}Parameterization automatically escapes special characters, making injection impossible.
2. Cross-Site Scripting (XSS)
Go is often used for the server side of web applications. If you return user input without escaping, you open the door to XSS attacks.
Vulnerable code (using fmt.Fprintf):
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") fmt.Fprintf(w, "<h1>Hello, %s!</h1>", name) // XSS!})Solution: Use the html/template package for automatic escaping.
import "html/template"
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { tmpl := template.Must(template.New("index").Parse("<h1>Hello, {{.}}!</h1>")) name := r.URL.Query().Get("name") tmpl.Execute(w, name) // Safe})The html/template engine automatically escapes HTML, JavaScript, and CSS contexts.
3. Race Conditions
Go is famous for its lightweight concurrency (goroutines and channels). However, incorrect use of shared resources (e.g., maps or counters) without synchronization leads to data races.
Vulnerable code:
var counter int
func increment() { for i := 0; i < 1000; i++ { counter++ // Not atomic! }}
func main() { go increment() go increment()}Solution: Use mutexes (sync.Mutex) or atomic operations (sync/atomic).
import "sync"
var ( counter int mu sync.Mutex)
func increment() { for i := 0; i < 1000; i++ { mu.Lock() counter++ mu.Unlock() }}Always run tests with the -race flag: go test -race.
4. Memory & Goroutine Leaks
One of the most insidious vulnerabilities in Go is "forgotten" goroutines. If a goroutine blocks forever (e.g., waiting for data from a channel that will never close), it leads to memory leaks and application failure.
Example of a leak: