HIGH sql injectionecho go

Sql Injection in Echo Go

How Sql Injection Manifests in Echo Go

SQL injection in Echo Go applications typically occurs when user input is directly concatenated into SQL queries without proper sanitization. Echo Go's simplicity and rapid development capabilities can inadvertently lead developers to write vulnerable database code, especially when handling HTTP request parameters.

The most common vulnerability pattern in Echo Go involves using echo.Context parameters directly in query construction. Consider this vulnerable pattern:

func getUser(c echo.Context) error {
    id := c.Param("id")
    query := fmt.Sprintf("SELECT * FROM users WHERE id = %s", id)
    
    row, err := db.Query(query)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
    }
    defer row.Close()
    
    var user User
    if row.Next() {
        err = row.Scan(&user.ID, &user.Name, &user.Email)
        if err != nil {
            return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
        }
        return c.JSON(http.StatusOK, user)
    }
    return c.JSON(http.StatusNotFound, map[string]string{"error": "User not found"})
}

An attacker can exploit this by sending requests like /user/1 OR 1=1, which would return all users in the database. More sophisticated attacks might use /user/1; DROP TABLE users; to delete entire tables.

Echo Go's parameter binding can also introduce vulnerabilities when developers use c.Bind() without validation:

type UserFilter struct {
    Role string `json:"role"`
    Status string `json:"status"`
}

func getUsers(c echo.Context) error {
    var filter UserFilter
    if err := c.Bind(&filter); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid input"})
    }
    
    query := fmt.Sprintf("SELECT * FROM users WHERE role = '%s' AND status = '%s'", filter.Role, filter.Status)
    
    rows, err := db.Query(query)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
    }
    defer rows.Close()
    
    var users []User
    for rows.Next() {
        var user User
        err = rows.Scan(&user.ID, &user.Name, &user.Email)
        if err != nil {
            return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
        }
        users = append(users, user)
    }
    return c.JSON(http.StatusOK, users)
}

Without proper input validation, an attacker could send { "role": "admin' OR '1'='1", "status": "anything" } to bypass authentication or access unauthorized data.

Echo Go-Specific Detection

Detecting SQL injection in Echo Go applications requires both static code analysis and dynamic runtime testing. middleBrick's scanner specifically targets Echo Go patterns by examining HTTP endpoints for vulnerable database query construction.

middleBrick identifies SQL injection vulnerabilities by:

  • Scanning Echo Go route handlers for direct string concatenation in SQL queries
  • Testing parameterized inputs with SQL metacharacters (', ", ;, --, /* */)
  • Analyzing the application's response patterns to determine if queries are being executed
  • Checking for Echo Go's c.Param(), c.QueryParam(), and c.Bind() usage without proper sanitization

Here's how middleBrick detects SQL injection in an Echo Go application:

# Install middleBrick CLI
npm install -g middlebrick

# Scan an Echo Go API endpoint
middlebrick scan https://api.example.com/users/123

# Scan with JSON output for integration
middlebrick scan https://api.example.com/users/123 --format=json

The scanner tests for SQL injection by sending payloads like:

# Basic injection test
GET /users/1' OR '1'='1

# Union-based injection
GET /users/1' UNION SELECT username, password FROM users--

# Time-based injection (if database supports it)
GET /users/1' WAITFOR DELAY '0:0:5'--

middleBrick analyzes the HTTP responses to determine if the injection was successful. A successful SQL injection might return different response codes, altered response bodies, or execution time delays that indicate database interaction.

For Echo Go applications using GORM or other ORMs, middleBrick also checks for unsafe raw query usage:

// Vulnerable GORM usage
func getUsersByRole(c echo.Context) error {
    role := c.QueryParam("role")
    var users []User
    
    // This is vulnerable if role contains malicious input
    db.Raw("SELECT * FROM users WHERE role = ?", role).Scan(&users)
    
    return c.JSON(http.StatusOK, users)
}

middleBrick flags this pattern and tests whether the ORM's parameterization is being bypassed or if the query construction is unsafe.

Echo Go-Specific Remediation

Remediating SQL injection in Echo Go requires adopting parameterized queries and input validation. Echo Go's ecosystem provides several approaches to secure database interactions.

The most effective remediation is using prepared statements with proper parameter binding. Here's a secure version of the vulnerable user retrieval function:

func getUser(c echo.Context) error {
    id := c.Param("id")
    
    // Validate that id is a valid integer
    if _, err := strconv.Atoi(id); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid user ID"})
    }
    
    // Use prepared statement with parameter binding
    stmt, err := db.Prepare("SELECT id, name, email FROM users WHERE id = ?")
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Database error"})
    }
    defer stmt.Close()
    
    row := stmt.QueryRow(id)
    
    var user User
    if err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {
        if err == sql.ErrNoRows {
            return c.JSON(http.StatusNotFound, map[string]string{"error": "User not found"})
        }
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Database error"})
    }
    
    return c.JSON(http.StatusOK, user)
}

For Echo Go applications using GORM, the secure pattern is:

func getUsersByRole(c echo.Context) error {
    role := c.QueryParam("role")
    
    // Validate role against allowed values
    allowedRoles := map[string]bool{"admin": true, "user": true, "guest": true}
    if !allowedRoles[role] {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid role"})
    }
    
    var users []User
    
    // Use GORM's safe query builder
    result := db.Where("role = ?", role).Find(&users)
    if result.Error != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Database error"})
    }
    
    return c.JSON(http.StatusOK, users)
}

Echo Go developers should also implement input validation middleware. Here's a reusable validation middleware:

func validateInput(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        // Example: validate numeric parameters
        if param := c.Param("id"); param != "" {
            if _, err := strconv.Atoi(param); err != nil {
                return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid ID format"})
            }
        }
        
        // Example: validate query parameters
        if role := c.QueryParam("role"); role != "" {
            allowedRoles := map[string]bool{"admin": true, "user": true}
            if !allowedRoles[role] {
                return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid role"})
            }
        }
        
        return next(c)
    }
}

// Usage in Echo Go application
e := echo.New()
e.Use(validateInput)

For complex applications, consider using Echo Go's validation tags with struct binding:

type UserFilter struct {
    Role string `json:"role" validate:"oneof=admin user guest"`
    Status string `json:"status" validate:"oneof=active inactive"`
}

func getUsers(c echo.Context) error {
    var filter UserFilter
    if err := c.Bind(&filter); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid input format"})
    }
    
    // Validate using go-playground/validator
    if err := validate.Struct(filter); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
    }
    
    // Safe query using GORM
    var users []User
    db.Where("role = ? AND status = ?", filter.Role, filter.Status).Find(&users)
    
    return c.JSON(http.StatusOK, users)
}

Related CWEs: inputValidation

CWE IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

How can I test if my Echo Go API is vulnerable to SQL injection?
Use middleBrick's scanner to test your Echo Go endpoints. The scanner automatically sends SQL injection payloads to your API and analyzes responses for vulnerability indicators. You can also manually test by sending requests with single quotes, SQL keywords, and special characters to see if you get database errors or unexpected results.
Does Echo Go provide built-in protection against SQL injection?
Echo Go itself doesn't provide SQL injection protection - it's a web framework focused on routing and middleware. Protection comes from how you interact with databases. Use parameterized queries, prepared statements, and ORMs like GORM that properly escape inputs. Echo Go's middleware system can help implement input validation across your application.