README.md

О проекте

Проект scan2ban предназначен для выявления, регистрации и блокировки адресов трафик с которых имеет признаки сканирования.

Функционирующая на кодовой базе проекта сеть детекторов (рунет) классифицирует входящие пакеты и формирует на выходе маркированную таблицу источников с указанием типа зарегистрированного трафика, которая может быть использована для противодействия сбору информации об инфраструктуре и защите информационных ресурсов от автоматизировнных атак (см.раздел Импорт стоп-листов).

Проект характеризуется малым обьемом кода, низкими требованиями к вычислительным ресурсам, возможностью автономного функционирования, возможностью добавлять свои правила, модифицировать параметры встроенных политик, а так-же поддержкой как ipv4 так и ipv6.

Текущий статус проекта доступен на сайте статистики http://www.scan2ban.ru.

Структура пакета

  • config.yml[.example]: конфигурационный файл
  • scan2ban.py: основной сценарий
  • export.py: выгрузка таблицы состояния адресов
  • genstat.py: выборки из таблицы событий/ips
  • report.py: генерация html страницы со статистикой за период
  • modules/: сценарии подготовки и очистки правил nf/ipset для модулей
  • rules.d/: каталог правил
  • db/scheme: sql для создания базы
  • misc/html: шаблоны для html отчетов

Установка

Проверялась на ubuntu/20 и debian/10

Устанавить необходимые пакеты: apt install git python3-yaml ipset Из под рута склонировать репу: git clone https://gitflic.ru/project/konsul/scan2ban.git Перейти в каталог scan2ban и cкопировать пример конфига: cp config.yml.example config.yml.

Быстрый пуск

Провести инициализацию правил iptables/ipset: ./scan2ban.py --init. Настройки по-умолчанию расчитаны на машину с пустыми правилами iptables и в достаточной мере расслаблены, во избежании непреднамеренной блокировки доступа.

Запустить основной сценарий ./scan2ban.py --run

После запуска произойдет следующее:

  • будет загружен config.yml
  • посредством команды netstat -ant будут определены открытые порты и добавлены в перечень greyport
  • запущена команда journalctl -f вывод которой и будет будет анализироваться

Изменения в поведении машины будут следующими:

  • Весь входящий трафик, кроме уже установленных соединение будет заблокирован.
  • При поступлени двух пакетов на открытый порт в период не меньше секунды и не более полуминуты, будет добавлено правило разрешающее доступ с адреса на указанный порт (модуль greyport). Вывод в консоль будет аналогичным следующему:
[NOTICE]: [greyport] Новый запрос на допуск: 27.71.238.208:tcp:22
[NOTICE]: [greyport] Повторный запрос на допуск: 27.71.238.208:tcp:22
[NOTICE]: [greyport] Открыт tcp:22 для адреса 27.71.238.208
  • При поступлении трех пакетов (не совсем точно, но для простоты пусть так) на закрытые порты, доступ адресу будет заблокирован на сутки (модуль portscan)
[NOTICE]: Новый адрес: 49.232.150.20                         
[NOTICE]: Блокировка [portscan]: 49.232.150.20       
  • Если доступ был открыт какому либо адресу на ssh и с адреса были произведены три неудачные попытки авторизоваться, доступ адресу будет заблокирован на сутки (модуль generic)
[NOTICE]: Блокировка [sshd failed authentication]: 143.110.248.52

Для остановки можно нажать CTRL-C. При последующем запуске состояние заблокированных адресов будет загружено из /var/tmp/scan2ban.db.

Правила iptables/ipset можно удалить командой ./scan2ban.py --clean.

База данных

По умолчанию используется sqlite3 которая не требует предварительных действий. Применение sqlite упрощает подготовку холодного пускаi, однако не дает возможности работать в распределеном режиме и накладывает некоторые ограничения на выгрузку данных ввиду ограниченной поддержки SQL.

Работа с sqlite (локальный режим)

Убедиться, что в конфиге раскоментирована секция sqlite3 (по-умолчанию). БД будет создана при старте автоматически в файле /var/tmp/scan2ban.db.

Работа с postgresql

В случае необходимости, можно использовать общую базу данных которая будет использоватья одновременно всеми настроеными агентами. Следует учитывать, что список блокировок и оценок так-же будет единым.

Установить сервер (или только клиент, если база внешняя), а так-же пакет python3-psycopg2

На сервере создать базу и пользователя для нее:

sudo -u postgres psql
postgres=# create database s2b;
postgres=# create user s2buser with encrypted password 'mypass';
postgres=# grant all privileges on database s2b to s2buser;

Проверить, что доступ настроен корректно psql -d s2b -h x.x.x.x -U s2buser -W

Раскоментировать в конфиге секцию база данных pg, подставить параметры соединения к postgres, закоментировать секцию sqlite3.

Запуск

Запуск в консольном режиме

./scan2ban.py [-f]. Поддерживаемые опции:

  • Режимы пуска:
    • –init: иницализация правил
    • –run: рабочий режим
    • –clean: удаление правил
    • –test: прогон загрузки конфига и подключения (или создания если нет) БД.
    • –load-whois [all|blocked|ip]: обновление записи whois для всех записей ips|заблокированных|адреса
  • Опции:
    • -f|–foreground: вывод статистики по обработанным строкам (есть граф. артефакты, надо переписать)
    • -d|–debug: отладочный вывод
    • -с|–config: путь до конфигурационного файла. по умолчанию config.yml
    • –line-debug: вывод считанных строк
    • –score: вывод начисление оценок для адресов
    • –no-selfcheck: не проводить проверку журналирования. необходимо при пуске режима load-whois

