Sessions: Les magasins tiers n'appliquent pas correctement les options aux codecs.

Créé le 10 juil. 2015  ·  17Commentaires  ·  Source: gorilla/sessions

Nous venons de mettre à niveau ce package et il ne définit pas correctement les cookies. Il définit des éléments tels que _ga=GA1.1.922831813.14264788986 au lieu des données de cookie encodées en base64. Avez-vous une idée de ce qui se passe ou du moment où cela s'est cassé afin que nous puissions revenir à une version fonctionnelle ? Merci!

bug stale

Commentaire le plus utile

Notez que cela affecte également la majorité des implémentations de magasins, car ils utilisent tous securecookie pour enregistrer l'ID dans leur magasin principal.

redistore a la moitié d'une implémentation correcte si vous appelez manuellement SetMaxAge mais ne l'applique pas directement pour le cas par défaut. Il se trouve que la valeur par défaut (30 jours) est la même que celle de securecookie.

D'un coup d'œil rapide, il semble que _tous_ les autres magasins soient affectés - la définition d'un MaxAge via la structure Options n'est pas appliquée aux instances de Securecookie sous-jacentes générées à partir de CodecsFromPairs. La plupart des utilisateurs finaux n'auraient pas rencontré ce bogue, car je soupçonne que la plupart des utilisateurs définissent des dates d'expiration inférieures à celles que securecookie intègre implicitement dans le HMAC.

FWIW : Je pense également que les expirations de cookies dans un « avenir lointain » ne sont pas idéales à moins que vous n'en ayez un besoin très spécifique.

Tous les 17 commentaires

Le cookie "_ga" n'est pas lié aux gorilles/sessions - c'est un Google Analytics
biscuit.

Si gorilla/sessions ne définit pas de cookies (il n'y a eu aucune rupture
modifications) pouvez-vous publier le code correspondant et la sortie du volet Cookies
depuis votre navigateur ? (inspecteur > ressources > cookies sous Chrome)

Le ven. 10 juil. 2015 à 7h12 marksalpeter [email protected]
a écrit:

Nous venons de mettre à niveau ce package et il ne définit pas correctement les cookies. C'est
définir des trucs comme _ga=GA1.1.922831813.14264788986 au lieu de la normale
données de cookie encodées en base64. Une idée de ce qui se passe ou quand cela s'est cassé alors
pouvons-nous revenir à une version de travail? Merci!

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/gorilla/sessions/issues/48.

Salut Matt,

Je travaille également sur le projet avec Mark et je peux vous donner plus de détails. Nous venons donc de mettre à jour le package car le problème d'origine était que les cookies expiraient tous après 30 jours, même lorsque nous définissions MaxAge sur un très grand nombre. Voici le code d'authentification qui est en ligne actuellement et qui nous pose problème :

const (
AUTH_SESSION_NAME = "session d'authentification"
)

var (
config = jconfig.LoadConfig( global.CONFIG() + "securecookie.json" )
authKey = []byte(config.GetString("authorization_key"))
cryptageKey = []byte(config.GetString("encryption_key"))
session = sessions.NewCookieStore(authKey, cryptageKey)
)

func init() {

apiRouter.HandleFunc("/auth/"   , Authenticate      )
apiRouter.HandleFunc("/deauth/"  , Deauthenticate   )
apiRouter.HandleFunc("/reauth/" , ReAuthenticate    )

// register complex data types for saving in sessions
gob.Register(&models.Device{})
gob.Register(&models.Manager{})
gob.Register(&models.SalesRep{})

// modify the options of the session store so that the auth cookie never expires (this is set so it expires in 200 years...)
session.Options.MaxAge = 6307200000

}

