Go Application Security: Top 10 Vulnerabilities for Beginners
Go (Golang) is renowned for its performance and built-in security mechanisms, such as strong typing and automatic memory management. However, like any other language, it does not protect the developer from logical errors, improper input handling, or insecure configuration. In this article, we will break down the top 10 vulnerabilities that beginner Go developers most often encounter and show how to avoid them.
1. SQL Injection
The most common web application vulnerability. It occurs when user input is directly concatenated into an SQL query.
Dangerous code:
username := r.URL.Query().Get("username")query := fmt.Sprintf("SELECT * FROM users WHERE name='%s'", username)rows, err := db.Query(query)Safe code (using parameterized queries):
username := r.URL.Query().Get("username")query := "SELECT * FROM users WHERE name=$1"rows, err := db.Query(query, username)Tip: Always use placeholders ($1, ?, or @p1) depending on your database. Never build queries using fmt.Sprintf with user data.
2. Cross-Site Scripting (XSS)
This vulnerability occurs when an application displays unvalidated user input in HTML code. In Go, this often happens when using html/template without escaping.
Dangerous code:
tmpl := template.Must(template.New("page").Parse("<h1>{{.}}</h1>"))tmpl.Execute(w, r.URL.Query().Get("name")) // <script>alert('xss')</script>The built-in html/template package automatically escapes output, so in the standard case you are protected. But be careful with the template.HTML function — it disables escaping.
Safe approach: Use text/template only for text data, and for the web use html/template. Avoid using template.HTML unless absolutely necessary.
3. Race Conditions
Go actively uses goroutines, and if multiple goroutines simultaneously read and write to the same variable without synchronization, a data race occurs. This can lead to incorrect application state and data leaks.
Dangerous code:
var counter intfor i := 0; i < 1000; i++ { go func() { counter++ // Unsafe! }()}Safe code (using a mutex):
var ( counter int mu sync.Mutex)for i := 0; i < 1000; i++ { go func() { mu.Lock() counter++ mu.Unlock() }()}Tip: Always use sync.Mutex, sync.RWMutex, or atomic operations (sync/atomic) when working with shared data. Run tests with the -race flag to detect races.
4. Path Traversal
This vulnerability allows an attacker to read files outside the permitted directory using ../ characters.
Dangerous code:
filename := r.URL.Query().Get("file")data, _ := os.ReadFile("/data/" + filename) // /data/../../etc/passwdSafe code (path sanitization):
filename := r.URL.Query().Get("file")cleanPath := filepath.Clean("/data/" + filename)if !strings.HasPrefix(cleanPath, "/data/") { http.Error(w, "Invalid path", http.StatusBadRequest) return}data, _ := os.ReadFile(cleanPath)Tip: Use