Запуск службы

Скопировать misc/scan2ban.serviceв /etc/systemd/system Поправить пути до каталога с программой systemctl daemon-reload systemctl start scan2ban

Служба настроена таким образом, что при старте иницализирует правила, запускает программу,а по команде stop останавливает программу и удаляет правила.

Для наблюдения за выводом можно использовать journalctl -fu scan2ban

Запуск в непривилигированном режиме

Для ограничения угроз связаных с выполнением основного сценария (мало-ли, где-то я ошибся), рекомендуется запускать службу в непривилегированном режиме:

useradd -m -s /bin/bash s2b # создаем пользователя
usermod -aG systemd-journal s2b # добавляем пользователя в группу позволяющую видеть все сообщения journald
cd /home/s2b
git clone https://gitflic.ru/project/konsul/scan2ban.git
cd scan2ban
cp misc/sudo.s2b /etc/sudoers.d/s2b # копируем конфиг для sudo. Внимание, в имени целевого файла не должно быть точек.
cp misc/scan2ban.regular.service /etc/systemd/system/scan2ban.service # и файл сервиса с параметром User
chown -R s2b /home/s2b

Проверить функционирование:

su - s2b
sudo -n iptables -L INPUT -n
sudo -n ipset list
journalctl -u noserv

Все команды должны пройти без ошибок, а journalctl не должен выводить предупреждения Hint: You are currently not seeing message ... . В конфиге отредактировать параметры iptcmd и ipscmd добавив в начало sudo -n .....

Работа в контейнере

В контейнерах не функционирует механизм iptables ... -j LOG, потому применяется -j NFLOG. Переключение осуществляется параметром config/fwlogmode. Предварительно необходимо установить и настроить пакет ulogd2 создав конфигурационный файл /etc/ulogd2.conf например:

[global]

# logfile for status messages
logfile="syslog"

# loglevel: debug(1), info(3), notice(5), error(7) or fatal(8) (default 5)
loglevel=3

# stack for NFLOG to syslog forward
stack=log1:NFLOG,base1:BASE,ifi1:IFINDEX,ip2str1:IP2STR,print1:PRINTPKT,sys1:SYSLOG

[sys1]
facility=LOG_DAEMON
level=LOG_DEBUG

После чего перегрузить приложение systemc restart ulogd2, запустить scan2ban.py и убедиться, что в journalctl -f появляются записи от правил.

работа в режиме ведомого

При выключении всех модулей и общей бд заполняемой с других машин, данная машина будет функционировать в пассивном режиме, с заданной периодичностью подгружая блокировки из БД. В качестве альтернативы, машина может быть запущена с локальной базой, а блокировки загружаться импортом (см. секцию импорта).

Что под капотом

Таблицы

  • ips - таблица состояний источников
    • ip: адрес наблюдения (ip адрес в формате int)
    • count: счетчик суммы событий
    • tags: теги связанных событий
    • created: время первой активности
    • updated: время последней активности
    • timer: период активной блокировки
  • events - журнал событий
    • id: идентификатор
    • src источник (int)
    • dst: агент (int)
    • class: класс события, выставляется на основе event_class конфигурации модуля
    • attr_0-X: опциональные поля
    • time: время фиксации
    • action: битовая маска. 0 бит - пакет пропущен|заблокирован, 1 - добавлена блокировка, 2 - открыт greyport, 3 - снята блокировка
    • tags: классификаторы события
  • whois - кеш данных whois
    • id: идентификатор
    • net: asn_cidr как записан в whois
    • data: выгрузка в формате json
    • updated: время создания записи

Поля attr_0-X таблицы events заполняются в зависимости от класса события.

  • Для class == 0|1, attr_0 == порт куда пришел входящий пакет, attr_3 == порт с которого пришел запрос
  • Для class == 2, attr_0 == порт куда пришел запрос на веб-сервер, attr_1 = сам запрос, attr_2 = user agent
  • Для class == 3, attr0 - тип ICMP, attr3 - CODE, attr1 - ID/SEQ

В каталоге modules, присутствуют подкаталоги с именами модулей и суффиксом .d. Там расположены стартовые и финишные сценарии вызываемые при помощи функции execFromFile и настраивающие правила фильтрации и ipset. Срикпты start и stop вызываются соответственно при --init и --clean. Прочие - при специфических событиях. Если строка в скрипте начинается с #, она игноирурется. Если в теле строки встречается #, все что после него - интерпретируется в качестве условного кода и строка выполняется только если результат True, что в частности используется для формирования разных правил для режимов log/nflog.

При необходимости, можно создать сценарии с именем как было + суффикс .pre и .post, которые будут вызываться перед и после вызова самого файла.

Общая логика

В конец цепочки iptables/INPUT ставится фиксация в журнал всех пакетов не отработаных раннее. Сценарий построчно разбирает записи с тегом [S2B_NF] следующим образом:

  • Проверят адрес источника в базе и если он там отсутствует вносит его в таблицу ips с фиксацией в колонке expired времени жизни записи расчитываемой как текущее время + config/blocktime и выводя сообщение [NOTICE]: Новый адрес:
  • Если адрес уже присутствует в таблице, прибавляется к полю count число очков по форумуле: event score * event weight * module weight, где event score/weight - конкретные параметры из tcp.yml/web.yml, а module weight - вес заданный для модуля (config.yml/tcp|web) в целом.

  • Если число очков для адреса превысило config/blockscore, адрес блокируется

Блокировка осуществляется внесением записи в ipset s2b-drop, совпадение по которой перенаправляет пакет в цепочку s2bdroplog в которой фиксируется факт отброшенного пакета (теги [DROP][S2B_NF]). В поле block соответствующей записи таблицы ips выставляется в 1.

Раз в config/cleanperiod все заблокированные адреса проверяются на факт истечения времени жизни. Для записей, у которых время жизни истекло, поле block = 0.

Раз в config/syncperiod производится проверка появления в базе правил не существующих на текущей установке. Если такие записи есть, они добавляются в правило с сообщением: Блокируем по бд:. Если локальное правило отсутствует в бд, правило удаляется с сообщением: Удаляем блокировку по бд

При включении модуля greyport в цепочке INPUT следом на s2b-droplog добавляется прыжок на s2b-greyport пропускающее пакеты из ipset с именами ‘s2b-протоколо-порт’. Данные ipset наполняются по мере работы модуля. База и состояние доступа являются локальные и не передаются в общую БД.

Работа системы оценок

Решение о блокировки принимается на основе суммы оценок для адреса. Каждое событие с ним связанное прибавляет счетчик на число очков для данного события. В дефолтном варианте для proto-модуля несколько портов (telnet/ssh) score = 3, что вызывает моментальную блокировку адреса (т.к. config.yml/blockscore = 3). Для всех прочих в config.yml задано значение по умолчанию = 1, т.е для внесения адреса в блокируемые необходимы три попытки доступа.

Структура правил

При запуске основного сценария, после загрузки конфигурационного файла производится загрузка правил из структуры каталогов rules.d/. Каждое правило записывается в отдельный файл с расширением .yml, а модуль к которому оно будет применяется задается параметром module в файле правила и никак не зависит от расположения файла в каталогах (структура создана для удобства навигации). Минимум необходимый полей зависит от модуля:

  • для модулей tcp/udp:
module: "tcp" # имя модуля tcp или udp
tag: "telnet" # маркер проставляемый пакетам в таблице events
port: 23      # номер порта

Следующия поля опциональны:

ignore: True если параметр выставлен в True, то порт добавляется в список игнорируемых о чем будет соответствующая запись при запуске. Доступ к нему не регулируется и не фиксируется. priority: 2 приоритет правила. Необходим при задании правил имеющих первоочедное значение. Например если на машине есть telnet, то можно не удалять правило из поставки, с создать свое с приоритетом 2 или больше. Все правилам у которых приоритет не указан, назначается приоритет модуля (default_priority) и в таком случае выбрано будет то, которое загрузилось последним. score: 3 оценка доступа к порту. По-умолчанию у всех правил оценка 1. При настройках по-умолчанию оценка до блокировка адреса =3, т.е. адрес будет заблокирован после первой-же попытки доступа к указанному порту. Если оценка = 0, то доступ будет фиксироваться но не влиять на блокироку. Следует избегать установки оценки 0 для открытых портов т.к. такая настройка вызовет множественые записи в журнале и бд.

  • для модуля web:
tag: "GET /"      # маркер проставляемый пакетам в таблице events
regular: "GET / " # регулярное выражение `re` применяемое к строке журнала
module: "web"     # имя модуля
  • для модуля greyport правила применяются только если в конфигурации модуля параметр discovery != True:
port: 8888
proto: "tcp"
module: "greyport"
tag: "greyport 8888 attempt"
  • для модуля generic поля описаны в базовом правиле generic.d/ssh-failed-auth.yml. Кроме того, для правил такого типа при добавлении поля example (см. rules.d/generic.d/smtp/open-relay.yml) при запуске будет производится контроль корректноси срабатывания регулярного выражения.

Следует учитывать, что загружаются только файлы правил с расширением .yml, все прочие будут проигнорированы.

Описание модулей

tcp|udp

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

При включении модуля udp необходимо убедиться, что адреса DNS-серверов внесены в список игнорируемых сетей (или прописаны в отдельном ACCEPT в штатной конфигурации iptables) в противном случае сервера будут через короткое время заблокированы.

В файле rules.d/nf.d/ignored-nets.d/private.yml перечислены частные сети, связность с которыми не попадает в журналы (ipset s2b-ignored-nets). При необходимости можно добавить аналогичный файл в local.d/ и перечислить в нем специфичные для площадки сети или адреса. Если необходимо сократить список игнорируемых сетей, то поставочный файл можно стереть и создать свой заново.

Игноирируемые порты собраны в ipset s2b-ignored-ports, куда они добавляются если в правиле для порта score = 0.

baseipt

Модуль настраивающий базовые правила для исходящего трафика. Включен по-умолчанию. Сценарии с правилами расположены в каталоге modules/baseipt.d/[start|stop]. Предназначен для пропуска исходящих соединений в условиях блокировки трафика (например при включении greyports) и отсутствии преднастроенных правил оператором. Большей частью подстраховка от запуска ‘не приходя в сознание’.

При необходимости, можно создать файлы start.pre,start.post,stop.pre,stop.post которые будут выполнены до или после start|stop соответственно.

web.yml

