CVE-2023-28119: Allocation of Resources Without Limits or Throttling (CWE-770)
value: The crewjam/saml go library contains a partial implementation of the SAML standard in golang. Prior to version 0.4.13, the package's use of `flate.NewReader` does not limit the size of the input. The user can pass more than 1 MB of data in the HTTP request to the processing functions, which will be decompressed server-side using the Deflate algorithm. Therefore, after repeating the same request multiple times, it is possible to achieve a reliable crash since the operating system kills the process. This issue is patched in version 0.4.13.

CWE description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
CVSS v3 base score: 7.5
Published: 2023-03-22T20:15Z
Repository: https://github.com/crewjam/saml
Language: Go
Files changed: flate.go, identity_provider.go, identity_provider_test.go, service_provider.go
Commit date: 2023-03-22 15:22:00 -0400
Commit message: Merge pull request from GHSA-5mqj-xc49-246p

https://github.com/crewjam/saml/security/advisories/GHSA-5mqj-xc49-246p
Diff: diff --git a/flate.go b/flate.go
new file mode 100644
index 0000000..4d14e78
--- /dev/null
+++ b/flate.go
@@ -0,0 +1,31 @@
+package saml
+
+import (
+	"compress/flate"
+	"fmt"
+	"io"
+)
+
+const flateUncompressLimit = 10 * 1024 * 1024 // 10MB
+
+func newSaferFlateReader(r io.Reader) io.ReadCloser {
+	return &saferFlateReader{r: flate.NewReader(r)}
+}
+
+type saferFlateReader struct {
+	r     io.ReadCloser
+	count int
+}
+
+func (r *saferFlateReader) Read(p []byte) (n int, err error) {
+	if r.count+len(p) > flateUncompressLimit {
+		return 0, fmt.Errorf("flate: uncompress limit exceeded (%d bytes)", flateUncompressLimit)
+	}
+	n, err = r.r.Read(p)
+	r.count += n
+	return n, err
+}
+
+func (r *saferFlateReader) Close() error {
+	return r.r.Close()
+}
diff --git a/identity_provider.go b/identity_provider.go
index 4705291..bcea582 100644
--- a/identity_provider.go
+++ b/identity_provider.go
@@ -1,10 +1,9 @@
 package saml
 
 import (
 	"bytes"
-	"compress/flate"
 	"crypto"
 	"crypto/tls"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/xml"
@@ -361,11 +360,11 @@ func NewIdpAuthnRequest(idp *IdentityProvider, r *http.Request) (*IdpAuthnReques
 	case "GET":
 		compressedRequest, err := base64.StdEncoding.DecodeString(r.URL.Query().Get("SAMLRequest"))
 		if err != nil {
 			return nil, fmt.Errorf("cannot decode request: %s", err)
 		}
-		req.RequestBuffer, err = ioutil.ReadAll(flate.NewReader(bytes.NewReader(compressedRequest)))
+		req.RequestBuffer, err = ioutil.ReadAll(newSaferFlateReader(bytes.NewReader(compressedRequest)))
 		if err != nil {
 			return nil, fmt.Errorf("cannot decompress request: %s", err)
 		}
 		req.RelayState = r.URL.Query().Get("RelayState")
 	case "POST":
diff --git a/identity_provider_test.go b/identity_provider_test.go
index 0c602b5..a88480e 100644
--- a/identity_provider_test.go
+++ b/identity_provider_test.go
@@ -1,8 +1,10 @@
 package saml
 
 import (
+	"bytes"
+	"compress/flate"
 	"crypto"
 	"crypto/rsa"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/pem"
@@ -1011,5 +1013,31 @@ func TestIDPNoDestination(t *testing.T) {
 	assert.Check(t, err)
 
 	err = req.MakeResponse()
 	assert.Check(t, err)
 }
+
+func TestIDPRejectDecompressionBomb(t *testing.T) {
+	test := NewIdentifyProviderTest(t)
+	test.IDP.SessionProvider = &mockSessionProvider{
+		GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session {
+			fmt.Fprintf(w, "RelayState: %s\nSAMLRequest: %s",
+				req.RelayState, req.RequestBuffer)
+			return nil
+		},
+	}
+
+	//w := httptest.NewRecorder()
+
+	data := bytes.Repeat([]byte("a"), 768*1024*1024)
+	var compressed bytes.Buffer
+	w, _ := flate.NewWriter(&compressed, flate.BestCompression)
+	w.Write(data)
+	w.Close()
+	encoded := base64.StdEncoding.EncodeToString(compressed.Bytes())
+
+	r, _ := http.NewRequest("GET", "/dontcare?"+url.Values{
+		"SAMLRequest": {encoded},
+	}.Encode(), nil)
+	_, err := NewIdpAuthnRequest(&test.IDP, r)
+	assert.Error(t, err, "cannot decompress request: flate: uncompress limit exceeded (10485760 bytes)")
+}
diff --git a/service_provider.go b/service_provider.go
index 3eac33f..6f6e7f4 100644
--- a/service_provider.go
+++ b/service_provider.go
@@ -1522,11 +1522,11 @@ func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData str
 		retErr.PrivateErr = fmt.Errorf("unable to parse base64: %s", err)
 		return retErr
 	}
 	retErr.Response = string(rawResponseBuf)
 
-	gr, err := ioutil.ReadAll(flate.NewReader(bytes.NewBuffer(rawResponseBuf)))
+	gr, err := ioutil.ReadAll(newSaferFlateReader(bytes.NewBuffer(rawResponseBuf)))
 	if err != nil {
 		retErr.PrivateErr = err
 		return retErr
 	}
 

Vulnerable code: 	"compress/flate"
		req.RequestBuffer, err = ioutil.ReadAll(flate.NewReader(bytes.NewReader(compressedRequest)))
	gr, err := ioutil.ReadAll(flate.NewReader(bytes.NewBuffer(rawResponseBuf)))
Fixed code: package saml

import (
	"compress/flate"
	"fmt"
	"io"
)

const flateUncompressLimit = 10 * 1024 * 1024 // 10MB

func newSaferFlateReader(r io.Reader) io.ReadCloser {
	return &saferFlateReader{r: flate.NewReader(r)}
}

type saferFlateReader struct {
	r     io.ReadCloser
	count int
}

func (r *saferFlateReader) Read(p []byte) (n int, err error) {
	if r.count+len(p) > flateUncompressLimit {
		return 0, fmt.Errorf("flate: uncompress limit exceeded (%d bytes)", flateUncompressLimit)
	}
	n, err = r.r.Read(p)
	r.count += n
	return n, err
}

func (r *saferFlateReader) Close() error {
	return r.r.Close()
}
		req.RequestBuffer, err = ioutil.ReadAll(newSaferFlateReader(bytes.NewReader(compressedRequest)))
	"bytes"
	"compress/flate"

func TestIDPRejectDecompressionBomb(t *testing.T) {
	test := NewIdentifyProviderTest(t)
	test.IDP.SessionProvider = &mockSessionProvider{
		GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session {
			fmt.Fprintf(w, "RelayState: %s\nSAMLRequest: %s",
				req.RelayState, req.RequestBuffer)
			return nil
		},
	}

	//w := httptest.NewRecorder()

	data := bytes.Repeat([]byte("a"), 768*1024*1024)
	var compressed bytes.Buffer
	w, _ := flate.NewWriter(&compressed, flate.BestCompression)
	w.Write(data)
	w.Close()
	encoded := base64.StdEncoding.EncodeToString(compressed.Bytes())

	r, _ := http.NewRequest("GET", "/dontcare?"+url.Values{
		"SAMLRequest": {encoded},
	}.Encode(), nil)
	_, err := NewIdpAuthnRequest(&test.IDP, r)
	assert.Error(t, err, "cannot decompress request: flate: uncompress limit exceeded (10485760 bytes)")
}
	gr, err := ioutil.ReadAll(newSaferFlateReader(bytes.NewBuffer(rawResponseBuf)))