cspj-application/server/internal/sql_injection/sql_injection.go

281 lines
9 KiB
Go
Raw Permalink Normal View History

2024-11-11 17:34:37 +08:00
package sql_injection
import (
2025-02-13 02:44:44 +08:00
"bytes"
2024-11-11 17:34:37 +08:00
"context"
"encoding/json"
"fmt"
"log"
"net/http"
2025-01-14 02:39:24 +08:00
"regexp"
2024-11-11 17:34:37 +08:00
"github.com/Vomitblood/cspj-application/server/internal/db"
)
2025-01-14 03:18:08 +08:00
// secure register endpoint
func UnsecureRegisterSql(w http.ResponseWriter, r *http.Request) {
2024-11-11 17:34:37 +08:00
// read the request body
var credentials struct {
2025-01-14 03:18:08 +08:00
Email string `json:"email"`
Password string `json:"password"`
RePassword string `json:"rePassword"`
}
if err := json.NewDecoder(r.Body).Decode(&credentials); err != nil {
http.Error(w, "Invalid request format", http.StatusBadRequest)
return
}
defer r.Body.Close()
2025-01-14 03:18:08 +08:00
// check if the email is an email using regex, if not reject
emailRegex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
re := regexp.MustCompile(emailRegex)
if !re.MatchString(credentials.Email) {
http.Error(w, "Invalid email format", http.StatusBadRequest)
return
}
2025-01-14 03:18:08 +08:00
// check if the password matches, if not reject
if credentials.Password != credentials.RePassword {
http.Error(w, "Passwords do not match", http.StatusBadRequest)
return
2024-11-11 17:34:37 +08:00
}
2025-01-14 03:18:08 +08:00
// get the number of emails that matches
// construct the sql query using concatenation, BAD
emailCheckSQL := fmt.Sprintf("SELECT COUNT(*) FROM users WHERE email = '%s'", credentials.Email)
var existingUserCount int
err := db.DbPool.QueryRow(context.Background(), emailCheckSQL).Scan(&existingUserCount)
if err != nil {
http.Error(w, "Error checking email in the database", http.StatusInternalServerError)
log.Printf("Error checking email: %v", err)
2024-11-11 17:34:37 +08:00
return
}
2025-01-14 03:18:08 +08:00
// if there is more than 0 matches, that means email already exists, reject
if existingUserCount > 0 {
http.Error(w, "Email already exists", http.StatusConflict)
2024-11-11 17:34:37 +08:00
return
}
2025-01-14 03:18:08 +08:00
// over here the validations has passed, so insert into the db
// also use concatenation here
2025-01-14 04:12:55 +08:00
insertSQL := fmt.Sprintf("INSERT INTO users (email, password, role) VALUES ('%s', '%s', 'user')", credentials.Email, credentials.Password)
2025-01-14 03:18:08 +08:00
_, err = db.DbPool.Exec(context.Background(), insertSQL)
2024-11-11 17:34:37 +08:00
if err != nil {
2025-01-14 03:18:08 +08:00
http.Error(w, "Error inserting user into the database", http.StatusInternalServerError)
log.Printf("Error inserting user: %v", err)
2024-11-11 17:34:37 +08:00
return
}
2025-01-14 03:18:08 +08:00
// send back status ok
w.WriteHeader(http.StatusOK)
w.Write([]byte("User registered successfully"))
log.Println("User registered successfully:", credentials.Email)
2024-11-11 17:34:37 +08:00
}
2025-01-14 03:18:08 +08:00
// secure register endpoint
func SecureRegisterSql(w http.ResponseWriter, r *http.Request) {
2025-01-14 02:39:24 +08:00
// read the request body
var credentials struct {
Email string `json:"email"`
Password string `json:"password"`
RePassword string `json:"rePassword"`
}
if err := json.NewDecoder(r.Body).Decode(&credentials); err != nil {
http.Error(w, "Invalid request format", http.StatusBadRequest)
return
}
defer r.Body.Close()
// check if the email is an email using regex, if not reject
emailRegex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
re := regexp.MustCompile(emailRegex)
if !re.MatchString(credentials.Email) {
http.Error(w, "Invalid email format", http.StatusBadRequest)
return
}
// check if the password matches, if not reject
if credentials.Password != credentials.RePassword {
http.Error(w, "Passwords do not match", http.StatusBadRequest)
return
}
// get the number of emails that matches
2025-01-14 03:18:08 +08:00
// use parameterization
2025-01-14 02:39:24 +08:00
var existingUserCount int
emailCheckSQL := `SELECT COUNT(*) FROM users WHERE email = $1`
err := db.DbPool.QueryRow(context.Background(), emailCheckSQL, credentials.Email).Scan(&existingUserCount)
if err != nil {
http.Error(w, "Error checking email in the database", http.StatusInternalServerError)
log.Printf("Error checking email: %v", err)
return
}
// if there is more than 0 matches, that means email already exists, reject
if existingUserCount > 0 {
http.Error(w, "Email already exists", http.StatusConflict)
return
}
// over here the validations has passed, so insert into the db
2025-01-14 03:18:08 +08:00
// use parameterization
2025-01-14 02:39:24 +08:00
insertSQL := `INSERT INTO users (email, password, role) VALUES ($1, $2, $3)`
2025-01-14 04:12:55 +08:00
_, err = db.DbPool.Exec(context.Background(), insertSQL, credentials.Email, credentials.Password, "user")
2025-01-14 02:39:24 +08:00
if err != nil {
http.Error(w, "Error inserting user into the database", http.StatusInternalServerError)
log.Printf("Error inserting user: %v", err)
return
}
// send back status ok
w.WriteHeader(http.StatusOK)
w.Write([]byte("User registered successfully"))
log.Println("User registered successfully:", credentials.Email)
}
2025-01-14 03:18:08 +08:00
func UnsecureLoginSql(w http.ResponseWriter, r *http.Request) {
2025-02-13 02:44:44 +08:00
log.Println("/unsecure-login-sql hit")
2025-01-14 03:18:08 +08:00
// decode the json body
var requestData struct {
Email string `json:"email"`
Password string `json:"password"`
}
2025-01-14 04:12:55 +08:00
// decode the request body
2025-01-14 03:18:08 +08:00
if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil {
http.Error(w, "Invalid request format", http.StatusBadRequest)
2025-01-14 04:12:55 +08:00
log.Println("Failed to decode JSON body:", err)
2025-01-14 03:18:08 +08:00
return
}
2025-01-14 04:12:55 +08:00
// make insecure sql command
query := fmt.Sprintf("SELECT id, email, password FROM users WHERE email = '%s' AND password = '%s'", requestData.Email, requestData.Password)
var id int
2025-01-14 03:18:08 +08:00
var email string
2025-01-14 04:12:55 +08:00
var password string
2025-01-14 04:12:55 +08:00
// execute the query to retrieve the data
err := db.DbPool.QueryRow(context.Background(), query).Scan(&id, &email, &password)
2025-01-14 03:18:08 +08:00
if err != nil {
http.Error(w, "Invalid email or password", http.StatusUnauthorized)
2025-01-14 04:12:55 +08:00
log.Println("Error during query execution:", err)
2025-01-14 03:18:08 +08:00
return
}
2025-01-14 04:12:55 +08:00
// if the email and password match then send back the user data
response := map[string]interface{}{
2025-01-14 03:18:08 +08:00
"id": id,
"email": email,
}
if err := json.NewEncoder(w).Encode(response); err != nil {
2025-01-14 03:18:08 +08:00
http.Error(w, "Failed to encode response as JSON", http.StatusInternalServerError)
log.Printf("JSON encoding error: %v", err)
}
}
2025-01-14 03:18:08 +08:00
// very secure login endpoint
func SecureLoginSql(w http.ResponseWriter, r *http.Request) {
2025-02-13 02:44:44 +08:00
log.Println("/secure-login-sql hit")
2024-11-11 18:47:15 +08:00
var requestData struct {
2025-01-14 03:18:08 +08:00
Email string `json:"email"`
Password string `json:"password"`
2024-11-11 18:47:15 +08:00
}
2025-02-13 02:44:44 +08:00
type AIResponse struct {
Confidence float64 `json:"confidence"`
Query string `json:"query"`
Result string `json:"result"`
}
2024-11-11 18:47:15 +08:00
// 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
2025-01-14 03:18:08 +08:00
if requestData.Email == "" || requestData.Password == "" {
2024-11-11 18:47:15 +08:00
http.Error(w, "Invalid request format", http.StatusBadRequest)
return
}
2025-01-14 03:18:08 +08:00
// retrieve list of existing emails from db
existingEmails, err := db.FetchEmails()
2024-11-11 17:34:37 +08:00
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
2025-01-14 03:18:08 +08:00
log.Printf("Failed to fetch emails: %v", err)
2024-11-11 17:34:37 +08:00
return
}
2025-01-14 03:18:08 +08:00
// check if the email exists in the allowed list
2024-11-11 17:34:37 +08:00
// this step is crucial
// server will reject ANYTHING that does not match the list
2025-01-14 03:18:08 +08:00
if !existingEmails[requestData.Email] {
http.Error(w, "Invalid email or password", http.StatusBadRequest)
2024-11-11 17:34:37 +08:00
return
}
2025-02-13 02:44:44 +08:00
// make insecure sql command just to send to ai model to check
insecureQuery := fmt.Sprintf("SELECT id, email, password FROM users WHERE email = '%s' AND password = '%s'", requestData.Email, requestData.Password)
// send the query to the ai model server
aiReqBody, _ := json.Marshal(map[string]string{"query": insecureQuery})
aiResp, err := http.Post("http://127.0.0.1:5000/predict", "application/json", bytes.NewBuffer(aiReqBody))
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Println("Failed to reach AI model:", err)
return
}
defer aiResp.Body.Close()
var aiResponse AIResponse
if err := json.NewDecoder(aiResp.Body).Decode(&aiResponse); err != nil {
http.Error(w, "Failed to decode AI response", http.StatusInternalServerError)
log.Println("Error decoding AI response:", err)
return
}
log.Println("AI Response:", aiResponse.Result)
// reject the original request if ai result is fail
if aiResponse.Result == "fail" {
http.Error(w, "Potential SQL injection detected", http.StatusForbidden)
log.Println("AI detected SQL injection attempt")
return
}
2025-01-14 03:18:08 +08:00
// retrieve the email and password from the database
2024-11-11 17:34:37 +08:00
var id int
2025-01-14 03:18:08 +08:00
var email string
2025-01-14 04:12:55 +08:00
var storedPassword string
2025-01-14 03:18:08 +08:00
query := "SELECT id, email, password FROM users WHERE email = $1"
2025-01-14 04:12:55 +08:00
err = db.DbPool.QueryRow(context.Background(), query, requestData.Email).Scan(&id, &email, &storedPassword)
2025-01-14 03:18:08 +08:00
if err != nil {
http.Error(w, "Invalid email or password", http.StatusNotFound)
return
}
2025-01-14 04:12:55 +08:00
// compare the provided password with the stored plain-text password
if storedPassword != requestData.Password {
2025-01-14 03:18:08 +08:00
http.Error(w, "Invalid email or password", http.StatusUnauthorized)
2024-11-11 17:34:37 +08:00
return
}
// send back the user data as a json response
response := map[string]interface{}{
2025-01-14 03:18:08 +08:00
"id": id,
"email": email,
2024-11-11 17:34:37 +08:00
}
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)
}
}