VYPR
Medium severityNVD Advisory· Published Nov 29, 2024· Updated Apr 15, 2026

CVE-2024-52801

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.

PackageAffected versionsPatched versions
github.com/drakkan/sftpgo/v2Go
>= 2.3.0, < 2.6.42.6.4

Patches

2
f30a9a2095bf

OIDC cookie: use a cryptographically secure random string

https://github.com/drakkan/sftpgoNicola MurinoNov 20, 2024via ghsa
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

4

News mentions

0

No linked articles in our index yet.