Autorizzazione dei cookie Golang

Aug 24 2020

Sto scrivendo il sistema di accesso in Go (Golang) utilizzando i cookie. Penso che non sia abbastanza sicuro. Puoi fornire alcuni suggerimenti su come migliorare la sicurezza.
File principale:

package main

import (
    "fmt"
    "golang.org/x/crypto/bcrypt"
    "html/template"
    "math/rand"
    "net/http"
    "strings"
    "time"
)

var (
    runes  = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890")
    t *template.Template
)

func init()  {
    rand.Seed(time.Now().Unix())
    t, _ = template.ParseFiles("main.html","signup.html","signin.html")
}

type User struct {
    Login, Email string
}



func genToken() string {
    s := make([]rune, 15)
    for i := range s {
        s[i] = runes[rand.Intn(len(runes))]
    }
    return string(s)
}


func setCookie(w http.ResponseWriter, name, value string,d int) {
    cookie := http.Cookie{
        Name:    name,
        Value:   value,
    }
    if d != 0{
        expires := time.Now().AddDate(0,0,d)
        cookie.Expires = expires
    }
    http.SetCookie(w, &cookie)
}

func getCookie(r *http.Request, name string) string {
    c, err := r.Cookie(name)
    if err != nil {
        return ""
    }
    return c.Value
}

func deleteCookie(w http.ResponseWriter,name string){
    cookie := http.Cookie{
        Name: name,
        MaxAge: -1,
    }
    http.SetCookie(w, &cookie)
}

func signup(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
      case "GET":
        t.ExecuteTemplate(w,"signup.html",nil)
      case "POST":
        r.ParseForm()
        data := r.Form
        var error string
        if data["login"][0] == ""{
            error = "Login can't be empty"
        } else if data["email"][0] == ""{
            error = "Email can't be empty"
        } else if data["password"][0] == ""{
            error = "Password cant't be empty"
        } else if len(data["login"][0]) < 4{
            error = "Login must be at least 4 characters"
        } else if DB.checkLogin(data["login"][0]){
            error = "User with such login already exists"
        } else if !strings.ContainsRune(data["email"][0],'@'){
            error = "Email must contain @"
        } else if DB.checkEmail(data["email"][0]) {
            error = "User with such email already exists"
        } else if len(data["password"][0]) < 8{
            error = "Password must be at least 8 characters"
        } else if data["password2"][0] != data["password"][0]{
            error = "Passwords don't match"
        }
        if error != ""{
            values :=&User{}
            values.Login = data["login"][0]
            values.Email = data["email"][0]
            t, err := template.ParseFiles("signup.html")
            if err != nil{
                http.Error(w,"Internal server error",500)
            }
            t.Execute(w,values)
            fmt.Fprintln(w,"<hr><span style='color:red;'>" + error + "</span>")
        } else {
            hashedPassword, err := bcrypt.GenerateFromPassword([]byte(data["password"][0]),10)
            if err != nil{
                http.Error(w,"Internal server error",500)
            }
            DB.newUser(data["login"][0],data["email"][0],string(hashedPassword))
            http.Redirect(w,r,"/login",http.StatusSeeOther)
        }
    }
}

func signin(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
      case "GET":
        t.ExecuteTemplate(w,"signin.html",nil)
      case "POST":
          r.ParseForm()
          data := r.Form
          var error string
          if !DB.checkLogin(data["login"][0]){
              error = "User with such login doesn't exists"
          } else {
              if !DB.checkPassword(data["login"][0],data["password"][0]){
                  error = "Wrong password"
              }
          }
          if error != ""{
              values :=&User{}
              values.Login = data["login"][0]
              t, err := template.ParseFiles("signin.html")
              if err != nil{
                  http.Error(w,"Internal server error",http.StatusInternalServerError)
              }
              t.Execute(w,values)
              fmt.Fprintln(w,"<hr><span style='color:red;'>" + error + "</span>")
          } else {
              expiresAfter := 0
              if r.FormValue("remember") == "1"{
                  expiresAfter = 30
              }
              token := genToken()
              setCookie(w,"login",data["login"][0],expiresAfter)
              setCookie(w,"session_token",token,expiresAfter)
              DB.newSession(data["login"][0],token)
              http.Redirect(w,r,"/",http.StatusSeeOther)
          }
    }
}

