Многоуровневая система защиты от ботов и DDoS-атак на базе Redis + PoW JS Challenge
inline_check_lite.php — PHP-скрипт встроенной защиты от ботов, работающий на базе Redis. Подключается через auto_prepend_file или require в начале каждой страницы и выполняет многоуровневую проверку до загрузки CMS, что критически снижает нагрузку на сервер.
| Режим | Компоненты | Принцип действия | Нагрузка при атаке | Требования |
|---|---|---|---|---|
| Лёгкий Lite | PHP + Redis | Выдаёт ошибку 503 (Soft Ban) |
Средняя | Обычный хостинг |
| PRO Hard | PHP + Redis + API | Полная блокировка iptables DROP (Hard Ban) |
Нулевая | VPS / Root |
Каждый домен получает уникальный site_id — первые 8 символов MD5 от hostname. Все Redis-ключи имеют префикс bot_protection:{site_id}:, что позволяет одному Redis обслуживать несколько сайтов без пересечения данных.
$_JSC_CONFIG['mode']Каждый новый посетитель обязательно проходит JS Challenge (PoW). Пока cookie mk_verified не установлена — страница не откроется.
Когда: Сайт под DDoS-атакой, временная мера.
Анализ поведения запроса, начисление баллов подозрительности. При достижении порога — challenge. Легитимные пользователи ничего не видят.
Когда: Основной рабочий режим, продакшен.
JS Challenge никогда не показывается. Работают только: Rate Limit, UA Rotation Detection, Hammer Protection, API-блокировка.
Когда: API / отладка / достаточно Rate Limit.
| Характеристика | always | auto | never |
|---|---|---|---|
| Challenge для всех | ✓ Да | ✗ Нет | ✗ Нет |
| Challenge для подозрительных | ✓ Да | ✓ Да | ✗ Нет |
| Влияние на UX | 🔴 Высокое | 🟢 Минимальное | 🟢 Нулевое |
| Влияние на SEO | 🟡 Среднее | 🟢 Нет | 🟢 Нет |
| Защита от простых ботов | 🟢 Макс | 🟢 Высокая | 🟡 Средняя |
| Защита от продвинутых | 🟢 Макс | 🟡 Хорошая | 🔴 Rate Limit |
| Rate Limit | ✓ | ✓ | ✓ |
| UA Rotation | ✓ | ✓ | ✓ |
| Hammer Protection | ✓ | ✓ | ✓ |
| Подходит для API | ✗ | ~ | ✓ |
autoФункция _jsc_check_anomaly() анализирует каждый запрос и начисляет баллы подозрительности. Когда сумма ≥ suspicion_threshold — показывается JS Challenge.
| Сценарий | Баллы | Результат |
|---|---|---|
| Обычный браузер, первый визит | 0 | ✓ Пропуск |
curl без заголовков | 2 + 1 = 3 | ✗ Challenge |
| Python requests + дефолтный UA | 1 + 1 = 2 | ✓ Пропуск |
| Python requests + burst 5 req/sec | 1 + 1 + 3 = 5 | ✗ Challenge |
| Scrapy с пустым UA | 2 + 1 + 1 = 4 | ✗ Challenge |
| Бот без cookie + burst + rate | 3 + 2 = 5 | ✗ Challenge |
Параметр pow_enabled переключает между двумя принципиально разными типами проверки. Это не то же самое, что визуальный стиль — это разная логика верификации.
Браузер вычисляет тысячи SHA-256 хешей, перебирая nonce, пока хеш не начнётся с N нулей. Аналог майнинга — вычислительно дорого.
Сервер отправляет 5 случайных чисел (10–99). Браузер просто складывает их и отправляет сумму. Лёгкий, мгновенный.
| Характеристика | ⛏️ PoW (pow_enabled=true) | 🧮 Sum (pow_enabled=false) |
|---|---|---|
| Алгоритм | Brute-force SHA-256 с поиском nonce | Сложение 5 чисел |
| Нагрузка на CPU | Средняя (1–5 сек) | Нулевая (мгновенно) |
| Требует crypto.subtle | Да (с fallback на Sum) | Нет |
| Время решения (difficulty=3) | ~1–3 сек | ~мгновенно |
| Время решения (difficulty=5) | ~10–60 сек | — |
| Макс. время на сервере | 120 сек | 300 сек |
| Защита от curl/wget | ✓ Полная | ✓ Полная |
| Защита от Python requests | ✓ Полная | ✓ Полная |
| Защита от headless-браузеров | ✓ Замедляет (CPU cost) | ✗ Проходят мгновенно |
| Защита от DDoS ботнета | ✓ Каждый бот тратит CPU | ~ Только факт JS |
| Влияние на мобильные | Слабые устройства — медленнее | Без проблем |
| Показывает статистику | Хеши/сек, время, прогресс | Нет |
pow_difficulty)Количество ведущих нулей в SHA-256 хеше. Каждый +1 увеличивает сложность примерно в ×16:
crypto.subtle (HTTP без HTTPS, очень старый браузер), PoW-challenge автоматически переключается на Sum-задачу. В PoW-ответе сервера всегда есть поле fallback с числами для суммирования.pow_style)Независимо от типа challenge (PoW или Sum), можно выбрать внешний вид страницы проверки:
Минималистичная страница в стиле Cloudflare. Белый фон, оранжевый спиннер, логотип Cloudflare, зелёная галочка по завершению.
Когда: Для сайтов где Cloudflare — привычный вид. Пользователи не пугаются.
Страница в стиле форума SMF 2.0. Тёмно-синяя шапка, серые панели, классический форумный дизайн.
#e5e5e8 в стиле SMF#315d7d → #1e5380Когда: Для форумов SMF, DLE, или когда нужен «нейтральный» вид системы защиты.
| Тип + Стиль | Конфигурация | Результат | Рекомендация |
|---|---|---|---|
| PoW + Cloudflare | pow_enabled: true |
Страница в стиле CF, SHA-256 brute-force, прогресс хешей | Для обычных сайтов |
| PoW + SMF | pow_enabled: true |
Страница в стиле форума, SHA-256 brute-force, прогресс хешей | Для форумов SMF/DLE |
| Sum + Cloudflare | pow_enabled: false |
Страница в стиле CF, мгновенное сложение чисел | Лёгкая проверка |
| Sum + SMF | pow_enabled: false |
Страница в стиле форума, мгновенное сложение чисел | Лёгкая проверка |
$_JSC_CONFIG = array(
'enabled' => true,
'secret_key' => 'ваш_секретный_ключ',
'cookie_name' => 'mk_verified',
'token_lifetime' => 129600,
'pow_enabled' => true, // PoW — тяжёлый challenge
'pow_difficulty' => 3, // 3 нуля — ~1-3 сек
'pow_timeout' => 60, // таймаут 60 сек
'pow_style' => 'cloudflare', // стиль Cloudflare
'mode' => 'auto',
);
$_JSC_CONFIG = array(
'enabled' => true,
'secret_key' => 'ваш_секретный_ключ',
'cookie_name' => 'mk_verified',
'token_lifetime' => 129600,
'pow_enabled' => false, // Sum — мгновенный challenge
'pow_style' => 'smf', // стиль форума
'mode' => 'auto',
);
$_JSC_CONFIG = array(
'enabled' => true,
'secret_key' => 'ваш_секретный_ключ',
'cookie_name' => 'mk_verified',
'token_lifetime' => 129600,
'pow_enabled' => true,
'pow_difficulty' => 5, // 5 нулей — ~30-60 сек!
'pow_timeout' => 120, // увеличен таймаут
'pow_style' => 'cloudflare',
'mode' => 'always', // для ВСЕХ посетителей
);
$CUSTOM_USER_AGENTS WhitelistПодстроки User-Agent, пропускаемые без JS Challenge и Rate Limit. Для собственных сервисов, мониторинга, cron.
$CUSTOM_USER_AGENTS = array('sitecheckerbotcrawler', 'botprotection', 'murkir-cleanup');
Сравнение регистронезависимое. Достаточно частичного совпадения. Визиты логируются в Redis как CustomUA.
$ADMIN_IP_WHITELIST IP WhitelistIP администраторов — полностью исключены из всех проверок: без Challenge, без Rate Limit, без Hammer.
$ADMIN_IP_WHITELIST = array(
'::1', '127.0.0.1',
'185.109.48.79',
'2a03:3f40:2:e:0:4:0:2',
);
Поддерживает CIDR (192.168.0.0/24). Без маски — /32 (IPv4) или /128 (IPv6).
$ADMIN_URL_WHITELIST URL WhitelistAJAX определяется по: X-Requested-With: XMLHttpRequest, Content-Type: application/json, Accept: application/json, X-JSC-Response.
$_JSC_CONFIG Challengesmf (стиль форума) или cloudflare (Cloudflare-like)auto / always / never$_JSC_AUTO_CONFIG Auto Mode$_HAMMER_PROTECTION Anti-HammerБлокировка IP, которые «долбят» challenge-страницы или 502-страницы, не решая их.
$_API_CONFIG API10+ разных UA с одного IP за 5 минут — типичный признак ботнета.
Без Redis защита работать не будет. Redis хранит счётчики, баны и кеш.
# Ubuntu / Debian:
sudo apt update && sudo apt install redis-server
# CentOS / AlmaLinux:
sudo yum install redis
Откройте: sudo nano /etc/redis/redis.conf
# Ограничение памяти (10mb для малых, 50-100mb для крупных)
maxmemory 10mb
# Политика удаления: старые ключи при переполнении
maxmemory-policy allkeys-lru
# Точность проверки
maxmemory-samples 10
После изменений: sudo systemctl restart redis
| Тип сайта | Рекомендация | Примерный объём |
|---|---|---|
| Обычный сайт | 10mb | ~40k записей |
| Крупный портал | 50mb | ~200k записей |
| High Load / DDoS | 256mb+ | 1M+ записей |
Загрузите все файлы в папку /js_challenge_lite/.
Основной скрипт. Работает автономно. Если API выключен — блокирует через PHP (503). Если включен — шлёт команды в API для iptables.
Панель управления. Логин/Пароль: admin / ваш_пароль (Смените!).
Скрипт-мост между PHP и Linux iptables. Принимает команды «Забанить / Разбанить».
Запускается по CRON. Проверяет Redis на истёкшие баны и даёт команду в iptables разблокировать IP.
Настройки API (ключи, лимиты балансировки нагрузки).
Вставьте в самый верх index.php:
require_once $_SERVER['DOCUMENT_ROOT'] . '/js_challenge_lite/inline_check_lite.php';
В конфиг пула (/etc/php/8.3/fpm/pool.d/www.conf):
php_admin_value[auto_prepend_file] = "/path/to/js_challenge_lite/inline_check_lite.php"
location ~ \.php$ {
fastcgi_param PHP_VALUE "auto_prepend_file=/path/to/inline_check_lite.php";
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
}
auto_prepend_file = /path/to/inline_check_lite.php
'api_enabled' => false.sudo nano /etc/sudoers.d/iptables
Полный блок: IPv4, IPv6, Nginx Reload, ACCEPT:
# Правила для IPv4
Cmnd_Alias IPTABLES_IPV4 = /sbin/iptables -C INPUT -s * -p tcp --dport 80 -j DROP, \
/sbin/iptables -I INPUT -s * -p tcp --dport 80 -j DROP, \
/sbin/iptables -I INPUT -s * -p tcp --dport 443 -j DROP, \
/sbin/iptables -D INPUT -s * -p tcp --dport 80 -j DROP, \
/sbin/iptables -D INPUT -s * -p tcp --dport 443 -j DROP, \
# ... (полные пути /sbin, /usr/sbin, /bin)
# Правила для IPv6
Cmnd_Alias IPTABLES_IPV6 = /sbin/ip6tables -C INPUT -s * -p tcp --dport 80 -j DROP, \
# ... (аналогично IPv4)
# Информационные команды
Cmnd_Alias IPTABLES_INFO = /sbin/iptables -S INPUT, \
/sbin/iptables -L INPUT -n -v, \
# ...
# Сохранение правил
Cmnd_Alias IPTABLES_SAVE = /bin/sh -c *iptables-save*rules.v4*, \
/bin/sh -c *ip6tables-save*rules.v6*, \
# ...
# Правила ACCEPT для IPv4/IPv6
Cmnd_Alias IPTABLES_IPV4_ACCEPT = /sbin/iptables -A INPUT -p tcp -s * --dport * -j ACCEPT, \
# ...
# Перезагрузка Nginx
Cmnd_Alias NGINX_RELOAD = /usr/sbin/nginx -s reload, \
/bin/systemctl reload nginx
# Назначаем права (раскомментируйте нужную строку):
nginx ALL=(ALL) NOPASSWD: IPTABLES_IPV4, IPTABLES_IPV6, IPTABLES_INFO, \
IPTABLES_MISC, IPTABLES_SAVE, NGINX_RELOAD, IPTABLES_IPV4_ACCEPT, \
IPTABLES_IPV6_ACCEPT, IPTABLES_FULL_INFO
#www-data ALL=(ALL) NOPASSWD: ...
#apache ALL=(ALL) NOPASSWD: ...
sudo chown root:root /etc/sudoers.d/iptables
sudo chmod 0440 /etc/sudoers.d/iptables
sudo visudo -c
В inline_check_lite.php, redis-bot_admin.php и cleanup.php установите 'enabled' => true и укажите api_key.
1. Откройте cron: crontab -e
2. Добавьте строку:
*/3 * * * * php /path/to/js_challenge_lite/cleanup.php > /dev/null 2>&1
Скрипт запускается каждые 3 минуты, проверяет Redis на истёкшие баны и разблокирует IP в iptables.
Все ключи имеют префикс bot_protection:{site_id}:, где site_id — первые 8 символов MD5 от hostname.
| Ключ | TTL | Описание |
|---|---|---|
rate:{user_hash} | 3600 | Данные rate limit пользователя |
blocked:{user_hash} | 900 | Блокировка по rate limit |
blocked:hammer:{ip} | 3600 | Блокировка по hammer |
blocked:no_cookie:{ip} | 3600 | Блокировка за атаку без cookie |
ua_blocked:{ip} | 7200 | Блокировка за ротацию UA |
ua:{ip} | 3600 | История UA с IP |
jsc_auto:pending:{ip} | 300 | Ожидающий challenge |
jsc_auto:requests:{id} | 120 | Счётчик запросов для auto-mode |
jsc_stats:* | 7 дней | Статистика JS Challenge |
search_stats:* | 7–30 дней | Статистика поисковых ботов |
ip_whitelist:{ip} | 86400 | Кеш проверки IP whitelist |
hammer_stats:* | 7 дней | Статистика hammer-атак |
rdns:cache:{ip_hash} | 3600 | Кеш rDNS-верификации |
search_log | — | Последние 500 визитов ботов |
secret_key — главный секрет для токенов. Уникальный для каждого сайта!api_key — ключ доступа к API блокировки$ADMIN_IP_WHITELIST — ваши реальные IP-адреса
Cookie mk_verified содержит SHA-256(IP + дата + secret_key). Проверяется за текущий день и 2 предыдущих — cookie валидна ~3 дня даже без учёта token_lifetime.
User Identifier = cookie_uid + SHA-256(UA + Accept-Language). Смена UA у того же пользователя создаёт новый идентификатор и новый rate limit.
| Компонент | Требование | Назначение |
|---|---|---|
| PHP | 5.6 – 8.3 | Основной скрипт |
| Redis | Сервер + phpredis | Хранение данных (DB #1) |
| cURL | PHP extension | API-блокировка |
| DNS | gethostbyaddr | rDNS-верификация |
| random_bytes | PHP 7.0+ или polyfill | Генерация токенов |