Bezbednost: open redirect i kolačići bez Secure atributa

- _next parametar: sanitizacija preko url.Parse (Host+Scheme prazan = relativan URL)
  umesto ručnog string check-a koji CodeQL nije prepoznavao
- Kolačići: dodat Secure atribut (true u produkciji, false u razvoju)
  na 4 mesta: ntech_sesija brisanje (auth.go, prijava.go),
  ntech_flash_greska postavljanje i brisanje (auth.go, dashboard.go)
This commit is contained in:
2026-06-16 03:32:07 +02:00
parent f9af825a27
commit 532f95848c
4 changed files with 30 additions and 16 deletions
+7 -4
View File
@@ -3,6 +3,7 @@ package handler
import ( import (
"log/slog" "log/slog"
"net/http" "net/http"
"os"
appdb "ntech/internal/db" appdb "ntech/internal/db"
"ntech/internal/db/sqlite" "ntech/internal/db/sqlite"
@@ -19,10 +20,12 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
if kol, err := r.Cookie("ntech_flash_greska"); err == nil { if kol, err := r.Cookie("ntech_flash_greska"); err == nil {
flashGreska = kol.Value flashGreska = kol.Value
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "ntech_flash_greska", Name: "ntech_flash_greska",
Value: "", Value: "",
Path: "/", Path: "/",
MaxAge: -1, MaxAge: -1,
HttpOnly: true,
Secure: os.Getenv("NTECH_ENV") == "production",
}) })
} }
+7 -2
View File
@@ -7,6 +7,7 @@ import (
"io" "io"
"log/slog" "log/slog"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -264,8 +265,12 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
} }
sledeci := "/podesavanja" sledeci := "/podesavanja"
if next := r.FormValue("_next"); strings.HasPrefix(next, "/") && (len(next) == 1 || (next[1] != '/' && next[1] != '\\')) { if next := r.FormValue("_next"); next != "" {
sledeci = next if u, err := url.Parse(next); err == nil && u.Host == "" && u.Scheme == "" {
if p := u.RequestURI(); p != "" {
sledeci = p
}
}
} }
// backup podešavanja — pri neispravnom unosu javljamo jasnu grešku // backup podešavanja — pri neispravnom unosu javljamo jasnu grešku
+7 -5
View File
@@ -262,11 +262,13 @@ func (h *Handler) Odjava(w http.ResponseWriter, r *http.Request) {
_ = h.SesijeRepo.Obrisi(r.Context(), kolacic.Value) _ = h.SesijeRepo.Obrisi(r.Context(), kolacic.Value)
} }
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: imeKolacica, Name: imeKolacica,
Value: "", Value: "",
Path: "/", Path: "/",
Expires: time.Unix(0, 0), Expires: time.Unix(0, 0),
MaxAge: -1, MaxAge: -1,
Secure: os.Getenv("NTECH_ENV") == "production",
HttpOnly: true,
}) })
http.Redirect(w, r, "/prijava", http.StatusSeeOther) http.Redirect(w, r, "/prijava", http.StatusSeeOther)
} }
+9 -5
View File
@@ -5,6 +5,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"net/http" "net/http"
"os"
"time" "time"
"ntech/internal/db/sqlite" "ntech/internal/db/sqlite"
@@ -34,11 +35,13 @@ func RequireAuth(db *sql.DB, totpKljuc []byte) func(http.Handler) http.Handler {
if err != nil { if err != nil {
// nevažeći token — briši kolačić i preusmeri // nevažeći token — briši kolačić i preusmeri
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "ntech_sesija", Name: "ntech_sesija",
Value: "", Value: "",
Path: "/", Path: "/",
Expires: time.Unix(0, 0), Expires: time.Unix(0, 0),
MaxAge: -1, MaxAge: -1,
Secure: os.Getenv("NTECH_ENV") == "production",
HttpOnly: true,
}) })
http.Redirect(w, r, "/prijava", http.StatusSeeOther) http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return return
@@ -154,6 +157,7 @@ func postaviFlashGresku(w http.ResponseWriter, poruka string) {
Path: "/", Path: "/",
MaxAge: 60, MaxAge: 60,
HttpOnly: true, HttpOnly: true,
Secure: os.Getenv("NTECH_ENV") == "production",
}) })
} }