
Куча OOB запись в MariaDB JSON_SCHEMA_VALID() → устойчивое повышение привилегий (при поддержке лаборатории)
Переполнение кучи в JSON_SCHEMA_VALID() MariaDB → устойчивое повышение привилегий → RCE через UDF
| Затронуто | MariaDB 11.4.x (подтверждено на 11.4.9) |
| Ошибка | Запись за пределами буфера в json_get_normalized_string() — strncpy в 128-байтовый DYNAMIC_STRING без проверки границ |
| Воздействие | Пользователь с правами только SELECT → ALL PRIVILEGES WITH GRANT OPTION → выполнение произвольных команд через UDF |
| Источник | sql/json_schema_helper.cc:91 |
При поддержке лаборатории. Скрипт использует Docker / root-интроспекцию для чтения
/proc/1/memи определения раскладки кучи для каждого соединения. Фактическая цепочка эксплойта — чистый SQL через TCP. Боевой эксплойт потребовал бы примитив утечки информации для замены шага интроспекции памяти.
lowpriv может выполнять только SELECT в базе данных test. Системные таблицы запрещены.

Один Python-скрипт выполняет подготовку кучи, двухшаговую произвольную запись
через метаданные пользовательских переменных, закрепляет повышение привилегий через GRANT ALL
и достигает выполнения кода через UDF:
python3 exploit.py

lowpriv теперь имеет ALL PRIVILEGES WITH GRANT OPTION, может читать системные таблицы,
читать/записывать произвольные файлы и выполнять команды ОС от имени пользователя mysql.
Грант переживает перезапуск сервера.


┌──────────────────────────────────────────────────────────────────┐
│ SELECT json_schema_valid(overflow), │
│ @ccc...c := hop1, │
│ @aaa...a := hop2 │
└──────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌──────────────┐
│ 192-байт │ │ Запись в @c │ │ Запись в @a │
│ перепол- │ │ через │ │ через │
│ нение │ │ повреждённый │ │ перенаправ- │
│ повреждает│ │ указатель: │ │ ленный │
│ value ptr │ │ │ │ указатель: │
│ entry_c │ │ entry_a → │ │ master_access│
│ (частичная│ │ .value = │ │ = 0xFFFF.. │
│ перезапись│ │ &master_ │ │ (ALL PRIVS) │
│ 2 байт) │ │ access │ │ │
│ │ │ .length= 9 │ └──────────────┘
└───────────┘ └──────────────┘
Подготовка кучи — 100+ пользовательских переменных исчерпывают tcache, заставляя
Entry_a → Value_a → Entry_b → Value_b → Entry_c → Value_c выделяться
последовательно.
Переполнение — JSON_SCHEMA_VALID вызывает 192-байтовый strncpy за
пределы 128-байтового буфера, повреждая entry_c→value (частичная перезапись
указателя на 2 байта в пределах той же страницы 64 КБ), чтобы он указывал на entry_a + 32.
Шаг 1 — Присваивание @c записывает 126 байт через повреждённый
указатель в метаданные entry_a, устанавливая:
entry_a→value = &Security_context::master_accessentry_a→length = 9Шаг 2 — Присваивание @a записывает 8 байт 0xFF через
перенаправленный указатель → .
После закрепления сервер перезапускается (восстановление после сбоя), и повышенные привилегии используются для установки разделяемой библиотеки UDF:
LOAD_FILE('/tmp/raptor_udf.so') INTO DUMPFILE '/usr/lib/mysql/plugin/raptor.so'CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'raptor.so'SELECT sys_exec('id > /tmp/pwned')# 1. Сборка и запуск контейнера
./setup.sh
# 2. Отключение ASLR на хосте Docker
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
# 3. Запуск эксплойта (автокалибровка для каждой попытки)
python3 exploit.py
# 4. Пользовательская команда
python3 exploit.py --cmd 'cat /etc/passwd > /tmp/out'
/proc/sys/kernel/randomize_va_space = 0)--cap-add SYS_PTRACE (для доступа к /proc/1/mem)--calibrate Измерить константы раскладки кучи и выйти
--cmd CMD Команда для выполнения UDF на этапе 2 (по умолчанию: id > /tmp/pwned)
--stage1-only Выполнить только повышение привилегий, пропустить RCE через UDF
--attempts N Максимум попыток этапа 1 (по умолчанию: 5)
--host HOST Хост MariaDB (по умолчанию: 127.0.0.1)
--port PORT Порт MariaDB (по умолчанию: 3306)
Скрипт эксплойта подключается к контейнеру как root для чтения /proc/1/mem.
Это используется для двух целей:
user_var_entry
и проверка их смежности (entry_a+32 в пределах той же страницы 64 КБ,
что и entry_c→value для частичной перезаписи 2 байт).Security_context — поиск поля master_access для
целевой двухшаговой записи.Сканирование выполняется встроенно для каждой попытки, поскольку раскладка кучи различается между соединениями (даже при ASLR=0) из-за того, что пул потоков MariaDB назначает разные арены. Фактическая цепочка эксплойта — переполнение + hop1 + hop2 — представляет собой чистый SQL, выполняемый через TCP-соединение.
В реальном сценарии злоумышленнику потребовалась бы отдельная уязвимость утечки информации (или побочный канал) для получения этих адресов.
entry_a→valuemaster_access = ALL PRIVILEGESЗакрепление — Сессия теперь обладает всеми привилегиями. GRANT ALL фиксирует
повышение привилегий в таблице mysql.global_priv на базе Aria. Сессия
в конечном итоге падает во время очистки (остаточное повреждение кучи), но
GRANT уже сохранён в контрольной точке и переживает перезапуск.
| Ограничение | Решение |
|---|
STRING_RESULT выполняет length++ перед проверкой перераспределения | Полезная нагрузка имеет размер N−1 байт, поэтому N−1+1 = N совпадает с сохранённой длиной → нет перераспределения при повреждённом указателе |
126-байтовый hop2 повреждает поля THD за пределами Security_context | Установить entry_a→length = 9 в hop1, чтобы hop2 записывал только 8 байт (master_access) + 1 NUL |
Смещение master_access в Security_context | 1712 байт от базового адреса структуры (priv_user[384] + proxy_user[645] + priv_host[256] + priv_role[384] + выравнивание + указатели) |
| Восстановление после сбоя Aria откатывает незафиксированные записи | GRANT ALL + SLEEP на 10 с позволяет Aria выполнить контрольную точку до сбоя при очистке сессии |
| Раскладка кучи различается между соединениями (даже при ASLR=0) | Встроенное сканирование /proc/1/mem для каждой попытки обнаруживает entry_a и master_access для каждого соединения |
| plugin_dir принадлежит root | Dockerfile заранее задаёт chmod 777 (удобство лаборатории) |
| Файл | Описание |
|---|
exploit.py | Двухэтапный эксплойт: повышение привилегий (TCP SQL) + RCE через UDF |
raptor_udf.c | Исходный код UDF — sys_exec() вызывает system() |
Dockerfile | Образ контейнера лаборатории (компилирует UDF, открывает plugin_dir) |
init.sql | Создаёт пользователя lowpriv |
setup.sh | Собирает и запускает лабораторию |
screenshots/ | Снимки терминала |