Sessions: Las tiendas de terceros no aplican correctamente las opciones a los códecs.

Creado en 10 jul. 2015  ·  17Comentarios  ·  Fuente: gorilla/sessions

Acabamos de actualizar este paquete y no está configurando las cookies correctamente. Está configurando cosas como _ga = GA1.1.922831813.14264788986 en lugar de los datos de cookies codificados en base64 normales. ¿Alguna idea de qué está pasando o cuándo se rompió para que podamos volver a una versión funcional? ¡Gracias!

bug stale

Comentario más útil

Tenga en cuenta que esto también afecta a la mayoría de las implementaciones de la tienda, ya que todas usan securecookie para guardar la ID en su tienda backend.

redistore tiene la mitad de una implementación correcta si llama manualmente SetMaxAge pero no aplica esto directamente para el caso predeterminado. Da la casualidad de que el valor predeterminado (30 días) es el mismo que el de las cookies seguras.

De un vistazo rápido, parece que _todas_ las demás tiendas se ven afectadas; la configuración de un MaxAge a través de la estructura Options no se aplica a las instancias de securecookie subyacentes generadas a partir de CodecsFromPairs. La mayoría de los usuarios finales no se habrían encontrado con este error, ya que sospecho que la mayoría de los usuarios establecen fechas de caducidad _menos_ que lo que securecookie está incorporando implícitamente en el HMAC.

FWIW: También creo que los vencimientos de las cookies en un "futuro lejano" no son excelentes a menos que tenga una necesidad muy específica.

Todos 17 comentarios

La cookie "_ga" no está relacionada con gorilla / sessions; es una cookie de Google Analytics.
Galleta.

Si gorilla / session no está configurando cookies (no ha habido ninguna ruptura
cambios) ¿Puede publicar el código relevante y la salida del panel Cookies
desde tu navegador? (inspector> recursos> cookies en Chrome)

El viernes 10 de julio de 2015 a las 7:12 a. M. Marksalpeter [email protected]
escribió:

Acabamos de actualizar este paquete y no está configurando las cookies correctamente. Es
configurando cosas como _ga = GA1.1.922831813.14264788986 en lugar del normal
Datos de cookies codificados en base64. ¿Alguna idea de qué está pasando o cuándo se rompió?
¿Podemos volver a una versión funcional? ¡Gracias!

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/gorilla/sessions/issues/48.

Hola Matt,

También estoy trabajando en el proyecto con Mark y puedo darte más detalles. Así que acabamos de actualizar el paquete porque el problema original era que todas las cookies expiraban después de 30 días, incluso cuando configuramos MaxAge en un número realmente grande. Aquí está el código de autenticación que está activo en este momento y nos está causando problemas:

const
AUTH_SESSION_NAME = "sesión de autenticación"
)

var (
config = jconfig.LoadConfig (global.CONFIG () + "securecookie.json")
authKey = [] byte (config.GetString ("clave_autorización"))
encryptionKey = [] byte (config.GetString ("clave_encriptación"))
sesión = sesiones.NewCookieStore (authKey, encryptionKey)
)

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

}

// este controlador otorga permisos en el sistema para dispositivos y administradores
// si el solicitante ya está autorizado, mostrará su información. Para autorizar un diferente
// cuenta, primero el cliente debe cancelar la autenticación.
//
// Params
// usuario:
//
// nota: solo se garantiza que los datos devueltos por esta función de autenticación sean precisos
// cuando el usuario se autentica por primera vez. de lo contrario, mostrará los datos almacenados en caché en su sesión
//
func Authenticate (respuesta http.ResponseWriter, solicitud * 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)
    }

}

}

Y esto es lo que obtenemos en los registros después de 30 días:

Cookie de dispositivo: autenticación-sesión = _KaJDVq4lIdQgeHiHcnSMw1IEPDyg3-9XEIBBPxw ==; Ruta = /; Caduca = Lunes, 15 de mayo de 2215 20:47:49 UTC; Edad máxima = 6307200000

