VNX-GO-023: SQL injection via string concatenation
Detects SQL text where a value is concatenated in a literal position (after a quote or a comparison) and the statement is run by a database call or is a complete statement; other positions only when the value is request data. Bind-parameter numbers, converted or quoted values and self-built fragments are not reported.
| Field | Value |
|---|---|
| Rule ID | VNX-GO-023 |
| Severity | High |
| SARIF level | error |
| Kind | sast |
| Languages | Go |
| CWE | CWE-89 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1059.003 |
| CWSS vector | CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H |
| Tags | sql-injection database query |
| Source | internal/sast/rules/vnx-go-023.rego |
Overview
This rule flags instances where SQL queries are constructed by concatenating strings that may contain user input. This pattern can lead to SQL injection vulnerabilities because attackers can manipulate the input to change the query’s meaning and execute arbitrary SQL commands.
This maps to CWE-89: Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’).
Why This Matters
SQL injection occurs when user input is incorrectly allowed to influence SQL queries. Attackers can inject malicious SQL that can read, modify, or delete data, bypass authentication, or even execute administrative operations on the database server.
This is one of the most critical web application security risks and can lead to complete data breach, data manipulation, or loss of data integrity.
What Gets Flagged
The rule reports a value concatenated where SQL expects a literal (after a
quote or a comparison) in a statement that is run or is complete. The
fmt.Sprintf form of the same defect is VNX-GO-017’s.
// FLAGGED: a value after a comparison, run through a variable
query := "SELECT * FROM users WHERE id = " + userID
return db.QueryRow(query).Scan(&user)
// FLAGGED: a value inside quotes, inline in the call
rows, err := db.QueryContext(ctx, "SELECT * FROM products WHERE category = '"+category+"'")
// FLAGGED: ORDER BY from the request
sort := r.URL.Query().Get("sort")
rows, err := db.Query("SELECT id FROM items ORDER BY " + sort)
// NOT FLAGGED: bind parameters and self-built clauses
db.Query("SELECT id FROM t WHERE a = $" + strconv.Itoa(n), v)
q := "SELECT id FROM t" + " WHERE " + strings.Join(clauses, " AND ")
Remediation
-
Use parameterized queries (prepared statements): This is the most effective defense against SQL injection:
// SAFE: Use parameterized queries func getUser(db *sql.DB, userID string) error { query := "SELECT * FROM users WHERE id = $1" return db.QueryRow(query, userID).Scan(&user) } // SAFE: Named parameters with sqlx func searchProducts(db *sqlx.DB, category string, maxPrice float64) error { query := "SELECT * FROM products WHERE category = :category AND price < :maxPrice" _, err := db.NamedQuery(query, map[string]interface{}{ "category": category, "maxPrice": maxPrice, }) return err } -
Use query builders: Libraries like squirrel or gorp help build safe queries:
// SAFE: Using squirrel query builder func searchProducts(db *sql.DB, category string) error { query := sq.Select("*").From("products"). Where(sq.Eq{"category": category}). Where(sq.Lt{"price": maxPrice}) // ... execute query } -
Use ORMs with built-in protection: Frameworks like GORM automatically handle parameterization:
// SAFE: Using GORM func getUser(db *gorm.DB, userID string) error { var user User return db.Where("id = ?", userID).First(&user).Error } -
Input validation as defense-in-depth: While not a primary defense, validate input matches expected patterns:
// Additional validation (use with parameterized queries) if !isNumeric(userID) { return errors.New("invalid user ID") } // Then use parameterized query
References
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’)
- OWASP Application Security Verification Standard v4.0 – V5.3 Database Security
- OWASP SQL Injection Prevention Cheat Sheet
- Go database/sql package documentation
- Squirrel query builder
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-go-023
// nosec vnx-go-023
Or record an ignore rule: vulnetix ignore add --rule vnx-go-023 --reason "...". See Suppressing findings.