fix: validate proxy IP headers and force attachment for unsafe MIME types (BUG-112, BUG-118)

BUG-112: clientIPWithProxies now validates extracted X-Real-IP and
X-Forwarded-For values with net.ParseIP. Non-IP strings are rejected,
falling back to RemoteAddr. Prevents attackers from choosing arbitrary
rate-limit bucket keys via header injection.

BUG-118: Files with MIME types that could execute active content
(HTML, SVG, XML, PDF) are now served with Content-Disposition: attachment
instead of inline, preventing content hosting under the OwnCord origin.
This commit is contained in:
J3vb
2026-04-02 13:23:09 +02:00
parent fcffdb8d6b
commit d5ec7ca877
2 changed files with 30 additions and 3 deletions
+7 -2
View File
@@ -211,15 +211,20 @@ func clientIPWithProxies(r *http.Request, trustedCIDRs []string) string {
}
// Prefer X-Real-IP when coming from a trusted proxy.
// BUG-112: Validate extracted IP to prevent spoofed rate-limit keys.
if xri := strings.TrimSpace(r.Header.Get("X-Real-IP")); xri != "" {
return xri
if net.ParseIP(xri) != nil {
return xri
}
}
// Fall back to the leftmost (client) entry in X-Forwarded-For.
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
parts := strings.SplitN(xff, ",", 2)
if client := strings.TrimSpace(parts[0]); client != "" {
return client
if net.ParseIP(client) != nil {
return client
}
}
}
+23 -1
View File
@@ -57,6 +57,22 @@ func sanitizeUploadFilename(name string) string {
return name
}
// isUnsafeInlineMIME returns true for MIME types that could execute active
// content (scripts, markup) if served inline under the OwnCord origin.
func isUnsafeInlineMIME(mimeType string) bool {
// Normalize: take the base type before any parameters (e.g. "text/html; charset=utf-8").
base := strings.SplitN(mimeType, ";", 2)[0]
base = strings.TrimSpace(strings.ToLower(base))
switch base {
case "text/html", "application/xhtml+xml",
"image/svg+xml", "text/xml", "application/xml",
"application/pdf",
"text/xsl", "text/xslt":
return true
}
return false
}
// MountUploadRoutes registers upload and file-serving endpoints.
// allowedOrigins controls the Access-Control-Allow-Origin header on served files.
func MountUploadRoutes(r chi.Router, database *db.DB, store *storage.Storage, limiter *auth.RateLimiter, allowedOrigins []string) {
@@ -267,7 +283,13 @@ func handleServeFile(database *db.DB, store *storage.Storage, allowedOrigins []s
// Set headers before ServeContent to ensure correct MIME type.
w.Header().Set("Content-Type", aa.MimeType)
w.Header().Set("Content-Disposition", mime.FormatMediaType("inline", map[string]string{"filename": aa.Filename}))
// BUG-118: Force download for MIME types that could execute content
// under the OwnCord origin (HTML, SVG, XML, PDF).
disposition := "inline"
if isUnsafeInlineMIME(aa.MimeType) {
disposition = "attachment"
}
w.Header().Set("Content-Disposition", mime.FormatMediaType(disposition, map[string]string{"filename": aa.Filename}))
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, immutable", fileCacheMaxAgeSeconds))
// CORS: allow webview to read the response body using configured origins.
if origin := r.Header.Get("Origin"); origin != "" {