CVE-2023-28119 (published 2023-03-22T20:15Z)

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.\n

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-770
cwe_name: Allocation of Resources Without Limits or Throttling
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.
extended_description: Code frequently has to work with limited resources, so programmers must be careful to ensure that resources are not consumed too quickly, or too easily.  Without use of quotas, resource limits, or other protection mechanisms, it can be easy for an attacker to consume many resources by rapidly making many requests, or causing larger resources to be used than is needed. When too many resources are allocated, or if a single resource is too large, then it can prevent the code from working correctly, possibly leading to a denial of service.
         

Fix information:
repo_url: https://github.com/crewjam/saml
commit_details: repo_url: https://github.com/crewjam/saml
author: Ross Kinder
author_date: 2023-03-22 15:22:00-04:00
msg: Merge pull request from GHSA-5mqj-xc49-246p

https://github.com/crewjam/saml/security/advisories/GHSA-5mqj-xc49-246p
repository: repo_url: https://github.com/crewjam/saml
repo_name: crewjam/saml
description: SAML library for go
repo_language: Go
owner: crewjam
file_changes: filename: flate.go
change_type: ModificationType.ADD
diff: @@ -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()
+}

programming_language: Go
filename: identity_provider.go
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,6 @@ package saml
 
 import (
 	"bytes"
-	"compress/flate"
 	"crypto"
 	"crypto/tls"
 	"crypto/x509"
@@ -363,7 +362,7 @@ func NewIdpAuthnRequest(idp *IdentityProvider, r *http.Request) (*IdpAuthnReques
 		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)
 		}

programming_language: Go
filename: identity_provider_test.go
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,8 @@
 package saml
 
 import (
+	"bytes"
+	"compress/flate"
 	"crypto"
 	"crypto/rsa"
 	"crypto/x509"
@@ -1013,3 +1015,29 @@ func TestIDPNoDestination(t *testing.T) {
 	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)")
+}

programming_language: Go
filename: service_provider.go
change_type: ModificationType.MODIFY
diff: @@ -1524,7 +1524,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData str
 	}
 	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

programming_language: Go