JediGest
  • МОИ УСЛУГИ
  • ПУТЕШЕСТВИЯ
  • АДМИНИСТРИРОВАНИЕ И ВАЙБ-КОДИНГ
  • ОБЗОРЫ, ОТЗЫВЫ
  • АВТОМОБИЛИ
  • ИЗБРАННОЕ

 

 

 

MAX Bot API: Мои первые шаги в разработке ботов

Информация о материале
Автор: Геннадий Едиг
Категория: Администрирование и Вайб-кодинг
Опубликовано: 19 июля 2026
Просмотров: 1

В этой статье я собрал все ключевые возможности MAX Bot API, которые мы открыли и протестировали в процессе разработки ботов для платформы MAX. Статья построена на реальном опыте и содержит рабочие примеры кода на Python.

Базовые принципы работы с API

Все запросы к MAX Bot API направляются на домен https://platform-api2.max.ru. Авторизация выполняется через заголовок Authorization: <токен_бота> — передача токена через query-параметры больше не поддерживается.

Важно: До 19 июля 2026 необходимо перенаправить запросы с platform-api.max.ru на platform-api2.max.ru.

Основные методы API

1. Отправка сообщений

Метод POST /messages позволяет отправлять сообщения в чаты и личные диалоги.

Ключевые параметры:

  • user_id или chat_id — указывается в query-строке
  • text — текст сообщения (до 4000 символов)
  • attachments — вложения (кнопки, медиафайлы)
  • format — форматирование текста (markdown или html)

Пример отправки сообщения на Python:

async def send_message_via_api(chat_id, message_text, bot_token):
    url = f"https://platform-api2.max.ru/messages?chat_id={chat_id}"
    headers = {
        "Authorization": bot_token,
        "Content-Type": "application/json"
    }
    payload = {"text": message_text}
    
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=payload, headers=headers) as response:
            return response.status == 200

2. Получение сообщений из канала

Метод GET /messages возвращает сообщения из чата или канала.

Параметры:

  • chat_id — ID канала (обязательно, если не указан message_ids)
  • count — количество сообщений (до 100)
  • from — временная метка для пагинации

Важная особенность: Сообщения возвращаются в обратном порядке — самые новые первыми.

Пример получения постов с пагинацией:

async def get_channel_posts(chat_id, count=100, from_time=None):
    headers = {"Authorization": BOT_TOKEN}
    params = {"chat_id": chat_id, "count": count}
    if from_time:
        params["from"] = from_time
    
    async with aiohttp.ClientSession() as session:
        async with session.get(API_URL, headers=headers, params=params) as response:
            data = await response.json()
            return data.get("messages", [])

3. Управление администраторами канала

Назначение прав администратора

Метод POST /chats/{chatId}/members/admins — бот должен сам быть администратором с правом add_admins.

Доступные права:

  • read_all_messages — чтение всех сообщений (обязательно для работы с сообщениями)
  • write — создание постов в каналах (также даёт редактирование и удаление)
  • edit — редактирование постов
  • delete — удаление постов
  • pin_message — закрепление сообщений
  • add_remove_members — управление участниками
  • add_admins — управление администраторами
  • change_chat_info — изменение информации о канале

Важно: Право write автоматически даёт edit и delete. Отдельного права "только создавать посты" не существует.

Пример выдачи прав:

async def grant_admin_rights(chat_id, user_id, bot_token):
    url = f"{API_URL}/chats/{chat_id}/members/admins"
    headers = {
        "Authorization": bot_token,
        "Content-Type": "application/json"
    }
    payload = {
        "admins": [
            {
                "user_id": user_id,
                "permissions": [
                    "read_all_messages",
                    "write"
                ]
            }
        ]
    }
    
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=payload, headers=headers) as response:
            return response.status == 200

Получение списка администраторов

Метод GET /chats/{chatId}/members/admins — возвращает всех администраторов канала.

Пример:

async def get_chat_admins(chat_id):
    url = f"{API_URL}/chats/{chat_id}/members/admins"
    headers = {"Authorization": BOT_TOKEN}
    
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as response:
            data = await response.json()
            return data.get("members", [])

Отзыв прав администратора

Метод DELETE /chats/{chatId}/members/admins/{userId}.

async def revoke_admin_rights(chat_id, user_id):
    url = f"{API_URL}/chats/{chat_id}/members/admins/{user_id}"
    headers = {"Authorization": BOT_TOKEN}
    
    async with aiohttp.ClientSession() as session:
        async with session.delete(url, headers=headers) as response:
            return response.status == 200

4. Проверка участников канала

Метод GET /chats/{chatId}/members?user_ids={userId} — позволяет проверить, подписан ли пользователь на канал.

async def check_user_subscription(chat_id, user_id):
    endpoint = f"/chats/{chat_id}/members?user_ids={user_id}"
    url = f"{API_URL}{endpoint}"
    headers = {"Authorization": BOT_TOKEN}
    
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as response:
            data = await response.json()
            members = data.get("members", [])
            return bool(members) and members[0].get("status") in ["member", "administrator", "creator"]

Получение событий в реальном времени

API поддерживает два способа получения событий:

Webhook (рекомендуется для production)

Метод POST /subscriptions настраивает доставку событий на ваш HTTPS-сервер.

Требования:

  • HTTPS на порту 443
  • Сертификат от доверенного CA
  • Ответ HTTP 200 в течение 30 секунд

Пример настройки:

curl -X POST "https://platform-api2.max.ru/subscriptions" \
  -H "Authorization: {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-domain.com/webhook",
    "update_types": ["message_created", "bot_started"],
    "secret": "your_secret"
  }'

Long Polling (только для разработки)

Метод GET /updates — подходит для тестирования, но не для production из-за ограничений.

Архитектура ботов из нашего проекта

Мы разработали два независимых скрипта для разных задач:

1. check_bot.py — архивация постов канала

Функционал:

  • Синхронизация при старте: проверяет все посты в канале и добавляет недостающие в БД
  • Слушает канал через long polling и сохраняет новые посты

Ключевые функции:

  • sync_posts() — синхронизация с пагинацией через параметр from
  • save_channel_post() — сохранение в MySQL

2. echo_bot.py — диалоговый бот с управлением правами

Команды для всех пользователей:

  • /status — проверка статуса подписки
  • /admin — запрос прав администратора канала

Админ-команды:

  • /admin_add <user_id> <user_name> — добавить администратора бота
  • /admin_remove <user_id> — удалить администратора бота
  • /admin_revoke <user_id> — отозвать права администратора канала
  • /admin_list — список администраторов бота
  • /admin_users — список всех пользователей

Система уведомлений: Все важные события отправляются администраторам бота через API.

Получение chat_id

Chat ID можно получить только через события:

  • Webhook: в объекте Update
  • Long Polling: в объекте Update

Типичные ошибки и их решения

"Chat not found" при отправке сообщения

Администратор должен первым написать боту — бот не может инициировать диалог.

"proto.payload" и "errors.required"

Проблема в структуре запроса. Проверьте, что chat_id передаётся в параметрах URL, а не в теле.

"Can't set message modify permission without read_all_messages"

Права, связанные с сообщениями, требуют read_all_messages.

"permission.denied" при выдаче прав

У бота нет права add_admins — проверьте через GET /chats/{chatId}/members/admins.

Рекомендации по разработке

  1. Для production используйте Webhook, Long Polling только для тестирования
  2. Токен передавайте только в заголовке Authorization
  3. При работе с пагинацией используйте параметр from с timestamp
  4. При выдаче прав обязательно проверяйте права бота через GET /chats/{chatId}/members/admins
  5. Для сложной логики используйте отдельные скрипты, разделяя ответственность

Эта статья может служить как шпаргалка при создании новых ботов для платформы MAX. Все примеры взяты из реального проекта и проверены на практике.

Создание бота для комментариев в канале MAX: полное руководство или первые шаги :)

Информация о материале
Автор: Геннадий Едиг
Категория: Администрирование и Вайб-кодинг
Опубликовано: 04 июля 2026
Просмотров: 104

В этой статье я расскажу, как создать бота для платформы MAX, который позволяет пользователям оставлять комментарии под постами в канале. Мы пройдём весь путь — от идеи до работающего решения, разберём все технические нюансы и ошибки, с которыми можно столкнуться.

Цель: автоматизировать сбор комментариев в отдельном чате, привязав их к конкретным постам, и сохранять всё в базу данных MySQL для дальнейшего использования (например, для сайта).

Архитектура решения

Наша система состоит из трёх основных компонентов:

  1. Канал MAX — публикуются посты.
  2. Бот — слушает канал, удаляет исходный пост, создаёт новый пост с кнопкой open_app для запуска мини-приложения.
  3. Групповой чат — пользователи оставляют комментарии, которые бот сохраняет в базу данных.
  4. База данных MySQL — хранит посты и комментарии.

Схема работы:

  • Пользователь публикует пост в канале.
  • Бот получает событие, сохраняет пост в БД.
  • Бот удаляет исходный пост.
  • Бот отправляет новый пост с текстом оригинала и кнопкой open_app.
  • Пользователь нажимает кнопку, открывается мини-приложение (или диплинк, если open_app не работает).
  • Комментарии пишутся в чате, бот привязывает их к последнему посту.

Подготовка

1. Регистрация бота и мини-приложения

Подробнее: Создание бота для комментариев в канале MAX: полное руководство или первые шаги :)

Нетрадиционные методы защиты Joomla и VirtueMart: опыт реального администрирования

Информация о материале
Автор: Геннадий Едиг
Категория: Администрирование и Вайб-кодинг
Опубликовано: 18 июня 2026
Просмотров: 71

Большинство статей по безопасности Joomla повторяют одни и те же рекомендации: обновляйтесь, используйте сложные пароли, ставьте Admin Tools. Это работает, но есть и другой путь — защита на уровне файловой системы и веб-сервера, которая не зависит от уязвимостей в коде.

В этой статье я поделюсь методами, которые применил на реальном проекте (интернет-магазин на Joomla + VirtueMart) и которые кардинально отличаются от стандартных подходов.


Основная идея: запретить, а не лечить

Традиционный подход к безопасности Joomla строится на обнаружении и блокировке атак через плагины (Admin Tools, RSFirewall и т.д.). Мой подход — превентивный: даже если злоумышленник найдет уязвимость и загрузит вредоносный файл, он не сможет его выполнить.

Это достигается через два ключевых механизма:

  1. Запрет выполнения PHP на уровне веб-сервера (Nginx) в папках, где его быть не должно
  2. Защита файлов от изменения на уровне файловой системы (chattr +i)

1. Запрет выполнения PHP через Nginx

Стандартный подход (неэффективный)

Обычно в Joomla используют .htaccess с правилами вроде:

<FilesMatch "\.(php|phtml)$">
    Order allow,deny
    Deny from all
</FilesMatch>

Но это работает только в Apache и не защищает от обхода через другие расширения.

Мой подход (Nginx)

В конфигурации Nginx я добавил следующие блоки:

1.1. Защита папок загрузок и кэша

# ЗАПРЕТ PHP В ПАПКАХ ЗАГРУЗОК
location ~* ^/(images|media|stories)/.*\.(php|phtml|php\d*|phar)$ {
    deny all;
    return 403;
}

# ЗАПРЕТ PHP В ПАПКАХ КЭША И ВРЕМЕННЫХ
location ~* ^/(tmp|cache|administrator/cache|images/tmp)/.*\.(php|phtml|php\d*|phar)$ {
    deny all;
    return 403;
}

Почему это работает:

  • Перехватывает любой запрос к PHP-файлам внутри этих папок
  • Возвращает 403 Forbidden, даже если файл физически существует
  • Не зависит от Joomla или PHP

1.2. Защита конфигурационного файла

# ЗАПРЕТ ДОСТУПА К CONFIGURATION.PHP
location ~ ^/configuration\.php$ {
    deny all;
    return 403;
}

1.3. Защита специфических папок сторонних компонентов

# ЗАПРЕТ PHP В ПАПКЕ com_excel2vm/xls
location /administrator/components/com_excel2vm/xls/ {
    location ~ \.(php|phtml|php\d*|phar)$ {
        deny all;
        return 403;
    }
}

1.4. Запрет листинга директорий

location / {
    autoindex off;
    try_files /does_not_exists @fallback;
}

2. Защита файлов через chattr +i (неизменяемость)

Этот метод я считаю самым мощным, но о нем почти не пишут в контексте Joomla.

Что такое chattr +i?

Команда chattr +i делает файл неизменяемым. Даже root не сможет его изменить, переименовать или удалить, пока атрибут не будет снят.

# Защита файлов от изменения
chattr +i /var/www/www-root/data/www/mi55.ru/index.php
chattr +i /var/www/www-root/data/www/mi55.ru/configuration.php
chattr +i /var/www/www-root/data/www/mi55.ru/defines.php

Что я защитил рекурсивно

# Защита всех компонентов, модулей, плагинов
chattr -R +i /var/www/www-root/data/www/mi55.ru/components/
chattr -R +i /var/www/www-root/data/www/mi55.ru/administrator/components/
chattr -R +i /var/www/www-root/data/www/mi55.ru/modules/
chattr -R +i /var/www/www-root/data/www/mi55.ru/plugins/
chattr -R +i /var/www/www-root/data/www/mi55.ru/templates/
chattr -R +i /var/www/www-root/data/www/mi55.ru/libraries/
chattr -R +i /var/www/www-root/data/www/mi55.ru/api/
chattr -R +i /var/www/www-root/data/www/mi55.ru/cli/

Почему это эффективно

  1. Даже если злоумышленник получит доступ к файловой системе (например, через LFI), он не сможет изменить код
  2. Защита сохраняется после обновлений (нужно только временно снять атрибут)
  3. Работает на уровне ядра Linux — обойти сложнее, чем права доступа (chmod)

Проверка защиты

