Instantly share code, notes, and snippets.
Created
April 2, 2020 17:55
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save JokerCatz/a760be50ea34d9480b4c98488e4269ab to your computer and use it in GitHub Desktop.
Golang get Rails redis session & verify auth token
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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