Sicherheit & Robustheit härten
- SQLite-DSN: busy_timeout(5000) + WAL + foreign_keys gegen SQLITE_BUSY - uid ist nie 0 mehr (0 = Sentinel für gelöscht/anonym), Retry bei Kollision - scanEntries gibt Scan-Fehler zurück statt Zeilen still zu überspringen - Session-timeout serverseitig auf 30 Tage gedeckelt (clientgesteuert) - Bild-Upload: Dimensionen vor Decode prüfen (Decompression-Bomb-Schutz) - Secure-Cookie via TLS-Erkennung (r.TLS / X-Forwarded-Proto) - Rate-Limit (httprate, 20/min/IP) auf Login & Registrierung - Vote nur auf existierende Beiträge (keine Waisen-Votes) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
_ "image/png"
|
||||
"io"
|
||||
"math/rand/v2"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
@@ -48,7 +49,7 @@ func scanEntries(rows *sql.Rows) ([]Entry, error) {
|
||||
var e Entry
|
||||
if err := rows.Scan(&e.PID, &e.UID, &e.CreatedAt, &e.Content, &e.Filepath,
|
||||
&e.ReplyTo, &e.ReplyCount, &e.LastActivity, &e.Deleted, &e.Username); err != nil {
|
||||
continue
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
@@ -393,6 +394,12 @@ func handleVote(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nur auf existierende Beiträge abstimmen, sonst entstehen Waisen-Votes.
|
||||
if _, err := entryByPID(pid); err != nil {
|
||||
writeError(w, http.StatusNotFound, "Beitrag nicht gefunden")
|
||||
return
|
||||
}
|
||||
|
||||
var prev string
|
||||
readErr := db.QueryRow(`SELECT mode FROM vote WHERE uid = ? AND pid = ?`, uid, pid).Scan(&prev)
|
||||
if readErr != nil && readErr != sql.ErrNoRows {
|
||||
@@ -428,6 +435,21 @@ func storeImage(fh *multipart.FileHeader) (string, error) {
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// Dimensionen aus dem Header prüfen, bevor wir die vollen Pixel allokieren:
|
||||
// eine kleine Datei kann riesige Maße deklarieren (Decompression-Bomb) und
|
||||
// sonst beim Decode den Speicher sprengen.
|
||||
const maxPixels = 50_000_000 // ~50 MP
|
||||
cfg, _, err := image.DecodeConfig(src)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if cfg.Width <= 0 || cfg.Height <= 0 || cfg.Width*cfg.Height > maxPixels {
|
||||
return "", fmt.Errorf("bild zu groß: %dx%d", cfg.Width, cfg.Height)
|
||||
}
|
||||
if _, err := src.Seek(0, io.SeekStart); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
img, format, err := image.Decode(src)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
Reference in New Issue
Block a user