From d297eb565814e1ab3d350b9eefc35a219fb51a88 Mon Sep 17 00:00:00 2001 From: r Date: Sat, 7 Oct 2023 09:11:43 +0000 Subject: [PATCH 1/8] Use stricter cookie attributes --- service/client.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/service/client.go b/service/client.go index e4ab8cb..18ebb52 100644 --- a/service/client.go +++ b/service/client.go @@ -33,9 +33,11 @@ func (c *client) setSession(sess *model.Session) error { return err } http.SetCookie(c.w, &http.Cookie{ - Name: "session", - Value: sb.String(), - Expires: time.Now().Add(365 * 24 * time.Hour), + Name: "session", + Path: "/", + HttpOnly: true, + Value: sb.String(), + Expires: time.Now().Add(365 * 24 * time.Hour), }) return nil } @@ -53,6 +55,7 @@ func (c *client) getSession() (sess *model.Session, err error) { func (c *client) unsetSession() { http.SetCookie(c.w, &http.Cookie{ Name: "session", + Path: "/", Value: "", Expires: time.Now(), }) From c7f40c1e1568bf3f0fcb15c0fc0d01f0f253675d Mon Sep 17 00:00:00 2001 From: r Date: Sat, 7 Oct 2023 09:19:56 +0000 Subject: [PATCH 2/8] Cleanup oauth redirect URL generation --- service/service.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/service/service.go b/service/service.go index 6b8d0ee..ca80a2b 100644 --- a/service/service.go +++ b/service/service.go @@ -859,6 +859,7 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sess *mod if err != nil { return } + rurl = app.AuthURI sess = &model.Session{ ID: sid, Instance: instance, @@ -867,20 +868,6 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sess *mod CSRFToken: csrf, Settings: *model.NewSettings(), } - - u, err := url.Parse("/oauth/authorize") - if err != nil { - return - } - - q := make(url.Values) - q.Set("scope", "read write follow") - q.Set("client_id", app.ClientID) - q.Set("response_type", "code") - q.Set("redirect_uri", s.cwebsite+"/oauth_callback") - u.RawQuery = q.Encode() - - rurl = instanceURL + u.String() return } From 927072e26a470ed3084e36ae2413f4dfd088905f Mon Sep 17 00:00:00 2001 From: r Date: Sat, 7 Oct 2023 10:20:11 +0000 Subject: [PATCH 3/8] Remove unused session ID field --- model/session.go | 1 - service/service.go | 5 ----- util/rand.go | 4 ---- 3 files changed, 10 deletions(-) diff --git a/model/session.go b/model/session.go index 6ada4aa..f9e4287 100644 --- a/model/session.go +++ b/model/session.go @@ -1,7 +1,6 @@ package model type Session struct { - ID string `json:"id,omitempty"` UserID string `json:"uid,omitempty"` Instance string `json:"ins,omitempty"` ClientID string `json:"cid,omitempty"` diff --git a/service/service.go b/service/service.go index ca80a2b..2f87fa3 100644 --- a/service/service.go +++ b/service/service.go @@ -840,10 +840,6 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sess *mod instanceURL = "https://" + instance } - sid, err := util.NewSessionID() - if err != nil { - return - } csrf, err := util.NewCSRFToken() if err != nil { return @@ -861,7 +857,6 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sess *mod } rurl = app.AuthURI sess = &model.Session{ - ID: sid, Instance: instance, ClientID: app.ClientID, ClientSecret: app.ClientSecret, diff --git a/util/rand.go b/util/rand.go index 90e66a5..f1692b9 100644 --- a/util/rand.go +++ b/util/rand.go @@ -16,10 +16,6 @@ func NewRandID(n int) (string, error) { return enc.EncodeToString(data), nil } -func NewSessionID() (string, error) { - return NewRandID(24) -} - func NewCSRFToken() (string, error) { return NewRandID(24) } From ed521dd33d0d002c577a75e349136fed25b7fda5 Mon Sep 17 00:00:00 2001 From: r Date: Sun, 15 Oct 2023 15:46:54 +0000 Subject: [PATCH 4/8] Restrict instance level custom CSS to static directory --- bloat.conf | 1 - main.go | 9 +-------- templates/header.tmpl | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/bloat.conf b/bloat.conf index f29e553..9bd2781 100644 --- a/bloat.conf +++ b/bloat.conf @@ -38,5 +38,4 @@ post_formats=PlainText:text/plain,HTML:text/html,Markdown:text/markdown,BBCode:t # single_instance=pl.mydomain.com # Path to custom CSS. Value can be a file path relative to the static directory. -# or a URL starting with either "http://" or "https://". # custom_css=custom.css diff --git a/main.go b/main.go index 657912d..9e9ba4e 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( "net/http" "os" "path/filepath" - "strings" "bloat/config" "bloat/renderer" @@ -47,14 +46,8 @@ func main() { errExit(err) } - customCSS := config.CustomCSS - if len(customCSS) > 0 && !strings.HasPrefix(customCSS, "http://") && - !strings.HasPrefix(customCSS, "https://") { - customCSS = "/static/" + customCSS - } - s := service.NewService(config.ClientName, config.ClientScope, - config.ClientWebsite, customCSS, config.SingleInstance, + config.ClientWebsite, config.CustomCSS, config.SingleInstance, config.PostFormats, renderer) handler := service.NewHandler(s, *verbose, config.StaticDirectory) diff --git a/templates/header.tmpl b/templates/header.tmpl index 8a1b0ca..7ebf65e 100644 --- a/templates/header.tmpl +++ b/templates/header.tmpl @@ -20,7 +20,7 @@ {{if gt .Count 0}}({{.Count}}){{end}} {{.Title}} {{if .CustomCSS}} - + {{end}} {{if $.Ctx.FluorideMode}} From 67b13c71baea56eeb15532ca1b1377f6da8d18ac Mon Sep 17 00:00:00 2001 From: r Date: Sun, 15 Oct 2023 15:53:44 +0000 Subject: [PATCH 5/8] Use CSP header to restrict resource loading This helps mitigate XSS exploits. Users will have to save the settings again to make the custom CSS work. --- model/session.go | 2 ++ service/service.go | 16 ++++++++++++++-- service/transport.go | 25 +++++++++++++++++++++---- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/model/session.go b/model/session.go index f9e4287..61a409c 100644 --- a/model/session.go +++ b/model/session.go @@ -27,6 +27,7 @@ type Settings struct { AntiDopamineMode bool `json:"adm,omitempty"` HideUnsupportedNotifs bool `json:"hun,omitempty"` CSS string `json:"css,omitempty"` + CSSHash string `json:"cssh,omitempty"` } func NewSettings() *Settings { @@ -43,5 +44,6 @@ func NewSettings() *Settings { AntiDopamineMode: false, HideUnsupportedNotifs: false, CSS: "", + CSSHash: "", } } diff --git a/service/service.go b/service/service.go index 2f87fa3..c925b83 100644 --- a/service/service.go +++ b/service/service.go @@ -1,6 +1,8 @@ package service import ( + "crypto/sha256" + "encoding/base64" "errors" "fmt" "mime/multipart" @@ -1014,8 +1016,18 @@ func (s *service) SaveSettings(c *client, settings *model.Settings) (err error) default: return errInvalidArgument } - if len(settings.CSS) > 1<<20 { - return errInvalidArgument + if len(settings.CSS) > 0 { + if len(settings.CSS) > 1<<20 { + return errInvalidArgument + } + // For some reason, browsers convert CRLF to LF before calculating + // the hash of the inline resources. + settings.CSS = strings.ReplaceAll(settings.CSS, "\x0d\x0a", "\x0a") + + h := sha256.Sum256([]byte(settings.CSS)) + settings.CSSHash = base64.StdEncoding.EncodeToString(h[:]) + } else { + settings.CSSHash = "" } c.s.Settings = *settings return c.setSession(c.s) diff --git a/service/transport.go b/service/transport.go index 1182d6c..d032cce 100644 --- a/service/transport.go +++ b/service/transport.go @@ -26,6 +26,16 @@ const ( CSRF ) +const csp = "default-src 'none';" + + " img-src *;" + + " media-src *;" + + " font-src *;" + + " child-src *;" + + " connect-src 'self';" + + " form-action 'self';" + + " script-src 'self';" + + " style-src 'self'" + func NewHandler(s *service, verbose bool, staticDir string) http.Handler { r := mux.NewRouter() @@ -58,14 +68,14 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler { }(time.Now()) } - var ct string + h := c.w.Header() switch rt { case HTML: - ct = "text/html; charset=utf-8" + h.Set("Content-Type", "text/html; charset=utf-8") + h.Set("Content-Security-Policy", csp) case JSON: - ct = "application/json" + h.Set("Content-Type", "application/json") } - c.w.Header().Add("Content-Type", ct) err = c.authenticate(at, s.instance) if err != nil { @@ -73,6 +83,13 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler { return } + // Override the CSP header to allow custom CSS + if rt == HTML && len(c.s.Settings.CSS) > 0 && + len(c.s.Settings.CSSHash) > 0 { + v := fmt.Sprintf("%s 'sha256-%s'", csp, c.s.Settings.CSSHash) + h.Set("Content-Security-Policy", v) + } + err = f(c) if err != nil { writeError(c, err, rt, req.Method == http.MethodGet) From 9b053e32ec8a42c74f3c09d28a8df4086b5b2945 Mon Sep 17 00:00:00 2001 From: r Date: Sun, 22 Oct 2023 11:11:21 +0000 Subject: [PATCH 6/8] Fix replace syntax --- service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/service.go b/service/service.go index c925b83..db309ff 100644 --- a/service/service.go +++ b/service/service.go @@ -1022,7 +1022,7 @@ func (s *service) SaveSettings(c *client, settings *model.Settings) (err error) } // For some reason, browsers convert CRLF to LF before calculating // the hash of the inline resources. - settings.CSS = strings.ReplaceAll(settings.CSS, "\x0d\x0a", "\x0a") + settings.CSS = strings.Replace(settings.CSS, "\x0d\x0a", "\x0a", -1) h := sha256.Sum256([]byte(settings.CSS)) settings.CSSHash = base64.StdEncoding.EncodeToString(h[:]) From 597cfc6b1ed23dc85774a43055416c98b77cae67 Mon Sep 17 00:00:00 2001 From: r Date: Sun, 22 Oct 2023 11:12:27 +0000 Subject: [PATCH 7/8] fluoride: Add image preview for profile image --- static/fluoride.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/fluoride.js b/static/fluoride.js index abeed21..d5c5b5d 100644 --- a/static/fluoride.js +++ b/static/fluoride.js @@ -325,7 +325,7 @@ document.addEventListener("DOMContentLoaded", function() { links[j].target = "_blank"; } - var links = document.querySelectorAll(".status-media-container .img-link"); + var links = document.querySelectorAll(".status-media-container .img-link, .user-profile-img-container .img-link"); for (var j = 0; j < links.length; j++) { handleImgPreview(links[j]); } From f4881e72675e87a9eae716436c3ac18a788d596d Mon Sep 17 00:00:00 2001 From: r Date: Wed, 25 Oct 2023 06:40:34 +0000 Subject: [PATCH 8/8] Remove form-action CSP directive Chrome incorrectly restricts the redirect URL to the sources specified in the form-action value, which prevents the instance oauth page from loading. --- service/transport.go | 1 - 1 file changed, 1 deletion(-) diff --git a/service/transport.go b/service/transport.go index d032cce..f7e31d6 100644 --- a/service/transport.go +++ b/service/transport.go @@ -32,7 +32,6 @@ const csp = "default-src 'none';" + " font-src *;" + " child-src *;" + " connect-src 'self';" + - " form-action 'self';" + " script-src 'self';" + " style-src 'self'"