CVE-2023-46575: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: A SQL injection vulnerability exists in Meshery prior to version v0.6.179, enabling a remote attacker to retrieve sensitive information and execute arbitrary code through the “order” parameter
CWE 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.
CVSS v3 base score: 9.8
Published: 2023-11-24T14:15Z
Repository: https://github.com/meshery/meshery
Language: Go
Files changed: server/handlers/database_handlers.go, server/models/meshery_application_persister.go, server/models/meshery_filter_persister.go, server/models/meshery_k8scontext_persister.go, server/models/meshery_pattern_persister.go, server/models/pattern_resource_persister.go, server/models/performance_profile_persister.go, server/models/sql-utils.go
Commit date: 2023-11-19 16:59:32 +0800
Commit message: ## Add "order by" parameter filter

## and export SanitizeOrderInput function

Signed-off-by: phant0ms <ph1nt0ms@gmail.com>

Diff: diff --git a/server/handlers/database_handlers.go b/server/handlers/database_handlers.go
index 7a386179bc..cb8764db6a 100644
--- a/server/handlers/database_handlers.go
+++ b/server/handlers/database_handlers.go
@@ -71,10 +71,11 @@ func (h *Handler) GetSystemDatabase(w http.ResponseWriter, r *http.Request, _ *m
 		tableFinder = tableFinder.Limit(limit)
 	}
 	if offset != 0 {
 		tableFinder = tableFinder.Offset(offset)
 	}