2015/07/10 15:56:04 /routers/api/Auth.go:70: securecookie: marca de tiempo vencida

Parece que todas las cookies caducan después de 30 días, independientemente de lo que establezcamos en MaxAge. Cualquier ayuda será enorme para nosotros. ¡Gracias!

Muy bien, me he burlado de un programa de demostración mínimo en el que configuré MaxAge en toda la tienda en 2 meses (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))
}

Esto me da el tiempo de vencimiento que esperaría:

Expiry time test

Adelantar manualmente mi reloj ~ 33 días también me genera el error expired timestamp de securecookie , que es un error.

Borrar las cookies en el navegador, restablecer el reloj al ahora () y luego forzar MaxAge en el *securecookie.SecureCookie subyacente a través de lo siguiente:

    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)
        }
    }

Esto todavía me da una cookie de navegador con una caducidad de +2 meses en el futuro porque el campo maxAge de cada códec no afecta lo que ve el navegador cuando escribimos el http.Cookie . Sin embargo, configurar mi reloj +33 días en el futuro no genera el error de marca de tiempo vencido.

Así que investigué un poco:

TL; DR : La causa principal es que el http.Cookie escrito en el navegador obtiene la marca de tiempo que configuramos en nuestras opciones, pero el HMAC para la instancia de securecookie subyacente es una concatenación de date | value | mac - donde date es el _default_ MaxAge que nuestras Opciones no están arreglando. La validación de HMAC falla posteriormente una vez que llegamos a> 30 días.

La solución será que sessions.NewCookieStore abarque los códecs que crea y aplique Options.MaxAge a cada instancia *securecookie.SecureCookie para permitirle corregir la falta de coincidencia entre el vencimiento de la cookie y la validación de la marca de tiempo HMAC.

cc / @kisielk para validar mis hallazgos antes de presionar una solución.

PD: Sin relación con esto, es una buena práctica abrir un solo grupo de bases de datos en su programa, ya sea a través de un global o pasando explícitamente un puntero. Abrir y cerrar el grupo por solicitud tiene un impacto en el rendimiento. *sql.DB es seguro para el acceso simultáneo.

Tenga en cuenta que esto también afecta a la mayoría de las implementaciones de la tienda, ya que todas usan securecookie para guardar la ID en su tienda backend.

redistore tiene la mitad de una implementación correcta si llama manualmente SetMaxAge pero no aplica esto directamente para el caso predeterminado. Da la casualidad de que el valor predeterminado (30 días) es el mismo que el de las cookies seguras.

De un vistazo rápido, parece que _todas_ las demás tiendas se ven afectadas; la configuración de un MaxAge a través de la estructura Options no se aplica a las instancias de securecookie subyacentes generadas a partir de CodecsFromPairs. La mayoría de los usuarios finales no se habrían encontrado con este error, ya que sospecho que la mayoría de los usuarios establecen fechas de caducidad _menos_ que lo que securecookie está incorporando implícitamente en el HMAC.

FWIW: También creo que los vencimientos de las cookies en un "futuro lejano" no son excelentes a menos que tenga una necesidad muy específica.

También me gustaría señalar que la marca de tiempo actual está completamente descifrada en la implementación actual. Teóricamente, podría decodificar la cookie en base64, cambiar la marca de tiempo al día actual, codificar la cookie en base64, y luego la propiedad MaxAge no importaría en absoluto, incluso si corrigiera el problema options.MaxAge.

Sí, la cookie del "futuro lejano" era solo para una prueba, ahora tenemos el valor de MaxAge establecido en 0 en nuestro servidor de producción. Encontramos el problema durante el fin de semana y escribimos una solución similar, pero un poco menos concisa que la suya.

¡Gracias por investigar el problema!

@wbaron - sin problemas. Lo arreglaremos pronto en el paquete upstream y
Te haré ping una vez hecho para que no tengas que mantener tu propia bifurcación (si
no quiero).

