Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Decompression exploit check (h2non#404)
* Bump bimg version to 1.1.7

* Add decompression bomb exploit check

* Update README with new flag

* Fix tests
  • Loading branch information
SeaaaaaSharp authored Dec 3, 2022
commit 35c87ba9d5f443b78dd70bfb88a0bac85bf407ab
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ Options:
-url-signature-key The URL signature key (32 characters minimum)
-allowed-origins <urls> Restrict remote image source processing to certain origins (separated by commas). Note: Origins are validated against host *AND* path.
-max-allowed-size <bytes> Restrict maximum size of http image source (in bytes)
-max-allowed-resolution <megapixels> Restrict maximum resolution of the image [default: 18.0]
-certfile <path> TLS certificate file path
-keyfile <path> TLS private key file path
-authorization <value> Defines a constant Authorization header value passed to all the image source servers. -enable-url-source flag must be defined. This overwrites authorization headers forwarding behavior via X-Forward-Authorization
Expand Down
71 changes: 43 additions & 28 deletions controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"encoding/json"
"fmt"
"mime"
"path"
"net/http"
"path"
"strconv"
"strings"

Expand All @@ -16,14 +16,14 @@ import (
func indexController(o ServerOptions) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != path.Join(o.PathPrefix, "/") {
ErrorReply(r, w, ErrNotFound, ServerOptions{})
return
ErrorReply(r, w, ErrNotFound, ServerOptions{})
return
}

body, _ := json.Marshal(Versions{
Version,
bimg.Version,
bimg.VipsVersion,
Version,
bimg.Version,
bimg.VipsVersion,
})
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(body)
Expand Down Expand Up @@ -120,6 +120,21 @@ func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, operation
return
}

sizeInfo, err := bimg.Size(buf)

if err != nil {
ErrorReply(r, w, NewError("Error while processing the image: "+err.Error(), http.StatusBadRequest), o)
return
}

// https://en.wikipedia.org/wiki/Image_resolution#Pixel_count
imgResolution := float64(sizeInfo.Width) * float64(sizeInfo.Height)

if (imgResolution / 1000000) > o.MaxAllowedPixels {
ErrorReply(r, w, ErrResolutionTooBig, o)
return
}

image, err := operation.Run(buf, opts)
if err != nil {
// Ensure the Vary header is set when an error occurs
Expand Down Expand Up @@ -149,34 +164,34 @@ func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, operation
func formController(o ServerOptions) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
operations := []struct {
name string
method string
args string
name string
method string
args string
}{
{"Resize", "resize", "width=300&height=200&type=jpeg"},
{"Force resize", "resize", "width=300&height=200&force=true"},
{"Crop", "crop", "width=300&quality=95"},
{"SmartCrop", "crop", "width=300&height=260&quality=95&gravity=smart"},
{"Extract", "extract", "top=100&left=100&areawidth=300&areaheight=150"},
{"Enlarge", "enlarge", "width=1440&height=900&quality=95"},
{"Rotate", "rotate", "rotate=180"},
{"AutoRotate", "autorotate", "quality=90"},
{"Flip", "flip", ""},
{"Flop", "flop", ""},
{"Thumbnail", "thumbnail", "width=100"},
{"Zoom", "zoom", "factor=2&areawidth=300&top=80&left=80"},
{"Color space (black&white)", "resize", "width=400&height=300&colorspace=bw"},
{"Add watermark", "watermark", "textwidth=100&text=Hello&font=sans%2012&opacity=0.5&color=255,200,50"},
{"Convert format", "convert", "type=png"},
{"Image metadata", "info", ""},
{"Gaussian blur", "blur", "sigma=15.0&minampl=0.2"},
{"Pipeline (image reduction via multiple transformations)", "pipeline", "operations=%5B%7B%22operation%22:%20%22crop%22,%20%22params%22:%20%7B%22width%22:%20300,%20%22height%22:%20260%7D%7D,%20%7B%22operation%22:%20%22convert%22,%20%22params%22:%20%7B%22type%22:%20%22webp%22%7D%7D%5D"},
{"Resize", "resize", "width=300&height=200&type=jpeg"},
{"Force resize", "resize", "width=300&height=200&force=true"},
{"Crop", "crop", "width=300&quality=95"},
{"SmartCrop", "crop", "width=300&height=260&quality=95&gravity=smart"},
{"Extract", "extract", "top=100&left=100&areawidth=300&areaheight=150"},
{"Enlarge", "enlarge", "width=1440&height=900&quality=95"},
{"Rotate", "rotate", "rotate=180"},
{"AutoRotate", "autorotate", "quality=90"},
{"Flip", "flip", ""},
{"Flop", "flop", ""},
{"Thumbnail", "thumbnail", "width=100"},
{"Zoom", "zoom", "factor=2&areawidth=300&top=80&left=80"},
{"Color space (black&white)", "resize", "width=400&height=300&colorspace=bw"},
{"Add watermark", "watermark", "textwidth=100&text=Hello&font=sans%2012&opacity=0.5&color=255,200,50"},
{"Convert format", "convert", "type=png"},
{"Image metadata", "info", ""},
{"Gaussian blur", "blur", "sigma=15.0&minampl=0.2"},
{"Pipeline (image reduction via multiple transformations)", "pipeline", "operations=%5B%7B%22operation%22:%20%22crop%22,%20%22params%22:%20%7B%22width%22:%20300,%20%22height%22:%20260%7D%7D,%20%7B%22operation%22:%20%22convert%22,%20%22params%22:%20%7B%22type%22:%20%22webp%22%7D%7D%5D"},
}

html := "<html><body>"

for _, form := range operations {
html += fmt.Sprintf(`
html += fmt.Sprintf(`
<h1>%s</h1>
<form method="POST" action="%s?%s" enctype="multipart/form-data">
<input type="file" name="file" />
Expand Down
1 change: 1 addition & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var (
ErrNotImplemented = NewError("Not implemented endpoint", http.StatusNotImplemented)
ErrInvalidURLSignature = NewError("Invalid URL signature", http.StatusBadRequest)
ErrURLSignatureMismatch = NewError("URL signature mismatch", http.StatusForbidden)
ErrResolutionTooBig = NewError("Image resolution is too big", http.StatusUnprocessableEntity)
)

type Error struct {
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ go 1.12

require (
github.com/garyburd/redigo v1.6.0 // indirect
github.com/h2non/bimg v1.1.7
github.com/h2non/filetype v1.1.0
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad // indirect
github.com/rs/cors v0.0.0-20170727213201-7af7a1e09ba3
github.com/h2non/bimg v1.1.4
github.com/h2non/filetype v1.1.0
gopkg.in/throttled/throttled.v2 v2.0.3
)
7 changes: 3 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/h2non/bimg v1.1.2 h1:J75W2eM5FT0KjcwsL2aiy1Ilu0Xy0ENb0sU+HHUJAvw=
github.com/h2non/bimg v1.1.2/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8=
github.com/h2non/bimg v1.1.4 h1:6qf7qDo3d9axbNUOcSoQmzleBCMTcQ1PwF3FgGhX4O0=
github.com/h2non/bimg v1.1.4/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8=
github.com/h2non/bimg v1.1.7 h1:JKJe70nDNMWp2wFnTLMGB8qJWQQMaKRn56uHmC/4+34=
github.com/h2non/bimg v1.1.7/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8=
github.com/h2non/filetype v1.1.0 h1:Or/gjocJrJRNK/Cri/TDEKFjAR+cfG6eK65NGYB6gBA=
github.com/h2non/filetype v1.1.0/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad h1:eMxs9EL0PvIGS9TTtxg4R+JxuPGav82J8rA+GFnY7po=
Expand Down
2 changes: 1 addition & 1 deletion image.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import (
"errors"
"fmt"
"io"
"strings"
"io/ioutil"
"math"
"net/http"
"strings"

"github.com/h2non/bimg"
)
Expand Down
3 changes: 3 additions & 0 deletions imaginary.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
aURLSignatureKey = flag.String("url-signature-key", "", "The URL signature key (32 characters minimum)")
aAllowedOrigins = flag.String("allowed-origins", "", "Restrict remote image source processing to certain origins (separated by commas). Note: Origins are validated against host *AND* path.")
aMaxAllowedSize = flag.Int("max-allowed-size", 0, "Restrict maximum size of http image source (in bytes)")
aMaxAllowedPixels = flag.Float64("max-allowed-resolution", 18.0, "Restrict maximum resolution of the image (in megapixels)")
aKey = flag.String("key", "", "Define API key for authorization")
aMount = flag.String("mount", "", "Mount server local directory")
aCertFile = flag.String("certfile", "", "TLS certificate file path")
Expand Down Expand Up @@ -95,6 +96,7 @@ Options:
-url-signature-key The URL signature key (32 characters minimum)
-allowed-origins <urls> Restrict remote image source processing to certain origins (separated by commas)
-max-allowed-size <bytes> Restrict maximum size of http image source (in bytes)
-max-allowed-resolution <megapixels> Restrict maximum resolution of the image [default: 18.0]
-certfile <path> TLS certificate file path
-keyfile <path> TLS private key file path
-authorization <value> Defines a constant Authorization header value passed to all the image source servers. -enable-url-source flag must be defined. This overwrites authorization headers forwarding behavior via X-Forward-Authorization
Expand Down Expand Up @@ -158,6 +160,7 @@ func main() {
ForwardHeaders: parseForwardHeaders(*aForwardHeaders),
AllowedOrigins: parseOrigins(*aAllowedOrigins),
MaxAllowedSize: *aMaxAllowedSize,
MaxAllowedPixels: *aMaxAllowedPixels,
LogLevel: getLogLevel(*aLogLevel),
ReturnSize: *aReturnSize,
}
Expand Down
8 changes: 4 additions & 4 deletions log.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ func (r *LogRecord) WriteHeader(status int) {

// LogHandler maps the HTTP handler with a custom io.Writer compatible stream
type LogHandler struct {
handler http.Handler
io io.Writer
logLevel string
handler http.Handler
io io.Writer
logLevel string
}

// NewLog creates a new logger
Expand Down Expand Up @@ -79,7 +79,7 @@ func (h *LogHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
record.time = finishTime.UTC()
record.elapsedTime = finishTime.Sub(startTime)

switch h.logLevel{
switch h.logLevel {
case "error":
if record.status >= http.StatusInternalServerError {
record.Log(h.io)
Expand Down
5 changes: 3 additions & 2 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package main

import (
"context"
"log"
"net/http"
"net/url"
"log"
"os"
"os/signal"
"syscall"
"path"
"strconv"
"strings"
"syscall"
"time"
)

Expand All @@ -22,6 +22,7 @@ type ServerOptions struct {
HTTPReadTimeout int
HTTPWriteTimeout int
MaxAllowedSize int
MaxAllowedPixels float64
CORS bool
Gzip bool // deprecated
AuthForwarding bool
Expand Down
12 changes: 6 additions & 6 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

func TestIndex(t *testing.T) {
opts := ServerOptions{PathPrefix: "/"}
opts := ServerOptions{PathPrefix: "/", MaxAllowedPixels: 18.0}
ts := testServer(indexController(opts))
defer ts.Close()

Expand Down Expand Up @@ -275,7 +275,7 @@ func TestFit(t *testing.T) {
}

func TestRemoteHTTPSource(t *testing.T) {
opts := ServerOptions{EnableURLSource: true}
opts := ServerOptions{EnableURLSource: true, MaxAllowedPixels: 18.0}
fn := ImageMiddleware(opts)(Crop)
LoadSources(opts)

Expand Down Expand Up @@ -316,7 +316,7 @@ func TestRemoteHTTPSource(t *testing.T) {
}

func TestInvalidRemoteHTTPSource(t *testing.T) {
opts := ServerOptions{EnableURLSource: true}
opts := ServerOptions{EnableURLSource: true, MaxAllowedPixels: 18.0}
fn := ImageMiddleware(opts)(Crop)
LoadSources(opts)

Expand All @@ -339,7 +339,7 @@ func TestInvalidRemoteHTTPSource(t *testing.T) {
}

func TestMountDirectory(t *testing.T) {
opts := ServerOptions{Mount: "testdata"}
opts := ServerOptions{Mount: "testdata", MaxAllowedPixels: 18.0}
fn := ImageMiddleware(opts)(Crop)
LoadSources(opts)

Expand Down Expand Up @@ -374,7 +374,7 @@ func TestMountDirectory(t *testing.T) {
}

func TestMountInvalidDirectory(t *testing.T) {
fn := ImageMiddleware(ServerOptions{Mount: "_invalid_"})(Crop)
fn := ImageMiddleware(ServerOptions{Mount: "_invalid_", MaxAllowedPixels: 18.0})(Crop)
ts := httptest.NewServer(fn)
url := ts.URL + "?top=100&left=100&areawidth=200&areaheight=120&file=large.jpg"
defer ts.Close()
Expand Down Expand Up @@ -408,7 +408,7 @@ func TestMountInvalidPath(t *testing.T) {
func controller(op Operation) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
buf, _ := ioutil.ReadAll(r.Body)
imageHandler(w, r, buf, op, ServerOptions{})
imageHandler(w, r, buf, op, ServerOptions{MaxAllowedPixels: 18.0})
}
}

Expand Down