forked from VSRC-Arsenal/cookiemonster-custom
/
flask.go
162 lines (130 loc) · 4.54 KB
/
flask.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package monster
import (
"bytes"
"encoding/base64"
"fmt"
"strings"
)
type flaskParsedData struct {
data string
timestamp string
decodedTimestamp []byte
signature string
decodedSignature []byte
algorithm string
toBeSigned []byte
compressed bool
parsed bool
}
func (d *flaskParsedData) String() string {
if !d.parsed {
return "Unparsed data"
}
return fmt.Sprintf("Compressed: %t\nData: %s\nTimestamp: %s\nSignature: %s\nAlgorithm: %s\n", d.compressed, d.data, d.timestamp, d.signature, d.algorithm)
}
const (
flaskDecoder = "flask"
flaskMinLength = 10
flaskSeparator = `.`
)
var (
flaskAlgorithmLength = map[int]string{
20: "sha1",
32: "sha256",
48: "sha384",
64: "sha512",
}
flaskSalt = []byte(`cookie-session`)
)
func flaskDecode(c *Cookie) bool {
if len(c.raw) < flaskMinLength {
return false
}
rawData := c.raw
var parsedData flaskParsedData
// If the first character is a dot, it's compressed.
if rawData[0] == '.' {
parsedData.compressed = true
rawData = rawData[1:]
}
// Break the cookie out into the session data, timestamp, and signature,
// in that order. Note that we assume the use of `TimestampSigner`.
components := strings.Split(rawData, flaskSeparator)
if len(components) != 3 {
return false
}
parsedData.data = components[0]
parsedData.timestamp = components[1]
parsedData.signature = components[2]
// The current timestamp is embedded in a `>Q` Python struct. This can
// be up to eight bytes (usually three), but never more.
decodedTimestamp, err := base64.RawURLEncoding.DecodeString(parsedData.timestamp)
if err != nil || len(decodedTimestamp) > 8 {
return false
}
parsedData.decodedTimestamp = decodedTimestamp
// Flask encodes the signature with URL-safe base64
// without padding, so we must use `RawURLEncoding`.
decodedSignature, err := base64.RawURLEncoding.DecodeString(parsedData.signature)
if err != nil {
return false
}
// Determine the algorithm from the digest length, or give up if we can't
// figure it out.
if alg, ok := flaskAlgorithmLength[len(decodedSignature)]; ok {
parsedData.algorithm = alg
} else {
return false
}
parsedData.decodedSignature = decodedSignature
parsedData.toBeSigned = []byte(parsedData.data + flaskSeparator + parsedData.timestamp)
// If this is a compressed cookie, it needs to have the dot in front which
// we previously stripped from `data`.
if parsedData.compressed {
parsedData.toBeSigned = append([]byte("."), parsedData.toBeSigned...)
}
parsedData.parsed = true
c.wasDecodedBy(flaskDecoder, &parsedData)
return true
}
func flaskUnsign(c *Cookie, secret []byte) bool {
// We need to extract `toBeSigned` to prepare what we'll be signing.
parsedData := c.parsedDataFor(flaskDecoder).(*flaskParsedData)
// Derive the correct signature, if this was the correct secret key.
computedSignature := flaskCompute(parsedData.algorithm, secret, parsedData.toBeSigned)
// Compare this signature to the one in the `Cookie`.
return bytes.Compare(parsedData.decodedSignature, computedSignature) == 0
}
func flaskResign(c *Cookie, data string, secret []byte) string {
// We need to extract `toBeSigned` to prepare what we'll be signing.
parsedData := c.parsedDataFor(flaskDecoder).(*flaskParsedData)
// We need to assemble the TBS string with new data.
toBeSigned := base64.RawURLEncoding.EncodeToString([]byte(data)) + flaskSeparator + parsedData.timestamp
return toBeSigned + flaskSeparator + base64.RawURLEncoding.EncodeToString(flaskCompute(parsedData.algorithm, secret, []byte(toBeSigned)))
}
func flaskCompute(algorithm string, secret []byte, data []byte) []byte {
switch algorithm {
case "sha1":
// Flask forces us to derive a key for HMAC-ing.
derivedKey := sha1HMAC(secret, flaskSalt)
// Derive the correct signature, if this was the correct secret key.
return sha1HMAC(derivedKey, data)
case "sha256":
// Flask forces us to derive a key for HMAC-ing.
derivedKey := sha256HMAC(secret, flaskSalt)
// Derive the correct signature, if this was the correct secret key.
return sha256HMAC(derivedKey, data)
case "sha384":
// Flask forces us to derive a key for HMAC-ing.
derivedKey := sha384HMAC(secret, flaskSalt)
// Derive the correct signature, if this was the correct secret key.
return sha384HMAC(derivedKey, data)
case "sha512":
// Flask forces us to derive a key for HMAC-ing.
derivedKey := sha512HMAC(secret, flaskSalt)
// Derive the correct signature, if this was the correct secret key.
return sha512HMAC(derivedKey, data)
default:
panic("unknown algorithm")
}
}