
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
Класс: 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 | High: 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; состояние сбора up |
Пройти ../ в этом пути | нет filepath.Clean / нет отклонения .. | Доказано в лаборатории. Ядро разрешило обход до /sockets/oracle.sock |
| Подключиться к Linux-абстрактному сокету | тот же Dial для @name | Доказано в лаборатории. __unix_socket__=@prom-lab-oracle; состояние сбора up |
| HTTP GET по этому сокету от имени UID Prometheus | сбор — это GET; __metrics_path__ — это путь запроса | Доказано в лаборатории. GET /fs-traversal и /abstract; User-Agent Prometheus/; lab_witness принят |
| Достичь сокетов, которые TCP-сбор не может назвать | 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 — это connect/GET, а не эксфильтрация Docker JSON из TSDB |
| Изменять Docker (создавать контейнер, писать) | глаголы сбора | Закрыто. Сбор только GET |
| Добавлять задания сбора через неаутентифицированный HTTP Prometheus | /-/reload, admin API | Закрыто. Оба флага по умолчанию выключены; у reload нет тела |
| Использовать это на стандартной конфигурации example | __unix_socket__ не задан; example собирает localhost:9090 по TCP | Закрыто. Встроенные k8s/consul/docker SD выдают только __meta_*; эти префиксы удаляются после relabel |
Модель безопасности вендора уже позволяет SD выбирать TCP-цели сбора. Новый примитив — это хостовый IPC, который __address__ не мог назвать.
Я зафиксировал prometheus/prometheus v3.15.0 после строки FEATURE 3.15 для сбора через UDS. Стандартный 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 по-прежнему отклоняет / в адресе. URL() сохраняет scheme/host/path из __scheme__ / __address__ / __metrics_path__; сокет используется только для Dial.
Уже зафиксированные неверные пути: оценка этого как in-model NONE, потому что SD уже выбирает TCP-цели (новый примитив — это хостовый IPC); рассмотрение unix:///var/run/docker.sock как __address__ в качестве дыры (он отклоняется); утверждение, что стандартная конфигурация example — это данная уязвимость. Театр: reverse shell. Оракул — состояние сбора up на сокетах, названных в JSON SD.
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, оба со здоровьем up. TCP slash так и не стал активной целью.
Не выполняйте Dial по __unix_socket__ из SD, пока это не санитизированный абсолютный путь файловой системы. Отклоняйте пустые значения, NUL, Linux-абстрактные имена @, относительные пути и любые .. после filepath.Clean. Список разрешённых префиксов из scrape YAML — сильнее. Удаление метки из PopulateDiscoveredLabels, если она не пришла из конфигурации оператора, также закрывает инъекцию через SD.