Command Injection in Echo Go
How Command Injection Manifests in Echo Go
Command injection in Echo Go typically occurs when user-controlled input is passed directly to system commands without proper sanitization. In Echo Go applications, this vulnerability often appears in handler functions that execute shell commands, interact with the operating system, or invoke external processes.
A common Echo Go pattern that leads to command injection involves using os/exec package functions with unvalidated input. For example:
func getFileHandler(c echo.Context) error {
filename := c.QueryParam("file")
cmd := exec.Command("cat", "/var/www/files/"+filename)
output, err := cmd.Output()
if err != nil {
return err
}
return c.String(http.StatusOK, string(output))
}This handler is vulnerable because an attacker can manipulate the file parameter to execute arbitrary commands. By providing input like file=example.txt; rm -rf /, the command becomes cat /var/www/files/example.txt; rm -rf /, executing both commands sequentially.
Another Echo Go-specific manifestation occurs with template rendering and dynamic command construction. Consider this pattern:
func processTemplate(c echo.Context) error {
data := c.QueryParam("data")
template := template.Must(template.New("cmd").Parse(
"echo '{{.Data}}' > /tmp/output.txt",
))
var buf bytes.Buffer
template.Execute(&buf, struct{ Data string }{Data: data})
cmd := exec.Command("bash", "-c", buf.String())
return cmd.Run()
}Here, template injection combined with command execution creates a dangerous attack vector. An attacker can inject shell syntax through the data parameter, which gets executed when the template renders and the command runs.
Echo Go's middleware chain can also introduce command injection risks when user input flows through multiple handlers. For instance, a middleware that logs request data might inadvertently execute commands:
func logMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
logData := c.QueryParam("log")
cmd := exec.Command("echo", logData)
cmd.Run()
return next(c)
}
}While this specific example is benign, a malicious middleware could execute system commands based on request parameters, creating a widespread vulnerability across all endpoints.
Echo Go's context handling can also contribute to command injection when context values are used in system commands. For example:
func processUpload(c echo.Context) error {
file, err := c.FormFile("file")
if err != nil {
return err
}
// Vulnerable: user-controlled filename in system command
cmd := exec.Command("file", file.Filename)
output, _ := cmd.Output()
return c.String(http.StatusOK, string(output))
}The filename from the uploaded file is directly passed to the file command without validation, allowing potential command injection through crafted filenames.
Echo Go-Specific Detection
Detecting command injection in Echo Go applications requires both static code analysis and dynamic testing. Static analysis involves examining code for dangerous patterns where user input reaches system command execution.
Using middleBrick to scan Echo Go APIs provides comprehensive detection of command injection vulnerabilities. The scanner examines your API endpoints for patterns like:
$ middlebrick scan https://api.example.com --output json
{
"risk_score": 65,
"category_breakdown": {
"input_validation": 80,
"command_injection": 90,
"data_exposure": 45
},
"findings": [
{
"severity": "high",
"category": "Command Injection",
"endpoint": "/api/process",
"description": "User input from 'file' parameter reaches exec.Command without validation",
"remediation": "Validate and sanitize all user inputs before command execution"
}
]
}middleBrick's black-box scanning approach tests your Echo Go API endpoints by sending malicious payloads to identify command injection vulnerabilities without requiring source code access. The scanner sends payloads like $(whoami), || id, and ; to test if commands execute.
For Echo Go applications, middleBrick specifically checks for:
- Direct use of
os/exec.Commandwith query parameters, form data, or headers - Template rendering that incorporates user input into command strings
- Middleware that processes request data and executes system commands
- Context values used in command construction without validation
- Unsafe use of backticks, semicolons, and pipe characters in user input
- Path traversal combined with command execution
Dynamic testing in Echo Go should include sending test payloads to endpoints that might execute commands. For example, testing the vulnerable handler from earlier:
package main
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/labstack/echo/v4"
)
func TestCommandInjection(t *testing.T) {
e := echo.New()
// Register vulnerable endpoint
e.GET("/getFile", getFileHandler)
// Test with command injection payload
req := httptest.NewRequest(http.MethodGet, "/getFile?file=example.txt; whoami", nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
// Check if command injection succeeded
assert.NotContains(t, rec.Body.String(), "whoami")
}This test would fail if the endpoint is vulnerable, as the whoami command output would appear in the response.
Echo Go's logging can also help detect command injection attempts. Configure Echo to log suspicious patterns:
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "${time_rfc3339} ${status} ${method} ${uri} ${latency_human} ${error}\n",
Output: io.MultiWriter(
os.Stdout,
&commandInjectionLogger{},
),
}))
type commandInjectionLogger struct{}
func (c *commandInjectionLogger) Write(p []byte) (n int, err error) {
logEntry := string(p)
// Check for suspicious patterns
if strings.Contains(logEntry, ";") || strings.Contains(logEntry, "|") ||
strings.Contains(logEntry, "$") || strings.Contains(logEntry, "&") {
log.Printf("SUSPICIOUS: possible command injection attempt: %s", logEntry)
}
return os.Stdout.Write(p)
}This logging middleware flags requests containing characters commonly used in command injection attacks.
Echo Go-Specific Remediation
Remediating command injection in Echo Go requires a defense-in-depth approach. The primary strategy is to eliminate command execution with user input entirely, but when system commands are necessary, implement strict validation and use safe APIs.
The safest approach is to avoid shell command execution altogether. Instead of using exec.Command with shell interpretation, use the argument array form:
// Vulnerable - shell interpretation enabled
cmd := exec.Command("bash", "-c", "cat /var/www/files/"+filename)
// Secure - no shell interpretation
cmd := exec.Command("cat", "/var/www/files/"+filename)
// Even better - use Go libraries instead of shell commands
content, err := os.ReadFile(filepath.Join("/var/www/files", filename))
When you must execute system commands, validate input against a strict whitelist. For file operations, validate against allowed patterns:
func validateFilename(filename string) (string, error) {
// Only allow alphanumeric, hyphens, underscores, and single dots
if matched, _ := regexp.MatchString(`^[a-zA-Z0-9._-]+$`, filename); !matched {
return "", fmt.Errorf("invalid filename")
}
// Prevent directory traversal
if strings.Contains(filename, "..") {
return "", fmt.Errorf("directory traversal detected")
}
return filename, nil
}
func getFileHandler(c echo.Context) error {
filename := c.QueryParam("file")
validated, err := validateFilename(filename)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid filename",
})
}
cmd := exec.Command("cat", "/var/www/files/"+validated)
output, err := cmd.Output()
if err != nil {
return err
}
return c.String(http.StatusOK, string(output))
}For Echo Go applications that need to execute multiple commands, use argument arrays instead of shell interpretation:
// Vulnerable - shell interpretation
cmd := exec.Command("bash", "-c", "ls -la /var/www && grep -i error /var/log/app.log")
// Secure - argument array
cmd := exec.Command("sh", "-c", "ls -la /var/www && grep -i error /var/log/app.log")
// Better - separate commands
lsCmd := exec.Command("ls", "-la", "/var/www")
grepCmd := exec.Command("grep", "-i", "error", "/var/log/app.log")
Echo Go's middleware can help centralize command injection protection. Create a validation middleware:
func commandInjectionProtection(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Check query parameters
for _, v := range c.QueryParams() {
if containsSuspiciousPatterns(v) {
return echo.NewHTTPError(http.StatusBadRequest, "invalid input detected")
}
}
// Check form data
if c.Request().MultipartForm != nil {
for _, v := range c.Request().MultipartForm.Value {
for _, value := range v {
if containsSuspiciousPatterns(value) {
return echo.NewHTTPError(http.StatusBadRequest, "invalid input detected")
}
}
}
}
return next(c)
}
}
func containsSuspiciousPatterns(input string) bool {
suspicious := []string{";", "|", "&", "$(", "`", "&&", "||", "//"}
for _, pattern := range suspicious {
if strings.Contains(input, pattern) {
return true
}
}
return false
}
// Use the middleware globally
e := echo.New()
e.Use(commandInjectionProtection)For applications that must execute user-specified commands, implement a command whitelist:
var allowedCommands = map[string]bool{
"ls": true,
"cat": true,
"grep": true,
"wc": true,
}
func executeCommand(c echo.Context) error {
command := c.QueryParam("cmd")
args := c.QueryParams()["arg"]
if !allowedCommands[command] {
return echo.NewHTTPError(http.StatusBadRequest, "command not allowed")
}
cmd := exec.Command(command, args...)
output, err := cmd.Output()
if err != nil {
return err
}
return c.JSON(http.StatusOK, map[string]string{
"output": string(output),
})
}Echo Go's error handling can also help mitigate command injection by preventing information disclosure. Configure Echo to return generic error messages:
e.HTTPErrorHandler = func(err error, c echo.Context) {
if he, ok := err.(*echo.HTTPError); ok {
c.JSON(he.Code, map[string]string{
"error": "An error occurred",
})
} else {
c.JSON(http.StatusInternalServerError, map[string]string{
"error": "Internal server error",
})
}
}Finally, implement comprehensive logging to detect and respond to command injection attempts:
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "${time_rfc3339} ${status} ${method} ${uri} ${latency_human}\n",
}))
e.Logger.SetLevel(log.DEBUG)
e.Logger.Debug("Command injection protection enabled")
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |