
Prometheus v3.15.0 — High — Remote — SSRF — http_sd __unix_socket__ несанитизированный Dial, оставшийся от UDS scrape
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · prometheus-unix-socket-dial
Разбор: https://abraxaslabs.tech/research/prometheus-unix-socket-dial
Класс: SSRF Доступ: Удалённый
Prometheus v3.15.0 (5241a27) - CNCF / Prometheus
В v3.15.0 добавлен сбор метрик через Unix Domain Socket (#12024 / #18091). Путь задаётся меткой цели __unix_socket__, а не списком разрешённых значений оператора. http_sd и file_sd копируют эту метку как есть. PopulateLabels её никогда не проверяет. Затем newUnixSocketScrapeClient вызывает DialContext(ctx, "unix", socketPath) без filepath.Clean, без списка разрешённых значений и без отклонения ../ или абстрактных имён Linux @.
TCP-сбор метрик так не может. CheckTargetAddress по-прежнему отбрасывает значения __address__, содержащие /, поэтому unix:///var/run/docker.sock в качестве адреса отклоняется. Новая метка — это и есть дыра.
Стандартная конфигурация не задаёт эту метку. Неаутентифицированный HTTP-доступ к Prometheus не может добавлять задания. Это ограничено SD.
| ID | CVE пока нет |
| Класс | SSRF (несанитизированный unix Dial из меток SD) |
| Доступ | Удалённый (ограничен SD: тот, кто пишет http_sd / file_sd) |
| CWE | CWE-918, CWE-22 |
| CVSS | Высокий: 8.6 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N |
| Продукт | Prometheus |
| Затронуто | v3.15.0 (5241a27fe3c6983549fccc32f6e65917408c63cd), образ prom/prometheus:v3.15.0 |
| Аутентификация | Ограничено SD: тот, кто пишет метки http_sd / file_sd (или relabel, копирующий недоверенные метаданные в __unix_socket__) |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1 |
Достаточно контроля над JSON http_sd (или записываемого file_sd). URL SD — это конфигурация оператора. Остаётся то, что labels группы могут задать __unix_socket__, и Prometheus выполнит Dial по нему.
| Привилегия | Метод / точка входа | Доказано в лаборатории или выведено логически |
|---|---|---|
| Подключение к unix-сокету хоста, который может открыть UID Prometheus | clientForTarget -> DialContext(ctx, "unix", socketPath) из __unix_socket__ | Доказано в лаборатории. Путь /sockets/jail/../oracle.sock из http_sd; состояние scrape up |
Обход ../ в этом пути | нет filepath.Clean / нет отклонения .. | Доказано в лаборатории. Ядро разрешило обход до /sockets/oracle.sock |
| Подключение к абстрактному сокету Linux | тот же Dial для @name | Доказано в лаборатории. __unix_socket__=@prom-lab-oracle; состояние scrape up |
| HTTP GET на этот сокет от имени UID Prometheus | scrape — это GET; __metrics_path__ — путь запроса | Доказано в лаборатории. GET /fs-traversal и /abstract; User-Agent Prometheus/; lab_witness принят |
| Доступ к сокетам, которые TCP scrape не может адресовать | CheckTargetAddress отклоняет / в __address__ | Доказано в лаборатории. unix:///var/run/docker.sock в качестве __address__ залогирован как is not a valid hostname; не активная цель; prometheus_target_sync_failed_total=1 |
| GET к HTTP API Docker Engine / kubelet, если эти сокеты в области доступа | __unix_socket__=/var/run/docker.sock плюс путь Docker API | Выведено логически. Тот же несанитизированный Dial. В этой лаборатории не монтируется реальный сокет движка. JSON API обычно не проходят парсинг экспозиции, поэтому High — это подключение/GET, а не эксфильтрация Docker JSON из TSDB |
| Изменение состояния Docker (создание контейнера, запись) | глаголы scrape | Закрыто. Scrape выполняется только через GET |
| Добавление заданий scrape через неаутентифицированный HTTP Prometheus | /-/reload, admin API | Закрыто. Оба флага по умолчанию выключены; у reload нет тела запроса |
| Использование этого на конфигурации по умолчанию | __unix_socket__ не задан; пример выполняет scrape localhost:9090 по TCP | Закрыто. Встроенные k8s/consul/docker SD выдают только __meta_*; эти префиксы удаляются после relabel |
Модель безопасности вендора уже позволяет SD выбирать цели TCP scrape. Новый примитив — это межпроцессное взаимодействие хоста, которое __address__ не мог адресовать.
Я зафиксировал prometheus/prometheus v3.15.0 после появления строки FEATURE для UDS scrape в 3.15. В стандартной OSS-сборке на этой фиксации admin API, lifecycle HTTP, remote-write receiver и OTLP receiver выключены. PopulateDiscoveredLabels копирует каждую метку группы http_sd. __unix_socket__ не относится к __meta_*, поэтому переживает очистку метаданных. PopulateLabels проверяет только __address__.
func (sp *scrapePool) clientForTarget(t *Target) *http.Client {
socketPath := t.labels.Get(UnixSocketLabel)
if socketPath == "" {
return sp.client
}
client, err := newUnixSocketScrapeClient(socketPath, ...)
func newUnixSocketScrapeClient(socketPath string, ...) (*http.Client, error) {
optFuncs = append([]config_util.HTTPClientOption{config_util.WithDialContextFunc(
func(ctx context.Context, _, _ string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, "unix", socketPath)
},
)}, optFuncs...)
CheckTargetAddress по-прежнему отклоняет / в address. URL() сохраняет scheme/host/path из __scheme__ / __address__ / __metrics_path__; сокет используется только для dial.
Уже зафиксированные ошибочные ходы: оценка этого как in-model NONE, поскольку SD уже выбирает TCP-цели (новый примитив — это host IPC); трактовка unix:///var/run/docker.sock как __address__ в качестве дыры (он отклоняется); утверждение, что конфиг из примера по умолчанию является этим остатком. Театральщина: reverse shell. Оракул — состояние scrape health up на сокетах, которые назвал SD JSON.
python3 prometheus-unix-socket-dial-Abraxas-Labs.py
или
cd lab
./run.sh
prom/prometheus:v3.15.0 использует общий netns с Python-сайдкаром. Сайдкар обслуживает http_sd по адресу 127.0.0.1:8080/targets.json и два unix HTTP-оракула: файловый /sockets/oracle.sock и абстрактный @prom-lab-oracle. Prometheus опубликован на 127.0.0.1:19390. Admin API и lifecycle остаются отключёнными.
SUCCESS prometheus-unix-socket-dial sd=http_sd fs-traversal=yes abstract=yes tcp-slash-rejected=yes how=sd-labels PROMETHEUS-UNIX-SOCKET-DIAL-WITNESS
IOC: scrapeUrl=http://localhost:1/fs-traversal и /abstract, оба со статусом health up. TCP slash так и не стал активной целью.
Не вызывайте Dial __unix_socket__ из SD, пока это не будет очищенный абсолютный путь файловой системы. Отклоняйте пустые значения, NUL, абстрактные имена Linux @, относительные пути и любые .. после filepath.Clean. Белый список префиксов из scrape YAML надёжнее. Удаление метки из PopulateDiscoveredLabels, если она не пришла из конфигурации оператора, также закрывает инъекцию через SD.