1. MinIO简介

MinIO是美国MinIO公司的一款开源的对象存储服务器, 是一款高性能、分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件。即X86等低成本机器也能够很好的运行MinIO。MinIO中存在一处信息泄露漏洞,由于Minio集群进行信息交换的9000端口,在未经配置的情况下通过发送特殊HPPT请求进行未授权访问,进而导致MinIO对象存储的相关环境变量泄露,环境变量中包含密钥信息。泄露的信息中包含登录账号密码。

2.漏洞描述 MinIO是美国MinIO公司的一款开源的对象存储服务器。该产品支持构建用于机器学习、分析和应用程序数据工作负载的基础架构。 MinIO 存在信息泄露漏洞,该漏洞源于在集群部署中MinIO会返回所有环境变量,导致信息泄露。

CVE-2023-28432 CNNVD-202303-1795

3.影响版本 2019-12-17T23-16-33Z <= MinIO < RELEASE.2023-03-20T20-16-18Z

4.fofa查询语句 banner=“MinIO” || header=“MinIO” || title=“MinIO Browser”

5.漏洞复现 漏洞链接:http://xxx.com/minio/bootstrap/v1/verify 注意数据包使用post的方式

CVE-2023-28432 MinIO 信息泄露漏洞_环境变量




漏洞分析

我先访问到main.go,主函数代码主要指向“minio "github.com/minio/minio/cmd"”
说明Minio启动之后大部分逻辑都会在cmd这个目录中,可能会包含路由规则。

# https://github.com/minio/minio/blob/RELEASE.2023-03-13T19-46-17Z/main.go
package main // import "github.com/minio/minio"

import (
    "os"

    // MUST be first import.
    _ "github.com/minio/minio/internal/init"

    minio "github.com/minio/minio/cmd"
)

func main() {
    minio.Main(os.Args)
}

接下来,我看到routers.go的第75行包含路由注册,通过信息泄漏的载荷之中“bootstrap”字段结合路由注册代码,进行跟踪。

# https://github.com/minio/minio/blob/RELEASE.2023-03-13T19-46-17Z/cmd/routers.go line 75
func configureServerHandler(endpointServerPools EndpointServerPools) (http.Handler, error) {
    // Initialize router. `SkipClean(true)` stops minio/mux from
    // normalizing URL path minio/minio#3256
    router := mux.NewRouter().SkipClean(true).UseEncodedPath()

    // Initialize distributed NS lock.
    if globalIsDistErasure {
        registerDistErasureRouters(router, endpointServerPools)
    }

    // Add Admin router, all APIs are enabled in server mode.
    registerAdminRouter(router, true)

    // Add healthcheck router
    registerHealthCheckRouter(router)

    // Add server metrics router
    registerMetricsRouter(router)

    // Add STS router always.
    registerSTSRouter(router)

    // Add KMS router
    registerKMSRouter(router)

    // Add API router
    registerAPIRouter(router)

    router.Use(globalHandlers...)

    return router, nil
}

紧接着,我找到bootstrap-peer-server.go,这个文件作为后续的代码逻辑。

https://github.com/minio/minio/blob/RELEASE.2023-03-13T19-46-17Z/cmd/bootstrap-peer-server.go line 38
const (
    bootstrapRESTVersion       = "v1"
    bootstrapRESTVersionPrefix = SlashSeparator + bootstrapRESTVersion
    bootstrapRESTPrefix        = minioReservedBucketPath + "/bootstrap"
    bootstrapRESTPath          = bootstrapRESTPrefix + bootstrapRESTVersionPrefix
)

在新逻辑中首先找到接收HTTP请求的函数,分别在第130行与第132行。通过代码逻辑来看,getServerSystemCfg应该作为接受参数之后

# https://github.com/minio/minio/blob/RELEASE.2023-03-13T19-46-17Z/cmd/bootstrap-peer-server.go line 130
// HealthHandler returns success if request is valid
func (b *bootstrapRESTServer) HealthHandler(w http.ResponseWriter, r *http.Request) {}