+	order = models.SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 	if order != "" {
 		if sort == "desc" {
 			tableFinder = tableFinder.Order(clause.OrderByColumn{Column: clause.Column{Name: order}, Desc: true})
 		} else {
 			tableFinder = tableFinder.Order(order)
diff --git a/server/models/meshery_application_persister.go b/server/models/meshery_application_persister.go
index fe2f8f7f6b..61d2fd5731 100644
--- a/server/models/meshery_application_persister.go
+++ b/server/models/meshery_application_persister.go
@@ -22,11 +22,11 @@ type MesheryApplicationPage struct {
 	Applications []*MesheryApplication `json:"applications"`
 }
 
 // GetMesheryApplications returns all of the applications
 func (maap *MesheryApplicationPersister) GetMesheryApplications(search, order string, page, pageSize uint64, updatedAfter string) ([]byte, error) {
-	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
+	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 
 	if order == "" {
 		order = "updated_at desc"
 	}
 
diff --git a/server/models/meshery_filter_persister.go b/server/models/meshery_filter_persister.go
index 7469fa19c9..738f4ff5ae 100644
--- a/server/models/meshery_filter_persister.go
+++ b/server/models/meshery_filter_persister.go
@@ -26,11 +26,11 @@ type MesheryFilterPage struct {
 }
 
 // GetMesheryFilters returns all of the 'private' filters. Though private has no meaning here since there is only
 // one local user. We make this distinction to be consistent with the remote provider
 func (mfp *MesheryFilterPersister) GetMesheryFilters(search, order string, page, pageSize uint64, visibility []string) ([]byte, error) {
-	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
+	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 
 	if order == "" {
 		order = "updated_at desc"
 	}
 
@@ -63,11 +63,11 @@ func (mfp *MesheryFilterPersister) GetMesheryFilters(search, order string, page,
 }
 
 // GetMesheryCatalogFilters returns all of the published filters
 func (mfp *MesheryFilterPersister) GetMesheryCatalogFilters(page, pageSize, search, order string) ([]byte, error) {
 	var err error
-	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
+	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 
 	if order == "" {
 		order = "updated_at desc"
 	}
 
diff --git a/server/models/meshery_k8scontext_persister.go b/server/models/meshery_k8scontext_persister.go
index 17b2a0288c..83c3acf1b0 100644
--- a/server/models/meshery_k8scontext_persister.go
+++ b/server/models/meshery_k8scontext_persister.go
@@ -23,11 +23,11 @@ type MesheryK8sContextPage struct {
 	Contexts   []*K8sContext `json:"contexts"`
 }
 
 // GetMesheryK8sContexts returns all of the contexts
 func (mkcp *MesheryK8sContextPersister) GetMesheryK8sContexts(search, order string, page, pageSize uint64) ([]byte, error) {
-	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
+	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 
 	if order == "" {
 		order = "updated_at desc"
 	}
 
diff --git a/server/models/meshery_pattern_persister.go b/server/models/meshery_pattern_persister.go
index 0e8e963db9..36c0d002cb 100644
--- a/server/models/meshery_pattern_persister.go
+++ b/server/models/meshery_pattern_persister.go
@@ -27,11 +27,11 @@ type MesheryPatternPage struct {
 }
 
 // GetMesheryPatterns returns all of the 'private' patterns. Though private has no meaning here since there is only
 // one local user. We make this distinction to be consistent with the remote provider
 func (mpp *MesheryPatternPersister) GetMesheryPatterns(search, order string, page, pageSize uint64, updatedAfter string, visibility []string) ([]byte, error) {
-	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
+	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 
 	if order == "" {
 		order = "updated_at desc"
 	}
 
@@ -63,11 +63,11 @@ func (mpp *MesheryPatternPersister) GetMesheryPatterns(search, order string, pag
 }
 
 // GetMesheryCatalogPatterns returns all of the published patterns
 func (mpp *MesheryPatternPersister) GetMesheryCatalogPatterns(page, pageSize, search, order string) ([]byte, error) {
 	var err error
-	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
+	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 
 	if order == "" {
 		order = "updated_at desc"
 	}
 
diff --git a/server/models/pattern_resource_persister.go b/server/models/pattern_resource_persister.go
index e4f7d20b62..cb0fdab93f 100644
--- a/server/models/pattern_resource_persister.go
+++ b/server/models/pattern_resource_persister.go
@@ -55,11 +55,11 @@ func (prp *PatternResourcePersister) GetPatternResourceByAttributes(name, namesp
 
 	return &pr, err
 }
 
 func (prp *PatternResourcePersister) GetPatternResources(search, order, name, namespace, typ, oamType string, page, pageSize uint64) (*PatternResourcePage, error) {
-	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
+	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 
 	if order == "" {
 		order = "updated_at desc"
 	}
 
@@ -104,11 +104,11 @@ func (prp *PatternResourcePersister) Exists(name, namespace, typ, oamType string
 		Found bool
 	}
 
 	prp.DB.
 		Raw(`
-	SELECT 
+	SELECT
 		EXISTS(SELECT 1 FROM pattern_resources WHERE name = ? AND namespace = ? AND type = ? AND oam_type = ? AND deleted = false) AS "found"`,
 			name,
 			namespace,
 			typ,
 			oamType,
diff --git a/server/models/performance_profile_persister.go b/server/models/performance_profile_persister.go
index 7baabb387e..64dcdf33dc 100644
--- a/server/models/performance_profile_persister.go
+++ b/server/models/performance_profile_persister.go
@@ -22,11 +22,11 @@ type PerformanceProfilePage struct {
 	Profiles   []*PerformanceProfile `json:"profiles"`
 }
 
 // GetPerformanceProfiles returns all of the performance profiles
 func (ppp *PerformanceProfilePersister) GetPerformanceProfiles(_, search, order string, page, pageSize uint64) ([]byte, error) {
-	order = sanitizeOrderInput(order, []string{"updated_at", "created_at", "name", "last_run"})
+	order = SanitizeOrderInput(order, []string{"updated_at", "created_at", "name", "last_run"})
 	if order == "" {
 		order = "updated_at desc"
 	}
 
 	count := int64(0)
diff --git a/server/models/sql-utils.go b/server/models/sql-utils.go
index 56ad813cfd..b8870dd088 100644
--- a/server/models/sql-utils.go
+++ b/server/models/sql-utils.go
@@ -10,20 +10,20 @@ import (
 	"github.com/layer5io/meshkit/logger"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/viper"
 )
 
-// sanitizeOrderInput takes in the "order by" query, a validColums
+// SanitizeOrderInput takes in the "order by" query, a validColums
 // string slice and returns a sanitized query
 //
 // it will allow to run order by query only on the columns that are present
 // in the validColumns string slice, if any other column is requested in the
 // query then it will be IGNORED and an empty query would be returned instead
 //
-// sanitizeOrderInput also expects the query to be no longer than two words, that is
+// SanitizeOrderInput also expects the query to be no longer than two words, that is
 // the query may look like "updated_at DESC" or "name ASC"
-func sanitizeOrderInput(order string, validColumns []string) string {
+func SanitizeOrderInput(order string, validColumns []string) string {
 	parsedOrderStr := strings.Split(order, " ")
 	if len(parsedOrderStr) != 2 {
 		return ""
 	}
 

Vulnerable code: 	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = sanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	SELECT 
	order = sanitizeOrderInput(order, []string{"updated_at", "created_at", "name", "last_run"})
// sanitizeOrderInput takes in the "order by" query, a validColums
// sanitizeOrderInput also expects the query to be no longer than two words, that is
func sanitizeOrderInput(order string, validColumns []string) string {
Fixed code: 	order = models.SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	order = SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	SELECT
	order = SanitizeOrderInput(order, []string{"updated_at", "created_at", "name", "last_run"})
// SanitizeOrderInput takes in the "order by" query, a validColums
// SanitizeOrderInput also expects the query to be no longer than two words, that is
func SanitizeOrderInput(order string, validColumns []string) string {