Broken Authentication

Broken Authentication in Go (Gin): Remediation Guide [CVE-2026-41571]

[Fixed month year] Updated CVE-2026-41571

Overview

CVE-2026-41571 describes a Broken Authentication vulnerability in Note Mark where, in v0.19.2, IsPasswordMatch falls back to a hard-coded bcrypt("null") placeholder when a user has no stored password. Since OIDC-registered users are created with empty passwords, attackers can submit password: "null" to the internal login endpoint and obtain a valid session. This unauthenticated bypass is fixed in v0.19.3. This pattern maps to CWE-287 (Improper Authentication). In Go with Gin, the vulnerability typically occurs when the code treats missing passwords as if they were valid by comparing the provided password against a static placeholder hash, enabling login without a real credential. The result is an authentication bypass that undermines identity verification and session integrity.

Affected Versions

0.19.2

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
  "net/http"
  "github.com/gin-gonic/gin"
  "golang.org/x/crypto/bcrypt"
)

type User struct {
  Username     string
  PasswordHash string
}

var users map[string]*User
var placeholderHash string

func init() {
  // Placeholder hash corresponds to the password "null" used in vulnerable code paths
  ph, _ := bcrypt.GenerateFromPassword([]byte("null"), bcrypt.DefaultCost)
  placeholderHash = string(ph)

  users = map[string]*User{
    "alice": {Username: "alice", PasswordHash: ""}, // no password configured
    "bob":   {Username: "bob", PasswordHash: hashOf("s3cr3t")}, // proper password
  }
}

func hashOf(pw string) string {
  h, _ := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
  return string(h)
}

func main() {
  r := gin.Default()
  r.POST("/vuln/login", vulnerableLogin)
  r.POST("/fix/login", fixedLogin)
  _ = r.Run(":8080")
}

func vulnerableLogin(c *gin.Context) {
  type cred struct { Username string; Password string }
  var in cred
  if err := c.BindJSON(&in); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "bad"})
    return
  }
  u := users[in.Username]
  if u == nil {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
    return
  }
  hash := u.PasswordHash
  if hash == "" {
    // Vulnerable: fall back to placeholder hash for empty passwords
    hash = placeholderHash
  }
  if bcrypt.CompareHashAndPassword([]byte(hash), []byte(in.Password)) != nil {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
    return
  }
  c.JSON(http.StatusOK, gin.H{"status": "logged_in"})
}

func fixedLogin(c *gin.Context) {
  type cred struct { Username string; Password string }
  var in cred
  if err := c.BindJSON(&in); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "bad"})
    return
  }
  u := users[in.Username]
  if u == nil {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
    return
  }
  // Fixed: do not allow login when password is not configured
  if u.PasswordHash == "" {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "no password configured"})
    return
  }
  if bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(in.Password)) != nil {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
    return
  }
  c.JSON(http.StatusOK, gin.H{"status": "logged_in"})
}

CVE References

Choose which optional cookies to allow. You can change this any time.