Обрабатывает записи журнала с префиксом S2B_WEB. По сравнению с модулем протоколов добавляется поле regular, который содержит регулярное выражение на соответствие которому проверяется запрос и производится классификация запроса..

Мониторинг запросов к веб-серверу осуществляется настройками в /etc/nginx/nginx.conf, где в секцию http добаляется правило журналирования в системный журнал:

    log_format s2b 'SRC=$remote_addr DST=$server_addr DPT=$server_port USER=$remote_user REQ="$request" STATUS=$status REF="$http_referer" XFORW="$http_x_forwarded_for" AGENT="$http_user_agent"';
    access_log /var/log/nginx/access.log;
    access_log syslog:server=unix:/dev/log,nohostname,tag=S2B_WEB s2b;
    error_log /var/log/nginx/error.log;

в config.yml секции web: enabled: True

greyport

Модуль предназначен для противодействия one-shot опросам открытых портов. Тактика защиты строится на том, что подавляющая часть сканеров такого типа обходится посылкой одного-двух пакетов на интересующий нарушителя порт, в то время как легальный клиенты (ssh, браузер) отсылают серию запросов на протяжении некоторого интервала. Таким образом, блокируя прохождение первого пакета и в зависимости от параметра greyport/retry|mindelay|maxdelay открывая порт для конкретного адреса, можно добиться невидимости для существенной части сканеров с сохранением порта в общей доступности.

Логика открытия следующая: при фиксации в журнале входящего пакета который не был ACCEPT нигде раннее, в словарь вносится комбинция адрес-протокол-порт с временем создания и для пакетов которые пришли спустя ВремяСоздания + greyport/mindelay прибавляется счетчик обращений. По достижении счетчиком значения greyport/retry адрес добавляется в соответствующий ipset и дальнейший обмен происходит без наблюдения до момента истечения (ВремяСоздания + greyport/lifetime). Если порог не был достигнут за время greyport/maxdelay, то запись удаляется без открытия порта. Например так, выглядит типичное обращение к ssh от сканера:

[NOTICE]: [greyport] Новый запрос на допуск: 118.32.108.127:tcp:22
[NOTICE]: [greyport] Удаление записи для 118.32.108.127:tcp:22 по истечении таймера ожидания

Т.е. был один пакет, а через полминуты (greport/maxdelay = 30) запись была удалена. Если-же обращений несколько, то записи выглядят так:

[NOTICE]: [greyport] Новый запрос на допуск: 109.252.150.70:tcp:22
[NOTICE]: [greyport] Повторный запрос на допуск 109.252.150.70:tcp:22 до интервала
[NOTICE]: [greyport] Повторный запрос на допуск 109.252.150.70:tcp:22 до интервала
[NOTICE]: [greyport] Повторный запрос на допуск: 109.252.150.70:tcp:22
[NOTICE]: [greyport] Открыт tcp:22 для адреса 109.252.150.70

В данном случае запускался putty, который отослал четыре пакета в интервале 5 секунд (greyport/mindelay = 5) которые не были засчитаны и еще один, который привл к открытию порта (greyport/retry = 2). Следующий пакет уже был пропущен. Выбор mindelay - компромисс между надежностью укрытия и временем ожидания первого открытия соединения от легитимного клиента.

Параметры модуля:

  • enabled: True - включен или выключен
  • retry: 2 - число обращений до открытия доступа
  • mindelay: 1 - время после первого обращения в течении которого повторные запросы не влияют на счетчик
  • maxdelay: 30 - время в течении которого ожидаются повторные обращения
  • lifetime: 87600 - время на которое будет открыт порт (секунды)
  • logpolicy: DROP - действие по-умолчанию (добавляет в конец цепочки журналирования portscan)
  • allow_action: ACCEPT - действие по-умолчани для адресов которым доступ открыт

generic

Модуль предназначен для отработки по произвольным событиям наблюдаемым в journalctl. Настройки производятся путем добавления правил в rules.d/generic.d/ . В базовой поставке присутствует фиксация входов по ssh с неверным паролем и последующей блокировкой адреса (фактически аналог fail2ban). В отличии от прочих модулей, поля в таблицах заполняются на основе конфигурации (в примере идет заполнение адреса источника запроса и самого запроса).

lazyscan

Модуль нацелен на выявление сканирования ‘размазанного’ по времени. Например:

Время                Источник        Агент   Класс  Порт
2023-05-06 03:58:39  184.105.247.200 agent-B  0     80
2023-05-06 11:30:10  184.105.247.200 agent-B  0     389
2023-05-07 03:06:37  184.105.247.200 agent-A  0     5094
2023-05-07 06:29:27  184.105.247.200 agent-B  0     3306
2023-05-08 17:58:12  184.105.247.200 agent-A  0     5938
2023-05-09 19:22:42  184.105.247.200 agent-A  0     9200
2023-05-11 10:38:58  184.105.247.200 agent-C  0     18245
2023-05-11 14:01:26  184.105.247.200 agent-B  1     123
2023-05-12 13:17:53  184.105.247.200 agent-A  0     6080
2023-05-13 07:19:49  184.105.247.200 agent-B  0     2031
2023-05-15 19:11:42  184.105.247.200 agent-B  0     1883
2023-05-16 10:05:37  184.105.247.200 agent-C  7     None
2023-05-16 10:05:37  184.105.247.200 agent-C  1     3283
2023-05-16 15:09:56  184.105.247.200 agent-A  5     9000