@marksalpeter - ¿Puedes señalar dónde? Securecookie MACs el
nombre | fecha | valor antes de codificarlo en base64 para una representación de cadena. Si
usted decodifica en el lado del cliente, no puede modificar la marca de tiempo allí (es
MAC'ed, no hay nada que realmente puedas modificar) y si lo intentaste, el
la cookie debería fallar la validación en la decodificación (solicitud) ya que la MAC fallaría
para decodificar correctamente contra la clave hash / auth. Árbitro:
https://github.com/gorilla/securecookie/blob/master/securecookie.go#L185 -L191

El martes 14 de julio de 2015 a las 10:40 p.m., wbaron [email protected] escribió:

Sí, la cookie del "futuro lejano" fue solo para una prueba, tenemos la MaxAge configurada en
0 en nuestro servidor de producción ahora. Encontramos el problema durante el fin de semana y
escribió una solución similar a la suya, con un poco menos de concisión.

¡Gracias por investigar el problema!

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/gorilla/sessions/issues/48#issuecomment -121257995.

@elithrar mi error. cuando decodifico las cookies obtengo [timestamp] | [MAC'ed info] Simplemente asumí que la marca de tiempo antes de la información máxima era la que estabas usando.

@elithrar me suena bien lo que propones. Es una pena que tenga que aplicarse a cada tienda individualmente: /

@kisielk - Definitivamente una pena. Quería evitar tocar al tercero
tiendas, pero no había una manera clara de acoplar eficazmente la tienda de terceros
Options.MaxAge con el método s.MaxAge en securecookie.

Estoy considerando proporcionar una función func CodecMaxAge(codecs []Codec, age int) []Codec en securecookie (desventaja: no puede ser un método en un
interfaz + agrega a la API pública) que internamente hace el tipo
Asertion + llama a la edad máxima (edad) en cada códec. Esto mantiene redundante (bucle
sobre el segmento, escriba afirmar al tipo de cookie) código de las tiendas de terceros
ya que solo necesitarán llamar a securecookie.CodecMaxAge(mystore.Codecs, mystore.opts.MaxAge) en su función NewXXXXStore para configurar el
campo s.maxAge subyacente.

Abierto a mejores ideas, pero esta parece ser la más sencilla.

El viernes 17 de julio de 2015 a las 8:03 a. M. Kamil Kisiel [email protected]
escribió:

@elithrar https://github.com/elithrar lo que propones suena bien
me. Es una pena que tenga que aplicarse a cada tienda individualmente: /

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/gorilla/sessions/issues/48#issuecomment -122133977.

Sí, creo que está bien. Enviaré algunos PR a las otras tiendas para que lo utilicen una vez que consigas el cambio aquí.

Para fines de seguimiento:

  • [x] [gorilla / 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) - Bolt
  • [] [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
  • [] [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb en 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 en 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

Los rastrearé una vez que las relaciones públicas se hayan fusionado.

Este problema se ha marcado automáticamente como obsoleto porque no se ha actualizado recientemente. Se cerrará automáticamente en unos días.

Al revisar este problema después de encontrarlo desde otro paquete, no parece que deba cerrarse ... La lista de verificación de @elithrar aún necesita otras 10 verificaciones, a menos que esa información esté desactualizada.

Debe ser exacto: pero no todas las tiendas se mantienen activamente o tienen un PR enviado.

Este problema se ha marcado automáticamente como obsoleto porque no se ha actualizado recientemente. Se cerrará automáticamente en unos días.

Este problema se ha marcado automáticamente como obsoleto porque no se ha actualizado recientemente. Se cerrará automáticamente en unos días.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

luca-moser picture luca-moser  ·  3Comentarios

cless picture cless  ·  23Comentarios

danvonk picture danvonk  ·  9Comentarios

elithrar picture elithrar  ·  22Comentarios

CasperHK picture CasperHK  ·  11Comentarios