// ce gestionnaire accorde des autorisations dans le système pour les appareils et les gestionnaires
// si le demandeur est déjà autorisé, il affichera ses informations. Pour autoriser un différent
// compte, le client doit d'abord se désauthentifier.
//
// Paramètres
// utilisateur:
//
// remarque : les données renvoyées par cette fonction d'authentification ne sont garanties que pour être exactes
// lorsque l'utilisateur s'authentifie pour la première fois. sinon il affichera les données mises en cache dans leur session
//
func Authenticate (réponse http.ResponseWriter, demande *http.Request) {

log.Println("Authenticate")

// open the session
auth_session, err :=  session.Get(request, AUTH_SESSION_NAME)
if err != nil {
    log.Println("there was an error retreiving the session:", err)
    InternalServerError(response, request)
    return
}

// already authroized as a manager
if manager, ok := auth_session.Values["manager"].(*models.Manager); ok {
    log.Printf("already logged in as manager %d", manager.Id)
    Success(response, request, manager)

// already authrorized as a device
} else if device, ok := auth_session.Values["device"].(*models.Device); ok {
    log.Printf("already logged in as device %d", device.Id)
    Success(response, request, device)

// attempt to gain authroization
} else {            
    fp  := parsers.FormParser(request)  

    // login as a manager
    if username, password := fp.GetString("user", ""), fp.GetString("pass", ""); username != "" && password != ""  {

        db := database.Open()
        defer db.Close()


        if manager := db.LoginAsManager(username, password); manager != nil {
            auth_session.Values["manager"] = manager

            // manager session error
            if err := auth_session.Save(request, response); err != nil {
                log.Printf("manager could not save session %s !\n", err.Error())
                InternalServerError(response, request)

            // manager login in success
            } else {
                log.Printf("logged in as manager %d !\n", manager.Id)
                Success(response, request, manager)     
            }

        // manager login failed
        } else {
            log.Printf("manager credentials not valid!")
            Unauthroized(response, request)
        }

    // login as a salesrep
    } else if username, password := fp.GetString("username", ""), fp.GetString("password", ""); username != "" && password != "" {

        db := database.Open()
        defer db.Close()


        if sales_rep_id := db.LoginAsSalesRep(username, password); sales_rep_id > 0 {

            sales_reps := db.GetSalesRep(&models.SalesRep{ Id: sales_rep_id });

            auth_session.Values["sales_rep"] = sales_reps[0];

            // sales rep session error
            if err := auth_session.Save(request, response); err != nil {
                log.Printf("sales rep could not save session %s !\n", err.Error())
                InternalServerError(response, request)

            // sales rep login in success
            } else {
                log.Printf("logged in as sales rep %d !\n", manager.Id)
                Success(response, request, manager)     
            }

        // sales rep login failed
        } else {
            log.Printf("sales rep credentials not valid!")
            Unauthroized(response, request)
        }

    // login as a device
    } else if pin_code := fp.GetInt32("PinCode", -1); pin_code > 0 {

        db := database.Open()
        defer db.Close()

        if devices := db.GetDevice(&models.Device{ PinCode:pin_code }, nil); len(devices) > 0 {
            auth_session.Values["device"] = &devices[0]

            // device session error
            if err := auth_session.Save(request, response); err != nil {
                log.Printf("device could not save session %s !\n", err.Error())
                InternalServerError(response, request)

            // device login in success
            } else {
                log.Printf("logged in as device %d !\n", devices[0].Id)
                Success(response, request, devices[0])      
            }

        // device login failed
        } else {
            log.Printf("device credentials not valid!")
            Unauthroized(response, request)
        }


    // no valid credentials were provided   
    } else {
        log.Println("no valid deivce or manager credentials")
        BadRequest(response, request)
    }

}

}

Et voici ce que nous obtenons dans les journaux après 30 jours :

Cookie de l'appareil : authentification-session=_KaJDVq4lIdQgeHiHcnSMw1IEPDyg3-9XEIBBPxw== ; Chemin=/; Expire=Lun, 15 mai 2215 20:47:49 UTC ; Max-Age=630720000

2015/07/10 15:56:04 /routers/api/Auth.go:70 : securecookie : horodatage expiré

Les cookies semblent tous expirer après 30 jours, quelle que soit la configuration de MaxAge. Toute aide vous être énorme pour nous. Merci!

D'accord, j'ai simulé un programme de démonstration minimal dans lequel j'ai défini MaxAge sur l'ensemble du magasin à 2 mois (86400 * 60):

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/sessions"
)

var store = sessions.NewCookieStore([]byte("some-appropriately-auth-key"))

func SomeHandler(w http.ResponseWriter, r *http.Request) {
    session, err := store.Get(r, "example")
    if err != nil {
        http.Error(w, "No good!", 500)
        return
    }

    session.Values["gorilla"] = "sessions!"
    err = session.Save(r, w)
    if err != nil {
        http.Error(w, "No good!", 500)
        return
    }

    fmt.Fprintf(w, "%v", session.Values["gorilla"])
}

func main() {
    store.Options = &sessions.Options{
        MaxAge: 86400 * 60, // 2 months
    }

    http.HandleFunc("/", SomeHandler)
    log.Fatal(http.ListenAndServe(":8002", nil))
}

Cela me donne le délai d'expiration auquel je m'attends :

Expiry time test

Le réglage manuel de mon horloge d'environ 33 jours me rapporte également l'erreur expired timestamp de securecookie - qui est une erreur.