Выявление строится на предположении что за заданный период времени (по-умолчанию 7 дней) адрес произвел попытки соединится на закрытые порты не меньше заданного (по-умолчанию 5) числа раз. Проверке подвергаются адреса не находящиеся в блокировке и проявившие свою активность как минимум однажды. Проверка адреса отмечается в выводе записью:

[NOTICE]: [portscan|lazyscan] Проверка по критериям: 64.62.197.162

В случае если пороговые значения достигнуты, адрес добавляется в блок-лист на заданный период (по-умолчанию 7 дней)

[NOTICE]: [portscan|lazyscan] Блокировка [lazyscan]: 74.50.74.234           

Так как расчетное время блокировки не уменьшается, то последующие запросы с данного адреса автоматически продлят блокировку еще на неделю, вне зависимости от класса события. Выйти из блока адрес может только по отсутствие активности на протяжении всего времени блокировки.

response

Функционал предназначен для вызова произвольных команд ос по тем или иным событиям. Командный файл должен находится в каталоге modules/response.d/ и может быть одним из следующих:

  • addblock: адрес добавлен в блокировку
  • rmblock: адрес удален из блокировки
  • greyportopen: greyport открыт для адреса
  • greyportclose: greyport закрыт для адреса
  • genericevent: событие от generic модуля
  • portevent: событие от модуля portscan
  • webevent: событие от модуля web
  • registerip: новый адрес
  • sync: события от модуля синхронизации. action = [add|del] добавляется или удаляется адрес

Поддерживается подстановка параметров по аналогии с другими командными файлами. Как правило, всегда присутствует подмена , остальное имеет смысл грепнуть по коду и функции response.

Если создать файл modules/response.d/portevent следующего содержания:

echo {src} - {proto}:{dport} >>/tmp/scanq

То по каждому прилетевшему пакету, адрес с которого пакет пришел и протокол:порт будет добавляться в файл /tmp/scanq.

inactive

Управление логикой снятия блокировки по истечении таймера. Если выставлен в False, то проверки таймеров истечения блокировки проверяться не будут, как следствие раз заблокированные адреса остануться в списке неограниченно.

Параметр lazyscan: True|False регулирует вызов проверки на ленивое сканирование перед снятияем блокировки. Сам модуль должен быть включен отдельно.

поддержка whois

При включении модуля whois к записям в таблице ips будут добавляться следующие теги в формте ’tag=val;:

  • asn = номер автономной системы
  • asn_country страна приписки
  • asn_description описание as
  • asn_network_cidr обслуживаемая автономкой сеть
  • asn_network_name идентификатор организации

Для его работы необходим модуль ipwhois который можно установить командой: pip3 install ipwhois.

При включении, во время работы служба будет выводить сообщения по факту добавления записей:

[notice|whois] Для 103.41.144.165 зарегистрирована AS134884 сеть: 103.41.144.0/22 зона: IN

При необходимости кеш может быть дозаполнен в ручном режиме вызовом команды:

./scan2ban.py --no-selfcheck --load-whois blocked

Будет осуществлена выгрузка блокированных адресов и для тех у которых не определены теги asn информация будет дополнена.

whitemark

Модуль предназначен для разблокировки или внесения в белый список адресов с которых пришли пакеты с определенными параметрами. Рекомендуемым способом идентификации в текущей реализации является icmp с модуляцией по длине пакета. По умолчанию, для внесения источника в белый список необходимо прислать два icmp пакета с размером который выбирается в случайном интервале от 100 до 1000 байт (seq: ['auto',2]). Здесь auto - генератор, 2 - размер последовательности. При запуске будет выведена строка:

[notice|init] Коды whitemark для icmp/len: [328, 128]

для получения значения размера секции данных, как правило нужно вычесть из соответствующего числа 28 (20 байт заголовок ip, 8 - icmp). Таким образом для отсылки кода нужно выполнить на источнике две команды:

ping -c1 -s 300 x.x.x.x
ping -c1 -s 100 x.x.x.x

После чего в журнале будет выведено сообщение:

[notice|whitemark|109.252.157.119] принят код
[notice|whitemark] снимается блокировка
[notice|whitemark] Новая игнорируемая сеть: 109.252.157.119 comment: whitemark,expired:1699728538

Действия задаются списком в параметре action, unblock - занулят оценку и снимает блокировку, whitelist - вносит адрес в список игнорируемых на период указаный в параметре expired (по умолчанию 1 час).

Для задания статического кода, можно указать в списке необходимые размеры пакета:

   seq: ['323','88','3']

Работа с GeoIP (только postgres)

Для агрегации данных по странам, можно использовать раскладку с https://cdn.jsdelivr.net/npm/@ip-location-db/asn-country/asn-country-ipv4.csv (https://github.com/sapics/ip-location-db)

Диапазоны адресов необходимо сконвертировать в формат CIDR. Наприме при помощи сценария misc/asn2sql.py (скачанный файл должен лежать в текущем каталоге): misc/asn2sql.py > geoip.sql

Грузим в таблицу: psql .... < geoip.sql

Примеры запросов в файле SQL.md

поддержка ipv6

Включается опцией ipv6: True. Добавляется отдельный ipset и настройки ip6tables. Так как трафика там откровенно мало, логика скорее всего где нибудь страдает.

Работы с данными

