CVE-2023-33967 (Go)

EaseProbe's MySQL and PostgreSQL data-checking probes build SQL statements from configured data keys whose components identify the database, table, selected field, lookup key, and expected value. The identifier components of those data keys are attacker- or operator-controlled configuration input, but they are concatenated directly into SQL query strings and, for PostgreSQL, also influence the selected database connection. An attacker who can supply or modify these data-checking entries can inject identifier-closing characters or SQL syntax so that the generated query no longer treats the input as identifiers, potentially altering the health-check query, bypassing checks, reading unintended data, or executing destructive SQL depending on database behavior and permissions.

Weakness:
CWE-89: name: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.

Repository: https://github.com/megaease/easeprobe

Vulnerable code:
file_path: probe/client/mysql/mysql.go
snippet: func (r *MySQL) getSQL(str string) (string, error) {
	if len(strings.TrimSpace(str)) == 0 {
		return "", fmt.Errorf("Empty SQL data")
	}
	fields := strings.Split(str, ":")
	if len(fields) != 5 {
		return "", fmt.Errorf("Invalid SQL data - [%s]. (syntax: database:table:field:key:value)", str)
	}
	db := fields[0]
	table := fields[1]
	field := fields[2]
	key := fields[3]
	value := fields[4]
	//check value is int or not
	if _, err := strconv.Atoi(value); err != nil {
		return "", fmt.Errorf("Invalid SQL data - [%s], the value must be int", str)
	}

	sql := fmt.Sprintf("SELECT %s FROM %s.%s WHERE %s = %s", field, db, table, key, value)
	return sql, nil
}

Fixed code:
file_path: probe/client/mysql/mysql.go
snippet: func (r *MySQL) getSQL(str string) (string, error) {
	if len(strings.TrimSpace(str)) == 0 {
		return "", fmt.Errorf("Empty SQL data")
	}
	fields := strings.Split(str, ":")
	if len(fields) != 5 {
		return "", fmt.Errorf("Invalid SQL data - [%s]. (syntax: database:table:field:key:value)", str)
	}
	db := global.EscapeQuote(fields[0])
	table := global.EscapeQuote(fields[1])
	field := global.EscapeQuote(fields[2])
	key := global.EscapeQuote(fields[3])
	value := global.EscapeQuote(fields[4])
	//check value is int or not
	if _, err := strconv.Atoi(value); err != nil {
		return "", fmt.Errorf("Invalid SQL data - [%s], the value must be int", str)
	}

	sql := fmt.Sprintf("SELECT `%s` FROM `%s`.`%s` WHERE `%s` = %s", field, db, table, key, value)
	return sql, nil
}

file_path: global/global.go
snippet: 
// EscapeQuote escape the string the single quote, double quote, and backtick
func EscapeQuote(str string) string {
	type Escape struct {
		From string
		To   string
	}
	escape := []Escape{
		{From: "`", To: ""}, // remove the backtick
		{From: `\`, To: `\\`},
		{From: `'`, To: `\'`},
		{From: `"`, To: `\"`},
	}

	for _, e := range escape {
		str = strings.ReplaceAll(str, e.From, e.To)
	}
	return str
}