diff --git a/cycletls/decompress_test.go b/cycletls/decompress_test.go new file mode 100644 index 00000000..3e909c51 --- /dev/null +++ b/cycletls/decompress_test.go @@ -0,0 +1,86 @@ +package cycletls + +import ( + "bytes" + "compress/gzip" + "testing" + + "github.com/andybalholm/brotli" +) + +func TestUnBrotliDataValid(t *testing.T) { + var buf bytes.Buffer + w := brotli.NewWriter(&buf) + if _, err := w.Write([]byte("hello cycle")); err != nil { + t.Fatal(err) + } + if err := w.Close(); err != nil { + t.Fatal(err) + } + + got, err := unBrotliData(buf.Bytes()) + if err != nil { + t.Fatalf("valid brotli: %v", err) + } + if string(got) != "hello cycle" { + t.Fatalf("got %q", got) + } + + decoded := DecompressBody(buf.Bytes(), []string{"br"}, nil) + if string(decoded) != "hello cycle" { + t.Fatalf("DecompressBody got %q", decoded) + } +} + +func TestUnBrotliDataInvalidDoesNotPanic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("invalid brotli panicked: %v", r) + } + }() + + inputs := [][]byte{ + {}, + {0x00}, + []byte("not brotli"), + {0x06, 0x00}, + bytes.Repeat([]byte{0xff}, 64), + } + for _, in := range inputs { + got, err := unBrotliData(in) + out := DecompressBody(in, []string{"br"}, nil) + if err != nil { + if !bytes.Equal(out, in) { + t.Errorf("DecompressBody should return original body on error, got %q for %v (unz=%q err=%v)", out, in, got, err) + } + continue + } + _ = got + } +} + +func TestReadLimitedCapsSize(t *testing.T) { + r := bytes.NewReader(make([]byte, maxDecompressedBody+1)) + if _, err := readLimited(r); err == nil { + t.Fatal("expected size limit error") + } +} + +func TestGUnzipDataValid(t *testing.T) { + var buf bytes.Buffer + w := gzip.NewWriter(&buf) + if _, err := w.Write([]byte("hello gzip")); err != nil { + t.Fatal(err) + } + if err := w.Close(); err != nil { + t.Fatal(err) + } + + got, err := gUnzipData(buf.Bytes()) + if err != nil { + t.Fatalf("valid gzip: %v", err) + } + if string(got) != "hello gzip" { + t.Fatalf("got %q", got) + } +} diff --git a/cycletls/go.mod b/cycletls/go.mod index 43847899..ffb5f708 100644 --- a/cycletls/go.mod +++ b/cycletls/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.4 require ( github.com/Danny-Dasilva/fhttp v0.0.0-20260106165651-41258808b131 - github.com/andybalholm/brotli v1.2.0 + github.com/andybalholm/brotli v1.2.7 github.com/gorilla/websocket v1.5.1 github.com/quic-go/quic-go v0.53.0 github.com/refraction-networking/uquic v0.0.6 diff --git a/cycletls/go.sum b/cycletls/go.sum index 11917afd..10d39567 100644 --- a/cycletls/go.sum +++ b/cycletls/go.sum @@ -8,13 +8,11 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1 h1:/lqhaiz7xdPr6kuaW1tQ/8DdpWdxkdyd9W/6EHz4oRw= -github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1/go.mod h1:Hvab/V/YKCDXsEpKYKHjAXH5IFOmoq9FsfxjztEqvDc= github.com/Danny-Dasilva/fhttp v0.0.0-20260106165651-41258808b131 h1:gRgU8L1jIWP8noBJk8p1mD3JSJNVDB3HNncjKT6SAFg= github.com/Danny-Dasilva/fhttp v0.0.0-20260106165651-41258808b131/go.mod h1:Hvab/V/YKCDXsEpKYKHjAXH5IFOmoq9FsfxjztEqvDc= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/brotli v1.2.7 h1:UgafRPGMam5ySHfRkhthMqOFA6IFoeF3D7mWIDLjG5A= +github.com/andybalholm/brotli v1.2.7/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= diff --git a/cycletls/utils.go b/cycletls/utils.go index c6e11a8a..269c473d 100644 --- a/cycletls/utils.go +++ b/cycletls/utils.go @@ -23,6 +23,10 @@ import ( const ( chrome = "chrome" //chrome User agent enum firefox = "firefox" //firefox User agent enum + + // maxDecompressedBody caps gzip/deflate/brotli inflate so io.ReadAll + // cannot grow without bound on a malicious Content-Encoding payload. + maxDecompressedBody = 64 << 20 // 64 MiB ) // Cipher suite mappings from hex to uTLS constants @@ -108,14 +112,25 @@ func DecompressBody(Body []byte, encoding []string, content []string) (parsedBod } } +func readLimited(r io.Reader) ([]byte, error) { + limited := io.LimitReader(r, maxDecompressedBody+1) + data, err := io.ReadAll(limited) + if err != nil { + return nil, err + } + if int64(len(data)) > maxDecompressedBody { + return nil, errors.New("decompressed body exceeds size limit") + } + return data, nil +} + func gUnzipData(data []byte) (resData []byte, err error) { gz, err := gzip.NewReader(bytes.NewReader(data)) if err != nil { return []byte{}, err } defer gz.Close() - respBody, err := io.ReadAll(gz) - return respBody, err + return readLimited(gz) } func enflateData(data []byte) (resData []byte, err error) { zr, err := zlib.NewReader(bytes.NewReader(data)) @@ -123,13 +138,11 @@ func enflateData(data []byte) (resData []byte, err error) { return []byte{}, err } defer zr.Close() - enflated, err := io.ReadAll(zr) - return enflated, err + return readLimited(zr) } func unBrotliData(data []byte) (resData []byte, err error) { br := brotli.NewReader(bytes.NewReader(data)) - respBody, err := io.ReadAll(br) - return respBody, err + return readLimited(br) } func ModernChromeSpec(forceHTTP1 bool) *utls.ClientHelloSpec { diff --git a/go.mod b/go.mod index 68bb34db..188de401 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.24 require ( github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.26 // indirect - github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1 // indirect - github.com/andybalholm/brotli v1.2.0 // indirect + github.com/Danny-Dasilva/fhttp v0.0.0-20260106165651-41258808b131 // indirect + github.com/andybalholm/brotli v1.2.7 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect diff --git a/go.sum b/go.sum index 1d606377..df65f863 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,13 @@ github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.26 h1:6fexoGmvzoXMSk14BZ0AirapVm github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.26/go.mod h1:QFi/EVO7qqru3Ftxz1LR+96jIc91Tifv0DnskF/gWQ8= github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1 h1:/lqhaiz7xdPr6kuaW1tQ/8DdpWdxkdyd9W/6EHz4oRw= github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1/go.mod h1:Hvab/V/YKCDXsEpKYKHjAXH5IFOmoq9FsfxjztEqvDc= +github.com/Danny-Dasilva/fhttp v0.0.0-20260106165651-41258808b131/go.mod h1:Hvab/V/YKCDXsEpKYKHjAXH5IFOmoq9FsfxjztEqvDc= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/brotli v1.2.7 h1:UgafRPGMam5ySHfRkhthMqOFA6IFoeF3D7mWIDLjG5A= +github.com/andybalholm/brotli v1.2.7/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= diff --git a/src/go.mod b/src/go.mod index c565826a..c7436fba 100644 --- a/src/go.mod +++ b/src/go.mod @@ -10,7 +10,7 @@ require github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.29 require ( github.com/Danny-Dasilva/fhttp v0.0.0-20260106165651-41258808b131 // indirect - github.com/andybalholm/brotli v1.2.0 // indirect + github.com/andybalholm/brotli v1.2.7 // indirect github.com/gaukas/clienthellod v0.4.2 // indirect github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect diff --git a/src/go.sum b/src/go.sum index d7945b9a..10d39567 100644 --- a/src/go.sum +++ b/src/go.sum @@ -11,8 +11,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/Danny-Dasilva/fhttp v0.0.0-20260106165651-41258808b131 h1:gRgU8L1jIWP8noBJk8p1mD3JSJNVDB3HNncjKT6SAFg= github.com/Danny-Dasilva/fhttp v0.0.0-20260106165651-41258808b131/go.mod h1:Hvab/V/YKCDXsEpKYKHjAXH5IFOmoq9FsfxjztEqvDc= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/brotli v1.2.7 h1:UgafRPGMam5ySHfRkhthMqOFA6IFoeF3D7mWIDLjG5A= +github.com/andybalholm/brotli v1.2.7/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=