From 0afbdd97d927d85afb9ebbc4ddd54bfff0755e5f Mon Sep 17 00:00:00 2001 From: Vomitblood Date: Tue, 12 Nov 2024 11:53:55 +0800 Subject: [PATCH] added sql injection simulation for authentication on server --- README.md | 3 + server/internal/db/db.go | 22 ++--- server/internal/http_server/http_server.go | 2 + .../internal/sql_injection/sql_injection.go | 81 ++++++++++++++++++- 4 files changed, 96 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a27898c..134e7f4 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ PGPASSWORD=asdfpassword !only listening on localhost is supported. DO NOT run this on a public ip. +- `/health` - `/setup-demo-db` - `/nuke-db` - `/fetch-all-users` @@ -40,7 +41,9 @@ PGPASSWORD=asdfpassword ### SQL Injection - `/sql-execute` +- `/login-sql` - `/secure-sql-execute` +- `/secure-login-sql` - `/secure-get-user` #### 1. Parameterization of Queries diff --git a/server/internal/db/db.go b/server/internal/db/db.go index 7b3cca9..7b2cb15 100644 --- a/server/internal/db/db.go +++ b/server/internal/db/db.go @@ -45,20 +45,20 @@ func ConnectToDb() (*pgxpool.Pool, error) { func SetupDemoDb(w http.ResponseWriter, r *http.Request) { // create table and insert demo data createTableSQL := ` - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - username VARCHAR(50) UNIQUE NOT NULL, - email VARCHAR(100) NOT NULL, - password VARCHAR(100) NOT NULL - );` + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(100) NOT NULL, + password VARCHAR(100) NOT NULL +);` // also avoid duplicate entries insertDataSQL := ` - INSERT INTO users (username, email, password) VALUES - ('alice', 'alice@example.com', 'asdfalicepassword'), - ('bob', 'bob@example.com', 'asdfbobpassword'), - ('charlie', 'charlie@example.com', 'asdfcharliepassword') - ON CONFLICT (username) DO NOTHING;` + INSERT INTO users (username, email, password) VALUES + ('alice', 'alice@example.com', 'asdfalicepassword'), + ('bob', 'bob@example.com', 'asdfbobpassword'), + ('charlie', 'charlie@example.com', 'asdfcharliepassword') + ON CONFLICT (username) DO NOTHING;` // execute create table _, err := DbPool.Exec(context.Background(), createTableSQL) diff --git a/server/internal/http_server/http_server.go b/server/internal/http_server/http_server.go index 415e518..be6aec3 100644 --- a/server/internal/http_server/http_server.go +++ b/server/internal/http_server/http_server.go @@ -21,7 +21,9 @@ func ServeApi() { http.HandleFunc("/nuke-db", db.NukeDb) http.HandleFunc("/fetch-all-users", db.FetchAllUsers) http.HandleFunc("/execute-sql", sql_injection.ExecuteSql) + http.HandleFunc("/login-sql", sql_injection.LoginSql) http.HandleFunc("/secure-execute-sql", sql_injection.SecureExecuteSql) + http.HandleFunc("/secure-login-sql", sql_injection.SecureLoginSql) http.HandleFunc("/secure-get-user", sql_injection.SecureGetUser) log.Println("Server is running on http://localhost:5000") if err := http.ListenAndServe(":5000", nil); err != nil { diff --git a/server/internal/sql_injection/sql_injection.go b/server/internal/sql_injection/sql_injection.go index adf3522..a6dfdc4 100644 --- a/server/internal/sql_injection/sql_injection.go +++ b/server/internal/sql_injection/sql_injection.go @@ -46,6 +46,50 @@ func ExecuteSql(w http.ResponseWriter, r *http.Request) { w.Write([]byte(response)) } +// unsecure login +// login endpoint with sql injection vulnerability +func LoginSql(w http.ResponseWriter, r *http.Request) { + // parse the request body to get username and password + var credentials struct { + Username string `json:"username"` + Password string `json:"password"` + } + + // decode the json body + if err := json.NewDecoder(r.Body).Decode(&credentials); err != nil { + http.Error(w, "Invalid request format", http.StatusBadRequest) + return + } + defer r.Body.Close() + + // construct the unsafe query + query := fmt.Sprintf( + "SELECT id, username FROM users WHERE username = '%s' AND password = '&s'", + credentials.Username, + credentials.Password, + ) + + // execute the query without sanitizing the input + var id int + var username string + err := db.DbPool.QueryRow(context.Background(), query).Scan(&id, &username) + if err != nil { + http.Error(w, "Invalid credentials", http.StatusUnauthorized) + return + } + + // if the user is found, return success response + response := map[string]interface{}{ + "message": "Login successful", + "user_id": id, + "username": username, + } + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + log.Printf("JSON encoding error: %v", err) + } +} + // secure version // only allow parameterized queries with validation func SecureExecuteSql(w http.ResponseWriter, r *http.Request) { @@ -95,7 +139,7 @@ func SecureExecuteSql(w http.ResponseWriter, r *http.Request) { // return json response jsonResp, err := json.Marshal(response) if err != nil { - http.Error(w, "Failed to encode response as JSON", http.StatusInternalServerError) + http.Error(w, "Failed to encode response", http.StatusInternalServerError) return } @@ -103,6 +147,41 @@ func SecureExecuteSql(w http.ResponseWriter, r *http.Request) { w.Write(jsonResp) } +// secure login +func SecureLoginSql(w http.ResponseWriter, r *http.Request) { + var credentials struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := json.NewDecoder(r.Body).Decode(&credentials); err != nil { + http.Error(w, "Invalid request format", http.StatusBadRequest) + return + } + defer r.Body.Close() + + // secure version using parameterized queries + query := "SELECT id, username FROM users WHERE username = $1 AND password = $2" + var id int + var username string + err := db.DbPool.QueryRow(context.Background(), query, credentials.Username, credentials.Password).Scan(&id, &username) + if err != nil { + http.Error(w, "Invalid credentials", http.StatusUnauthorized) + return + } + + // send back the response if great success + response := map[string]interface{}{ + "message": "Login successful", + "user_id": id, + "username": username, + } + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + log.Printf("JSON encoding error: %v", err) + } +} + // even more secure func SecureGetUser(w http.ResponseWriter, r *http.Request) { // decode the json body