CVE-2022-35919: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: MinIO is a High Performance Object Storage released under GNU Affero General Public License v3.0. In affected versions all 'admin' users authorized for `admin:ServerUpdate` can selectively trigger an error that in response, returns the content of the path requested. Any normal OS system would allow access to contents at any arbitrary paths that are readable by MinIO process. Users are advised to upgrade. Users unable to upgrade may disable ServerUpdate API by denying the `admin:ServerUpdate` action for your admin users via IAM policies.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 2.7
Published: 2022-08-01T22:15Z
Repository: https://github.com/minio/minio
Language: Go
Files changed: cmd/update.go
Commit date: 2022-07-28 17:44:21 -0700
Commit message: do not allow filesystem fallback in server download (#15429)

It is possible for anyone with admin access to relatively
to get any content of any random OS location by simply
providing the file with 'mc admin update alias/ /etc/passwd`.

Workaround is to disable 'admin:ServiceUpdate' action. Everyone
is advised to upgrade to this patch.

Thanks to @alevsk for finding this bug.
Diff: diff --git a/cmd/update.go b/cmd/update.go
index c027a63086..8cae892e57 100644
--- a/cmd/update.go
+++ b/cmd/update.go
@@ -289,67 +289,54 @@ func getUserAgent(mode string) string {
 
 	return strings.Join(userAgentParts, "")
 }
 
 func downloadReleaseURL(u *url.URL, timeout time.Duration, mode string) (content string, err error) {
-	var reader io.ReadCloser
-	if u.Scheme == "https" || u.Scheme == "http" {
-		req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-		if err != nil {
-			return content, AdminError{
-				Code:       AdminUpdateUnexpectedFailure,
-				Message:    err.Error(),
-				StatusCode: http.StatusInternalServerError,
-			}
+	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
+	if err != nil {
+		return content, AdminError{
+			Code:       AdminUpdateUnexpectedFailure,
+			Message:    err.Error(),
+			StatusCode: http.StatusInternalServerError,
 		}
-		req.Header.Set("User-Agent", getUserAgent(mode))
+	}
+	req.Header.Set("User-Agent", getUserAgent(mode))
 
-		client := &http.Client{Transport: getUpdateTransport(timeout)}
-		resp, err := client.Do(req)
-		if err != nil {
-			if xnet.IsNetworkOrHostDown(err, false) {
-				return content, AdminError{
-					Code:       AdminUpdateURLNotReachable,
-					Message:    err.Error(),
-					StatusCode: http.StatusServiceUnavailable,
-				}
-			}
+	client := &http.Client{Transport: getUpdateTransport(timeout)}
+	resp, err := client.Do(req)
+	if err != nil {
+		if xnet.IsNetworkOrHostDown(err, false) {
 			return content, AdminError{
-				Code:       AdminUpdateUnexpectedFailure,
+				Code:       AdminUpdateURLNotReachable,
 				Message:    err.Error(),
-				StatusCode: http.StatusInternalServerError,
+				StatusCode: http.StatusServiceUnavailable,
 			}
 		}
-		if resp == nil {
-			return content, AdminError{
-				Code:       AdminUpdateUnexpectedFailure,
-				Message:    fmt.Sprintf("No response from server to download URL %s", u),
-				StatusCode: http.StatusInternalServerError,
-			}
+		return content, AdminError{
+			Code:       AdminUpdateUnexpectedFailure,
+			Message:    err.Error(),
+			StatusCode: http.StatusInternalServerError,
 		}
-		reader = resp.Body
-		defer xhttp.DrainBody(resp.Body)
-
-		if resp.StatusCode != http.StatusOK {
-			return content, AdminError{
-				Code:       AdminUpdateUnexpectedFailure,
-				Message:    fmt.Sprintf("Error downloading URL %s. Response: %v", u, resp.Status),
-				StatusCode: resp.StatusCode,
-			}
+	}
+	if resp == nil {
+		return content, AdminError{
+			Code:       AdminUpdateUnexpectedFailure,
+			Message:    fmt.Sprintf("No response from server to download URL %s", u),
+			StatusCode: http.StatusInternalServerError,
 		}
-	} else {
-		reader, err = os.Open(u.Path)
-		if err != nil {
-			return content, AdminError{
-				Code:       AdminUpdateURLNotReachable,
-				Message:    err.Error(),
-				StatusCode: http.StatusServiceUnavailable,
-			}
+	}
+	defer xhttp.DrainBody(resp.Body)
+
+	if resp.StatusCode != http.StatusOK {
+		return content, AdminError{
+			Code:       AdminUpdateUnexpectedFailure,
+			Message:    fmt.Sprintf("Error downloading URL %s. Response: %v", u, resp.Status),
+			StatusCode: resp.StatusCode,
 		}
 	}
 
-	contentBytes, err := ioutil.ReadAll(reader)
+	contentBytes, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 		return content, AdminError{
 			Code:       AdminUpdateUnexpectedFailure,
 			Message:    fmt.Sprintf("Error reading response. %s", err),
 			StatusCode: http.StatusInternalServerError,

Vulnerable code: 	var reader io.ReadCloser
	if u.Scheme == "https" || u.Scheme == "http" {
		req, err := http.NewRequest(http.MethodGet, u.String(), nil)
		if err != nil {
			return content, AdminError{
				Code:       AdminUpdateUnexpectedFailure,
				Message:    err.Error(),
				StatusCode: http.StatusInternalServerError,
			}
		req.Header.Set("User-Agent", getUserAgent(mode))
		client := &http.Client{Transport: getUpdateTransport(timeout)}
		resp, err := client.Do(req)
		if err != nil {
			if xnet.IsNetworkOrHostDown(err, false) {
				return content, AdminError{
					Code:       AdminUpdateURLNotReachable,
					Message:    err.Error(),
					StatusCode: http.StatusServiceUnavailable,
				}
			}
				Code:       AdminUpdateUnexpectedFailure,
				StatusCode: http.StatusInternalServerError,
		if resp == nil {
			return content, AdminError{
				Code:       AdminUpdateUnexpectedFailure,
				Message:    fmt.Sprintf("No response from server to download URL %s", u),
				StatusCode: http.StatusInternalServerError,
			}
		reader = resp.Body
		defer xhttp.DrainBody(resp.Body)

		if resp.StatusCode != http.StatusOK {
			return content, AdminError{
				Code:       AdminUpdateUnexpectedFailure,
				Message:    fmt.Sprintf("Error downloading URL %s. Response: %v", u, resp.Status),
				StatusCode: resp.StatusCode,
			}
	} else {
		reader, err = os.Open(u.Path)
		if err != nil {
			return content, AdminError{
				Code:       AdminUpdateURLNotReachable,
				Message:    err.Error(),
				StatusCode: http.StatusServiceUnavailable,
			}
	contentBytes, err := ioutil.ReadAll(reader)
Fixed code: 	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return content, AdminError{
			Code:       AdminUpdateUnexpectedFailure,
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
	}
	req.Header.Set("User-Agent", getUserAgent(mode))
	client := &http.Client{Transport: getUpdateTransport(timeout)}
	resp, err := client.Do(req)
	if err != nil {
		if xnet.IsNetworkOrHostDown(err, false) {
				Code:       AdminUpdateURLNotReachable,
				StatusCode: http.StatusServiceUnavailable,
		return content, AdminError{
			Code:       AdminUpdateUnexpectedFailure,
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
	}
	if resp == nil {
		return content, AdminError{
			Code:       AdminUpdateUnexpectedFailure,
			Message:    fmt.Sprintf("No response from server to download URL %s", u),
			StatusCode: http.StatusInternalServerError,
	}
	defer xhttp.DrainBody(resp.Body)

	if resp.StatusCode != http.StatusOK {
		return content, AdminError{
			Code:       AdminUpdateUnexpectedFailure,
			Message:    fmt.Sprintf("Error downloading URL %s. Response: %v", u, resp.Status),
			StatusCode: resp.StatusCode,
	contentBytes, err := ioutil.ReadAll(resp.Body)