CVE-2023-34231: Improper Neutralization of Special Elements used in a Command ('Command Injection') (CWE-77)
value: gosnowflake is th Snowflake Golang driver. Prior to version 1.6.19, a command injection vulnerability exists in the Snowflake Golang driver via single sign-on (SSO) browser URL authentication. In order to exploit the potential for command injection, an attacker would need to be successful in (1) establishing a malicious resource and (2) redirecting users to utilize the resource. The attacker could set up a malicious, publicly accessible server which responds to the SSO URL with an attack payload. If the attacker then tricked a user into visiting the maliciously crafted connection URL, the user’s local machine would render the malicious payload, leading to a remote code execution. This attack scenario can be mitigated through URL whitelisting as well as common anti-phishing resources. A patch is available in version 1.6.19.
CWE description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
CVSS v3 base score: 8.8
Published: 2023-06-08T20:15Z
Repository: https://github.com/snowflakedb/gosnowflake
Language: Go
Files changed: url_util.go, util_test.go
Commit date: 2023-03-20 17:24:48 -0700
Commit message: SNOW-761744 Added URL Validator and URL Encoder (#757)


Diff: diff --git a/url_util.go b/url_util.go
new file mode 100644
index 0000000..c6a9364
--- /dev/null
+++ b/url_util.go
@@ -0,0 +1,29 @@
+package gosnowflake
+
+import (
+	"net/url"
+	"regexp"
+)
+
+var (
+	matcher, _ = regexp.Compile(`^http(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\&\(\)\/\\\+&%\$#_=@]*)?$`)
+)
+
+func isValidURL(targetURL string) bool {
+	if !matcher.MatchString(targetURL) {
+		logger.Infof(" The provided URL is not a valid URL - " + targetURL)
+		return false
+	}
+	return true
+}
+
+func urlEncode(targetString string) string {
+	// We use QueryEscape instead of PathEscape here
+	// for consistency across Drivers. For example:
+	// QueryEscape escapes space as "+" whereas PE
+	// it as %20F. PE also does not escape @ or &
+	// either but QE does.
+	// The behavior of QE in Golang is more in sync
+	// with URL encoders in Python and Java hence the choice
+	return url.QueryEscape(targetString)
+}
diff --git a/util_test.go b/util_test.go
index b0a5f3c..9778d18 100644
--- a/util_test.go
+++ b/util_test.go
@@ -228,5 +228,46 @@ func TestGetMin(t *testing.T) {
 		if test.out != a {
 			t.Errorf("failed get min. in: %v, expected: %v, got: %v", test.in, test.out, a)
 		}
 	}
 }
+
+type tcURLList struct {
+	in  string
+	out bool
+}
+
+func TestValidURL(t *testing.T) {
+	testcases := []tcURLList{
+		{"https://ssoTestURL.okta.com", true},
+		{"https://ssoTestURL.okta.com:8080", true},
+		{"https://ssoTestURL.okta.com/testpathvalue", true},
+		{"-a calculator", false},
+		{"This is a random test", false},
+		{"file://TestForFile", false},
+	}
+	for _, test := range testcases {
+		result := isValidURL(test.in)
+		if test.out != result {
+			t.Errorf("Failed to validate URL, input :%v, expected: %v, got: %v", test.in, test.out, result)
+		}
+	}
+}
+
+type tcEncodeList struct {
+	in  string
+	out string
+}
+
+func TestEncodeURL(t *testing.T) {
+	testcases := []tcEncodeList{
+		{"Hello @World", "Hello+%40World"},
+		{"Test//String", "Test%2F%2FString"},
+	}
+
+	for _, test := range testcases {
+		result := urlEncode(test.in)
+		if test.out != result {
+			t.Errorf("Failed to encode string, input %v, expected: %v, got: %v", test.in, test.out, result)
+		}
+	}
+}

Fixed code: package gosnowflake

import (
	"net/url"
	"regexp"
)

var (
	matcher, _ = regexp.Compile(`^http(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\&\(\)\/\\\+&%\$#_=@]*)?$`)
)

func isValidURL(targetURL string) bool {
	if !matcher.MatchString(targetURL) {
		logger.Infof(" The provided URL is not a valid URL - " + targetURL)
		return false
	}
	return true
}

func urlEncode(targetString string) string {
	// We use QueryEscape instead of PathEscape here
	// for consistency across Drivers. For example:
	// QueryEscape escapes space as "+" whereas PE
	// it as %20F. PE also does not escape @ or &
	// either but QE does.
	// The behavior of QE in Golang is more in sync
	// with URL encoders in Python and Java hence the choice
	return url.QueryEscape(targetString)
}

type tcURLList struct {
	in  string
	out bool
}

func TestValidURL(t *testing.T) {
	testcases := []tcURLList{
		{"https://ssoTestURL.okta.com", true},
		{"https://ssoTestURL.okta.com:8080", true},
		{"https://ssoTestURL.okta.com/testpathvalue", true},
		{"-a calculator", false},
		{"This is a random test", false},
		{"file://TestForFile", false},
	}
	for _, test := range testcases {
		result := isValidURL(test.in)
		if test.out != result {
			t.Errorf("Failed to validate URL, input :%v, expected: %v, got: %v", test.in, test.out, result)
		}
	}
}

type tcEncodeList struct {
	in  string
	out string
}

func TestEncodeURL(t *testing.T) {
	testcases := []tcEncodeList{
		{"Hello @World", "Hello+%40World"},
		{"Test//String", "Test%2F%2FString"},
	}

	for _, test := range testcases {
		result := urlEncode(test.in)
		if test.out != result {
			t.Errorf("Failed to encode string, input %v, expected: %v, got: %v", test.in, test.out, result)
		}
	}
}