Утилита genstat.py позволяет делать выборки по событиям из таблицы events и ips. По большому счету представляет собой простой конструктор для SELECT. В качестве обязательного параметра указывается один из трех режимов:

  • –count col1[,col2,col3…] - выборка соответствующий полей с группировкой по числу событий count(*) из events
  • –get col1[,col2,col3…] - произвольная выборка из events
  • –ips col1[,col2,col3…] - произвольная выборка из таблицы ips

Опциональные параметры настраивают вывод и фильтрацию:

  • –debug - вывод строки запроса к БД
  • –filter-last - события за последнее время. Применяется по умолчанию и равен 24 часам. Если параметр задан без мультипликаторов (–filter-last 600), то рассматриваются как секунды. Допускаются мультипликаторы m|h|d, например 24d = за последние 24 дня
  • –filter-class - учитываются только указанный класс событий (один)
  • –filter-dst - только события для указанного источника
  • –filter-src - для указаного агента
  • –filter-attr0 - attr_0 (как правило порт куда пришел пакет) равен параметру
  • –filter-x - произвольное условие в формате SQL. Будет добавлено к прочим в скобках.
  • –order-by - сортировка по параметрам. Указывается порядковый номер колонки как она перечислена в count/get. Если сортировка по нескольким колонам, то через запятую. например: ‘0,1’
  • –order-inverted - инвертирование сортировки (по убыванию)
  • –group-by - группировка по параметрам. Аналогично order
  • –limit X - выодить только X строк
  • –human-time - если в колонках присутствует time, то время будет конвертировано из секунд в читаемый формат.

Примеры:

  • ./genstat.py --get src,attr_0,time --filter-class 1 --debug --human-time --filter-last 10m - udp запросы (класс = 1) за последние 10 минут
  • ./genstat.py --count src --limit 10 --group-by 1 --order-by 1 --order-inverted - топ 10 адресов по числу событий за 24 часа.
  • ./genstat.py --get 'attr_0,count(*)' --filter-last 1h --filter-dst x.x.x.x --group-by 0 --order-by 1 --order-inverted --limit 10 - топ 10 опрашиваемых портов на агета x.x.x.x за последний час
  • ./genstat.py --count "cast(to_timestamp(time) as date)" --debug --filter-last 30d --order-by 0 --filter-dst x.x.x.x --filter-x 'attr_0 = 80 or attr_0 = 443' - динамика запросов на порты 80 и 443 по дням
  • ./genstat.py --count src --filter-class 6 адреса засветившиеся в переборе паролей за последние сутки (не совсем так, скорее в модуле generic который по-умолчанию включен для ssh-bruteforce)
  • ./genstat.py --count src,dst --filter-class 5 - статистика работы greyport. Первая колонка адреса, вторая - агент, третья - число обращений.
  • ./genstat.py --get time,src,dst,attr_1 --filter-class 6 --filter-last 60M --human-time --order-by 0 - выгрузка по срабатывниям модуля generic за последний час
  • ./genstat.py --ips "ip,expired-created" --filter-x 'expired-created > 0' --order-by 1 --order-inverted --limit 30 - топ 30 самых долгоживущих адресов (разница между полями created и expired)
  • ./genstat.py --ips "cast(to_timestamp(created) as date),count(*)" --group-by 0 --order-by 0 - динамика появления новых адресов
  • ./genstat.py --get "extract(isodow from cast(to_timestamp(time) as date)),count(*)" --filter-class 0 --filter-last 1m --group-by 0 - активность опроса портов по дням недели за последний месяц
  • ./genstat.py --get "extract(hour from to_timestamp(time)),count(*)" --filter-class 0 --filter-last 1m --group-by 0 --order-by 0 - распределение запросов по часам
  • /genstat.py --get "(select country from geoip where net >>= '0.0.0.0'::inet + cast(events.src as bigint)),count(*)" --filter-class 0 --filter-last 1m --group-by 0 --order-by 1 - топ опрашивающих стран за месяц
  • ./genstat.py –get time,events.src,events.src,events.dst,events.tags,“whois.data::jsonb->‘asn_description’” –filter-last 1M –whois –filter-x ‘events.src << whois.net’ - вывод asn_description

HTML отчеты

Для работы требует apt install pandas python3-matplotlib. Генератор статистики report.py создает сводку по ряду параметров за заданный период (по-умолчанию 24 часа). По-умолчанию все укладывается в /var/www/html/s2b/{PERIOD}.html. Для больше информации ./report.py --help.

Для простоты, можно создать сводный index.html включив несколько отчетных периодов в индекс, например при помощи jquery. В misc/html лежит пример индекса и css для получения страшненьких табличек. Подотчеты генерятся например так:

./report.py
./report.py --period 7d
./report.py --period 31d

Пример вывода можно увидеть в images/stat.png или на http://www.scan2ban.ru

Типы таблиц:

  • Текущие блокировки: раскладка по причинам блокирования адресов
  • Интенсивные источки: сортировка по максимальному числу событий за период
  • Опрос TCP/UDP: сортировка по числу событий связанных с портом
  • Запросы oneshot: число адресов которые опрашивали только конкретный порт
  • Адреса с широким охватом: сортировка по диапазону отсканированных портов (макс. очевидно 65535, т.е. опрошены все)
  • Адреса с макс. охватом агентов: адреса которые были зафиксированы на всех (или на максимальном числе агентов)
  • Сканирующие сети: слияние источников по сетям класса С с суммированием событий. В третьей колонке число адресов из сети которые были засвечены
  • WEB-запросы: сортировка по классам запросов к WEB (только если включен модуль webscan)

