Implement max input dimensions and path traversal validation
- Reject input images exceeding MaxInputDimension (8192px) to prevent DoS - Detect path traversal: ../, encoded variants, backslashes, null bytes
This commit is contained in:
@@ -53,6 +53,11 @@ func (p *ImageProcessor) Process(
|
||||
origWidth := bounds.Dx()
|
||||
origHeight := bounds.Dy()
|
||||
|
||||
// Validate input dimensions to prevent DoS via decompression bombs
|
||||
if origWidth > MaxInputDimension || origHeight > MaxInputDimension {
|
||||
return nil, ErrInputTooLarge
|
||||
}
|
||||
|
||||
// Determine target dimensions
|
||||
targetWidth := req.Size.Width
|
||||
targetHeight := req.Size.Height
|
||||
|
||||
@@ -3,6 +3,7 @@ package imgcache
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -74,6 +75,11 @@ func ParseImageURL(urlPath string) (*ParsedURL, error) {
|
||||
|
||||
// parseImageComponents parses <host>/<path>/<size>.<format> structure.
|
||||
func parseImageComponents(remainder string) (*ParsedURL, error) {
|
||||
// Check for path traversal before any other processing
|
||||
if err := checkPathTraversal(remainder); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find the last path segment which contains size.format
|
||||
lastSlash := strings.LastIndex(remainder, "/")
|
||||
if lastSlash == -1 {
|
||||
@@ -131,6 +137,64 @@ func parseImageComponents(remainder string) (*ParsedURL, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// checkPathTraversal detects path traversal attempts in a URL path.
|
||||
// It checks for various attack vectors including:
|
||||
// - Direct ../ sequences
|
||||
// - URL-encoded variants (%2e%2e, %252e%252e)
|
||||
// - Backslash variants (..\)
|
||||
// - Null byte injection (%00)
|
||||
func checkPathTraversal(path string) error {
|
||||
// First, URL-decode the path to catch encoded attacks
|
||||
// Decode multiple times to catch double-encoding
|
||||
decoded := path
|
||||
for range 3 {
|
||||
newDecoded, err := url.PathUnescape(decoded)
|
||||
if err != nil {
|
||||
// Malformed encoding is suspicious
|
||||
return ErrPathTraversal
|
||||
}
|
||||
|
||||
if newDecoded == decoded {
|
||||
break
|
||||
}
|
||||
|
||||
decoded = newDecoded
|
||||
}
|
||||
|
||||
// Normalize backslashes to forward slashes
|
||||
normalized := strings.ReplaceAll(decoded, "\\", "/")
|
||||
|
||||
// Check for null bytes
|
||||
if strings.Contains(normalized, "\x00") {
|
||||
return ErrPathTraversal
|
||||
}
|
||||
|
||||
// Check for parent directory traversal
|
||||
// Look for "/.." or "../" patterns
|
||||
if strings.Contains(normalized, "/../") ||
|
||||
strings.Contains(normalized, "/..") ||
|
||||
strings.HasPrefix(normalized, "../") ||
|
||||
strings.HasSuffix(normalized, "/..") ||
|
||||
normalized == ".." {
|
||||
return ErrPathTraversal
|
||||
}
|
||||
|
||||
// Also check for ".." as a path segment in the original path
|
||||
// This catches cases where the path hasn't been normalized
|
||||
segments := strings.Split(path, "/")
|
||||
for _, seg := range segments {
|
||||
// URL decode the segment
|
||||
decodedSeg, _ := url.PathUnescape(seg)
|
||||
decodedSeg = strings.ReplaceAll(decodedSeg, "\\", "/")
|
||||
|
||||
if decodedSeg == ".." {
|
||||
return ErrPathTraversal
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseSizeFormat parses strings like "800x600.webp" or "orig.png"
|
||||
func parseSizeFormat(s string) (Size, ImageFormat, error) {
|
||||
matches := sizeFormatRegex.FindStringSubmatch(s)
|
||||
|
||||
Reference in New Issue
Block a user