# https://github.com/minio/minio/blob/RELEASE.2023-03-13T19-46-17Z/cmd/bootstrap-peer-server.go line 132
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))
}

继续查看getServerSystemCfg做了什么?主要获取的环境变量skipEnvs[envK]。

# https://github.com/minio/minio/blob/RELEASE.2023-03-13T19-46-17Z/cmd/bootstrap-peer-server.go line 110
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,
    }
}

skipEnvs[envK]是什么呢?skipEnvs[envK]包含环境变量信息。根据官方说明,MinIO在启动时会从环境变量中读取预先设置的用户和密码,默认情况下:minioadmin/minioadmin。

# https://github.com/minio/minio/blob/RELEASE.2023-03-13T19-46-17Z/cmd/bootstrap-peer-server.go line 103
var skipEnvs = map[string]struct{}{
    "MINIO_OPTS":         {},
    "MINIO_CERT_PASSWD":  {},
    "MINIO_SERVER_DEBUG": {},
    "MINIO_DSYNC_TRACE":  {},
}

我简单搜索了下MinIO项目,预先设置密码的情况还是比较严重。例如decom.sh等。

# https://github.com/minio/minio/blob/fb1492f5317bddb263092b69218c5a2c23025d19/docs/distributed/decom.sh line 23
export MC_HOST_myminio="http://minioadmin:minioadmin@localhost:9000/"

./mc admin user add myminio/ minio123 minio123
./mc admin user add myminio/ minio12345 minio12345

./mc admin policy create myminio/ rw ./docs/distributed/rw.json
./mc admin policy create myminio/ lake ./docs/distributed/rw.json

./mc admin policy attach myminio/ rw --user=minio123
./mc admin policy attach myminio/ lake,rw --user=minio12345

当通过信息泄漏获得账号密码之后,可以登陆MinIO更新恶意升级URL,并且执行update触发RCE。
MinIO对于更新包进行了sha256sum,但是由于envMinisignPubKey为空,所以sha256sum失效了。

# https://github.com/minio/minio/blob/RELEASE.2023-03-13T19-46-17Z/cmd/update.go line 541
minisignPubkey := env.Get(envMinisignPubKey, "")
    if minisignPubkey != "" {
        v := selfupdate.NewVerifier()
        u.Path = path.Dir(u.Path) + slashSeparator + releaseInfo + ".minisig"
        if err = v.LoadFromURL(u.String(), minisignPubkey, transport); err != nil {
            return AdminError{
                Code:       AdminUpdateApplyFailure,
                Message:    fmt.Sprintf("signature loading failed for %v with %v", u, err),
                StatusCode: http.StatusInternalServerError,
            }
        }
        opts.Verifier = v
    }

我反向跟踪 verifyBinary方法,找到Update的HTTP入口,其中就包含了调用sha256sum校验。

# https://github.com/minio/minio/blob/6017b63a0612daa0ab9ef87804a57b899766bb9c/cmd/admin-handlers.go line 77
// ServerUpdateHandler - POST /minio/admin/v3/update?updateURL={updateURL}
// ----------
// updates all minio servers and restarts them gracefully.
# https://github.com/minio/minio/blob/6017b63a0612daa0ab9ef87804a57b899766bb9c/cmd/admin-handlers.go line 157
for _, nerr := range globalNotificationSys.VerifyBinary(ctx, u, sha256Sum, releaseInfo, reader) {
        if nerr.Err != nil {
            err := AdminError{
                Code:       AdminUpdateApplyFailure,
                Message:    nerr.Err.Error(),
                StatusCode: http.StatusInternalServerError,
            }
            logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
            logger.LogIf(ctx, fmt.Errorf("server update failed with %w", err))
            writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
            return
        }
    }

    err = verifyBinary(u, sha256Sum, releaseInfo, mode, reader)