CVE-2024-35185 (published 2024-05-16T16:15Z)

Minder is a software supply chain security platform. Prior to version 0.0.49, the Minder REST ingester is vulnerable to a denial of service attack via an attacker-controlled REST endpoint that can crash the Minder server. The REST ingester allows users to interact with REST endpoints to fetch data for rule evaluation. When fetching data with the REST ingester, Minder sends a request to an endpoint and will use the data from the body of the response as the data to evaluate against a certain rule. If the response is sufficiently large, it can drain memory on the machine and crash the Minder server. The attacker can control the remote REST endpoints that Minder sends requests to, and they can configure the remote REST endpoints to return responses with large bodies. They would then instruct Minder to send a request to their configured endpoint that would return the large response which would crash the Minder server. Version 0.0.49 fixes this issue.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/stacklok/minder
commit_details: repo_url: https://github.com/stacklok/minder
author: Juan Antonio Osorio
author_date: 2024-05-16 00:49:03-06:00
msg: Merge pull request from GHSA-fjw8-3gp8-4cvx

This ensures that a malicious (or faulty) REST server does not crash
minder with too big requests. Minder will thus limit requests body reads
to 1Mb.

Signed-off-by: Juan Antonio Osorio <ozz@stacklok.com>
repository: repo_url: https://github.com/stacklok/minder
repo_name: stacklok/minder
description: Software Supply Chain Security Platform
repo_language: Go
owner: stacklok
file_changes: filename: rest.go
change_type: ModificationType.MODIFY
diff: @@ -40,6 +40,10 @@ import (
 const (
 	// RestRuleDataIngestType is the type of the REST rule data ingest engine
 	RestRuleDataIngestType = "rest"
+
+	// MaxBytesLimit is the maximum number of bytes to read from the response body
+	// We limit to 1MB to prevent abuse
+	MaxBytesLimit int64 = 1 << 20
 )
 
 type ingestorFallback struct {
@@ -201,16 +205,18 @@ func (rdi *Ingestor) parseBody(body io.Reader) (any, error) {
 		return nil, nil
 	}
 
+	lr := io.LimitReader(body, MaxBytesLimit)
+
 	if rdi.restCfg.Parse == "json" {
 		var jsonData any
-		dec := json.NewDecoder(body)
+		dec := json.NewDecoder(lr)
 		if err := dec.Decode(&jsonData); err != nil {
 			return nil, fmt.Errorf("cannot decode json: %w", err)
 		}
 
 		data = jsonData
 	} else {
-		data, err = io.ReadAll(body)
+		data, err = io.ReadAll(lr)
 		if err != nil {
 			return nil, fmt.Errorf("cannot read response body: %w", err)
 		}

programming_language: Go
filename: rest_test.go
change_type: ModificationType.MODIFY
diff: @@ -17,6 +17,7 @@ package rest
 
 import (
 	"context"
+	"encoding/binary"
 	"encoding/json"
 	"net/http"
 	"net/http/httptest"
@@ -319,3 +320,54 @@ func TestRestIngest(t *testing.T) {
 		})
 	}
 }
+
+func TestIngestor_parseBodyDoesNotReadTooLargeRequests(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name    string
+		cfg     *pb.RestType
+		body    string
+		wantErr bool
+	}{
+		{
+			name: "raw",
+			cfg:  &pb.RestType{},
+			// really large body
+			// casting to `int` should work.
+			body: strings.Repeat("a", int(MaxBytesLimit)+1),
+			// This case does not error, it simply truncates.
+			wantErr: false,
+		},
+		{
+			name: "json",
+			cfg: &pb.RestType{
+				Parse: "json",
+			},
+			// really large body
+			// casting to `int` should work.
+			body: "{\"a\":\"" + strings.Repeat("a", int(MaxBytesLimit)+1) + "\"}",
+			// This case will error out, as truncating
+			// makes the JSON invalid.
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			rdi := &Ingestor{
+				restCfg: tt.cfg,
+			}
+
+			got, err := rdi.parseBody(strings.NewReader(tt.body))
+			if tt.wantErr {
+				assert.Error(t, err, "expected error")
+			} else {
+				assert.NoError(t, err, "expected no error")
+				assert.Equal(t, int(MaxBytesLimit), binary.Size(got), "expected body to be truncated")
+			}
+		})
+	}
+}

programming_language: Go