- Как защитить сайты на PHP при помощи CrowdSec
- Первые шаги
- Тестируем базовую конфигурацию
- Защищаемся при помощи баунсера для PHP
- Сработало!
- Что еще мы можем сделать
- Обнаруживаем кроулеры и сканнеры
- Защищаемся при помощи капчи
- Пробуем различные методы защиты
- Защита PHP скриптов от копирования — это возможно?
- Решение
- 1. Выдача лицензии и проверка действительности лицензии скриптом
- 2. Проверка домена
- 3. Временная лицензия
- 4. Выполнение скрипта на локальном компьютере без лицензии
Как защитить сайты на PHP при помощи CrowdSec
По данным статистики W3Techs, PHP применяется в 79% сайтов (о которых есть данные об используемых языках программирования). Для их защиты явно нужен собственный баунсер, и он уже добавлен в наш хаб! В этом посте поговорим о том, как этот баунсер может помочь защитить сайты от атак.
Баунсеры CrowdSec можно настроить на разных уровнях вашего стека: существуют баунсеры для веб-сервера, брандмауэра и CDN. Теперь к ним присоединится и решение, которое можно использовать прямо на уровне приложения.
- Возможность найти ответ на потенциальные угрозы кибербезопасности в рамках существующей бизнес-логики.
- Больше свободы в выборе средств и сценариев защиты, когда дело доходит до реальной атаки.
Мы уже выпустили баунсер для WordPress (он доступен в виде плагина, который можно напрямую установить из бэк-офиса), но библиотеки PHP по сути могут использоваться в любых приложениях на базе PHP. Поэтому для примера давайте рассмотрим Drupal.
Наш баунсер поможет вам заблокировать IP-адреса злоумышленников и отделить реальных пользователей от ботов при помощи капчи. Хотим только напомнить, что ситуации ниже приводятся просто в качестве примера. Выбор реальных средств защиты, которые подходят к вашей конкретной ситуации, мы оставляем на ваше усмотрение.
Первые шаги
Итак, предположим, что мы запустили Drupal на машине на базе Debian и с Apache в качестве веб-сервера. Для других приложений на PHP порядок действий будет таким же.
В первую очередь давайте установим на сервер CrowdSec:
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash sudo apt install crowdsec
Дополнительную информацию об установке см. в официальной документации. CrowdSec самостоятельно обнаруживает все запущенные службы, поэтому вам не придется заниматься дополнительной настройкой после установки. Всё сразу же готово к работе!
Тестируем базовую конфигурацию
Теперь, когда CrowdSec установлен, давайте запустим сканер уязвимостей веб-приложений, например, Nikto, и посмотрим, что получится:
Наш IP-адрес был обнаружен и это привело к запуску различных сценариев. Последним из них стал
crowdsecurity/http-crawl-non_statics:
Вы уже знаете, что CrowdSec занимается только обнаружением подобных действий, а для защиты от них нужны баунсеры. Поэтому давайте применим наш баунсер для PHP.
Защищаемся при помощи баунсера для PHP
Итак, мы смогли обнаружить попытку вредоносного вмешательства, и теперь нам нужно заблокировать этот IP-адрес на уровне веб-сайта. На момент написания этих строк отдельный баунсер для Drupal ещё не выпущен, поэтому мы будем использовать универсальный баунсер для PHP.
Как работает этот баунсер? Баунсер PHP (как и любой другой) обращается к API CrowdSec, чтобы выяснить, следует ли ему заблокировать обнаруженные IP-адреса, выдать окно с капчей или разрешить передачу данных. Поскольку мы используем Apache в качестве веб-сервера, для его установки мы можем просто запустить скрипт.
git clone https://github.com/crowdsecurity/cs-php-bouncer.git cd cs-php-bouncer/ ./install.sh –apache
По умолчанию баунсер настроен для защиты веб-сайта целиком, но вы можете изменить эти настройки и использовать его только для конкретной страницы или раздела при помощи этой команды:
/etc/apache2/conf-enabled/crowdsec_apache.conf
Сработало!
PHP-баунсер установлен и настроен и уже забанил нас из-за предыдущих попыток сканировать сайт на уязвимости. Если попытаться получить доступ к веб-сайту теперь, мы увидим такое вот сообщение:
Баунсер успешно защитил сайт от наших действий! Если после запуска сканирования вас не заблокировали, вы можете добавить решение о блокировке в систему в ручном режиме при помощи команды
Чтобы продолжить тестирование функций, давайте отменим эти решения:
Что еще мы можем сделать
Итак, мы заблокировали один IP-адрес за подозрительную активность. Это отлично, но как насчёт IP-адресов, которые пытаются сканировать, кроулить или проводить DDoS-атаки? Все мы знаем, что в этом случае возможны ложные срабатывания системы. Так почему бы не отделить реальных пользователей от ботов, используя вместо блокировки капчу?
Обнаруживаем кроулеры и сканнеры
Никто не любит кроулеры и вредоносные пользовательские агенты, поэтому в нашем Хабе есть специальные сценарии, которые помогут их обнаружить. Давайте удостоверимся, что у вас загружены эти коллекции base-http-scenarios при помощи команды
cscli collections list | grep base-http-scenarios:
Если они отсутствуют, вы можете их скачать, установить и перезапустить CrowdSec при помощи команд:
sudo cscli collections install crowdsecurity/base-http-scenarios sudo systemctl reload crowdsec
Защищаемся при помощи капчи
Как мы уже написали выше, попытки обнаружить кибератаки могут привести к ложным срабатываниям системы кибербезопасности. Поэтому давайте добавим капчу для IP-адресов, которые вызвали запуск одного из этих сценариев. Это поможет избежать блокировки IP-адресов реальных пользователей. Чтобы добавить капчу, нам необходимо модифицировать файл profiles.yaml.
Добавим этот блок YAML в начало профиля:
/etc/crowdsec/profiles.yaml name: crawler_captcha_remediation filters: - Alert.Remediation == true && Alert.GetScenario() in ["crowdsecurity/http-crawl-non_statics", "crowdsecurity/http-bad-user-agent"] decisions: - type: captcha duration: 4h on_success: break ---
Теперь, если какой-то IP-адрес вызовет срабатывание сценария
crowdsecurity/http-crawl-non_statics
crowdsecurity/http-bad-user-agent
для него на четыре часа будет активирован вход только через капчу.
Не забываем перезагрузить CrowdSec:
sudo systemctl reload crowdsec
Пробуем различные методы защиты
Если просто перезапустить сканнер, он вызовет срабатывание сразу нескольких сценариев, и в итоге наш IP-адрес снова забанят. Давайте вместо этого сымитируем атаку, которая запустит сценарий bad-user-agent (список известных вредоносных пользовательских агентов выложен здесь). Напоминаем: IP-адрес блокируется после двух срабатываний сценария
Естественно, наши действия не прошли незамеченными:
Теперь, если мы попытаемся зайти на сайт, вместо сообщения о блокировке мы увидим капчу:
После правильного ввода капчи мы получим доступ к сайту.
Для продолжения тестирования давайте себя снова разбаним:
Теперь мы можем запустить сканер уязвимостей:
Как видим, в этот раз мы вызвали срабатывание сразу нескольких сценариев, и среди ответных действий есть и блокировка, и капча:
При попытке доступа к сайту решение о блокировке IP-адреса получает более высокий приоритет и нас снова банят:
Теперь вы знаете, как защитить свои сайты и приложения на базе PHP при помощи нашего баунсера! Если вас заинтересовала платформа CrowdSec и вы хотите попробовать её в действии, ознакомьтесь с нашим мануалом. А если хотите загрузить баунсер для PHP, он уже доступен в нашем Хабе и на GitHub.
Защита PHP скриптов от копирования — это возможно?
Бывает так, что вам неохота предоставлять исходные коды проектов, которые вы разрабатывали. Для этого можно использовать программы-обфускаторы, о которых недавно шла речь.
А бывает, что вам не так хочется закрыть исходный код, как защитить скрипт от копирования. На мой взгляд, сокрытие исходного кода, в большинстве случаев, не имеет смысла без защиты от копирования. Некоторые обфускаторы, шифрующие код (а не просто коверкающие), имеют возможность лочить скрипт под определенный домен или айпишник. Но, во-первых, мы же не хотим для каждого домена перешифровывать все исходники? Во-вторых, мне удалось разлочить эту защиту одной строкой в начале скрипта:
Я долго искал в интернете решение для защиты от копирования. На форумах этот вопрос часто обсуждался, в основном, задавали его новички, а опытные (видимо) программисты отвечали «— Ты дурак, кому нужен твой код. Учи матчасть, да и вообще php-скрипты ничего не достойны!». Что ж, подумал я. Наверное, действительно нельзя. Но подождите, тот же Битрикс (фу) выдает лицензии на отдельные сайты, при этом вы получаете открытый исходный код после покупки лицензии. Что же мешает скопировать его на несколько своих сайтов? Я не знаю, а если вы знаете — скажите мне пожалуйста.
- Скрипт, очевидно, должен быть зашифрован, например Зендом. Но мне понравился Lock It — во-первых, он не требует Зенд Оптимайзера, и, во-вторых — стоит недорого. Но сейчас речь не о том, как шифровать скрипт, а как защитить его от копирования. Поэтому идем дальше, просто будем считать, что исходный код закрыт. Очевидно, что это необходимое условие.
- Я хочу выдавать ключ (я назову его лицензией) на каждый экземпляр скрипта. То есть я хочу каждому человеку отдавать только лицензию, а скрипт пусть валяется во всеобщем доступе.
- Лицензию привязывать к домену, но если у домена есть синонимы — скрипт должен работать и при обращении через них. Главное, чтоб это был один и тот же экземпляр скрипта.
- Никаких коннектов на другой (мой) сервер. Скрипт должен быть самодостаточным.
- Никакого доверия скрипта к серверным переменным или переменным окружения во время проверки лицензии. Их можно легко переопределить.
Решение
1. Выдача лицензии и проверка действительности лицензии скриптом
Действительно, некрасиво хранить $secretword в самих скриптах. Поэтому здесь можно использовать шифрование с открытым ключом. При выдаче лицензии я буду подписывать ее своим закрытым ключем, а скрипт, при проверке лицензии, открытым ключем будет проверять действительность лицензии. Но я не нашел в стандартном комплекте PHP никаких функций шифрования с открытым ключом, даже RSA (я слепой?). Если поможете — буду благодарен.
Итак, скрипт проверил правильность лицензии. То есть, подходит ли указанный ключ к указанному домену. Идем дальше.
2. Проверка домена
Как скрипт может проверить, лежит ли он на указанном домене? У нас нет доверия к $_SERVER[‘HTTP_HOST’].
Так же, по условиям — никаких коннектов на другой сервер. Значит, коннектимся сами к себе по предполагаемому домену, и проверяем мы ли там находимся 🙂
А точнее:
1) сохраняем случайное число на сервре (например, во временном файле)2) обращаемся по адресу наш_домен.ру/наш_скрипт.php?action=скажи_число3) проверяем, какое число нам отдают по этому адресу. Если оно соответсвует тому, что мы сохранили, значит, по адресу находимся мы :)0) нулевым пунктом надо добавить отдачу сохраненного числа, если нас вызвали с параметром action=скажи_число
Я немного упростил алгоритм, на самом деле для каждого обращения к скрипту нужно отдельно учитывать эти случайные числа.
Теперь скрипт знает, что лицензия действительна, и что он лежит на соответствующем домене. Основная задача решена!
Вы скажите — wtf, скрипт при каждом обращении будет дергать сам себя? Действительно, жестоко как-то. Поэтому:
3. Временная лицензия
При первом обращении, если проверка прошла успешно, скрипт сохраняет во временном файле временную лицензию.
Временная лицензия представляет собой что-то ноподобие md5(сегодняшняя_дата, домен, секретное слово).
Теперь при каждом запросе мы проверяем только временную лицензию, которая действительна в течение дня. Как только со временной лицензией что-то неладное (поменяли, удалили, прошли сутки) — скрипт опять проверит всё серьезно и сохранит новую временную лицензию.
4. Выполнение скрипта на локальном компьютере без лицензии
Было бы идеально, если бы скрипт не требовал лицензии при запуске на локальном компьютере. Зачем, спрашивается, человеку требовать с меня лицензию, если он просто хочет протестировать скрипт на своем компьютере? Он должен скачать его, и пользоваться. А вот когда он поставит скрипт на сервер, тогда и прийдет ко мне.
Я не знаю как решить эту задачу. У меня пока есть 3 варианта решения, но они мне не нравятся:
1) Если скрипт лежит на домене без точек (типа myscript) — считать, что это виртуальный домен, значит, скорее всего, это локальное тестирование. Недостаток этого метода — умельцы создадут виртуальный домен на своем сервере, а настоящий домен сделают синонимом. Так же, непонятно что делать с доменом localhost.
2) Проверка $_SERVER[«REMOTE_ADDR»]. Проверяем наличие ‘127’ в начале ip-адреса. Недостаток — можно переопределить эту переменную перед выполнением скрипта.
3) Смешно, но можно проверить операционную систему сервера. И разрешить выполнение под Windows. Только не бейте меня, это всего лишь вариант.
Выкладываю пример скрипта для тестирования.
Я с благодарностью жду конструктивных комментариев. Возможно, вы найдете ошибку в этой защите, или подадите сежую идею.