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.
This commit is contained in:
r 2023-10-15 15:53:44 +00:00
parent ed521dd33d
commit 67b13c71ba
3 changed files with 37 additions and 6 deletions

View file

@ -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: "",
}
}

View file

@ -1,6 +1,8 @@
package service
import (
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"mime/multipart"
@ -1014,9 +1016,19 @@ func (s *service) SaveSettings(c *client, settings *model.Settings) (err error)
default:
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)
}

View file

@ -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)