PS Product SecurityKnowledge Base

๐Ÿช™ Golang Vulnerability Examples and Fixes

Use this page when: reviewing Go APIs, platform services, operators, or tooling where developers assume that โ€œcompiled and simpleโ€ automatically means safe.

How to read these examples

  • Vulnerable snippet shows the unsafe habit.
  • Safer pattern shows the direction you want in production code.
  • Why it matters ties the defect to attacker value and business impact.
  • Review cue is phrased so it can become a pull-request comment or checklist item.

Example 1 โ€” SQL injection in database/sql

Vulnerable snippet

id := r.URL.Query().Get("id")
rows, err := db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %s", id))
if err != nil { return err }

Safer pattern

id := r.URL.Query().Get("id")
rows, err := db.Query("SELECT * FROM users WHERE id = ?", id)
if err != nil { return err }

Why it matters

  • Formatting SQL strings before sending them to the database merges attacker input into the query itself.

Business impact

  • Data disclosure, auth bypass, destructive queries, and higher blast radius when service accounts are over-privileged.

Review cue

  • With database/sql, use placeholders and arguments. Ban fmt.Sprintf for SQL construction.

Example 2 โ€” Path traversal in file download handler

Vulnerable snippet

name := r.URL.Query().Get("name")
path := filepath.Join("/srv/public", name)
http.ServeFile(w, r, path)

Safer pattern

name := r.URL.Query().Get("name")
base := "/srv/public"
clean := filepath.Clean("/" + name)
path := filepath.Join(base, clean)
absBase, _ := filepath.Abs(base)
absPath, _ := filepath.Abs(path)
if !strings.HasPrefix(absPath, absBase+string(os.PathSeparator)) {
    http.Error(w, "invalid path", http.StatusBadRequest)
    return
}
http.ServeFile(w, r, absPath)

Why it matters

  • filepath.Join alone is not an authorization boundary; attackers can still reach parent directories if you never prove the final location.

Business impact

  • Exposure of env files, credentials, kubeconfigs, tokens, or source code from the host.

Review cue

  • After cleaning and resolving, assert the final path remains under the expected base directory.

Example 3 โ€” SSRF through arbitrary outbound requests

Vulnerable snippet

rawURL := r.URL.Query().Get("url")
resp, err := http.Get(rawURL)
if err != nil { return }
defer resp.Body.Close()

Safer pattern

rawURL := r.URL.Query().Get("url")
u, err := url.Parse(rawURL)
if err != nil || u.Scheme != "https" {
    http.Error(w, "bad url", http.StatusBadRequest)
    return
}
allowed := map[string]bool{"status.example.com": true, "cdn.example.com": true}
if !allowed[u.Hostname()] {
    http.Error(w, "destination not allowed", http.StatusForbidden)
    return
}
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(u.String())
if err != nil { return }
defer resp.Body.Close()

Why it matters

  • Server-side fetches inherit the network position and credentials of the service, not of the user who supplied the URL.

Business impact

  • Cloud metadata exposure, internal API access, reconnaissance, and unexpected use of trusted outbound integrations.

Review cue

  • Every feature that fetches a URL must define which hosts are allowed and which network ranges are forbidden.

Example 4 โ€” Command injection with sh -c

Vulnerable snippet

archive := r.FormValue("archive")
cmd := exec.Command("sh", "-c", "tar -xf "+archive+" -C /srv/import")
_ = cmd.Run()

Safer pattern

archive := r.FormValue("archive")
if strings.Contains(archive, "..") || strings.HasPrefix(archive, "-") {
    http.Error(w, "bad archive name", http.StatusBadRequest)
    return
}
cmd := exec.Command("tar", "-xf", archive, "-C", "/srv/import")
_ = cmd.Run()

Why it matters

  • sh -c delegates parsing to a shell, so attacker input can become additional commands or flags.

Business impact

  • RCE on build/import workers, tampered artifacts, host compromise, and supply-chain evidence corruption.

Review cue

  • Avoid sh -c for request-derived data. Pass fixed commands and validated arguments directly.

Example 5 โ€” Broken object-level authorization in handler logic

Vulnerable snippet

invoiceID := chi.URLParam(r, "invoiceID")
row := db.QueryRow("SELECT id, total FROM invoices WHERE id = ?", invoiceID)
// render invoice to currently authenticated user

Safer pattern

invoiceID := chi.URLParam(r, "invoiceID")
userID := auth.UserIDFromContext(r.Context())
row := db.QueryRow(
    "SELECT id, total FROM invoices WHERE id = ? AND owner_user_id = ?",
    invoiceID,
    userID,
)
// if no row, return 404/403

Why it matters

  • Authenticated does not mean authorized. Object IDs without owner/tenant scope create a direct cross-account data path.

Business impact

  • Data leakage, support incidents, regulatory exposure, and high-severity customer trust failures.

Review cue

  • Authorization must be checked where the record is fetched, not where a UI route decides what to show.

Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.