Графики:

  • Событий: число событий за период деленные на число агентов
  • Заблокировано источников: число блокировок сработавших за период (фактически число активных заблокированных адресов)
  • Зарегистрировано ноавых адресов: число адресов проявивших активность но отсутствовавшие в базе
  • Добавлено/удалено адресов: динамика добавления и удаления адресов из стоп-листа

Кроме этого можно передать сценарию ключ --details в этом случае кроме статистики будут выгружены данные по портам и адресам в csv на которые из таблиц появятся линки для отображения через chart.html/js (лежат в misc/html, можно скопировать вместе с индексом). Для выгрузки в каталоге куда всё сваливается нужно добавить каталог details/.

Использует plotly для отрисовки, сама библиотека подгружается с cdn.

На больших массивах выгрузка детализации может занимать время, нужно индексировать базу.

импорт стоп-листов

Для работы со списками заблокированных адресов есть функции export/import утилиты ctl.py. При выгрузке ./ctl.py --export будет выгружен актуальный список блокировок в формате csv/адрес,теги и сохранен в файл /tmp/scan2ban.ips.csv (задается опцией). Тот вариант который собирается автором в рамках проекта, доступен по адресу http://www.scan2ban.ru/scan2ban.ips.csv и обновляется раз в час.

Для импорта данных, необходимо выполнить ./ctl.py --import --dry-run, что приведет к выполнению ‘в холостую’ с выводом статистики по обновляемым или добавляемым вдресам. При импорте данные будут загружены в бд. Следует учитывать, что при импорте время блокировки задается статично как текущее + 25 часов (задается опцией, вариант по-умолчанию предполагает, что импорт идет раз в сутки). Если адреса нет в локальной бд - он будет добавлен. Если есть, то установлена блокировка и время истечения будет выставлено на заданный интервал если только в базе оно уже не большее.

При импорте есть возможность указать перечень тегов (–import-tags), адрес будет импортирован только при наличии у него соответствующей отметки.

ВНИМАНИЕ: первый импорт на ‘холодную базу’ приведет к загрузке больше десятка тысяч адресов. В избежании километровых уведомлений о постановке блокировки, первый импорт лучше делать не на ходу, а при остановленной службе.

отслеживание событий в реальном времени

Утилита ctl.py поддерживает параметры --mon [--stat-trigger X --dst x.x.x.x --src x.x.x.x --verbose] позволяющие показывать события в комплексе по мере их внесения в базу данных (лаг не более секунды). Параметры:

  • stat-period: задает периодичност вывода статистики. Если параметр задается как 1000e (оканчивается на e) то отсчет идет по числу событий. Все остальные варианты квалифицируются как временные (10M - десять минут)
  • dst: фильтрация по адресу агента
  • src: фильтрация по адресу сети (поддеживается в т.ч сеть в формате CIDR)
  • cls: список (через запятую) классов выводимых событий
  • verbose: вывод всех событий без обычной фильтрации (по-умолчанию выводятся только события связанные со сменой состояния адреса)

В режиме --verbose к строкам можно добавить произвольный ключ из whois:

./ctl.py --mon --add-whois asn_description --verbose
...
[2024/07/27 07:03:53] [xxxx]  218.23.188.117: a0:    23 a1: 'SYN' action:1 class:0  tag: 'telnet' whois:'CHINANET-BACKBONE No.31,Jin-rong Street, CN'

Практика применения

Самыйм легковесным вариантом является запуск всех агентов в режиме ведомого с периодической подгрузкой состояния блокировок с сайта. Однако как показывает практика, часть сканеров работает по узким сегментам и как следствие поймать их можно только анализируя трафик на конкретных адресах. Второй момент связан с тем, что стоп-лист проекта формируется на основании правил из поставки и не содержит специфики конкретной площадки, которая по перечню угроз может отличаться от того, что нафантизировал разработчик.

Оптимальным сценарием на настоящий момент видится создание как минимум одного выделенного агента с адресом не обслуживающие прочие задачи. Имеет смысл запустить типовые службы (как минимум ssh) на стандартных портах без прикрытия greyports и дополнить модуль generic запросами, идентифицирующими попытки несанкционированного доступа (например не удачные попытки авторизации). Такая конфигурация помимо сбора информации о сканерах портов, позволит замкнуть на него бот-сети занимающиеся перебором паролей, что позволит постепенно раскрывать участников сети, т.к. управляющая логика бот-сетей как правило заменяет заблокированных ботов на новые.

Прочие машины функционируют в ведомом режиме с включенным greyport, что обеспечивает отсечку one-shot сканеров и прикрытие от адресов засветившихся на мастер-агенте.

Общие замечания

Следут понимать, что разработанная архитектура не расчитана на противодействия целевым исследованиям. ‘Силовой’ прогон по некоторому набору портов будет зафиксирован но не блокирован полностью ввиду запаздывания отработки логики. Чем меньше набор сканируемых портов и короче время воздействия, тем выше вероятность у сканера проскочить до момента попадания в стоп-лист. Данную проблему решает модуль greyport, но его применение связано с возникновением задержек на запросы клиентов, что не всегда допустимо.

Для ориентировки: размер таблицы ips (адреса так или иначе засветившиеся в сканировании) за две недели составил 30000 адресов, таблицы events ~700000 событий для конфигурации с тремя разнесенными по разным сегментам агентами.

