CVE-2024-52801
Description
sftpgo is a full-featured and highly configurable event-driven file transfer solution. Server protocols: SFTP, HTTP/S, FTP/S, WebDAV. The OpenID Connect implementation allows authenticated users to brute force session cookies and thereby gain access to other users' data, since the cookies are generated predictably using the xid library and are therefore unique but not cryptographically secure. This issue was fixed in version v2.6.4, where cookies are opaque and cryptographically secure strings. All users are advised to upgrade. There are no known workarounds for this vulnerability.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/drakkan/sftpgo/v2Go | >= 2.3.0, < 2.6.4 | 2.6.4 |
Patches
2386448e6cbe4f30a9a2095bfOIDC cookie: use a cryptographically secure random string
4 files changed · +24 −25
internal/httpd/oauth2.go+1 −5 modified@@ -15,8 +15,6 @@ package httpd import ( - "crypto/sha256" - "encoding/hex" "encoding/json" "errors" "sync" @@ -53,10 +51,8 @@ type oauth2PendingAuth struct { } func newOAuth2PendingAuth(provider int, redirectURL, clientID string, clientSecret *kms.Secret) oauth2PendingAuth { - state := sha256.Sum256(util.GenerateRandomBytes(32)) - return oauth2PendingAuth{ - State: hex.EncodeToString(state[:]), + State: util.GenerateOpaqueString(), Provider: provider, ClientID: clientID, ClientSecret: clientSecret,
internal/httpd/oidc.go+3 −8 modified@@ -16,8 +16,6 @@ package httpd import ( "context" - "crypto/sha256" - "encoding/hex" "errors" "fmt" "net/http" @@ -204,12 +202,9 @@ type oidcPendingAuth struct { } func newOIDCPendingAuth(audience tokenAudience) oidcPendingAuth { - state := sha256.Sum256(util.GenerateRandomBytes(32)) - nonce := util.GenerateUniqueID() - return oidcPendingAuth{ - State: hex.EncodeToString(state[:]), - Nonce: nonce, + State: util.GenerateOpaqueString(), + Nonce: util.GenerateOpaqueString(), Audience: audience, IssuedAt: util.GetTimeAsMsSinceEpoch(time.Now()), } @@ -684,7 +679,7 @@ func (s *httpdServer) handleOIDCRedirect(w http.ResponseWriter, r *http.Request) RefreshToken: oauth2Token.RefreshToken, IDToken: rawIDToken, Nonce: idToken.Nonce, - Cookie: xid.New().String(), + Cookie: util.GenerateOpaqueString(), } if !oauth2Token.Expiry.IsZero() { token.ExpiresAt = util.GetTimeAsMsSinceEpoch(oauth2Token.Expiry)
internal/httpd/oidc_test.go+11 −11 modified@@ -152,8 +152,8 @@ func TestOIDCLoginLogout(t *testing.T) { assert.Contains(t, rr.Body.String(), util.I18nInvalidAuth) expiredAuthReq := oidcPendingAuth{ - State: xid.New().String(), - Nonce: xid.New().String(), + State: util.GenerateOpaqueString(), + Nonce: util.GenerateOpaqueString(), Audience: tokenAudienceWebClient, IssuedAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-10 * time.Minute)), } @@ -564,7 +564,7 @@ func TestOIDCRefreshToken(t *testing.T) { r, err := http.NewRequest(http.MethodGet, webUsersPath, nil) assert.NoError(t, err) token := oidcToken{ - Cookie: xid.New().String(), + Cookie: util.GenerateOpaqueString(), AccessToken: xid.New().String(), TokenType: "Bearer", ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-1 * time.Minute)), @@ -668,7 +668,7 @@ func TestOIDCRefreshToken(t *testing.T) { func TestOIDCRefreshUser(t *testing.T) { token := oidcToken{ - Cookie: xid.New().String(), + Cookie: util.GenerateOpaqueString(), AccessToken: xid.New().String(), TokenType: "Bearer", ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Minute)), @@ -782,7 +782,7 @@ func TestValidateOIDCToken(t *testing.T) { }, } token := oidcToken{ - Cookie: xid.New().String(), + Cookie: util.GenerateOpaqueString(), AccessToken: xid.New().String(), ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-2 * time.Minute)), } @@ -798,8 +798,8 @@ func TestValidateOIDCToken(t *testing.T) { server.tokenAuth = jwtauth.New("PS256", util.GenerateRandomBytes(32), nil) token = oidcToken{ - Cookie: xid.New().String(), - AccessToken: xid.New().String(), + Cookie: util.GenerateOpaqueString(), + AccessToken: util.GenerateUniqueID(), } oidcMgr.addToken(token) rr = httptest.NewRecorder() @@ -813,7 +813,7 @@ func TestValidateOIDCToken(t *testing.T) { assert.Len(t, oidcMgr.tokens, 0) token = oidcToken{ - Cookie: xid.New().String(), + Cookie: util.GenerateOpaqueString(), AccessToken: xid.New().String(), Role: "admin", } @@ -1107,7 +1107,7 @@ func TestMemoryOIDCManager(t *testing.T) { AccessToken: xid.New().String(), Nonce: xid.New().String(), SessionID: xid.New().String(), - Cookie: xid.New().String(), + Cookie: util.GenerateOpaqueString(), Username: xid.New().String(), Role: "admin", Permissions: []string{dataprovider.PermAdminAny}, @@ -1157,7 +1157,7 @@ func TestMemoryOIDCManager(t *testing.T) { token.UsedAt = usedAt oidcMgr.tokens[token.Cookie] = token newToken := oidcToken{ - Cookie: xid.New().String(), + Cookie: util.GenerateOpaqueString(), } oidcMgr.addToken(newToken) oidcMgr.cleanup() @@ -1663,7 +1663,7 @@ func TestDbOIDCManager(t *testing.T) { } token := oidcToken{ - Cookie: xid.New().String(), + Cookie: util.GenerateOpaqueString(), AccessToken: xid.New().String(), TokenType: "Bearer", RefreshToken: xid.New().String(),
internal/util/util.go+9 −1 modified@@ -22,8 +22,10 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/tls" "crypto/x509" + "encoding/hex" "encoding/json" "encoding/pem" "errors" @@ -550,7 +552,7 @@ func createDirPathIfMissing(file string, perm os.FileMode) error { return nil } -// GenerateRandomBytes generates the secret to use for JWT auth +// GenerateRandomBytes generates random bytes with the specified length func GenerateRandomBytes(length int) []byte { b := make([]byte, length) _, err := io.ReadFull(rand.Reader, b) @@ -560,6 +562,12 @@ func GenerateRandomBytes(length int) []byte { return b } +// GenerateOpaqueString generates a cryptographically secure opaque string +func GenerateOpaqueString() string { + randomBytes := sha256.Sum256(GenerateRandomBytes(32)) + return hex.EncodeToString(randomBytes[:]) +} + // GenerateUniqueID returns an unique ID func GenerateUniqueID() string { u, err := uuid.NewRandom()
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.