pentest: bind unlock cookie to HMAC per-paste token; serve only safe content types on /raw and can items with nosniff (#34)
This commit is contained in:
@@ -668,11 +668,39 @@ func (a *apiServer) handleRaw(w http.ResponseWriter, r *http.Request) {
|
||||
// #49 decision: raw reads count against the read budget too, with the
|
||||
// same per-viewer 15-minute dedupe window as page views.
|
||||
a.store.registerRead(row, currentViewerID(r))
|
||||
w.Header().Set("Content-Type", row.ContentType)
|
||||
// #34: content_type is attacker-controlled via the create API. Serving it
|
||||
// verbatim let a paste be stored with text/html (or image/svg+xml) and
|
||||
// render as active content on this origin when fetched from /raw —
|
||||
// stored XSS. Only pass through a fixed safe set; anything else is
|
||||
// served as plain text with nosniff.
|
||||
ct := row.ContentType
|
||||
if !safeRawContentType(ct) {
|
||||
ct = "text/plain; charset=utf-8"
|
||||
}
|
||||
w.Header().Set("Content-Type", ct)
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
a.store.IncrementViews(row.ID)
|
||||
w.Write([]byte(row.Content))
|
||||
}
|
||||
|
||||
// safeRawContentType reports whether ct is in the fixed set of types that are
|
||||
// safe to serve verbatim on /raw (no active-content execution contexts).
|
||||
func safeRawContentType(ct string) bool {
|
||||
base := ct
|
||||
if i := strings.IndexByte(ct, ';'); i >= 0 {
|
||||
base = ct[:i]
|
||||
}
|
||||
base = strings.ToLower(strings.TrimSpace(base))
|
||||
switch base {
|
||||
case "text/plain", "text/markdown", "text/x-markdown",
|
||||
"application/json", "application/pdf",
|
||||
"image/png", "image/jpeg", "image/gif", "image/webp",
|
||||
"application/octet-stream":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *apiServer) handleCanPage(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
can, err := a.store.GetCan(id)
|
||||
|
||||
Reference in New Issue
Block a user