Для отстройки параметров greyports можно использовать выборку по интересующему порту с агрегацией по числу запросов (грубо, без учета разнесения запросов по времени итп), например: select '0.0.0.0'::inet + cast(src as bigint) as ip,count(*) as req,row_number() OVER (order by count(*)) as rnum from events where attr_0 = 22 group by ip order by req;. На пилотном макете, из 3000+ уникальных адресов, retry=1 накрывал 2200 из них, retry=2 2700, retry=3 2900. Таким образом retry=1/2 выглядит как наиболее адекватный параметр. retry > 3 следует применять с осторожностью, так как в среднем от ‘нормального’ клиента приходит не более 5 запросов. Следует учитывать, что распределенные бот-сети подбирающие пароли вносят определенную аномалию в статистику, т.к. после того как поверхностное сканирование выявило открытый порт, к процессу подключаются случайные участники сети которые производят подключения раз за разом пока не истекут глобальные таймеры на опрос (сутки и более). Отличить их от нормальных клиентов не возможно, а установка retry или mindelay по верхней планке приведет к отказу в обслуживании легитимных клиентов. Что-бы уменьшить угрозу со стороны нарушителей такого типа рекомендуется в паре с greyport включать модуль generic с блокировкой по числу сбойных авторизаций.

Вынос той-же ssh на нестандартный порт под grey делает шансы обнаружить его крайне небольшими, т.к. должно совпать несколько условий: сканер не должен влететь к блок к моменту когда он опросит порт, запрос должен прийти минимум три раза. Утверждать, что такое в принципе невозможно нельзя. Однако за время наблюдения таких удачливых персонажей зафиксировано не было.

Качество работы модуля lazyscan зависит от числа активных агентов. Сканирующие сети, например ShadowServer, создают исключительно низкий трафик с одного адреса, что не позволит его блокировать с настройками по-умолчанию, т.к. один адрес за неделю опрашивает не более трех портов на одном хосте при требуемых пяти. Таким образом, для его блокировки в рамках одного агента, потребуется или снижение порога срабатываний или увеличение учетного периода.

В случае, если детекторов много, рекомендуется отключать модули inactive и lazyscan на всех кроме выбранного. Встроенная логика в большинстве случае препятствует паралельной работе на нескольки агентах, но т.к. сделана она довольно тривиально, есть вероятность что достаточно тяжелый lazyscan начнет отрабатывать сразу с нескольких агентов и база может сильно поднапрячся.

Пороговые значения блокировки адреса по умолчанию выставлены по конфигурации разработчика (три детектора), что означает попадание в блок любого адреса, который засветился со сканированием хотя-бы одного порта на всех трех агентах. (1 очко за каждое сканирование, 3 попадания = 3 очка и бан).

Побочные эффекты

Применяемая техника выявления сканирования создает возможность блокировать на защищаемых машинах произвольные адреса посредством подмены атакующим ip адреса отправителя (blind ip spoofing). Однако подобная тактика не выглядит особо эффективной, так как во-первых легко решается постановкой ACCEPT на пропусканием трафика к штатным службам (до правила блокировки) либо включением greyport. А во-вторых, если атакующий действует целесообразно, то он не заинтересован в привлечении внимания, что при сколь нибудь широком перечне подложных адресов не возможно. После ‘холодного старта’, когда база наберет постоянный пул, суточный прирост удерживается в районе 1000 адресов и постепенно падает. Резкий прирост суточной дельты можно расценивать как чёткий сигнал о наличии целенаправленной активности, что противоречит интересам атакующей стороны. На случай ‘интернет эпидемии’, следует использовать два или более агента разнесенных по не связанным сетям, что позволит одзнозначно понять являеться-ли возросшая активность ‘природным’ феноменом.

Прочее

SMTP honeypot

В составе модуля generic поставляется правило postfix-spam.yml в качестве примера выявления и блокировки спам-эмиттеров. Для включения его в работу необходимо установить postfix в варианте приёма почты для любых получателей с доставкой локально, что для сканеров выглядит как открытый релей. Например, предварительно сконфигурировав postfix как local delivery only, добавить в `/etc/postfix/main.cf следующие строчки:

luser_relay = ispam
local_recipient_maps =
home_mailbox = Maildir/
inet_interfaces = all
mydestination = regexp:/etc/postfix/match_all_destination_re

создать файл /etc/postfix/match_all_destination_re следующего содержания:

// all

И пользователя, в почтовый ящик которого будут укладываться входящие сообщения: useradd -s /bin/false -m ispam. Через некоторое время после рестарта релей обнаружат, после чего начнут проливать рассылки, которые будут доставляться в maildir пользователя ispam. Каждая рассылка завершается появлением в журнале записи вида:

..... postfix/smtpd[1137656]: disconnect from unknown[163.123.142.126] ehlo=1 mail=1 rcpt=1 data=1 rset=1 quit=1 commands=6

где ключевое слово mail=XXX указывающее, что адрес залил указанное количество сообщений. По наличию этого тега будет прозводится блокировка адреса с тегом spam.

Есть подозрения, что поисковики релеев бывают в более хитром варианте, когда предварительная проверка предполагает тестовую рассылку на контрольный адрес. Есть над чем подумать.

Описание

защита от сканирования портов/portscan protection

Конвейеры
0 успешных
0 с ошибкой