CVE-2015-10044: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: A vulnerability classified as critical was found in gophergala sqldump. This vulnerability affects unknown code. The manipulation leads to sql injection. The patch is identified as 76db54e9073b5248b8863e71a63d66a32d567d21. It is recommended to apply a patch to fix this issue. VDB-218350 is the identifier assigned to this vulnerability.
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-01-15T10:15Z
Repository: https://github.com/gophergala/sqldump
Language: Go
Files changed: README.md, auth.go, aux.go, dump.go, sqldump.go
Commit date: 2015-01-25 02:26:19 +0100
Commit message: Protection against sql injection doesnt work with ? for table. Using HTMLEscapeString instead

Diff: diff --git a/README.md b/README.md
index a075e97..074b9d2 100644
--- a/README.md
+++ b/README.md
@@ -5,17 +5,17 @@ A small tool for assisting in administration of databases. My first 48 hours in
 Unfortunately I do not have that much time, so it's just close to the backend with some basic html. 
 Use your fantasy for best UX and choose high levels of abstraction and imagination for fancy output with latest js-technology. 
 
 ## prepare
 
-    sudo mysqladmin --defaults-file=/etc/mysql/debian.cnf create gotestdb
-    sudo mysql --defaults-file=/etc/mysql/debian.cnf -e "GRANT ALL PRIVILEGES  ON gotestdb.*  TO 'go_user'@'localhost' IDENTIFIED BY 'mypassword'  WITH GRANT OPTION;"
-    mysql -p"mypassword" -u go_user gotestdb -e 'create table posts (title varchar(64) default null, start date default null);'
-    mysql -p"mypassword" -u go_user gotestdb -e 'insert into posts values("hello","2015-01-01");'
-    mysql -p"mypassword" -u go_user gotestdb -e 'insert into posts values("more","2015-01-03");'
-    mysql -p"mypassword" -u go_user gotestdb -e 'insert into posts values("end","2015-01-23");'
-    mysql -p"mypassword" -u go_user gotestdb -B -e 'select * from posts;'
+    sudo mysqladmin --defaults-file=/etc/mysql/debian.cnf create galadb
+    sudo mysql --defaults-file=/etc/mysql/debian.cnf -e "GRANT ALL PRIVILEGES  ON galadb.*  TO 'galagopher'@'localhost' IDENTIFIED BY 'mypassword'  WITH GRANT OPTION;"
+    mysql -p"mypassword" -u galagopher galadb -e 'create table posts (title varchar(64) default null, start date default null);'
+    mysql -p"mypassword" -u galagopher galadb -e 'insert into posts values("hello","2015-01-01");'
+    mysql -p"mypassword" -u galagopher galadb -e 'insert into posts values("more","2015-01-03");'
+    mysql -p"mypassword" -u galagopher galadb -e 'insert into posts values("end","2015-01-23");'
+    mysql -p"mypassword" -u galagopher galadb -B -e 'select * from posts;'
 
 ## install
 
     export GOPATH=$PWD
     git clone https://github.com/gophergala/sqldump .
@@ -36,10 +36,11 @@ Use your fantasy for best UX and choose high levels of abstraction and imaginati
 - restriction on names of databases, tables, columns 
 - basic protection against sql injection via URI
 
 ## perspectives
 
-. output in tables
+- output in tables
+- menu line
 - choice for different database drivers
 - insert and edit records
 
 
diff --git a/auth.go b/auth.go
index 8b36c1f..38c40ab 100644
--- a/auth.go
+++ b/auth.go
@@ -1,6 +1,6 @@
-/* no Credentials management needed
+/* no session management needed
  * Credentials are stored at user side using secure cookies
  *
  * credits:
  * http://www.mschoebel.info/2014/03/09/snippet-golang-webapp-login-logout.html
  */
