Skip to content

Instantly share code, notes, and snippets.

@JokerCatz
Created April 2, 2020 17:55
Show Gist options
  • Save JokerCatz/a760be50ea34d9480b4c98488e4269ab to your computer and use it in GitHub Desktop.
Save JokerCatz/a760be50ea34d9480b4c98488e4269ab to your computer and use it in GitHub Desktop.
Golang get Rails redis session & verify auth token
package main
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"regexp"
"strconv"
"github.com/go-redis/redis"
"github.com/k0kubun/pp"
)
/*
example:
cookie
`_demo_session`
`d735fbdb06b27ac6e6ec3707ad76daf3`
redis
`__demo_session:session_id:2::a9108ba8d28e63ec172eb0bc8dfbeaf62b6354346fc7e83abdb308e9fdb72240`:
{"_csrf_token":"DAaTW1lYcZuJDpoQAqhXA+kBxQV92KK0ZICwjUtTXlw=","warden.user.user.key":[[1],"$2a$11$hUa0kE95kssmbqegSFS3Pe"],"warden.user.user.session":{"last_request_at":1585845619}}
html csrf token , verify : https://medium.com/rubyinside/a-deep-dive-into-csrf-protection-in-rails-19fa0a42c0ef
`FWSNQ05hF3GTq/RIbkWF0cp8FG4AacdIJxTkCGzIajEZYh4YFzlm6hqlblhs7dLSI33Ra32xZfxDlFSFJ5s0bQ==`
*/
const (
// RackIDversion ruby rack 的版本
RackIDversion = 2 // https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb
// AppName rails 的 project name
AppName = "demo"
// RedisHost redis session client
RedisHost = "localhost"
// RedisPort redis session client
RedisPort = 6379
// RedisDB redis session client
RedisDB = 8
// AuthenticityTokenLength 定義在 Rails 的 ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH
// https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
AuthenticityTokenLength = 32
)
// UserSession 阿就 session
type UserSession struct {
UserID *uint64
AuthSalt *string // authenticatable_salt (請對照 DB 的 users.encrypted_password 欄位前 30 char)
CsrfToken *string
}
var redisClient *redis.Client
func cacheKey(privateID string) string {
return fmt.Sprintf("__%s_session:session_id:%s", AppName, privateID)
}
func getPrivateID(cookieSerial string) string {
sum := sha256.Sum256(
[]byte(cookieSerial),
)
return fmt.Sprintf(
"%d::%s",
RackIDversion,
hex.EncodeToString(
sum[:], // [32]byte => []byte
),
)
}
func cookiePairFilter(cookieJar map[string]string) *UserSession {
cookieSerial, ok := cookieJar[fmt.Sprintf("_%s_session", AppName)]
if !ok {
fmt.Println("no session , skip check")
return nil
}
redisSessionKey := cacheKey(getPrivateID(cookieSerial))
fmt.Println("redis try to get ", redisSessionKey)
sessionData, err := redisClient.Get(redisSessionKey).Result()
if err != nil {
panic(err)
}
userSession := UserSession{}
csrfTokenRegex := regexp.MustCompile(`"_csrf_token":"([^"]+)"`)
userIDregex := regexp.MustCompile(`"warden\.user\.user\.key":\[\[(\d+)\],"([^"]+)"\]`)
csrfTokenMatch := csrfTokenRegex.FindStringSubmatch(sessionData)
if len(csrfTokenMatch) == 2 { //len must = 2
csrfToken := csrfTokenMatch[1]
userSession.CsrfToken = &csrfToken
}
userIDmatch := userIDregex.FindStringSubmatch(sessionData)
if len(userIDmatch) == 3 { //len must = 3
userID, err := strconv.ParseUint(userIDmatch[1], 10, 64)
if err == nil {
userSession.UserID = &userID
}
userSession.AuthSalt = &userIDmatch[2]
}
return &userSession
}
func xorBytes(s1 []byte, s2 []byte) []byte {
for index, s2temp := range s2 {
s1[index] = s1[index] ^ s2temp
}
return s1
}
func verifyAuthToken(userSession *UserSession, webAuthToken string) bool {
if userSession == nil {
fmt.Println("not login , skip verifyAuthToken")
return false
}
maskedToken, err := base64.StdEncoding.DecodeString(webAuthToken)
if err != nil {
fmt.Println("decode base64 webCsrfToken fail", err.Error())
return false
}
if len(maskedToken) != AuthenticityTokenLength*2 {
fmt.Println("len fail , token is malformed")
}
sourceToken := hex.EncodeToString(xorBytes(maskedToken[:AuthenticityTokenLength], maskedToken[AuthenticityTokenLength:]))
token, err := base64.StdEncoding.DecodeString(*userSession.CsrfToken)
if err != nil {
fmt.Println("decode base64 sessionCsrfToken fail", err.Error())
return false
}
sessionToken := hex.EncodeToString(token)
if sourceToken != sessionToken {
fmt.Println("sourceToken != sessionToken")
return false
}
return true
}
func init() {
redisClient = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", RedisHost, RedisPort),
DB: RedisDB,
})
// check client
_, err := redisClient.Ping().Result()
if err != nil {
panic(err)
}
}
func main() {
cookieJar := map[string]string{
`_demo_session`: `d735fbdb06b27ac6e6ec3707ad76daf3`,
}
userSession := cookiePairFilter(cookieJar)
pp.Println("user session", userSession)
webAuthToken := "FWSNQ05hF3GTq/RIbkWF0cp8FG4AacdIJxTkCGzIajEZYh4YFzlm6hqlblhs7dLSI33Ra32xZfxDlFSFJ5s0bQ=="
isVerify := verifyAuthToken(userSession, webAuthToken)
pp.Println("auth token verify", isVerify)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment