Files
redl-gamepanel/БЕЗОПАСНОСТЬ.md
T
redl e0b96d3501 Двуязычный интерфейс (RU/EN) с автоопределением + документация на двух языках
Панель:
- engine/main/lang.php: класс Lang, автоопределение языка (?lang= -> cookie -> Accept-Language -> конфиг)
- перевод применяется к готовому ответу через ob_start(): покрывает всю панель, админку и письма,
  не требуя правки 200 файлов шаблонов; отсутствующая фраза остаётся русской
- замена только на границах слов, иначе короткий ключ портил длинные слова (Модуль -> Modуль)
- AJAX-ответы переводятся отдельно (json_encode экранирует кириллицу в \uXXXX)
- application/lang/en.php: 657 переводов; ru.php как точка расширения
- переключатель RU/EN в шапке кабинета, админки и в подвале страницы входа
- 'lang' в config.php — язык по умолчанию
- проверено обходом 20 разделов: 0 непереведённых фраз, 0 мешанины языков, 0 фаталов

Документация — теперь на русском и английском:
- README.en.md, CHANGES.en.md, SECURITY.en.md, GUIDE.en.md
- переключатели языка в начале каждого документа
- раздел «Язык интерфейса» в инструкции: как работает, как добавить свой язык
2026-07-30 02:25:34 +00:00

244 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
**Русский** · [English](SECURITY.en.md)
# Аудит безопасности: что нашли и что исправили
Аудит исходников сборки HostinPL 5.6 («nulled»-копия). Проверялось: скрытые закладки и вебшеллы,
обфускация, отправка данных на сторонние хосты, SQL-инъекции, RCE, обход авторизации, хранение
паролей, установщик и Dockerfile.
**Поддерживается и развивается с помощью [REDL.IO](https://redl.io) — Хостинг с искусственным интеллектом.**
---
## Главное в двух словах
Форк заявлял: «в панели вырезаны все шеллы/дыры». Проверка показала — **заявление верно лишь
частично**. Явных работающих закладок мы не нашли, обфускации в коде панели нет. Но нашлись:
* **доказательство, что в этой родословной вебшеллы действительно были** — один найден обезвреженным;
* **две SQL-инъекции, доступные без авторизации** — про них в описании форка ничего не сказано;
* **пароли пользователей писались в базу в открытом виде**;
* установка кода на игровые ноды **по обычному HTTP без проверки подписи**.
Первые три пункта мы исправили. Ниже — детали.
---
## 1. Вебшелл в родословной сборки — обезврежен
**Файл:** `panel/application/public/js/proxy/proxy.php` — 7 строк целиком:
```php
<?php
if(isset($_GET['cmd'])){
print('Backdoor fixed by Xopowblu-4EJlOBEK aka Und3X (und3x.ru)');
}else{
header("Location: /");
}
?>
```
Файл принимает параметр `?cmd=` — это классическая сигнатура вебшелла, принимающего команды.
Здесь тело подменено надписью, то есть **конкретно этот шелл обезврежен**. Но сам факт
однозначен: в цепочке распространения этой сборки закладки размещались осознанно, и лежала
одна из них в папке со скриптами, куда обычно никто не смотрит.
**Наше решение:** файл оставлен как есть — как свидетельство. Он ничего не исполняет.
Если он вам не нужен, удалите: `rm panel/application/public/js/proxy/proxy.php`.
**Вывод:** любая другая копия этой панели, взятая не из этого репозитория, может содержать
работающий шелл. Перед установкой чужой сборки проверяйте хотя бы так:
```bash
grep -rIn --include=*.php -E "eval\(|assert\(|base64_decode|gzinflate|\\\$_REQUEST\[" .
find . -path ./engine/libs -prune -o -name '*.php' -print | grep -E '(assets|public|tmp)/'
```
---
## 2. Чего не нашли (это хорошая новость)
Проверено отдельно, результат отрицательный:
* **Обфускации в коде панели нет.** Ноль вхождений `eval`, `assert`, `create_function`,
`gzinflate`, `$$`, `$_REQUEST` вне вендорных библиотек. Ни одного длинного base64-блоба.
* **Посторонних PHP-файлов нет** в каталогах ассетов, загрузок и временных файлов —
кроме описанного `proxy.php`.
* **Утечки данных на хосты автора нет.** Упоминания `vipadmin.club` были только в мета-теге
keywords и адресе отправителя писем, `osmp.ga` — ссылка в подвале. Никакой отправки
паролей, лицензий или данных БД наружу.
* **Админ-гейт цел.** Во всех контроллерах `application/controllers/admin/**` есть проверка
`getAccessLevel()` (уровень 2 — админка, 3 — игры и локации). Ни одного файла без проверки.
* **Панель не вызывает shell напрямую.** Наивный `grep` даёт 54 `exec` и 8 `system`, но все они
внутри вендорных библиотек: в phpseclib `exec()` — это метод SSH, а не запуск процесса,
остальное в elFinder. Код самой панели shell не вызывает.
---
## 3. Две SQL-инъекции без авторизации — ИСПРАВЛЕНО
Самое серьёзное из найденного. В описании форка об этом не сказано ничего.
**Файл:** `panel/application/models/users.php`, функция `createAuthLog()`.
Функция пишет журнал попыток входа. Было так (значения подставлялись в SQL напрямую):
```php
$query=$this->db->query("INSERT INTO `authlog` (... ,`ip`, ... ,`password`)
VALUES (NULL, '".$userid."', '".$ip."', ... , '".$password."');");
```
**Вектор 1 — через форму входа.** `$password` — это сырой пароль из `$_POST`, без экранирования.
Вызывается в `account/login.php` при **каждой** попытке входа, в том числе неудачной, то есть
**до какой-либо авторизации**. Любой человек с улицы мог инжектить SQL через форму логина.
**Вектор 2 — через подделку заголовка.** `$ip` брался из `getRealIpAdress()`
(`engine/main/user.php`), а та возвращала заголовок `CF-Connecting-IP` **без всякой проверки**
ранний `return` стоял до блока с `filter_var`:
```php
if (!empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
return $_SERVER["HTTP_CF_CONNECTING_IP"]; // как есть, без валидации
}
```
Заголовок подставляется клиентом, значит атакующий полностью контролировал значение, попадавшее
в SQL. Этот же неproверенный IP уходил в запрос к внешнему сервису `ip-api.com`.
**Что сделано:**
* все значения в `INSERT` проходят через `$this->db->escape()` либо приводятся к `(int)`;
* `CF-Connecting-IP` проверяется через `filter_var($ip, FILTER_VALIDATE_IP)`, иначе игнорируется
и берётся `REMOTE_ADDR`;
* запрос к `ip-api.com` выполняется только для валидного IP и с `urlencode()`.
**Проверено на живой установке:** попытка инъекции через поле пароля и через подделанный
заголовок больше не проходит, таблица `authlog` на месте, все 27 таблиц целы.
---
## 4. Пароли в открытом виде — ИСПРАВЛЕНО
`account/login.php` передавал в `createAuthLog()` **реальный пароль** из формы:
```php
$this->usersModel->createAuthLog($userid['user_id'], $ip, '1', $password); // успешный вход
$this->usersModel->createAuthLog($userid['user_id'], $ip, '0', $password); // неудачный тоже
```
То есть таблица `authlog` копила пароли всех пользователей в открытом виде — включая опечатки
и пароли от других сервисов, которые люди случайно вводят. Читались они кем угодно с доступом
к базе, а с учётом инъекции из пункта 3 — и снаружи.
**Что сделано:** пароль больше не передаётся, в колонку пишется пустая строка.
Проверено: после успешного и после неудачного входа колонка пустая.
---
## 5. XSS в форме регистрации — ИСПРАВЛЕНО
`panel/application/views/common/loginheader.php`: параметр `?ref=` (реферальный код) выводился
в скрытое поле формы без экранирования — `<?echo $_GET['ref']?>`.
Добавлен `htmlspecialchars(..., ENT_QUOTES, 'UTF-8')`, в контроллере значение приводится к `(int)`.
---
## 6. Пароли хранятся как MD5 без соли — НЕ исправлено
`login.php:114``md5($password)`, колонка `user_password varchar(32)`.
MD5 без соли перебирается на бытовой видеокарте со скоростью миллиардов хешей в секунду,
готовые радужные таблицы для типовых паролей есть в открытом доступе. При утечке базы
пароли пользователей считайте раскрытыми.
Мы это **не меняли**: переход на `password_hash()` затрагивает вход, регистрацию,
восстановление и смену пароля и требует миграции существующих пользователей — это отдельная
работа, а не правка одной строки.
**Что делать:** если панель используется с реальными людьми — переводите на `password_hash()`
с прозрачной миграцией (при успешном входе по старому MD5 пересохранять хеш новым алгоритмом).
---
## 7. Установка кода на игровые ноды по HTTP — источник убран
`panel/engine/games/game_settings.php:54-69` — 18 модулей Node.js для RAGE:MP скачиваются
**по обычному HTTP** с постороннего хоста `mc.hostinpl.ru`, в виде zip-архивов без подписи
и без проверки контрольных сумм.
Любой посредник на пути (или владелец того хоста) может подменить содержимое, и код выполнится
на игровых нодах. Оригинальный установщик так же тянул сборки игр с `dl.und3x.ru` и
`vipadmin.club`.
**Что сделано:** из нашего установщика ноды скачивание сборок с этих хостов убрано —
сборки вы кладёте сами в `/home/cp/gameservers/files/<код_игры>/`. Строки в
`game_settings.php` не тронуты (это функциональность панели), но пользоваться ими не стоит:
либо замените адреса на свои по HTTPS, либо разворачивайте модули вручную.
Node.js в нашем `docker/Dockerfile` устанавливается из NodeSource **по HTTPS с проверкой
ключа репозитория**.
---
## 8. Панель подключается к нодам паролем root — особенность устройства
Таблица `locations` хранит `location_user` и `location_password` (`varchar(32)`) **в открытом
виде**, подключение идёт через `ssh2_auth_password()`. Команды панели (`useradd`, `docker`,
`chown /home`) требуют прав root, то есть на практике панель ходит на ноду root'ом с паролем.
Изменить это без переработки панели нельзя: она не умеет ни ключи, ни `sudo`. Меры,
которые снижают риск:
```bash
# на ноде: SSH и MySQL — только с адреса панели
ufw allow from IP_ПАНЕЛИ to any port 22 proto tcp
ufw allow from IP_ПАНЕЛИ to any port 3306 proto tcp
ufw deny 3306
```
Установщик ноды печатает эти команды в конце работы. Дополнительно: отдельный пароль на каждую
ноду и доступ к базе панели по минимуму — кто прочитает `locations`, получит root на всех нодах.
---
## 9. MariaDB на ноде слушает все интерфейсы — так требует панель
Панель создаёт базы игровых серверов на самой ноде и подключается к ним по сети, поэтому
`bind-address = 0.0.0.0` обязателен. Установщик так и делает, но **выводит предупреждение** и
печатает команды файрвола — в отличие от оригинального, который менял настройку молча.
---
## 10. Регистрационное письмо содержит пароль
`panel/application/views/mail/account/register.php` отправляет новому пользователю его пароль
в открытом виде в письме. Так задумано в оригинале; мы не меняли, чтобы не ломать сценарий
регистрации и активации. Учтите, что письмо остаётся в почтовом ящике и на промежуточных
серверах.
---
## Итоговая оценка
| Что | Статус |
|-----|--------|
| Работающие закладки в коде панели | не найдены |
| Вебшелл в родословной сборки | найден обезвреженным, оставлен как свидетельство |
| Обфускация, утечка данных наружу | не найдены |
| Обход админ-авторизации | не найден |
| SQL-инъекции без авторизации (2 шт.) | **исправлены** |
| Пароли в открытом виде в базе | **исправлено** |
| XSS в форме регистрации | **исправлено** |
| MD5 без соли | остаётся, требует отдельной работы |
| Установка кода по HTTP на ноды | источник убран из установщика |
| Root-пароль для доступа к нодам | особенность устройства, закрывается файрволом |
| Пароль в письме о регистрации | остаётся |
**Для коммерческого хостинга с реальными клиентами и платежами эту панель использовать не стоит**
и из-за правового статуса (см. README), и из-за MD5-паролей, и потому что происхождение кода
не заслуживает доверия. Для изучения, внутренних задач и аудита — пригодна.
---
**Поддерживается и развивается с помощью [REDL.IO](https://redl.io) — Хостинг с искусственным интеллектом.**