# Просмотр защищенных файлов
lsattr /var/www/www-root/data/www/mi55.ru/*.php | grep "i-"

# Результат:
----i---------e------- /var/www/.../configuration.php
----i---------e------- /var/www/.../defines.php
----i---------e------- /var/www/.../index.php

3. Регулярная очистка мусора

Не менее важный аспект — удаление лишних файлов, которые могут стать вектором атаки.

Что я удалил:

  1. Файлы macOS (._*, .DS_Store, __MACOSX) — они не нужны для работы
  2. Бэкапы в корне (папка /1C_DB_Backup/ была удалена)
  3. Пустые или подозрительные файлы (например, components/com_excel2vm/excel2vm.php размером 0 байт)
# Скрипт очистки
find /var/www/www-root/data/www/mi55.ru -name "._*" -type f -delete
find /var/www/www-root/data/www/mi55.ru -name ".DS_Store" -type f -delete
find /var/www/www-root/data/www/mi55.ru -name "__MACOSX" -type d -exec rm -rf {} \;

Сравнение с традиционными методами

Аспект Традиционный подход Мой подход
Защита от выполнения PHP Плагины (Admin Tools) Nginx (серверный уровень)
Защита файлов Права доступа (chmod) Неизменяемость (chattr +i)
Защита конфигурации .htaccess Запрет в Nginx + chattr
Зависимость от Joomla Высокая Минимальная
Обход через уязвимости Возможен Затруднен
Сложность настройки Низкая Средняя
Надежность Средняя Высокая

Важные нюансы

1. Обновление компонентов

При обновлении Joomla или VirtueMart необходимо временно снять защиту:

# Снятие защиты перед обновлением
chattr -i /var/www/www-root/data/www/mi55.ru/index.php
chattr -R -i /var/www/www-root/data/www/mi55.ru/libraries/

# ... выполнить обновление ...

# Повторное применение защиты
chattr +i /var/www/www-root/data/www/mi55.ru/index.php
chattr -R +i /var/www/www-root/data/www/mi55.ru/libraries/

2. Папки, которые нельзя защищать

Некоторые папки должны оставаться доступными для записи:

# НЕ ЗАЩИЩАТЬ!
/tmp/
/cache/
/administrator/cache/
/images/
/media/
/stories/
/logs/
/t3-assets/

Мониторинг и поддержка

Для поддержания безопасности я использую простой скрипт:

#!/bin/bash
# /usr/local/bin/security-check.sh

SITE="/var/www/www-root/data/www/mi55.ru"

echo "=== ПРОВЕРКА НОВЫХ PHP-ФАЙЛОВ ==="
find $SITE -name "*.php" -mtime -1 -not -path "*/tmp/*" -not -path "*/cache/*" 2>/dev/null

echo "=== ПРОВЕРКА ЗАЩИЩЕННЫХ ФАЙЛОВ ==="
lsattr $SITE/*.php 2>/dev/null | grep "i-"

echo "=== ПРОВЕРКА ПРАВ ДОСТУПА ==="
find $SITE -type f -perm /111 -name "*.php" 2>/dev/null | head -10

Результаты

После применения этих методов:

  1. Все проверки безопасности пройдены — curl-запросы к защищенным папкам возвращают 403
  2. Сайт работает стабильно — никаких проблем с производительностью
  3. Нет ложных срабатываний — легитимные запросы проходят нормально
  4. Упрощен мониторинг — теперь я проверяю только новые файлы, а не их содержимое

Заключение

Традиционные методы защиты Joomla хороши, но они не дают полной гарантии. Добавление серверного уровня защиты (Nginx) и неизменяемости файлов (chattr +i) создает многоуровневую систему, которую сложно обойти даже при наличии уязвимости в коде.

Этот подход требует больше времени на первоначальную настройку, но окупается за счет:

  • Минимальной зависимости от Joomla-плагинов
  • Защиты даже в случае компрометации файловой системы
  • Простоты мониторинга и обслуживания

Помните: безопасность — это процесс, а не состояние. Регулярно проверяйте новые файлы, обновляйте компоненты и адаптируйте защиту под меняющиеся угрозы.

Атака на мой сайт через JCE: как я обнаружил и отразил взлом

Информация о материале
Автор: Геннадий Едиг
Категория: Администрирование и Вайб-кодинг
Опубликовано: 18 июня 2026
Просмотров: 312

Реальная история администратора сайта mi55.ru, столкнувшегося с критической уязвимостью CVE-2026-48907

Вступление: как всё началось

17 июня 2026 года я заметил, что сайт начал работать нестабильно. В админке появились странные файлы, а антивирус на моём локальном компьютере при скачивании бэкапа зафиксировал множество угроз: Trojan:PHP/WebShell!MSR, Backdoor:PHP/Webshell!AMTB. Среди них были файлы с именами ac709ca65ebe.php, syshack-*.gif, kill.gif и kill.png.

Я не сразу понял, что произошло. Первое подозрение пало на старый компонент Excel2VM — он уже был в «чёрном списке» из-за известных уязвимостей. Но когда я начал копать логи, картина стала меняться.

Как я понял, что это JCE

Всё стало ясно после анализа логов доступа:

103.154.151.57 - - [18/Jun/2026:00:52:33 +0300] "POST /index.php?option=com_jce&task=plugin.rpc&plugin=browser" 200
46.246.28.170 - - [18/Jun/2026:05:15:53 +0300] "POST /index.php?option=com_jce&task=profiles.import" 200

Я увидел прямые обращения к /index.php?option=com_jce&task=profiles.import. Это был явный признак использования уязвимости CVE-2026-48907 — критической дыры в редакторе JCE, о которой за несколько дней до этого сообщали CISA и разработчики Joomla.

Что такое CVE-2026-48907

Это уязвимость с оценкой 10.0 по шкале CVSS — максимальная степень тяжести. Она затрагивает все версии JCE от 1.0.0 до 2.9.99.4 и позволяет неавторизованному пользователю загружать и выполнять PHP-код на сервере.

Согласно официальному описанию CISA:

«Редактор контента Widget Factory Joomla содержит неправильную уязвимость контроля доступа, которая может позволить загружать и выполнять PHP-код путем создания новых профилей редактора для пользователей без аутентификации».

Уязвимость была исправлена в версии 2.9.99.5, выпущенной 3 июня 2026 года. Но, как предупреждали разработчики Joomla:

«Если вы были атакованы до обновления, обновление не удалит то, что оставил злоумышленник».

Логи: что я увидел в реальном времени

18 июня 2026, 00:52:33

POST /index.php?option=com_jce&task=plugin.rpc&plugin=browser&b3405188b02d0859ac4bcc3b32d61cb8=1 HTTP/1.1" 200

Злоумышленник с IP 103.154.151.57 начал атаку через JCE, отправляя RPC-запросы к плагину браузера. Ответ 200 означал, что запрос был успешно выполнен — уязвимость была найдена.

18 июня 2026, 05:15:53

POST /index.php?option=com_jce&task=profiles.import HTTP/1.1" 200

IP 46.246.28.170 загрузил файл 246246.php7 через профиль импорта JCE. Это был первый бэкдор, который позже превратился в ac709ca65ebe.php.

После 05:16:00

POST /ac709ca65ebe.php HTTP/1.1" 200

Злоумышленник начал использовать бэкдор для выполнения команд. В течение нескольких минут были загружены:

  • 31a5e7cc9eindex.php — попытка загрузить новый бэкдор (заблокирована ModSecurity)
  • syshack-*.gif — шесть файлов-бэкдоров в папке /images/
  • kill.gif, kill.png, killmbg.gif — бэкдоры с текстом «Hacked by Antonkill - SYShack»

Что я сделал

1. Анализ логов

Первым делом я изучил логи доступа и аудит-логи ModSecurity. Это позволило понять, когда и как именно произошла атака, а не просто гадать на кофейной гуще.

2. Удаление бэкдоров

Я вручную удалил все обнаруженные вредоносные файлы:

rm -f /var/www/www-root/data/www/mi55.ru/ac709ca65ebe.php
rm -f /var/www/www-root/data/www/mi55.ru/31a5e7cc9eindex.php
rm -f /var/www/www-root/data/www/mi55.ru/images/syshack-*.gif
rm -f /var/www/www-root/data/www/mi55.ru/images/kill.gif
rm -f /var/www/www-root/data/www/mi55.ru/images/kill.png
rm -f /var/www/www-root/data/www/mi55.ru/images/killmbg.gif

3. Проверка пользователей Joomla

Я проверил всех администраторов и пользователей, чтобы убедиться, что злоумышленник не создал скрытую учётную запись:

SELECT id, name, username, email, registerDate FROM mi060816_users ORDER BY id DESC;

4. Восстановление конфигурации

Я проверил configuration.php — он был изменён. Восстановил его и сменил пароли от базы данных и административной панели.

5. Удаление JCE

Компонент JCE был временно удалён. Это радикальный шаг, но он гарантирует, что уязвимость не будет использована повторно.

6. Блокировка IP-адресов

Я заблокировал все IP-адреса, с которых велись атаки:

103.154.151.57, 46.246.28.170, 45.159.12.118

Что изменилось в безопасности сервера

Компонент Статус до атаки Статус после
JCE Установлен, уязвимая версия Удалён
Excel2VM Установлен, уязвимая версия Временно отключён
Файрвол Включён, стандартные правила Дополнен IP-банлистом
ModSecurity Включён Правила усилены
Пароли Старые Изменены все

Главный урок

  1. Логи — это всё. Если бы я не проанализировал логи доступа и ошибок, я бы до сих пор думал, что виноват Excel2VM. Именно логи показали реальный вектор атаки — JCE.
  2. Обновления не защищают от уже оставленного бэкдора. Как и предупреждали разработчики Joomla, обновление JCE не удалило бы вредоносные файлы. Пришлось чистить руками.
  3. WAF (ModSecurity) сработал, но не на 100%. Попытка загрузить 31a5e7cc9eindex.php через ac709ca65ebe.php была заблокирована правилом 933110. Однако бэкдор ac709ca65ebe.php уже был на сервере, и его использовали. Это показывает, что защита должна быть многоуровневой: WAF + мониторинг файлов + анализ логов.
  4. CISA была права. Уязвимость активно эксплуатируется, рабочий код эксплойта есть в открытом доступе, а атаки автоматизированы. Даже сайт без публичной регистрации небезопасен — я в этом убедился на собственном опыте.

Надежда на лучшее

Я надеюсь, что справился с проблемой. На данный момент все найденные бэкдоры удалены, JCE отключён, пароли сменены, а IP злоумышленников заблокированы. Но я понимаю, что расслабляться нельзя. Взлом произошёл, и он оставил след.

Теперь я внедряю дополнительные меры:

  • Ежедневный мониторинг изменений файлов в критических папках
  • Автоматическая проверка целостности Joomla
  • Регулярный аудит логов доступа
  • Уведомления о подозрительных действиях в реальном времени

Полезные ссылки

  • CVE-2026-48907 на NVD
  • CISA Known Exploited Vulnerabilities Catalog
  • JCE Security Advisory
  • Joomla Official Security Announcement

Статья подготовлена 18 июня 2026 года на основе реальных событий.

Решение ложных срабатываний ModSecurity (WAF) для CMS с HTML-контентом: практическое руководство

Информация о материале
Автор: Геннадий Едиг
Категория: Администрирование и Вайб-кодинг
Опубликовано: 13 июня 2026
Просмотров: 107

Решение ложных срабатываний ModSecurity для CMS: практическое руководство на примере jedig.ru

Введение

ModSecurity с правилами OWASP CRS (Core Rule Set) — это мощный инструмент защиты веб-приложений от атак. Однако при работе с современными CMS, такими как Joomla с компонентом VirtueMart, WordPress с визуальными редакторами или другими системами, администраторы часто сталкиваются с проблемой ложных срабатываний. Легитимный HTML-код, примеры команд и техническая документация, которые пользователи вводят в поля описания товаров, статей или комментариев, воспринимаются WAF как XSS-атака, попытка выполнения команд или SQL-инъекция.

В этой статье мы разберем две реальные ситуации, с которыми столкнулся сайт jedig.ru: блокировку HTML-описаний товаров в VirtueMart и блокировку технических статей в Joomla. Мы покажем правильные способы решения этих проблем без потери безопасности.

Проблема №1: Блокировка описаний товаров VirtueMart

На сайте jedig.ru, работающем под управлением Joomla с компонентом VirtueMart, при попытке сохранить товар с форматированным описанием (использующим HTML-теги для создания табов, списков, стилизации) ModSecurity блокировал запрос с ошибкой 403 Forbidden.

Что происходило

Администратор заполнял поле product_desc (описание товара) HTML-кодом следующего вида:

<ul class="nav nav-tabs" role="tablist">
  <li class="active">
    <a href="#review" role="tab" data-toggle="tab">📖 Обзор</a>
  </li>
  <li>
    <a href="#average" role="tab" data-toggle="tab">⭐ Отзывы</a>
  </li>
</ul>
<div class="tab-content">
  <div class="tab-pane active" id="review">
    <p>Описание товара...</p>
  </div>
</div>

При нажатии кнопки "Сохранить" сервер возвращал ошибку 403, и изменения не применялись.

Анализ логов ModSecurity

Открыв файл modsec_audit.log, мы обнаружили следующие записи о блокировке:

ModSecurity: Warning. Matched "Operator `Rx'" against variable `ARGS:product_desc'
[file "/etc/modsecurity/owasp/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf"]
[id "932130"]
[msg "Remote Command Execution: Unix Shell Expression Found"]

ModSecurity: Warning. detected XSS using libinjection.
[file "/etc/modsecurity/owasp/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"]
[id "941100"]
[msg "XSS Attack Detected via libinjection"]

ModSecurity: Warning. [id "941160"]
[msg "NoScript XSS InjectionChecker: HTML Injection"]

ModSecurity: Warning. [id "941310"]
[msg "US-ASCII Malformed Encoding XSS Filter - Attack Detected"]

ModSecurity: Access denied with code 403 (phase 2).
[id "949110"]
[msg "Inbound Anomaly Score Exceeded (Total Score: 20)"]

Что означают эти записи

  1. Правило 932130 (RCE) сработало на символы > и ( в HTML-коде, которые оно интерпретировало как попытку выполнения shell-команды.
  2. Правило 941100 (XSS libinjection) обнаружило HTML-теги <ul>, <a> и атрибуты, которые библиотека libinjection классифицировала как XSS-атаку.
  3. Правило 941160 (XSS NoScript) среагировало на атрибуты style=, href= и другие HTML-атрибуты.
  4. Правило 941310 (US-ASCII Malformed Encoding) сработало на кириллические символы в UTF-8 кодировке, которые после определенных трансформаций выглядели как подозрительные последовательности байтов.
  5. Правило 949110 (Anomaly Score) суммировало "штрафные баллы" от всех предыдущих правил и, так как общий счет превысил пороговое значение (20 > 5), заблокировало запрос.

Проблема №2: Блокировка технических статей в Joomla

Вторая проблема возникла при попытке опубликовать на jedig.ru техническую статью о настройке ModSecurity. В тексте статьи приводились примеры кода, команды для терминала, пути к файлам и даже примеры вредоносных конструкций (для иллюстрации).

Что происходило

Автор заполнял основное поле текста статьи в Joomla (jform[articletext]) HTML-разметкой, содержащей:

  • Примеры HTML-кода с тегами <script> и функциями alert('XSS')
  • Примеры команд терминала: sudo nano /etc/modsecurity/...
  • Примеры SQL-запросов и путей к системным файлам
  • Примеры конфигурации ModSecurity с директивами SecRule

При нажатии кнопки "Сохранить" сервер снова возвращал ошибку 403 Forbidden.

Анализ логов ModSecurity

В этот раз лог показал ещё более разнообразную картину:

ModSecurity: Warning. [id "930120"]
[msg "OS File Access Attempt"]
[data "Matched Data: etc/modsecurity found within ARGS:jform[articletext]"]

ModSecurity: Warning. [id "932235"]
[msg "Remote Command Execution: Unix Command Injection"]

ModSecurity: Warning. [id "932130"]
[msg "Remote Command Execution: Unix Shell Expression Found"]

ModSecurity: Warning. [id "932260"]
[msg "Remote Command Execution: Direct Unix Command Execution"]

ModSecurity: Warning. [id "941100"]
[msg "XSS Attack Detected via libinjection"]

ModSecurity: Warning. [id "941110"]
[msg "XSS Filter - Category 1: Script Tag Vector"]

ModSecurity: Warning. [id "941160"]
[msg "NoScript XSS InjectionChecker: HTML Injection"]

ModSecurity: Warning. [id "941390"]
[msg "Javascript method detected"]

ModSecurity: Warning. [id "942190"]
[msg "Detects MSSQL code execution and information gathering attempts"]

ModSecurity: Access denied with code 403 (phase 2).
[id "949110"]
[msg "Inbound Anomaly Score Exceeded (Total Score: 45)"]

Что означают эти записи

  1. 930120 (LFI) — обнаружил путь /etc/modsecurity в тексте статьи и принял его за попытку доступа к системным файлам.
  2. 932235, 932130, 932260 (RCE) — нашли примеры Unix-команд sudo, nano, shell-выражения и восприняли их как инъекцию команд.
  3. 941100, 941110, 941160, 941390 (XSS) — обнаружили теги <script>, функцию alert() и другие JavaScript-конструкции из раздела "Проверка результата".
  4. 942190 (SQLi) — нашёл подозрительные символы и SQL-паттерны в примерах кода.
  5. 949110 — суммарный штрафной балл достиг 45, запрос заблокирован.

Объединяющая причина обеих проблем

Обе проблемы имеют одну и ту же природу: OWASP CRS не знает контекста. Он видит паттерны, похожие на атаки, но не может отличить:

  • Реальную XSS-атаку от примера HTML-кода в описании товара
  • Попытку LFI-атаки от упоминания пути к файлу в технической статье
  • Инъекцию команд от примера команды для терминала в обучающем материале

Разработчики OWASP CRS предусмотрели специальный механизм для решения таких ситуаций — исключения по переменным.

Неправильные подходы к решению

Подход 1: Полное отключение правил для всей админки

Некоторые пытаются решить проблему, отключая правила для всего URI админки:

SecRule REQUEST_URI "@beginsWith /administrator/index.php" \
    "id:9001338,\
    phase:1,\
    pass,\
    nolog,\
    ctl:ruleRemoveById=941310"

Почему это не работает:

  • Правило срабатывает в phase:1, когда тело запроса еще не обработано
  • Отключается только одно правило (941310), но блокируют запрос другие правила
  • Снижается безопасность всей админки

Подход 2: Отключение всех правил XSS/RCE

Другие пытаются отключить все правила защиты от XSS и RCE:

SecRuleRemoveByTag "attack-xss"
SecRuleRemoveByTag "attack-rce"

Почему это опасно:

  • Полностью отключается защита от реальных XSS-атак
  • Сайт становится уязвимым для инъекций вредоносного кода
  • Это нарушение базовых принципов безопасности

Подход 3: Использование chain-правил с ctl:ruleRemoveById

SecRule REQUEST_URI "@rx ^/administrator/index\.php" \
    "id:9001338,\
    phase:2,\
    pass,\
    nolog,\
    chain"
    SecRule ARGS_NAMES "@streq product_desc" \
        "ctl:ruleRemoveById=932130,..."

Почему это работает, но не идеально:

  • Требует точного указания phase:2 (в phase:1 тело запроса недоступно)
  • Сложнее для чтения и поддержки
  • Менее предпочтительно, чем встроенный механизм исключений

Правильное решение: SecRuleUpdateTargetById и SecRuleUpdateTargetByTag

OWASP CRS предоставляет два мощных механизма для точечного исключения конкретных полей из проверки:

  • SecRuleUpdateTargetById — исключает поле из проверки конкретным правилом по ID
  • SecRuleUpdateTargetByTag — исключает поле из проверки всей группы правил по тегу

Шаг 1: Открытие файла исключений

Откройте файл локальных исключений CRS. Обычно это:

sudo nano /etc/modsecurity/owasp/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf

Шаг 2: Добавление правил исключения для VirtueMart

Для поля product_desc (описание товара) добавляем точечные исключения по ID правил:

# Исключения для поля product_desc (описание товара VirtueMart)
# Отключаем правила, которые ложно срабатывают на HTML-разметку в админке
SecRuleUpdateTargetById 932130 "!ARGS:product_desc"
SecRuleUpdateTargetById 941100 "!ARGS:product_desc"
SecRuleUpdateTargetById 941160 "!ARGS:product_desc"
SecRuleUpdateTargetById 941310 "!ARGS:product_desc"

Шаг 3: Добавление правил исключения для статей Joomla

Для поля jform[articletext] (основной текст статьи) используем исключения по тегам, чтобы охватить все возможные типы атак, которые могут встретиться в технических статьях:

# Исключения для основного текста статей Joomla (jform[articletext])
# Поле редактируется только авторизованными авторами/администраторами.
# В статьях IT-блога встречаются примеры кода, которые не являются атаками.
SecRuleUpdateTargetByTag "attack-xss" "!ARGS:jform[articletext]"
SecRuleUpdateTargetByTag "attack-rce" "!ARGS:jform[articletext]"
SecRuleUpdateTargetByTag "attack-sqli" "!ARGS:jform[articletext]"
SecRuleUpdateTargetByTag "attack-lfi" "!ARGS:jform[articletext]"

Шаг 4: Перезагрузка веб-сервера

Примените изменения:

sudo systemctl reload apache2
# или
sudo systemctl reload nginx

Как это работает

Директива SecRuleUpdateTargetById говорит ModSecurity:

"Когда будешь проверять правило с указанным ID, НЕ проверяй в нём указанную переменную"

Директива SecRuleUpdateTargetByTag говорит:

"Когда будешь проверять ВСЕ правила с указанным тегом, НЕ проверяй в них указанную переменную"

В нашем случае:

  • "!ARGS:product_desc" и "!ARGS:jform[articletext]" — восклицательный знак означает "ИСКЛЮЧИТЬ"
  • "attack-xss", "attack-rce" и т.д. — это теги, которыми OWASP CRS помечает группы правил

Почему это безопасно

1. Точечное исключение

Мы исключаем только конкретные поля (product_desc и jform[articletext]) из проверки определёнными правилами. Все остальные поля формы и все остальные правила продолжают работать.

2. Контекст использования

Поля product_desc и jform[articletext] заполняются только в админке сайта авторизованными пользователями (администраторами, редакторами, авторами). Доступ к админке защищен логином и паролем. HTML-код и примеры команд в этих полях — это легитимный контент.

3. Защита других полей

Если злоумышленник попытается внедрить XSS-код в другие поля (например, в параметр поиска search, имя пользователя username, заголовок статьи jform[title] или любой другой параметр), ModSecurity продолжит проверять эти поля и заблокирует атаку.

4. Соответствие best practices

Использование SecRuleUpdateTargetById и SecRuleUpdateTargetByTag — это официальный рекомендуемый способ решения ложных срабатываний в документации OWASP CRS.

Проверка результата

После применения решения:

  1. Попробуйте сохранить товар с HTML-описанием в админке VirtueMart
  2. Попробуйте сохранить техническую статью с примерами кода в редакторе статей Joomla
  3. Проверьте modsec_audit.log — записей о блокировке для полей product_desc и jform[articletext] больше не должно быть
  4. Убедитесь, что другие поля продолжают проверяться (попробуйте ввести <script>alert('XSS')</script> в поле поиска или в заголовок статьи — оно должно быть заблокировано)

Дополнительные рекомендации

1. Документируйте исключения

Всегда добавляйте комментарии к правилам исключения, объясняя:

  • Почему это исключение необходимо
  • Какое поле исключается
  • Какие правила отключаются
  • Кто добавил исключение и когда

2. Регулярно пересматривайте исключения

При обновлении OWASP CRS или изменении функционала сайта пересматривайте список исключений. Возможно, некоторые из них больше не нужны.

3. Используйте детальный логинг для отладки

Временно включите детальное логирование для отладки:

SecRuleEngine DetectionOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABCDEFHZ
SecAuditLogType Serial
SecAuditLog /var/log/modsec_audit.log

После настройки исключений верните SecRuleEngine On для включения блокировки.

4. Выбирайте между ById и ByTag

Используйте SecRuleUpdateTargetById, когда:

  • Нужно отключить только конкретные правила
  • Важна максимальная точность
  • Известны точные ID сработавших правил

Используйте SecRuleUpdateTargetByTag, когда:

  • Нужно отключить всю группу правил (например, все XSS-правила)
  • Поле может срабатывать на множество разных правил
  • Хотите избежать проблем при обновлении CRS (новые правила с тем же тегом тоже будут исключены)

5. Пример полного файла исключений

Вот как может выглядеть итоговый файл REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf для jedig.ru:

# ============================================
# Локальные исключения для jedig.ru
# Обновлено: 13.06.2026
# ============================================

# --- VirtueMart: описание товара ---
# Поле содержит HTML-разметку (табы, списки, стили)
SecRuleUpdateTargetById 932130 "!ARGS:product_desc"
SecRuleUpdateTargetById 941100 "!ARGS:product_desc"
SecRuleUpdateTargetById 941160 "!ARGS:product_desc"
SecRuleUpdateTargetById 941310 "!ARGS:product_desc"

# --- Joomla: основной текст статей ---
# IT-блог содержит примеры кода, команд, путей к файлам
SecRuleUpdateTargetByTag "attack-xss" "!ARGS:jform[articletext]"
SecRuleUpdateTargetByTag "attack-rce" "!ARGS:jform[articletext]"
SecRuleUpdateTargetByTag "attack-sqli" "!ARGS:jform[articletext]"
SecRuleUpdateTargetByTag "attack-lfi" "!ARGS:jform[articletext]"

Заключение

Ложные срабатывания ModSecurity при работе с CMS, использующими HTML-контент и техническую документацию — это распространенная проблема, которая имеет элегантное решение. Использование директив SecRuleUpdateTargetById и SecRuleUpdateTargetByTag позволяет точечно исключить конкретные поля из проверки определёнными правилами или группами правил, сохраняя при этом высокий уровень безопасности для всех остальных полей и векторов атак.

В случае с jedig.ru мы смогли решить обе проблемы:

  • Блокировку HTML-описаний товаров в VirtueMart — добавив 4 строки с исключениями по ID
  • Блокировку технических статей в Joomla — добавив 4 строки с исключениями по тегам

При этом защита от реальных XSS-атак, SQL-инъекций, RCE и LFI для всех остальных полей осталась полностью функциональной.

Помните: правильная настройка WAF — это баланс между безопасностью и удобством использования. Не отключайте защиту полностью — настраивайте её точечно под ваши конкретные задачи.

Полезные ссылки

  • OWASP ModSecurity Core Rule Set
  • Документация ModSecurity
  • Руководство по исключениям CRS
  1. Получение токенов для работы с API VK в 2026 году
  2. Есть ли жизнь после Joomla и VirtueMart?
  3. Почему ИИ вдруг “глупеет”? История одной, казалось бы, простой задачи
  4. Пошаговый разбор и решение ошибки savePhotoError в VK Market API

Страница 1 из 14

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Яндекс.Метрика