Effacer les cookies dans le navigateur, réinitialiser l'horloge à maintenant(), puis forcer MaxAge sur le *securecookie.SecureCookie sous-jacent via ce qui suit :

    store.Options = &sessions.Options{
        MaxAge: 86400 * 60, // 2 months
    }
    for _, s := range store.Codecs {
        if cookie, ok := s.(*securecookie.SecureCookie); ok {
            cookie.MaxAge(86400 * 90)
        }
    }

Cela me donne toujours un cookie de navigateur avec une expiration +2 mois dans le futur car le champ maxAge de chaque codec n'a pas d'impact sur ce que le navigateur voit lorsque nous écrivons le http.Cookie . Cependant, régler mon horloge à +33 jours dans le futur ne génère pas l'erreur d'horodatage expiré.

J'ai donc fait quelques recherches :

  • Notre erreur "securecookie: expired timestamp" est générée à partir de https://github.com/gorilla/securecookie/blob/master/securecookie.go#L244 -L246
  • Cette ligne définit le MaxAge lors de la création de CookieStore : https://github.com/gorilla/sessions/blob/master/store.go#L54 -L56
  • En appelant CodecsFromPairs avant d'appliquer les options, il corrige les s.MaxAge au sein de chaque *securecookie.SecureCookie (dans le cadre de la tranche de codec) pour sécuriser le cookie par défaut : 86400 * 30 .
  • Si nous dépassons notre sessions.CookieStore.Codecs (selon l'extrait ci-dessus) et définissons le MaxAge sur chaque instance de cookie sécurisé, nous contournons cela.

TL;DR : La cause première est que le http.Cookie écrit dans le navigateur obtient l'horodatage que nous avons défini dans nos options, mais le HMAC pour l'instance de Securecookie sous-jacente est une concaténation de date|value|mac - où la date est le _default_ MaxAge que nos options ne corrigent pas. La validation HMAC échoue ensuite une fois que nous atteignons > 30 jours.

Le correctif consistera à ce que sessions.NewCookieStore s'étende sur les codecs qu'il crée et applique Options.MaxAge à chaque instance *securecookie.SecureCookie pour lui permettre de corriger le décalage entre l'expiration du cookie et la validation de l'horodatage HMAC.

cc/ @kisielk pour valider mes découvertes avant de pousser un correctif.

PS : sans rapport avec cela, il est préférable d'ouvrir un seul pool de bases de données dans votre programme, que ce soit via un global ou en passant explicitement un pointeur. L'ouverture et la fermeture du pool par demande ont un impact sur les performances. *sql.DB est sûr pour l'accès simultané.

Notez que cela affecte également la majorité des implémentations de magasins, car ils utilisent tous securecookie pour enregistrer l'ID dans leur magasin principal.

redistore a la moitié d'une implémentation correcte si vous appelez manuellement SetMaxAge mais ne l'applique pas directement pour le cas par défaut. Il se trouve que la valeur par défaut (30 jours) est la même que celle de securecookie.

D'un coup d'œil rapide, il semble que _tous_ les autres magasins soient affectés - la définition d'un MaxAge via la structure Options n'est pas appliquée aux instances de Securecookie sous-jacentes générées à partir de CodecsFromPairs. La plupart des utilisateurs finaux n'auraient pas rencontré ce bogue, car je soupçonne que la plupart des utilisateurs définissent des dates d'expiration inférieures à celles que securecookie intègre implicitement dans le HMAC.

FWIW : Je pense également que les expirations de cookies dans un « avenir lointain » ne sont pas idéales à moins que vous n'en ayez un besoin très spécifique.

Je tiens également à souligner que l'horodatage actuel est complètement non crypté dans l'implémentation actuelle. Je pourrais, théoriquement, décoder le cookie en base64, changer l'horodatage pour le jour actuel, coder le cookie en base64, puis la propriété MaxAge n'aurait aucune importance, même si vous résolviez le problème options.MaxAge.

Oui, le cookie "futur lointain" était juste pour un test, nous avons maintenant le MaxAge défini sur 0 sur notre serveur de production. Nous avons trouvé le problème au cours du week-end et avons rédigé un correctif similaire, mais légèrement moins concis que le vôtre.

Merci de vous être penché sur le problème !

@wbaron - pas de problèmes. Nous allons bientôt le corriger dans le package en amont et
Je vous ferai un ping une fois terminé pour que vous n'ayez pas à entretenir votre propre fourchette (si vous
ne veux pas).

