281 lines
9 KiB
Go
281 lines
9 KiB
Go
package sql_injection
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"regexp"
|
|
|
|
"github.com/Vomitblood/cspj-application/server/internal/db"
|
|
)
|
|
|
|
// secure register endpoint
|
|
func UnsecureRegisterSql(w http.ResponseWriter, r *http.Request) {
|
|
// 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
|
|
// 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)
|
|
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
|
|
// also use concatenation here
|
|
insertSQL := fmt.Sprintf("INSERT INTO users (email, password, role) VALUES ('%s', '%s', 'user')", credentials.Email, credentials.Password)
|
|
_, err = db.DbPool.Exec(context.Background(), insertSQL)
|
|
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)
|
|
}
|
|
|
|
// secure register endpoint
|
|
func SecureRegisterSql(w http.ResponseWriter, r *http.Request) {
|
|
// 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
|
|
// use parameterization
|
|
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
|
|
// use parameterization
|
|
insertSQL := `INSERT INTO users (email, password, role) VALUES ($1, $2, $3)`
|
|
_, err = db.DbPool.Exec(context.Background(), insertSQL, credentials.Email, credentials.Password, "user")
|
|
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)
|
|
}
|
|
|
|
func UnsecureLoginSql(w http.ResponseWriter, r *http.Request) {
|
|
log.Println("/unsecure-login-sql hit")
|
|
// decode the json body
|
|
var requestData struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// decode the request body
|
|
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:", err)
|
|
return
|
|
}
|
|
|
|
// 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
|
|
var email string
|
|
var password string
|
|
|
|
// execute the query to retrieve the data
|
|
err := db.DbPool.QueryRow(context.Background(), query).Scan(&id, &email, &password)
|
|
if err != nil {
|
|
http.Error(w, "Invalid email or password", http.StatusUnauthorized)
|
|
log.Println("Error during query execution:", err)
|
|
return
|
|
}
|
|
|
|
// if the email and password match then send back the user data
|
|
response := map[string]interface{}{
|
|
"id": id,
|
|
"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)
|
|
}
|
|
}
|
|
|
|
// very secure login endpoint
|
|
func SecureLoginSql(w http.ResponseWriter, r *http.Request) {
|
|
log.Println("/secure-login-sql hit")
|
|
var requestData struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type AIResponse struct {
|
|
Confidence float64 `json:"confidence"`
|
|
Query string `json:"query"`
|
|
Result string `json:"result"`
|
|
}
|
|
|
|
// 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.Email == "" || requestData.Password == "" {
|
|
http.Error(w, "Invalid request format", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// retrieve list of existing emails from db
|
|
existingEmails, err := db.FetchEmails()
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
log.Printf("Failed to fetch emails: %v", err)
|
|
return
|
|
}
|
|
|
|
// check if the email exists in the allowed list
|
|
// this step is crucial
|
|
// server will reject ANYTHING that does not match the list
|
|
if !existingEmails[requestData.Email] {
|
|
http.Error(w, "Invalid email or password", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// retrieve the email and password from the database
|
|
var id int
|
|
var email string
|
|
var storedPassword string
|
|
query := "SELECT id, email, password FROM users WHERE email = $1"
|
|
err = db.DbPool.QueryRow(context.Background(), query, requestData.Email).Scan(&id, &email, &storedPassword)
|
|
if err != nil {
|
|
http.Error(w, "Invalid email or password", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// compare the provided password with the stored plain-text password
|
|
if storedPassword != requestData.Password {
|
|
http.Error(w, "Invalid email or password", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// send back the user data as a json response
|
|
response := map[string]interface{}{
|
|
"id": id,
|
|
"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)
|
|
}
|
|
}
|