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()
|
origWidth := bounds.Dx()
|
||||||
origHeight := bounds.Dy()
|
origHeight := bounds.Dy()
|
||||||
|
|
||||||
|
// Validate input dimensions to prevent DoS via decompression bombs
|
||||||
|
if origWidth > MaxInputDimension || origHeight > MaxInputDimension {
|
||||||
|
return nil, ErrInputTooLarge
|
||||||
|
}
|
||||||
|
|
||||||
// Determine target dimensions
|
// Determine target dimensions
|
||||||
targetWidth := req.Size.Width
|
targetWidth := req.Size.Width
|
||||||
targetHeight := req.Size.Height
|
targetHeight := req.Size.Height
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package imgcache
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -74,6 +75,11 @@ func ParseImageURL(urlPath string) (*ParsedURL, error) {
|
|||||||
|
|
||||||
// parseImageComponents parses <host>/<path>/<size>.<format> structure.
|
// parseImageComponents parses <host>/<path>/<size>.<format> structure.
|
||||||
func parseImageComponents(remainder string) (*ParsedURL, error) {
|
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
|
// Find the last path segment which contains size.format
|
||||||
lastSlash := strings.LastIndex(remainder, "/")
|
lastSlash := strings.LastIndex(remainder, "/")
|
||||||
if lastSlash == -1 {
|
if lastSlash == -1 {
|
||||||
@@ -131,6 +137,64 @@ func parseImageComponents(remainder string) (*ParsedURL, error) {
|
|||||||
}, nil
|
}, 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"
|
// parseSizeFormat parses strings like "800x600.webp" or "orig.png"
|
||||||
func parseSizeFormat(s string) (Size, ImageFormat, error) {
|
func parseSizeFormat(s string) (Size, ImageFormat, error) {
|
||||||
matches := sizeFormatRegex.FindStringSubmatch(s)
|
matches := sizeFormatRegex.FindStringSubmatch(s)
|
||||||
|
|||||||
Reference in New Issue
Block a user