CVE-2023-30625 (Go)

rudder-server is part of RudderStack, an open source Customer Data Platform (CDP). Versions of rudder-server prior to 1.3.0-rc.1 are vulnerable to SQL injection. This issue may lead to Remote Code Execution (RCE) due to the `rudder` role in PostgresSQL having superuser permissions by default. Version 1.3.0-rc.1 contains patches for this issue.

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/rudderlabs/rudder-server

Vulnerable code:
file_path: router/failed-events-manager.go
snippet: func (fem *FailedEventsManagerT) DropFailedRecordIDs(taskRunID string) {
	if !failedKeysEnabled {
		return
	}

	// Drop table
	table := fmt.Sprintf(`%s_%s`, failedKeysTablePrefix, taskRunID)
	sqlStatement := fmt.Sprintf(`DROP TABLE IF EXISTS %s`, table)
	_, err := fem.dbHandle.Exec(sqlStatement)
	if err != nil {
		pkgLogger.Errorf("Failed to drop table %s with error: %v", taskRunID, err)
	}
}

Fixed code:
file_path: router/failed-events-manager.go
snippet: func (*FailedEventsManagerT) SaveFailedRecordIDs(taskRunIDFailedEventsMap map[string][]*FailedEventRowT, txn *sql.Tx) {
	if !failedKeysEnabled {
		return
	}

	for taskRunID, failedEvents := range taskRunIDFailedEventsMap {
		table := getSqlSafeTablename(taskRunID)
		sqlStatement := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s (
		destination_id TEXT NOT NULL,
		record_id JSONB NOT NULL,
		created_at TIMESTAMP NOT NULL);`, table)
		_, err := txn.Exec(sqlStatement)
		if err != nil {
			_ = txn.Rollback()
			panic(err)
		}
		insertQuery := fmt.Sprintf(`INSERT INTO %s VALUES($1, $2, $3);`, table)
		stmt, err := txn.Prepare(insertQuery)
		if err != nil {
			_ = txn.Rollback()
			panic(err)
		}
		createdAt := time.Now()
		for _, failedEvent := range failedEvents {
			if len(failedEvent.RecordID) == 0 || !json.Valid(failedEvent.RecordID) {
				pkgLogger.Infof("skipped adding invalid recordId: %s, to failed keys table: %s", failedEvent.RecordID, table)
				continue
			}
			_, err = stmt.Exec(failedEvent.DestinationID, failedEvent.RecordID, createdAt)
			if err != nil {
				panic(err)
			}
		}

		stmt.Close()
	}
}

file_path: router/failed-events-manager.go
snippet: func (fem *FailedEventsManagerT) DropFailedRecordIDs(taskRunID string) {
	if !failedKeysEnabled {
		return
	}

	// Drop table
	table := getSqlSafeTablename(taskRunID)
	sqlStatement := fmt.Sprintf(`DROP TABLE IF EXISTS %s`, table)
	_, err := fem.dbHandle.Exec(sqlStatement)
	if err != nil {
		pkgLogger.Errorf("Failed to drop table %s with error: %v", taskRunID, err)
	}
}

file_path: router/failed-events-manager.go
snippet: func (fem *FailedEventsManagerT) FetchFailedRecordIDs(taskRunID string) []*FailedEventRowT {
	if !failedKeysEnabled {
		return []*FailedEventRowT{}
	}

	failedEvents := make([]*FailedEventRowT, 0)

	var rows *sql.Rows
	var err error
	table := getSqlSafeTablename(taskRunID)
	sqlStatement := fmt.Sprintf(`SELECT %[1]s.destination_id, %[1]s.record_id
                                             FROM %[1]s `, table)
	rows, err = fem.dbHandle.Query(sqlStatement)
	if err != nil {
		pkgLogger.Errorf("Failed to fetch from table %s with error: %v", taskRunID, err)
		return failedEvents
	}
	defer rows.Close()

	for rows.Next() {
		var failedEvent FailedEventRowT
		err := rows.Scan(&failedEvent.DestinationID, &failedEvent.RecordID)
		if err != nil {
			panic(err)
		}
		failedEvents = append(failedEvents, &failedEvent)
	}

	return failedEvents
}

file_path: router/failed-events-manager.go
snippet: func getSqlSafeTablename(taskRunID string) string {
	return `"` + strings.ReplaceAll(fmt.Sprintf(`%s_%s`, failedKeysTablePrefix, taskRunID), `"`, `""`) + `"`
}