Hello again,

This is a short tl;dr of the account lockout bypass via case permutation vulnerability tracked as CVE-2025-6004 outlined in cyala.ai - Cracking the Vault.

Here is the tl;dr:

  • Account lockouts are tracked in a map, where the keys contain the case-sensitive username originating from the request.
  • Login attempts perform user lookups based on the lower-cased username.
  • By altering the case of the username the account lockout mechanism is bypassed while still targeting the correct user account.

To demonstrate, the table below highlights the username field and how it is seen by the lockout handler and the login handler:

RequestLockout HandlerLogin Handler
adminadminadmin
ADMINADMINadmin
AdMiNAdMiNadmin

Identifying this flaw by just reading the Vault source code was quite a challenge for me. I’ve yet a lot to learn reading non-trivial go apps. That being said, I decided to write a boiled down version of the vulnerability for shits and giggles:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// tl;dr CVE-2025-6004
package main

import (
	"errors"
	"fmt"
	"strings"
)

type Locker struct {
	storage map[string]int
}

func (r *Locker) Increment(username string) {
	_, ok := r.storage[username]
	if !ok {
		r.storage[username] = 1
	} else {
		r.storage[username]++
	}
}

func (r *Locker) IsLocked(username string) bool {
	failedAttempts, ok := r.storage[username]
	if !ok {
		fmt.Printf("[lock-check] username=%#v is NOT locked.\n", username)
		return false
	}
	if failedAttempts < 3 {
		fmt.Printf("[lock-check] username=%#v is NOT locked.\n", username)
		return false
	}

	fmt.Printf("[lock-check] username=%#v is locked.\n", username)
	return true
}

type User struct {
	Username string
	Password string
}

type LoginLocker struct {
	locker      Locker
	userStorage map[string]*User
}

func (ll *LoginLocker) Login(username string, password string) error {
	// lock check is performed on the **case-sensitive** username.
	if ll.locker.IsLocked(username) {
		return errors.New("user account locked")
	}

	// Fetch user based on **case-insensitive** username.
	userKey := strings.ToLower(username)
	user, ok := ll.userStorage[userKey]
	if !ok {
		return errors.New("invalid username or password")
	}
	if user == nil || user.Password != password {
		ll.locker.Increment(username)
		return errors.New("invalid username or password")
	}
	return nil
}

func main() {
	l := LoginLocker{
		locker: Locker{storage: make(map[string]int, 0)},
		userStorage: map[string]*User{
			"admin": {Username: "admin", Password: "correct"},
		},
	}

	// Trigger account lock.
	for range 4 {
		err := l.Login("admin", "admin")
		if err != nil {
			fmt.Printf("failed to authenticate: %s\n", err.Error())
		}
	}

	// Bypass the account lock by switching username case.
	err := l.Login("Admin", "correct")
	if err != nil {
		fmt.Printf("failed to authenticate: %s\n", err.Error())
	}
}

’til next time.