package sql_injection import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "strings" "github.com/Vomitblood/cspj-application/server/internal/db" ) // unsecure version // take http reqeust body as raw sql and pass to db func ExecuteSql(w http.ResponseWriter, r *http.Request) { // read the request body sqlQuery, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read request body", http.StatusBadRequest) return } defer r.Body.Close() // execute the sql query without any sanitization rows, err := db.DbPool.Query(context.Background(), string(sqlQuery)) if err != nil { http.Error(w, "Query execution error", http.StatusInternalServerError) return } defer rows.Close() // prepare the response by iterating over the returned rows var response string for rows.Next() { values, err := rows.Values() if err != nil { http.Error(w, "Error reading query result", http.StatusInternalServerError) return } response += fmt.Sprintf("%v\n", values) } // send the response to the client w.Write([]byte(response)) } // secure version // only allow parameterized queries with validation func SecureExecuteSql(w http.ResponseWriter, r *http.Request) { var input struct { Query string `json:"query"` Params []interface{} `json:"params"` } // parse json request body if err := json.NewDecoder(r.Body).Decode(&input); err != nil { http.Error(w, "Invalid JSON format", http.StatusBadRequest) return } defer r.Body.Close() // simple validation, only allow select statements if !strings.HasPrefix(strings.ToUpper(input.Query), "SELECT") { http.Error(w, "Only SELECT queries are allowed", http.StatusForbidden) return } // execute the query as a parameterized statement rows, err := db.DbPool.Query(context.Background(), input.Query, input.Params...) if err != nil { http.Error(w, "Query execution error", http.StatusInternalServerError) return } defer rows.Close() // format the response var response []map[string]interface{} for rows.Next() { values, err := rows.Values() if err != nil { http.Error(w, "Error reading query result", http.StatusInternalServerError) return } rowMap := make(map[string]interface{}) fieldDescriptions := rows.FieldDescriptions() for i, fd := range fieldDescriptions { rowMap[string(fd.Name)] = values[i] } response = append(response, rowMap) } // return json response jsonResp, err := json.Marshal(response) if err != nil { http.Error(w, "Failed to encode response as JSON", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(jsonResp) } // even more secure func SecureGetUser(w http.ResponseWriter, r *http.Request) { // decode the json body var requestData struct { Username string `json:"username"` } // declare new json decoder with custom property jsonDecoder := json.NewDecoder(r.Body) // rejects any unknown fields in the json, more strict jsonDecoder.DisallowUnknownFields() if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil { http.Error(w, "Invalid request format", http.StatusBadRequest) log.Println("Failed to decode JSON body or extra fields present:", err) return } // validate that user is provided if requestData.Username == "" { http.Error(w, "Invalid request format", http.StatusBadRequest) log.Println("Username is missing in the request body") return } // retrieve list of existing usernames from db existingUsernames, err := db.FetchUsernames() if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) log.Printf("Failed to fetch usernames: %v", err) return } // check if the username exists in the allowed list // this step is crucial // server will reject ANYTHING that does not match the list if !existingUsernames[requestData.Username] { http.Error(w, "Invalid username", http.StatusBadRequest) return } // construct the query query := "SELECT id, username, email FROM users WHERE username = $1" var id int var dbUsername, email string err = db.DbPool.QueryRow(context.Background(), query, requestData.Username).Scan(&id, &dbUsername, &email) if err != nil { http.Error(w, "User not found", http.StatusNotFound) return } // send back the user data as a json response response := map[string]interface{}{ "id": id, "username": dbUsername, "email": email, } if err := json.NewEncoder(w).Encode(response); err != nil { http.Error(w, "Failed to encode response as JSON", http.StatusInternalServerError) log.Printf("JSON encoding error: %v", err) } }