func mainPage(w http.ResponseWriter, r *http.Request) {
    login := getCookie(r,"login")
    token := getCookie(r,"session_token")
    if !DB.checkToken(login,token){
        http.Redirect(w,r,"/login",http.StatusSeeOther)
    }
    user := DB.getUser(login)
    t.ExecuteTemplate(w,"main.html",user)
}

func logout(w http.ResponseWriter,r *http.Request){
    login := getCookie(r,"login")
    token := getCookie(r,"session_token")
    deleteCookie(w,"login")
    deleteCookie(w,"session_token")
    DB.deleteSession(login,token)
    http.Redirect(w,r,"/login",http.StatusSeeOther)
}

func main() {
    http.HandleFunc("/register", signup)
    http.HandleFunc("/login", signin)
    http.HandleFunc("/", mainPage)
    http.HandleFunc("/logout",logout)
    http.ListenAndServe(":8080", nil)
}

File di database:

package main

import (
    "database/sql"
    "golang.org/x/crypto/bcrypt"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

var DB = newDB("root:root574@/signin")

type db struct {
    DB *sql.DB
}

func newDB(name string) *db {
    conn, err := sql.Open("mysql", name)
    if err != nil {
        log.Fatal(err)
    }
    if err = conn.Ping(); err != nil {
        log.Fatal(err)
    }
    return &db{DB: conn}
}

func (db db) newUser(login, email, password string) {
    db.DB.Exec("INSERT INTO users(login,email,password) VALUES (?,?,?)", login, email, password)
}

func (db db) newSession(login, token string) {
    db.DB.Exec("INSERT INTO sessions(login,token) VALUES (?,?)",login,token)
}

func (db db) deleteSession(login, token string) {
    db.DB.Exec("DELETE FROM sessions WHERE login = ? and session_token = ?",login,token)
}

func (db db) checkLogin(login string) bool {
    var rows, _ = db.DB.Query("SELECT id FROM users WHERE login = ?", login)
    if rows.Next() {
        return true
    }
    rows.Close()
    return false
}

func (db db) checkEmail(email string) bool {
    var rows, _ = db.DB.Query("SELECT id FROM users WHERE email = ?", email)
    if rows.Next() {
        return true
    }
    rows.Close()
    return false
}

func (db db) checkPassword(login, password string) bool{
    var rows, _ = db.DB.Query("SELECT password FROM users WHERE login = ?", login)
    var dbpassword string
    rows.Next()
    rows.Scan(&dbpassword)
    rows.Close()
    if bcrypt.CompareHashAndPassword([]byte(dbpassword),[]byte(password)) != nil{
        return false
    }
    return true
}

func (db db) checkToken(login, token string) bool {
    var rows, _ = db.DB.Query("SELECT token FROM sessions WHERE login = ? and token = ?",login,token)
    if rows.Next(){
        return true
    }
    rows.Close()
    return false
}

func (db db) getUser(login string) *User {
    var rows, _ = db.DB.Query("select email FROM users WHERE login = ?",login)
    user := &User{}
    rows.Next()
    rows.Scan(&user.Email)
    rows.Close()
    user.Login = login
    return user
}

Tabella utenti database:

+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| id       | int          | NO   | PRI | NULL    | auto_increment |
| login    | varchar(255) | YES  |     | NULL    |                |
| email    | varchar(255) | YES  |     | NULL    |                |
| password | varchar(255) | YES  |     | NULL    |                |
+----------+--------------+------+-----+---------+----------------+

Tabella delle sessioni del database:

+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| login | varchar(255) | YES  |     | NULL    |       |
| token | varchar(15)  | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+

Risposte

2 Ludisposed Aug 27 2020 at 13:17

Non utilizzare mai valori casuali PSEUDO per i segreti!

Questo potrebbe essere sfruttato da un attaccante indovinando il valore del seme, in questo caso:

rand.Seed(time.Now().Unix())

E quindi generare un possibile valore di Cookie per accedere.

Usa invece implementazioni crittograficamente sicure, in golang dovresti usare crypto/rand