Session Fixation in Fiber with Cockroachdb
Session Fixation in Fiber with Cockroachdb — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an application assigns a user a session identifier before authentication and does not regenerate it after login. In a Fiber application using Cockroachdb as the session store, this typically happens when session IDs are created early—often via a cookie set before login—and then persisted in Cockroachdb without being rotated post-authentication.
With Cockroachdb, the session record is stored in a distributed SQL table keyed by the session ID. If the client supplies the pre-auth session ID and the server reuses that same ID after login, the attacker can craft a URL with a known session value (e.g., ?sessionid=attackerchosen) and trick a victim into authenticating under that ID. Because Cockroachdb retains the session row, the attacker can later use the same ID to hijack the authenticated session, provided the session data is not properly invalidated or rotated.
The risk is compounded if:
- The session cookie is created before login with a predictable or non-rotated ID.
- Session rows in Cockroachdb are not invalidated on privilege change (e.g., elevation to authenticated).
- Concurrent session controls are absent, allowing the same session ID to be used from multiple locations without invalidation.
In a black-box scan, middleBrick tests for session fixation by observing whether authentication changes the session identifier and whether prior session data remains accessible. For Fiber apps with Cockroachdb, findings often highlight missing session rotation after login and overly permissive session lookup queries that do not enforce proper ownership checks.
Cockroachdb-Specific Remediation in Fiber — concrete code fixes
Remediation centers on ensuring a new, cryptographically random session ID is issued after successful authentication and that the old Cockroachdb session row is invalidated. Below is a secure pattern using fiber and cockroachdb.
// main.go
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"net/http"
"time"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
type Session struct {
ID string \`json:"id\"`
UserID string \`json:"user_id\"`
ExpiresAt time.Time \`json:"expires_at\"`
}
func main() {
ctx := context.Background()
connPool, err := pgxpool.New(ctx, "postgresql://username:password@host:26257/sessiondb?sslmode=require")
if err != nil {
log.Fatalf("Unable to connect to Cockroachdb: %v", err)
}
defer connPool.Close()
app := fiber.New()
app.Post("/login", func(c *fiber.Ctx) error {
var creds struct {
Email string `json:"email"`
Password string `json:"password"`
}
if err := c.BodyParser(&creds); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request"})
}
// Validate credentials against users table (omitted for brevity)
userID, err := authenticateUser(ctx, connPool, creds.Email, creds.Password)
if err != nil || userID == "" {
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"error": "invalid credentials"})
}
// Invalidate any pre-existing session for this user (important for fixation prevention)
if err := invalidateUserSessions(ctx, connPool, userID); err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "unable to clear existing sessions"})
}
// Create a new, random session ID after authentication
newSessionID, err := generateSecureSessionID()
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "session generation failed"})
}
session := Session{
ID: newSessionID,
UserID: userID,
ExpiresAt: time.Now().Add(24 * time.Hour),
}
// Store the new session in Cockroachdb
if _, err := connPool.Exec(ctx, `
INSERT INTO sessions (id, user_id, expires_at)
VALUES ($1, $2, $3) ON CONFLICT (id) DO NOTHING`,
session.ID, session.UserID, session.ExpiresAt); err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "unable to create session"})
}
// Set secure cookie with the new session ID
c.Cookie(&fiber.Cookie{
Name: "session_id",
Value: session.ID,
Expires: session.ExpiresAt,
HTTPOnly: true,
Secure: true,
SameSite: fiber.CookieSameSiteStrictMode,
})
return c.JSON(fiber.Map{"message": "login successful"})
})
app.Listen(":3000")
}
func generateSecureSessionID() (string, error) {
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func authenticateUser(ctx context.Context, pool *pgxpool.Pool, email, password string) (string, error) {
var userID string
row := pool.QueryRow(ctx, "SELECT id FROM users WHERE email = $1 AND password_hash = crypt($2, password_hash) LIMIT 1", email, password)
err := row.Scan(&userID)
if err != nil {
return "", err
}
return userID, nil
}
func invalidateUserSessions(ctx context.Context, pool *pgxpool.Pool, userID string) error {
_, err := pool.Exec(ctx, "DELETE FROM sessions WHERE user_id = $1", userID)
return err
}
Key points specific to Cockroachdb:
- Use
ON CONFLICT (id) DO NOTHINGto avoid race conditions when inserting the new session, leveraging Cockroachdb’s strong consistency for conditional inserts. - Invalidate all prior sessions for the user before issuing the new ID to prevent fixation via lingering rows.
- Ensure session lookup queries in handlers include user ownership checks (e.g.,
SELECT ... FROM sessions WHERE id = $1 AND user_id = $2) to avoid horizontal privilege escalation.
middleBrick scans can help detect missing session rotation and overly permissive session queries in unauthenticated sweeps, providing prioritized findings with remediation guidance.