@marksalpeter - Pouvez-vous indiquer où ? Securecookie MAC le
nom|date|valeur avant de l'encoder en base64 pour une représentation sous forme de chaîne. Si
vous décodez côté client vous ne pouvez pas y modifier l'horodatage (c'est
MAC'ed - il n'y a rien que vous puissiez réellement modifier) ​​et si vous avez essayé, le
le cookie devrait échouer à la validation lors du décodage (demande) car le MAC échouerait
pour décoder correctement par rapport à la clé de hachage/auth. Réf :
https://github.com/gorilla/securecookie/blob/master/securecookie.go#L185 -L191

Le mardi 14 juillet 2015 à 22h40, wbaron [email protected] a écrit :

Oui, le cookie "futur lointain" était juste pour un test, nous avons le MaxAge réglé sur
0 sur notre serveur de production maintenant. Nous avons trouvé le problème ce week-end et
a écrit un correctif similaire au vôtre, avec un peu moins de concision.

Merci de vous être penché sur le problème !

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/gorilla/sessions/issues/48#issuecomment-121257995 .

@elithrar mon erreur. lorsque je décode les cookies, je reçois [timestamp]|[MAC'ed info] J'ai juste supposé que l'horodatage avant les informations maximales était ce que vous utilisiez.

@elithrar ce que vous proposez me semble bien. Dommage qu'il doive être appliqué à chaque magasin individuellement :/

@kisielk - C'est
magasins, mais il n'y avait pas de moyen propre de coupler efficacement un magasin tiers
Champs Options.MaxAge avec la méthode s.MaxAge dans securecookie.

J'envisage de fournir une fonction func CodecMaxAge(codecs []Codec, age int) []Codec dans securecookie (inconvénient : ne peut pas être une méthode sur un
interface + ajoute à l'API publique) qui fait en interne le type
assertion + appelle s.MaxAge(age) sur chaque codec. Cela reste redondant (boucle
sur slice, tapez assert to cookie type) code hors des magasins tiers
car ils auront juste besoin d'appeler securecookie.CodecMaxAge(mystore.Codecs, mystore.opts.MaxAge) dans leur fonction NewXXXXStore pour définir le
champ s.maxAge sous-jacent.

Ouvert à de meilleures idées, mais cela semble être le plus simple.

Le ven. 17 juil. 2015 à 08:03 Kamil Kisiel [email protected]
a écrit:

@elithrar https://github.com/elithrar ce que vous proposez sonne bien
moi. Dommage qu'il doive être appliqué à chaque magasin individuellement :/

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/gorilla/sessions/issues/48#issuecomment-122133977 .

Ouais je pense que c'est bien. J'enverrai des PR aux autres magasins pour l'utiliser une fois que vous aurez obtenu la monnaie ici.

À des fins de suivi :

  • [x] [gorille/sessions](https://github.com/gorilla/sessions)
  • [ ] [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
  • [ ] [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Boulon
  • [ ] [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
  • [ ] [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb sur AWS
  • [ ] [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
  • [ ] [github.com/hnakamur/gaesessions](https://github.com/hnakamur/gaesessions) - Memcache sur GAE
  • [x] [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
  • [ ] [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
  • [x] [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
  • [ ] [github.com/boj/redistore] (https://github.com/boj/redistore) - Redis
  • [x] [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
  • [ ] [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
  • [ ] [github.com/michaeljs1990/sqlitestore] (https://github.com/michaeljs1990/sqlitestore) - SQLite

Je vais les suivre une fois que les PR ont été fusionnés.

Ce problème a été automatiquement marqué comme obsolète car il n'a pas vu de mise à jour récente. Il sera automatiquement fermé dans quelques jours.

En examinant ce problème après l'avoir rencontré à partir d'un autre package, il ne semble pas qu'il devrait être fermé... La liste de contrôle de @elithrar nécessite encore 10 vérifications, à moins que ces informations ne soient obsolètes.

Il doit être précis : mais tous les magasins ne sont pas activement entretenus ou n'ont pas fait l'objet d'un PR.

Ce problème a été automatiquement marqué comme obsolète car il n'a pas vu de mise à jour récente. Il sera automatiquement fermé dans quelques jours.

Ce problème a été automatiquement marqué comme obsolète car il n'a pas vu de mise à jour récente. Il sera automatiquement fermé dans quelques jours.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

CasperHK picture CasperHK  ·  11Commentaires

danvonk picture danvonk  ·  9Commentaires

cless picture cless  ·  23Commentaires

elithrar picture elithrar  ·  22Commentaires

luca-moser picture luca-moser  ·  3Commentaires