Split large cookies

This commit is contained in:
Joel Speed 2018-01-27 22:48:52 +00:00
parent 7b7cc8fdc4
commit bcd5ac513c
No known key found for this signature in database
GPG Key ID: 83695B8B3A376982
2 changed files with 168 additions and 15 deletions

View File

@ -260,15 +260,92 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
return return
} }
func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie { func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) []*http.Cookie {
if value != "" { if value != "" {
value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now) value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
if len(value) > 4096 { }
// Cookies cannot be larger than 4kb c := p.makeCookie(req, p.CookieName, value, expiration, now)
log.Printf("WARNING - Cookie Size: %d bytes", len(value)) if len(c.Value) > 4096 {
return splitCookie(c)
}
return []*http.Cookie{c}
}
func copyCookie(c *http.Cookie) *http.Cookie {
return &http.Cookie{
Name: c.Name,
Value: c.Value,
Path: c.Path,
Domain: c.Domain,
Expires: c.Expires,
RawExpires: c.RawExpires,
MaxAge: c.MaxAge,
Secure: c.Secure,
HttpOnly: c.HttpOnly,
Raw: c.Raw,
Unparsed: c.Unparsed,
}
}
func splitCookie(c *http.Cookie) []*http.Cookie {
if len(c.Value) < 3840 {
return []*http.Cookie{c}
}
cookies := []*http.Cookie{}
valueBytes := []byte(c.Value)
count := 0
for len(valueBytes) > 0 {
new := copyCookie(c)
new.Name = fmt.Sprintf("%s-%d", c.Name, count)
count++
if len(valueBytes) < 3840 {
new.Value = string(valueBytes)
valueBytes = []byte{}
} else {
newValue := valueBytes[:3840]
valueBytes = valueBytes[3840:]
new.Value = string(newValue)
}
cookies = append(cookies, new)
}
return cookies
}
func joinCookies(cookies []*http.Cookie) (*http.Cookie, error) {
if len(cookies) == 0 {
return nil, fmt.Errorf("Could not load cookie.")
}
if len(cookies) == 1 {
return cookies[0], nil
}
c := copyCookie(cookies[0])
for i := 1; i < len(cookies); i++ {
c.Value += cookies[i].Value
}
c.Name = strings.TrimRight(c.Name, "-0")
return c, nil
}
func loadCookie(req *http.Request, cookieName string) (*http.Cookie, error) {
c, err := req.Cookie(cookieName)
if err == nil {
return c, nil
}
cookies := []*http.Cookie{}
err = nil
count := 0
for err == nil {
var c *http.Cookie
c, err = req.Cookie(fmt.Sprintf("%s-%d", cookieName, count))
if err == nil {
cookies = append(cookies, c)
count++
} }
} }
return p.makeCookie(req, p.CookieName, value, expiration, now) if len(cookies) == 0 {
return nil, fmt.Errorf("Could not find cookie %s", cookieName)
}
return joinCookies(cookies)
} }
func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie { func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
@ -298,6 +375,7 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
} }
func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) { func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) {
http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now())) http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now()))
} }
@ -306,24 +384,28 @@ func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, va
} }
func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) { func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now()) cookies := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
for _, clr := range cookies {
http.SetCookie(rw, clr) http.SetCookie(rw, clr)
}
// ugly hack because default domain changed // ugly hack because default domain changed
if p.CookieDomain == "" { if p.CookieDomain == "" && len(cookies) > 0 {
clr2 := *clr clr2 := *cookies[0]
clr2.Domain = req.Host clr2.Domain = req.Host
http.SetCookie(rw, &clr2) http.SetCookie(rw, &clr2)
} }
} }
func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) { func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now())) for _, c := range p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()) {
http.SetCookie(rw, c)
}
} }
func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) { func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
var age time.Duration var age time.Duration
c, err := req.Cookie(p.CookieName) c, err := loadCookie(req, p.CookieName)
if err != nil { if err != nil {
// always http.ErrNoCookie // always http.ErrNoCookie
return nil, age, fmt.Errorf("Cookie %q not present", p.CookieName) return nil, age, fmt.Errorf("Cookie %q not present", p.CookieName)

View File

@ -6,6 +6,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"math/rand"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -143,6 +144,73 @@ func TestIsValidRedirect(t *testing.T) {
assert.Equal(t, false, invalidHttps2) assert.Equal(t, false, invalidHttps2)
} }
func randomString(length int) string {
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}
func TestSplitCookie(t *testing.T) {
c1 := &http.Cookie{
Name: "cookie-name",
Value: randomString(5120),
Path: "/",
Domain: "foo.bar",
HttpOnly: true,
Secure: true,
Expires: time.Now(),
}
cookies := splitCookie(c1)
assert.Equal(t, 2, len(cookies))
assert.Equal(t, c1.Name+"-0", cookies[0].Name)
assert.Equal(t, c1.Name+"-1", cookies[1].Name)
assert.Equal(t, 3840, len(cookies[0].Value))
assert.Equal(t, 5120-3840, len(cookies[1].Value))
c2 := &http.Cookie{
Name: "cookie-name",
Value: randomString(3000),
Path: "/",
Domain: "foo.bar",
HttpOnly: true,
Secure: true,
Expires: time.Now(),
}
cookies2 := splitCookie(c2)
assert.Equal(t, 1, len(cookies2))
assert.Equal(t, c2.Name, cookies2[0].Name)
assert.Equal(t, c2.Value, cookies2[0].Value)
}
func TestJoinCookies(t *testing.T) {
c1 := &http.Cookie{
Name: "cookie-name",
Value: randomString(5120),
Path: "/",
Domain: "foo.bar",
HttpOnly: true,
Secure: true,
Expires: time.Now(),
}
// Split Cookies
cookies := splitCookie(c1)
assert.Equal(t, 2, len(cookies))
// join cookies should be the ivnerse
c2, _ := joinCookies(cookies)
assert.Equal(t, c1.Name, c2.Name)
assert.Equal(t, c1.Value, c2.Value)
}
type TestProvider struct { type TestProvider struct {
*providers.ProviderData *providers.ProviderData
EmailAddress string EmailAddress string
@ -555,7 +623,7 @@ func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
}) })
} }
func (p *ProcessCookieTest) MakeCookie(value string, ref time.Time) *http.Cookie { func (p *ProcessCookieTest) MakeCookie(value string, ref time.Time) []*http.Cookie {
return p.proxy.MakeSessionCookie(p.req, value, p.opts.CookieExpire, ref) return p.proxy.MakeSessionCookie(p.req, value, p.opts.CookieExpire, ref)
} }
@ -564,7 +632,9 @@ func (p *ProcessCookieTest) SaveSession(s *providers.SessionState, ref time.Time
if err != nil { if err != nil {
return err return err
} }
p.req.AddCookie(p.proxy.MakeSessionCookie(p.req, value, p.proxy.CookieExpire, ref)) for _, c := range p.proxy.MakeSessionCookie(p.req, value, p.proxy.CookieExpire, ref) {
p.req.AddCookie(c)
}
return nil return nil
} }
@ -853,8 +923,9 @@ func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
cookie := proxy.MakeSessionCookie(req, value, proxy.CookieExpire, time.Now()) for _, c := range proxy.MakeSessionCookie(req, value, proxy.CookieExpire, time.Now()) {
req.AddCookie(cookie) req.AddCookie(c)
}
// This is used by the upstream to validate the signature. // This is used by the upstream to validate the signature.
st.authenticator.auth = hmacauth.NewHmacAuth( st.authenticator.auth = hmacauth.NewHmacAuth(
crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders) crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)