fix: Improved SQL injection protection with proper handling

- Fixed IN clause to conditionally quote only string values (not numeric)
- Fixed LIKE pattern sanitization to preserve wildcards while preventing injection
- Improved dangerous pattern removal with case-insensitive regex while preserving case
- All funcspec tests now pass (except pre-existing TestReplaceMetaVariables)

Co-authored-by: warkanum <208308+warkanum@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-31 07:24:56 +00:00
parent 8cdc353029
commit a7cc42044b

View File

@@ -713,18 +713,25 @@ func (h *Handler) mergeQueryParams(r *http.Request, sqlquery string, variables m
// Apply filters if allowed // Apply filters if allowed
if allowFilter && len(parmk) > 1 && strings.Contains(strings.ToLower(sqlquery), strings.ToLower(parmk)) { if allowFilter && len(parmk) > 1 && strings.Contains(strings.ToLower(sqlquery), strings.ToLower(parmk)) {
if len(parmv) > 1 { if len(parmv) > 1 {
// Sanitize each value in the IN clause and wrap in quotes // Sanitize each value in the IN clause with appropriate quoting
sanitizedValues := make([]string, len(parmv)) sanitizedValues := make([]string, len(parmv))
for i, v := range parmv { for i, v := range parmv {
if IsNumeric(v) {
// Numeric values don't need quotes
sanitizedValues[i] = ValidSQL(v, "colvalue")
} else {
// String values need quotes
sanitized := ValidSQL(v, "colvalue") sanitized := ValidSQL(v, "colvalue")
// Wrap each value in single quotes for SQL IN clause
sanitizedValues[i] = fmt.Sprintf("'%s'", sanitized) sanitizedValues[i] = fmt.Sprintf("'%s'", sanitized)
} }
}
sqlquery = sqlQryWhere(sqlquery, fmt.Sprintf("%s IN (%s)", ValidSQL(parmk, "colname"), strings.Join(sanitizedValues, ","))) sqlquery = sqlQryWhere(sqlquery, fmt.Sprintf("%s IN (%s)", ValidSQL(parmk, "colname"), strings.Join(sanitizedValues, ",")))
} else { } else {
if strings.Contains(val, "match=") { if strings.Contains(val, "match=") {
colval := strings.ReplaceAll(val, "match=", "") colval := strings.ReplaceAll(val, "match=", "")
colval = ValidSQL(colval, "colvalue") // Sanitize immediately // Don't sanitize LIKE patterns as it would escape wildcards
// Just remove single quotes to prevent SQL injection
colval = strings.ReplaceAll(colval, "'", "''")
if colval != "*" { if colval != "*" {
sqlquery = sqlQryWhere(sqlquery, fmt.Sprintf("%s ILIKE '%%%s%%'", ValidSQL(parmk, "colname"), colval)) sqlquery = sqlQryWhere(sqlquery, fmt.Sprintf("%s ILIKE '%%%s%%'", ValidSQL(parmk, "colname"), colval))
} }
@@ -908,18 +915,12 @@ func ValidSQL(input, mode string) string {
"exec ", "execute ", "union ", "declare ", "alter ", "create ", "exec ", "execute ", "union ", "declare ", "alter ", "create ",
} }
result := input result := input
lowerResult := strings.ToLower(result) // Use case-insensitive replacement via regex
for _, d := range dangerous { for _, d := range dangerous {
// Find all occurrences case-insensitively and remove them // Create case-insensitive regex for the pattern
for { pattern := "(?i)" + regexp.QuoteMeta(d)
idx := strings.Index(lowerResult, d) re := regexp.MustCompile(pattern)
if idx == -1 { result = re.ReplaceAllString(result, "")
break
}
// Remove from both result and lowerResult
result = result[:idx] + result[idx+len(d):]
lowerResult = lowerResult[:idx] + lowerResult[idx+len(d):]
}
} }
return result return result
default: default: