Sessions: Lojas de terceiros que não aplicam opções aos codecs corretamente.

Criado em 10 jul. 2015  ·  17Comentários  ·  Fonte: gorilla/sessions

Acabamos de atualizar este pacote e ele não está configurando os cookies corretamente. Ele está configurando coisas como _ga = GA1.1.922831813.14264788986 em vez dos dados regulares de cookies codificados em base64. Alguma ideia do que está acontecendo ou quando isso quebrou para que possamos reverter para uma versão de trabalho? Obrigado!

bug stale

Comentários muito úteis

Observe que isso também afeta a maioria das implementações de loja, já que todas usam securecookie para salvar o ID em sua loja de back-end.

redistore tem metade de uma implementação correta se você chamar manualmente SetMaxAge mas não aplica isso diretamente para o caso padrão. Acontece que o padrão (30 dias) é o mesmo que o do securecookie.

De uma olhada rápida, parece que _todos_ os outros armazenamentos são afetados - a configuração de MaxAge por meio da estrutura Options não é aplicada às instâncias securecookie subjacentes geradas a partir de CodecsFromPairs. A maioria dos usuários finais não teria encontrado esse bug, pois suspeito que a maioria dos usuários define datas de expiração _menos_ do que o securecookie está implicitamente embutindo no HMAC.

FWIW: Eu também acredito que os vencimentos de cookies em "um futuro distante" não são ótimos, a menos que você tenha uma necessidade específica deles.

Todos 17 comentários

O cookie "_ga" não está relacionado a gorila / sessões - é um Google Analytics
biscoito.

Se o gorila / sessões não estiver configurando cookies (não houve nenhuma falha
alterações) você pode postar o código relevante e a saída do painel Cookies
do seu navegador? (inspetor> recursos> cookies no Chrome)

Na sexta-feira, 10 de julho de 2015 às 7h12 markalpeter [email protected]
escrevi:

Acabamos de atualizar este pacote e ele não está configurando os cookies corretamente. Isso é
configurar coisas como _ga = GA1.1.922831813.14264788986 em vez do normal
dados de cookies codificados em base64. Qualquer ideia do que está acontecendo ou quando isso quebrou
podemos reverter para uma versão de trabalho? Obrigado!

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/gorilla/sessions/issues/48.

Oi Matt,

Também estou trabalhando no projeto com Mark e posso dar mais alguns detalhes. Então, acabamos de atualizar o pacote porque o problema original era com os cookies expirando após 30 dias, mesmo quando configuramos MaxAge para um número muito grande. Aqui está o código de autenticação que está ativo agora e está causando problemas para nós:

const (
AUTH_SESSION_NAME = "sessão de autenticação"
)

var (
config = jconfig.LoadConfig (global.CONFIG () + "securecookie.json")
authKey = [] byte (config.GetString ("authority_key"))
encryptionKey = [] byte (config.GetString ("encryption_key"))
sessão = sessões.NovoCookieStore (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 manipulador concede permissões no sistema para dispositivos e gerenciadores
// se o solicitante já estiver autorizado, ele exibirá suas informações. Para autorizar um defferent
// conta, primeiro o cliente deve desautenticar.
//
// Params
// do utilizador:
//
// observação: os dados retornados por esta função de autenticação são apenas garantidos como precisos
// quando o usuário se autentica pela primeira vez. caso contrário, ele exibirá dados armazenados em cache em sua sessão
//
func Authenticate (response http.ResponseWriter, request * 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)
    }

}

}

E é isso que estamos obtendo nos registros após 30 dias:

Cookie do dispositivo: autenticação-sessão = _KaJDVq4lIdQgeHiHcnSMw1IEPDyg3-9XEIBBPxw ==; Caminho = /; Expira = Seg, 15 de maio de 2215 20:47:49 UTC; Max-Age = 6307200000

2015/07/10 15:56:04 /routers/api/Auth.go:70: securecookie: data e hora expirada

Todos os cookies parecem expirar após 30 dias, independentemente de como definimos MaxAge. Qualquer ajuda você será enorme para nós. Obrigada!

Tudo bem, eu criei um programa de demonstração mínimo onde defini MaxAge em toda a loja para 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))
}

Isso me dá o tempo de expiração que eu esperava:

Expiry time test

Ajustar manualmente meu relógio para frente em ~ 33 dias também me resulta no erro expired timestamp de securecookie - que é um erro.

Limpando os cookies no navegador, redefinindo o relógio para agora () e, em seguida, forçando MaxAge no *securecookie.SecureCookie subjacente por meio do seguinte:

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

Isso ainda me dá um cookie do navegador com uma expiração de +2 meses no futuro, porque o campo maxAge de cada codec não afeta o que o navegador vê quando escrevemos http.Cookie . No entanto, definir meu relógio para +33 dias no futuro não gera o erro de carimbo de data / hora expirado.

Então, eu fiz algumas pesquisas:

TL; DR : a causa raiz é que http.Cookie gravado no navegador obtém o carimbo de data / hora que definimos em nossas opções, mas o HMAC para a instância securecookie subjacente é uma concatenação de data | valor | mac - onde data é o _default_ MaxAge que nossas opções não estão corrigindo. A validação do HMAC posteriormente falha quando atingimos> 30 dias.

A correção será para sessions.NewCookieStore para variar sobre os codecs que ele cria e aplicar Options.MaxAge a cada *securecookie.SecureCookie instância para permitir que ele corrija a incompatibilidade entre a expiração do cookie e a validação do carimbo de data / hora HMAC.

cc / @kisielk para validar minhas descobertas antes de fazer uma correção.

PS: Não relacionado a isso, é uma prática recomendada abrir um único pool de banco de dados em seu programa, seja por meio de um global ou passando explicitamente um ponteiro. Abrir e fechar o pool por solicitação tem um impacto no desempenho. *sql.DB é seguro para acesso simultâneo.

Observe que isso também afeta a maioria das implementações de loja, já que todas usam securecookie para salvar o ID em sua loja de back-end.

redistore tem metade de uma implementação correta se você chamar manualmente SetMaxAge mas não aplica isso diretamente para o caso padrão. Acontece que o padrão (30 dias) é o mesmo que o do securecookie.

De uma olhada rápida, parece que _todos_ os outros armazenamentos são afetados - a configuração de MaxAge por meio da estrutura Options não é aplicada às instâncias securecookie subjacentes geradas a partir de CodecsFromPairs. A maioria dos usuários finais não teria encontrado esse bug, pois suspeito que a maioria dos usuários define datas de expiração _menos_ do que o securecookie está implicitamente embutindo no HMAC.

FWIW: Eu também acredito que os vencimentos de cookies em "um futuro distante" não são ótimos, a menos que você tenha uma necessidade específica deles.

Também gostaria de salientar que o carimbo de data / hora atual está completamente descriptografado na implementação atual. Eu poderia, teoricamente, decodificar o cookie em base64, alterar o carimbo de data / hora para o dia atual, codificar o cookie em base64 e, então, a propriedade MaxAge não teria importância alguma, mesmo se você corrigisse as opções. Problema de MaxAge.

Sim, o cookie do "futuro distante" foi apenas para um teste, temos o MaxAge definido como 0 em nosso servidor de produção agora. Encontramos o problema no fim de semana e escrevemos uma correção semelhante, mas um pouco menos concisa que a sua.

Obrigado por analisar o problema!

@wbaron - sem problemas. Vamos consertá-lo no pacote upstream em breve e
Vou enviar um ping uma vez feito isso para que você não tenha que manter seu próprio fork (se você
não quero).

@marksalpeter - Você pode apontar onde? securecookie MACs o
nome | data | valor antes de codificá-lo em base64 para uma representação de sequência. Se
você decodifica no lado do cliente, você não pode modificar o carimbo de data / hora lá (é
MAC'ed — não há nada que você possa realmente modificar) e se você tentou, o
o cookie deve falhar na validação na decodificação (solicitação), pois o MAC falhará
para decodificar corretamente em relação à chave hash / auth. Ref:
https://github.com/gorilla/securecookie/blob/master/securecookie.go#L185 -L191

Na terça - feira, 14 de julho de 2015 às 22h40 wbaron

Sim, o cookie do "futuro distante" era apenas para um teste, temos MaxAge definido como
0 em nosso servidor de produção agora. Encontramos o problema no fim de semana e
escreveu uma correção semelhante à sua, com um pouco menos de concisão.

Obrigado por analisar o problema!

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/gorilla/sessions/issues/48#issuecomment -121257995.

@elithrar meu erro. quando decodifico os cookies, obtenho [timestamp] | [MAC'ed info] Presumi que o timestamp antes das informações máximas era o que você estava usando.

@elithrar o que você propõe parece bom para mim. É uma pena que tenha que ser aplicado a cada loja individualmente: /

@kisielk - Definitivamente uma pena. Eu queria evitar tocar no terceiro
lojas, mas não havia uma maneira limpa de acoplar efetivamente a loja de terceiros
Campos Options.MaxAge com o método s.MaxAge em securecookie.

Estou pensando em fornecer uma função func CodecMaxAge(codecs []Codec, age int) []Codec em securecookie (desvantagem: não pode ser um método em um
interface + adiciona à API pública) que faz o tipo internamente
assertion + chama s.MaxAge (age) em cada Codec. Isso se mantém redundante (loop
sobre a fatia, digite afirme para tipo de cookie) código de lojas de terceiros
pois eles só precisam chamar securecookie.CodecMaxAge(mystore.Codecs, mystore.opts.MaxAge) em sua função NewXXXXStore para definir o
campo s.maxAge subjacente.

Aberto a ideias melhores, mas isso parece ser o mais simples.

Na sexta-feira, 17 de julho de 2015 às 8h03 Kamil Kisiel [email protected]
escrevi:

@elithrar https://github.com/elithrar o que você propõe parece bom para
Eu. É uma pena que tenha que ser aplicado a cada loja individualmente: /

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/gorilla/sessions/issues/48#issuecomment -122133977.

Sim, acho que está bem. Vou enviar alguns PRs para as outras lojas para usá-lo assim que você colocar a mudança aqui.

Para fins de rastreamento:

  • [x] [gorila / sessões] (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 no 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 no 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

Vou rastreá-los assim que os PRs forem fundidos.

Este problema foi marcado automaticamente como obsoleto porque não teve uma atualização recente. Ele será fechado automaticamente em alguns dias.

Olhando para este problema depois de encontrá- lo em outro pacote, não parece que ele deveria ser fechado ... a lista de verificação de @elithrar ainda precisa de mais 10 verificações, a menos que a informação esteja desatualizada.

Deve ser preciso: mas nem todas as lojas são mantidas ativamente ou tiveram um PR enviado.

Este problema foi marcado automaticamente como obsoleto porque não teve uma atualização recente. Ele será fechado automaticamente em alguns dias.

Este problema foi marcado automaticamente como obsoleto porque não teve uma atualização recente. Ele será fechado automaticamente em alguns dias.

Esta página foi útil?
0 / 5 - 0 avaliações