最近一直没有更新,因为换工作和工作内容的转变,从乙方视角切换到甲方视角,很多事情还在尝试和学习。后续会多更新一些内容,聊一聊有关甲方安全的一些思考和想法,也是督促自己去学习和分享。
0x00 前言
前两天,国外安全研究人员披露Grafana存在目录遍历漏洞(CVE-2021-43798),可以任意读取系统上的文件。公司正好有业务在用,就抓紧进行了验证。随后,Grafana官方在8.3.1版本修复了此漏洞。
在此之后,Grafana又发布了8.3.2版本修复了另一个目录遍历漏洞(CVE-2021-43813),该漏洞允许经过身份验证的用户访问扩展名为.md或.csv的任意文件,影响范围较小。
看赛博回忆录公众号对于CVE-2021-43798的分析,从历史漏洞入手去分析现有的漏洞,觉得这种工作和思考问题的方式值得学习,随后就沿着这个思路分析了一下Grafana SSRF漏洞(CVE-2020-13379)和此次两个目录遍历漏洞。
0x01 CVE-2020-13379
2020年,安全研究员rhynorater在Grafana上发现了一个SSRF漏洞,其结合重定向和URL参数注入漏洞实现了SSRF。有关该漏洞,作者在其博客进行了详细说明,并给出了相关演讲的PPT,该漏洞构造精彩,值得一看。
作者在其PPT中也给出了一些源代码分析方法论,值得学习,本文的三个漏洞,也会沿着这个思路去分析。
Defined Scope: Authed, Unauthed, Both
Unauthed = more CVSS and more Impact
Enumerate Attack Surface
Open ports -> app segments -> routes
Be Creative and Sniff for Blood
Craft attack scenarios and trace user input
可控的URL参数
漏洞入口在api.go文件,该文件定义了请求路由,如下路由当请求/avatar/:hash路径时,会获取到:hash值,并执行后面的avatarCacheServer.Handler。
而avatarCacheServer是avatar.NewCacheServer( )的返回值,跟进avatar.NewCacheServer( )到avatar.go文件。NewCacheServer( )函数返回了CacheServer。
而CacheServer是一个结构体,一个结构体(struct)就是一个字段的集合,类似一个对象。
跟进Handler方法,在pkg/api/avatar.go#76中定义了Handler函数方法。
在go语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针,所有给定类型的方法属于该类型的方法集。语法格式如下:
func (variable_name variable_data_type) function_name() [return_type]{
* 函数体*/
}
因此Handler属于CacheServer对象的方法,在此方法中,urlPath会获取到请求的url,即/avatar/:hash,之后通过截取urlPath获取到hash值,然后会进入到avatar.Update()方法。
跟进到Update( )方法,其调用了GoFetch( )方法,this.hash就是从前面/avatar/:hash经URL编码传递过来的hash,并将其路由到secure.grafana.com以访问用户的Gravatar图像,而:hash值可控,这就导致我们可以注入参数到URL。
一路跟进GoFetch()方法到fetch方法,这里将传入的url直接进行了http请求。
也就是GoFetch()后直接进行了HTTP请求,因此我们需要进一步探究向其传入的URL及其参数。
thunder.GoFetch(gravatarSource+this.hash+"?"+this.reqParams, this):
this.reqParams参数有三个值,"d","size","r",其作为gravatarSource(即secure.gravartar.com,提供头像等图片的站点)的url参数传入,在其官网有对这三个参数进行详细说明。
d:支持从其它主机加载图片
size:图片大小
r:图片等级
比如要加载grafana.com上的一张图片,可以这样写:
https://secure.gravatar.com/avatar/000000?d=https://grafana.com/static/img/home-toolbox.png
为了探究该站点是如何从其他主机加载图片的,我们对此站点请求进行进一步分析。
第一次重定向
在测试时,请求如下链接:
https://secure.gravatar.com/avatar/000000?d=https://grafana.com/static/img/home-toolbox.png
会被重定向到:
https://i0.wp.com/grafana.com/static/img/home-toolbox.png?ssl=1
也就是,如果提供d参数,会被重定向到i0.wp.com托管某些图像的位置,这是重定向链中的第一个重定向。
secure.gravatar.com/avatar/000000?d=anythingHere
=>
i0.wp.com/{domainOfImage}/{pathOfImage}
第二次重定向
那这个i0.wp.com是如何拿到指定地址的图片的?其通过link规范网页内容。
但是,此时还是不能通过i0.wp.com去访问到任意主机,因此必须对该域进行大量的检索,最后通过https://otx.alienvault.com/等威胁情报网站对该域下的URL进行搜索发现,对于*.bp.blogspot.com下的任何域名,i0.wp.com将进行重定向。
重定向到https://1.bp.blogspot.com/
此时,我们发现对于*.bp.blogspot.com下的任何域名,i0.wp.com将进行重定向,但我们需要做到任意重定向,这是一个白名单的配置,只要绕过了这个白名单就可以。
最后发现由于正则缺陷存在如下绕过方式:
http://i0.wp.com/test.com%3f/1.bp.blogspot.com/
目前已被修复。
至此,URL参数可控,且可以任意重定向,完成攻击链条。
最终payload
结合前文,梳理下完整流程:
1、Grafana通过/avatar/:hash接口获取到请求的头像地址hash,此处为:
test?d=test.com%3f/bp.blogspot.com
2、之后会从如下地址请求头像:
https://secure.gravatar.com/avatar/test?d=test.com%3f/bp.blogspot.com/
3、在有d参数的情况下,请求被重定向到:
http://i0.wp.com/test.com%3f/bp.blogspot.com/
4、由于白名单的正则缺陷,会重定向到指定的主机地址:
test.com
作者给出的最终paylod:
https://grafanaHost/avatar/test%3fd%3dtest.com%3f%252fbp.blogspot.com%252fYOURHOSTHERE
该漏洞作者以点到面,从路由到URL请求,到白名单绕过,层层深入,值得学习。
0x02 CVE-2021-43798 目录遍历
2021年12月6日,国外安全研究人员披露Grafana某些接口在提供静态文件时,攻击者通过构造恶意请求,可造成目录遍历,可任意读取系统上的文件。该漏洞影响范围广泛,涉及到v8.0.0-beta1到v8.3.0版本。根据上文SSRF漏洞的发现思路,我们分析下CVE-2021-43798。
漏洞分析
由于是目录遍历漏洞,跟路由请求有关,所以还是从api.go入手。api.go文件定义了请求路由,根据参数可以看出有些需要认证,有些不需要认证。
此次受影响的"/public/plugins/:pluginId/*"路由不需要认证,且pluginId和*可控
跟进到getPluginAssets。pluginID为获取到插件id,之后判断插件是否存在,然后从*获取path路径赋值给requestedFile,并与PluginDir进行拼接,且未做任何过滤处理。
os.Open直接打开文件路径,之后将文件内容返回。
如下是代码中使用到的filepath方法的输出,可以看出Clean()方法并没有把../过滤掉。
相关方法说明:
func Clean(path string) string
Clean函数通过单纯的词法操作返回和path代表同一地址的最短路径
它会不断的依次应用如下的规则,直到不能再进行任何处理:
- 将连续的多个路径分隔符替换为单个路径分隔符
- 如果指定的路径为空字符串,则返回字符串"."
- 剔除每一个.路径名元素(当前目录)。
- 剔除每一个路径内的..路径名元素(父目录)以及在其前面的非..元素。
- 剔除开始一个根路径的..路径名元素:即将路径开始处的"/.."替换为"/" (假设路径分隔符是'/')
func Join(elem ...string) string
Join函数可以将任意数量的路径元素放入一个单一路径里,会根据需要添加路径分隔符。
对于拼接路径的需求,可以使用Join函数来处理
漏洞复现
验证POC如下,若存在,请尽快修复:
/public/plugins/pluginsID/../../../../../../../../../../../etc/passwd
每个Grafana实例都预装了Prometheus或MySQL等插件,因此每个实例的以下URL都可能受到攻击。
/public/plugins/alertlist/
/public/plugins/alertmanager/
/public/plugins/annolist/
/public/plugins/barchart/
/public/plugins/bargauge/
/public/plugins/candlestick/
/public/plugins/cloudwatch/
/public/plugins/dashlist/
/public/plugins/elasticsearch/
/public/plugins/gauge/
/public/plugins/geomap/
/public/plugins/gettingstarted/
/public/plugins/grafana-azure-monitor-datasource/
/public/plugins/graph/
/public/plugins/heatmap/
/public/plugins/histogram/
/public/plugins/influxdb/
/public/plugins/jaeger/
/public/plugins/logs/
/public/plugins/loki/
/public/plugins/mssql/
/public/plugins/mysql/
/public/plugins/news/
/public/plugins/nodeGraph/
/public/plugins/opentsdb
/public/plugins/piechart/
/public/plugins/pluginlist/
/public/plugins/postgres/
/public/plugins/prometheus/
/public/plugins/stackdriver/
/public/plugins/stat/
/public/plugins/state-timeline/
/public/plugins/status-history/
/public/plugins/table/
/public/plugins/table-old/
/public/plugins/tempo/
/public/plugins/testdata/
/public/plugins/text/
/public/plugins/timeseries/
/public/plugins/welcome/
/public/plugins/zipkin/
............
对于插件也可通过暴力破解以及登录页面抓取等方式获取。
修复方案
Grafana官方在8.3.1版本修复了此漏洞,主要是对路径的过滤,修复代码如下:
可以看到其对从*获取到的路径与"/"拼接,之后使用Clean()剔除"/.."进行过滤,并使用filepath.Rel()获取到相对路径。
对于插件目录使用filepath.Abs()获取到绝对路径,之后将这两个路径拼接,并使用os.Open打开文件路径,将文件内容返回。
如上使用到的filepath方法的输出,可以看到,跟有漏洞版本一样都是使用了Clen(),但是使用参数的不同,造成的结果却千差万别
官方修复建议:
1、升级到Grafana 8.3.1、8.2.7、8.1.8和8.0.7
2、在Grafana前使用反向代理来规范化请求的PATH缓解该漏洞(最好进行升级,此方法有可能被绕过)
0x03 CVE-2021-43813 目录遍历
在修复任意文件读取漏洞后不久,Grafana官方又发布了8.3.2版本修复了CVE-2021-43813目录遍历漏洞。该漏洞允许经过身份验证的用户访问扩展名为.md或.csv的任意文件,.csv要开启和配置一个名为TestData DB数据源的开发者测试工具,该功能默认不开启。
影响范围
任意.md文件:5.0.0到8.3.1(CVE-2021-43813)
任意.csv文件:8.0.0-beta3到8.3.1(CVE-2021-43815)
漏洞分析
同样跟进到api.go,漏洞是读取任意.md的文件,寻找下哪个接口跟markdown有关,找到如下接口:
跟进到GetpluginMarkdown,从:pluginId获取到插件名赋值给pludinID,从:name获取路径给name,之后作为参数传入pluginMarkdown,之后将其读取的内容返回。
跟进到pluginMarkdown,其将传入的name路径使用ToUpper()和ToLower()进行了全大小和全小写格式化,之后与插件目录进行拼接,最后读取内容并返回。可以看出,此漏洞还是由于没有对传入的内容进行过滤就进行了拼接,导致任意.md文件读取。
漏洞复现
此漏洞只能读取任意.md文件,且需要经过身份认证,影响范围较小,就没有进行复现。验证POC如下,若存在,请尽快修复:
/api/plugins/pluginsID/markdown/../../../*.md
修复方案
Grafana官方在8.3.2或7.5.12版本修复了此漏洞,主要还是对路径的过滤,修复代码如下:
可以看到其对全大小写转换后的name路径使用了filepath.Clean()剔除"/.."进行过滤。又是Clean()
官方修复建议
1、升级到Grafana 8.3.2或7.5.12
2、如果无法升级,可在Grafana前使用反向代理来规范化请求的PATH缓解该漏洞,代理还必须能够处理url编码的路径。或者对于完全小写或完全大写的.md文件,通过阻止/api/plugins/.*/markdown/.*请求进行缓解。
0x04 结语
从上文的分析发现,此次Grafana的两个漏洞主要还是没有对用户输入进行过滤造成,而且有漏洞的代码和修复代码都是使用了filepath.Clean()过滤,只是使用方式不同,造成的结果也不同。最近log4j2的漏洞也闹的沸沸扬扬,其漏洞的成因很简单,也是未经过滤,信任用户一切输入,而且官方把使用方法都写在了用例里。这些不断提醒我们在编写业务或者挖掘漏洞的时候,一定要细心,不信任任何一个输入,不放过任何一个可能。
正如heige说的互联网安全是如此的脆弱,一行EXP就可以秒杀整个互联网,这是网络空间的COVID-19,正在迅速蔓延。大量攻击正在路上,务必做好防护。
不要信任用户的一切输入!
0x05 参考
[1]https://rhynorater.github.io/CVE-2020-13379-Write-Up
[2]https://docs.google.com/presentation/d/1He_zFFXCuft3LsZTXbHKoDxQHNoSveZg2c2uF1HKuaw/edit#slide=id.g8dc2d55207_8_626
[3]https://mp.weixin.qq.com/s/dqJ3F_fStlj78S0qhQ3Ggw