@@ -14,51 +14,57 @@ import (
 
 var cookieHandler = securecookie.New(
 	securecookie.GenerateRandomKey(64),
 	securecookie.GenerateRandomKey(32))
 
-func getCredentials(request *http.Request) (userName string, password string) {
-	if cookie, err := request.Cookie("Credentials"); err == nil {
+func getCredentials(request *http.Request) (userName string, password string, host string, port string) {
+	if cookie, err := request.Cookie("Datasource"); err == nil {
 		cookieValue := make(map[string]string)
-		if err = cookieHandler.Decode("Credentials", cookie.Value, &cookieValue); err == nil {
+		if err = cookieHandler.Decode("Datasource", cookie.Value, &cookieValue); err == nil {
 			userName = cookieValue["user"]
 			password = cookieValue["passwd"]
+			host = cookieValue["host"]
+			port = cookieValue["port"]
 		}
 	}
-	return userName, password
+	return userName, password, host, port
 }
 
-func setCredentials(userName string, pw string, w http.ResponseWriter) {
+func setCredentials( w http.ResponseWriter, userName string, pw string, host string, port string) {
 	value := map[string]string{
 		"user":   userName,
 		"passwd": pw,
+		"host": host,
+		"port": port,
 	}
-	if encoded, err := cookieHandler.Encode("Credentials", value); err == nil {
+	if encoded, err := cookieHandler.Encode("Datasource", value); err == nil {
 		cookie := &http.Cookie{
-			Name:  "Credentials",
+			Name:  "Datasource",
 			Value: encoded,
 			Path:  "/",
 		}
 		http.SetCookie(w, cookie)
 	}
 }
 
 func clearCredentials(w http.ResponseWriter) {
 	cookie := &http.Cookie{
-		Name:   "Credentials",
+		Name:   "Datasource",
 		Value:  "",
 		Path:   "/",
 		MaxAge: -1,
 	}
 	http.SetCookie(w, cookie)
 }
 
 func loginHandler(w http.ResponseWriter, request *http.Request) {
 	user := request.FormValue("user")
 	pass := request.FormValue("password")
+	host := request.FormValue("host")
+	port := request.FormValue("port")
 	if user != "" && pass != "" {
-		setCredentials(user, pass, w)
+		setCredentials(w, user, pass, host, port)
 	}
 	http.Redirect(w, request, "/", 302)
 }
 
 func logoutHandler(w http.ResponseWriter, request *http.Request) {
@@ -67,10 +73,12 @@ func logoutHandler(w http.ResponseWriter, request *http.Request) {
 }
 
 const loginPage = `
 <h1>Login</h1>
 <form method="post" action="/login">
-   <label for="user">User name</label><input type="text" id="user" name="user">
-   <label for="password">Password</label><input type="password" id="password" name="password">
+   <label for="user">User name</label><input type="text" id="user" name="user"><br>
+   <label for="password">Password</label><input type="password" id="password" name="password"><br>
+   <label for="host">Host</label><input type="text" id="host" name="host" value="localhost"><br>
+   <label for="port">Port</label><input type="text" id="port" name="port" value="3306"><br>
    <button type="submit">Login</button>
 </form>
 `
diff --git a/aux.go b/aux.go
index 31dca0c..91efd82 100644
--- a/aux.go
+++ b/aux.go
@@ -19,11 +19,11 @@ func checkY(err error) {
 func linkDeeper(cwd string, link string, name string) string {
 	return "<a href=\"" + cwd + "/" + link + "\">" + name + "</a>"
 }
 
 // Compose dataSourceName from components and globals
-func dsn(user string, pw string, db string) string {
+func dsn(user string, pw string, host string, port string, db string) string {
 	return user + ":" + pw + "@tcp(" + host + ":" + port + ")/" + db
 }
 
 // Converts an URL into an array of strings
 func url2array(r *http.Request) []string {
diff --git a/dump.go b/dump.go
index 95e172f..c0ed5ba 100644
--- a/dump.go
+++ b/dump.go
@@ -1,25 +1,26 @@
 package main
 
 /* TODO
- * turn into generic functions
+ * turn into more generic functions for printing into tables
  */
 
 import (
 	"database/sql"
 	"fmt"
 	_ "github.com/go-sql-driver/mysql"
 	"net/http"
 	"strconv"
 	"strings"
+	"text/template"
 )
 
 // Shows selection of databases at top level
 func home(w http.ResponseWriter, r *http.Request) {
 
-	user, pw := getCredentials(r)
-	conn, err := sql.Open("mysql", dsn(user, pw, database))
+	user, pw, h , p := getCredentials(r)
+	conn, err := sql.Open("mysql", dsn(user, pw, h, p, database))
 	checkY(err)
 	defer conn.Close()
 
 	statement, err := conn.Prepare("show databases")
 	checkY(err)
@@ -39,13 +40,13 @@ func home(w http.ResponseWriter, r *http.Request) {
 }
 
 //  Dump all tables of a database
 func dumpdb(w http.ResponseWriter, r *http.Request, parray []string) {
 
-	user, pw := getCredentials(r)
+	user, pw, h, p := getCredentials(r)
 	database := parray[0]
-	conn, err := sql.Open("mysql", dsn(user, pw, database))
+	conn, err := sql.Open("mysql", dsn(user, pw, h, p, database))
 	checkY(err)
 	defer conn.Close()
 
 	statement, err := conn.Prepare("show tables")
 	checkY(err)
@@ -65,22 +66,22 @@ func dumpdb(w http.ResponseWriter, r *http.Request, parray []string) {
 }
 
 //  Dump all records of a table, one per line
 func dumptable(w http.ResponseWriter, r *http.Request, parray []string) {
 
-	user, pw := getCredentials(r)
+	user, pw, h, p := getCredentials(r)
 	database := parray[0]
 	table := parray[1]
 
-	conn, err := sql.Open("mysql", dsn(user, pw, database))
+	conn, err := sql.Open("mysql", dsn(user, pw, h, p, database))
 	checkY(err)
 	defer conn.Close()
 
-	statement, err := conn.Prepare("select * from ?")
+	statement, err := conn.Prepare("select * from " + template.HTMLEscapeString(table))
 	checkY(err)
 
-	rows, err := statement.Query(table)
+	rows, err := statement.Query()
 	checkY(err)
 	defer rows.Close()
 
 	cols, err := rows.Columns()
 	checkY(err)
@@ -121,19 +122,19 @@ func dumprecord(w http.ResponseWriter, r *http.Request, parray []string) {
 	database := parray[0]
 	table := parray[1]
 	rec, err := strconv.Atoi(parray[2])
 	checkY(err)
 
-	user, pw := getCredentials(r)
-	conn, err := sql.Open("mysql", dsn(user, pw, database))
+	user, pw, h, p := getCredentials(r)
+	conn, err := sql.Open("mysql", dsn(user, pw, h, p, database))
 	checkY(err)
 	defer conn.Close()
 
-	statement, err := conn.Prepare("select * from ?")
+	statement, err := conn.Prepare("select * from " + template.HTMLEscapeString(table))
 	checkY(err)
 
-	rows, err := statement.Query(table)
+	rows, err := statement.Query()
 	checkY(err)
 	defer rows.Close()
 
 	columns, err := rows.Columns()
 	checkY(err)
diff --git a/sqldump.go b/sqldump.go
index 495b509..355a7ce 100644
--- a/sqldump.go
+++ b/sqldump.go
@@ -5,12 +5,10 @@ import (
 	_ "github.com/go-sql-driver/mysql"
 	"net/http"
 )
 
 var base_url = "http://localhost"
-var host = "localhost"
-var port = "3306"
 var database = "information_schema"
 
 func faviconHandler(w http.ResponseWriter, r *http.Request) {
 	http.StatusText(404)
 }
@@ -36,14 +34,14 @@ func pathHandler(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 }
 
 func indexHandler(w http.ResponseWriter, r *http.Request) {
-	u, _ := getCredentials(r)
+	user , _ , host , port := getCredentials(r)
 
-	if u != "" {
-		fmt.Fprintln(w, "<h1>", u, "</h1>")
+	if user != "" {
+		fmt.Fprintln(w, "<p>", user + "@" + host + ":" + port, "</p>")
 		pathHandler(w, r)
 	} else {
 		loginPageHandler(w, r)
 	}
 }

Vulnerable code:     sudo mysqladmin --defaults-file=/etc/mysql/debian.cnf create gotestdb
    sudo mysql --defaults-file=/etc/mysql/debian.cnf -e "GRANT ALL PRIVILEGES  ON gotestdb.*  TO 'go_user'@'localhost' IDENTIFIED BY 'mypassword'  WITH GRANT OPTION;"
    mysql -p"mypassword" -u go_user gotestdb -e 'create table posts (title varchar(64) default null, start date default null);'
    mysql -p"mypassword" -u go_user gotestdb -e 'insert into posts values("hello","2015-01-01");'
    mysql -p"mypassword" -u go_user gotestdb -e 'insert into posts values("more","2015-01-03");'
    mysql -p"mypassword" -u go_user gotestdb -e 'insert into posts values("end","2015-01-23");'
    mysql -p"mypassword" -u go_user gotestdb -B -e 'select * from posts;'
. output in tables
/* no Credentials management needed
func getCredentials(request *http.Request) (userName string, password string) {
	if cookie, err := request.Cookie("Credentials"); err == nil {
		if err = cookieHandler.Decode("Credentials", cookie.Value, &cookieValue); err == nil {
	return userName, password
func setCredentials(userName string, pw string, w http.ResponseWriter) {
	if encoded, err := cookieHandler.Encode("Credentials", value); err == nil {
			Name:  "Credentials",
		Name:   "Credentials",
		setCredentials(user, pass, w)
   <label for="user">User name</label><input type="text" id="user" name="user">
   <label for="password">Password</label><input type="password" id="password" name="password">
func dsn(user string, pw string, db string) string {
 * turn into generic functions
	user, pw := getCredentials(r)
	conn, err := sql.Open("mysql", dsn(user, pw, database))
	user, pw := getCredentials(r)
	conn, err := sql.Open("mysql", dsn(user, pw, database))
	user, pw := getCredentials(r)
	conn, err := sql.Open("mysql", dsn(user, pw, database))
	statement, err := conn.Prepare("select * from ?")
	rows, err := statement.Query(table)
	user, pw := getCredentials(r)
	conn, err := sql.Open("mysql", dsn(user, pw, database))
	statement, err := conn.Prepare("select * from ?")
	rows, err := statement.Query(table)
var host = "localhost"
var port = "3306"
	u, _ := getCredentials(r)
	if u != "" {
		fmt.Fprintln(w, "<h1>", u, "</h1>")
Fixed code:     sudo mysqladmin --defaults-file=/etc/mysql/debian.cnf create galadb
    sudo mysql --defaults-file=/etc/mysql/debian.cnf -e "GRANT ALL PRIVILEGES  ON galadb.*  TO 'galagopher'@'localhost' IDENTIFIED BY 'mypassword'  WITH GRANT OPTION;"
    mysql -p"mypassword" -u galagopher galadb -e 'create table posts (title varchar(64) default null, start date default null);'
    mysql -p"mypassword" -u galagopher galadb -e 'insert into posts values("hello","2015-01-01");'
    mysql -p"mypassword" -u galagopher galadb -e 'insert into posts values("more","2015-01-03");'
    mysql -p"mypassword" -u galagopher galadb -e 'insert into posts values("end","2015-01-23");'
    mysql -p"mypassword" -u galagopher galadb -B -e 'select * from posts;'
- output in tables
- menu line
/* no session management needed
func getCredentials(request *http.Request) (userName string, password string, host string, port string) {
	if cookie, err := request.Cookie("Datasource"); err == nil {
		if err = cookieHandler.Decode("Datasource", cookie.Value, &cookieValue); err == nil {
			host = cookieValue["host"]
			port = cookieValue["port"]
	return userName, password, host, port
func setCredentials( w http.ResponseWriter, userName string, pw string, host string, port string) {
		"host": host,
		"port": port,
	if encoded, err := cookieHandler.Encode("Datasource", value); err == nil {
			Name:  "Datasource",
		Name:   "Datasource",
	host := request.FormValue("host")
	port := request.FormValue("port")
		setCredentials(w, user, pass, host, port)
   <label for="user">User name</label><input type="text" id="user" name="user"><br>
   <label for="password">Password</label><input type="password" id="password" name="password"><br>
   <label for="host">Host</label><input type="text" id="host" name="host" value="localhost"><br>
   <label for="port">Port</label><input type="text" id="port" name="port" value="3306"><br>
func dsn(user string, pw string, host string, port string, db string) string {
 * turn into more generic functions for printing into tables
	"text/template"
	user, pw, h , p := getCredentials(r)
	conn, err := sql.Open("mysql", dsn(user, pw, h, p, database))
	user, pw, h, p := getCredentials(r)
	conn, err := sql.Open("mysql", dsn(user, pw, h, p, database))
	user, pw, h, p := getCredentials(r)
	conn, err := sql.Open("mysql", dsn(user, pw, h, p, database))
	statement, err := conn.Prepare("select * from " + template.HTMLEscapeString(table))
	rows, err := statement.Query()
	user, pw, h, p := getCredentials(r)
	conn, err := sql.Open("mysql", dsn(user, pw, h, p, database))
	statement, err := conn.Prepare("select * from " + template.HTMLEscapeString(table))
	rows, err := statement.Query()
	user , _ , host , port := getCredentials(r)
	if user != "" {
		fmt.Fprintln(w, "<p>", user + "@" + host + ":" + port, "</p>")