В нашей компании имеется распределенная сеть продаж. Связь офиса с магазином обеспечивает маршрутизатор, на котором настроен VPN до центра. И вот, с определенного момента, эта связь стала крайне некачественной, из-за шквала пакетов по 53-му DNS порту. Связь хоть и улучшилась после введения блокирующих правил файрволла, но атаки не прекратились.
Я обратился к провайдеру, с просьбой решить проблему на его стороне. На что получил ответ вынесенный в заголовок: «Ну вы же понимаете, интернет — это грязное место». И тогда я решил бороться с этим явлением самостоятельно.
В результате была собрана система сбора и анализа несанкционированных сетевых пакетов.
А чтобы не утомлять читателя техническими подробностями, самое интересное я расскажу сразу, а тем кто пожелает ее повторить или улучшить рекомендую дочитать до конца.
В результате почти двух месяцев работы, в систему поступило почти 200 миллионов записей. Большая часть (98%) — это атаки типа DNS amplification, которые не раз обсуждались на хабре [1], [2]. Причем атаки начинаются не сразу, а по прошествии некоторого времени, достаточного для попадания нового публичного адреса в базу сканирующих интернет ботов. В оставшейся части событий выделяется большой сегмент атак на 23-й порт. Как я выяснил — это китайские DVR системы Hikvision, разбросанные по всему миру и сканирующих весь интернет на предмет telnet подключений. На трети из них, кстати, как раз заводские логин и пароль. А все остальное — это уже рукотворные переборы портов, попытки залогиниться, опросить по snmp и прочее.
Для начала надо было настроить на всех удаленных маршрутизаторах файрвол. Вывел “образцовый” набор правил, проверил и начал настраивать. На восьмом маршрутизаторе мне это наскучило и я заглянул в журнал устройства: “vk.com не может подключится к компьютеру менеджера”, “youtube.ru не соединяется с кассой” и прочее, прочее. Но были и интересные записи: “src=1.1.1.1:34567 dst=Zyxel:23 dropped [4 times]”, “src=2.2.2.2:45678 dst=Zyxel:80 dropped [12 times]”.
Я решил просканировать эти адреса Nmap-ом: открыты 80-й и 22-й порты. Браузер сообщил, что первый адрес – это веб-камера в переговорной какого-то украинского предприятия (судя по geoip и whois), а второй это некий индийский ubiquiti маршрутизатор. Заводской логин и пароль сработали только на веб-камере, а вот с маршрутизатором заводские настройки оставили “всего лишь” на ssh!
Это ж какой «блондинкой» надо быть, чтобы выставить напоказ всему интернету устройство с заводским логином и паролем? И решил я таких «блондинок» собирать в одном месте и изучать. А в перспективе реализовать следующий алгоритм:
cбор атакующих — пассивный анализ — активный анализ — ответное действие
В маршрутизаторах Zyxel есть возможность отправлять журнал на центральный сервер syslog. Для этого я установил Linux Debian, а на него rsyslog:
Однако, простой установки, мало. Необходимо разрешить syslog-у принимать сообщения извне:
Ну а чтобы сей порт ловил данные из интернета, нужна еще трансляция на пограничном маршрутизаторе. В моем случае на Cisco я сделал так:
Теперь надо настроить удаленный маршрутизатор на отправку сообщений. В моем случае это Zyxel zywall 2Plus или USG20.
Модели этих маршрутизаторов отличаются достаточно сильно, как снаружи, так и изнутри. Однако весь смысл настройки сводится к отключению всех журналов, кроме файрволла, и отправке событий на syslog сервер. Вот как это выглядит на Zyxel USG 20:
настройка firewall:

настройка событий журнала syslog:

настройка адреса и типа сервера syslog:

Еще в настройках устройства обязательно выставить значимое имя хоста — для удобства сортировки.
Если все оставить так, то на syslog сервере будет создан текстовый файл local5 со всеми событиями соответствующими последнему правилу файрволла — то есть атаки на этот маршрутизатор:
Файл будет расти неограниченно, пока не съест весь логический раздел, поэтому надо настраивать ротацию журнала. Это можно сделать при помощи скриптов logrotate — разбивать журнал на дни, ежедневно архивировать и удалять старые архивы. Однако мне пришла идея получше — брать из записи только нужное, разбивать на поля и переносить их в базу данных.
В качестве СУБД я установил postgresql — потому что о работе с ней я ничего на тот момент не знал, а познакомиться очень хотелось.
После установки, подключился к postgres:
Cоздал нового пользователя:
И базу:
Первая таблица “wedro” должна содержать время события, имя маршрутизатора, адрес атакующего и атакованный порт. И все бы хорошо, но адрес атаковавшего хранить в текстовом виде мне показалось неправильным. В итоге я установил расширение IP4R — получилось не с первой попытки, но если будет интересно, расскажу. В результате таблицу создал так:
Теперь настало время создать обработчик журнала local5, который мою базу будет наполнять. Написан он был не за один день и даже претерпел пять редакций, так что приведу последнюю рабочую версию:
Суть данного “скрипта” такова:
Восклицательный знак я поставил, для отдельного комментария. Дело в том, что фо��мат журналов Zywall2Plus и USG20 отличаются тем, что один пишет год, а другой почему-то этого не делает. Чтобы привести эти записи к общему виду, я год удаляю из всех записей, а потом добавляю значение года из системы. Это вообще-то не очень правильно, и спустя некоторое время я понял, что сделать надо было по-другому. На каждый формат журнала надо было бы сделать некий шаблон обработки в виде модуля. Однако времени не было и писал как получится.
Чтобы база наполнялась автоматически, в настройке ротации журналов /etc/logrotate.d/ надо создать правило для local5, задающее ротацию по объему файла:
Однако ротация почему-то срабатывала не всегда, и я сделал в планировщике cron принудительную ротацию каждые 5 минут:
В итоге база действительно наполняется автоматом — за два дня порядка четырех миллионов записей с нескольких маршрутизаторов.
С самого начала стало ясно, что записей с 53-м портом слишком много, чтобы разглядеть что-то еще. В результате, ничего лучше, чем их удалять, я не пока придумал. На таблицу wedro был повешен триггер на AFTER INSERT:
При такой регулярной чистке база перестала расти угрожающими темпами и появилась возможность в ней что-то найти. Правда также появилась необходимость в AUTOVACUUM-е для освобождения когда-то занятых ресурсов.
Атаки с одних и тех же адресов достаточно часто повторяются. Поэтому я решил, что их надо собирать в отдельную таблицу. Так как данные обновляются каждые пять минут, смысла делать это в виде триггера нет — в результате пришлось освоить установку и работу планировщика pgagent.
Специально для этого были созданы еще две таблицы ipid и wedro_old:
А в pgagent-е было заведено ежечасное задание по наполнению этих таблиц:
В оставшихся записях оказалось довольно много атак на 23-й порт telnet. Причем ряд адресов делал это многократно и никаких других портов не сканировал. Если набрать такой адрес в браузере, откроется форма ввода учетных данных:

в некоторых случаях заводские admin/12345 пускают в интерфейс цифрового магнитофона Hikvision. Интересно также, что на ряде устройств имеется не задокументированный открытый порт 9527. Вот и пример для фазы активного анализа:
Ищем адреса атаковавших только 23-й порт:
Используем полученный список в Nmap-е для поиска уязвимых DVR-ов:
Выражения limit и offset использованы здесь из-за ограничений сканера на количество целей. На неограниченном списке он дает сбой.
Для найденных на предыдущем шаге узлов я также написал скрипт на expect для проверки заводских учетных данных и отключения сканера 23-го порта. Однако я достаточно далеко отошел от своей первоначальной цели — активного предотвращения несанкционированного доступа к моим маршрутизаторам. И думаю настало время подвести общий итог.
Интернет это действительно «грязное» место и моя система позволяет это оценить количественно. Думаю, из того что я здесь привел, понятно что система далека от совершенства и нуждается в доработках: нужен интерфейс, шаблоны устройств, объединение всех модулей в единый установочный пакет и еще много чего. Но система уже работает, собрана из доступных и бесплатных компонентов. Я не исключаю, что системы подобные моей, существуют и в коммерческом исполнении. Однако я сомневаюсь, что все модули таких систем будут доступны для настройки и изменения.
Я обратился к провайдеру, с просьбой решить проблему на его стороне. На что получил ответ вынесенный в заголовок: «Ну вы же понимаете, интернет — это грязное место». И тогда я решил бороться с этим явлением самостоятельно.
В результате была собрана система сбора и анализа несанкционированных сетевых пакетов.
А чтобы не утомлять читателя техническими подробностями, самое интересное я расскажу сразу, а тем кто пожелает ее повторить или улучшить рекомендую дочитать до конца.
В результате почти двух месяцев работы, в систему поступило почти 200 миллионов записей. Большая часть (98%) — это атаки типа DNS amplification, которые не раз обсуждались на хабре [1], [2]. Причем атаки начинаются не сразу, а по прошествии некоторого времени, достаточного для попадания нового публичного адреса в базу сканирующих интернет ботов. В оставшейся части событий выделяется большой сегмент атак на 23-й порт. Как я выяснил — это китайские DVR системы Hikvision, разбросанные по всему миру и сканирующих весь интернет на предмет telnet подключений. На трети из них, кстати, как раз заводские логин и пароль. А все остальное — это уже рукотворные переборы портов, попытки залогиниться, опросить по snmp и прочее.
Как я до этого дошел
Для начала надо было настроить на всех удаленных маршрутизаторах файрвол. Вывел “образцовый” набор правил, проверил и начал настраивать. На восьмом маршрутизаторе мне это наскучило и я заглянул в журнал устройства: “vk.com не может подключится к компьютеру менеджера”, “youtube.ru не соединяется с кассой” и прочее, прочее. Но были и интересные записи: “src=1.1.1.1:34567 dst=Zyxel:23 dropped [4 times]”, “src=2.2.2.2:45678 dst=Zyxel:80 dropped [12 times]”.
Я решил просканировать эти адреса Nmap-ом: открыты 80-й и 22-й порты. Браузер сообщил, что первый адрес – это веб-камера в переговорной какого-то украинского предприятия (судя по geoip и whois), а второй это некий индийский ubiquiti маршрутизатор. Заводской логин и пароль сработали только на веб-камере, а вот с маршрутизатором заводские настройки оставили “всего лишь” на ssh!
Это ж какой «блондинкой» надо быть, чтобы выставить напоказ всему интернету устройство с заводским логином и паролем? И решил я таких «блондинок» собирать в одном месте и изучать. А в перспективе реализовать следующий алгоритм:
cбор атакующих — пассивный анализ — активный анализ — ответное действие
Шаг первый: сбор атакующих
Центральный журнал
В маршрутизаторах Zyxel есть возможность отправлять журнал на центральный сервер syslog. Для этого я установил Linux Debian, а на него rsyslog:
sudo apt-get update sudo apt-get install rsyslog
Однако, простой установки, мало. Необходимо разрешить syslog-у принимать сообщения извне:
/etc/rsyslog.conf
... # provides UDP syslog reception $ModLoad imudp $UDPServerRun 514 ... # MAXA's rules - the local facilities local0.* /var/log/local0 local1.* /var/log/local1 local2.* /var/log/local2 local3.* /var/log/local3 local4.* /var/log/local4 local5.* /var/log/local5 local6.* /var/log/local6 local7.* /var/log/local7 # By the way - the above files should also be rotated by logrotate # And, of course, to work properly - there should be a static port translation on the border router ...
Ну а чтобы сей порт ловил данные из интернета, нужна еще трансляция на пограничном маршрутизаторе. В моем случае на Cisco я сделал так:
ip nat inside source static udp {syslog_IP} 514 {public_IP} 514 extendable
Теперь надо настроить удаленный маршрутизатор на отправку сообщений. В моем случае это Zyxel zywall 2Plus или USG20.
Модели этих маршрутизаторов отличаются достаточно сильно, как снаружи, так и изнутри. Однако весь смысл настройки сводится к отключению всех журналов, кроме файрволла, и отправке событий на syslog сервер. Вот как это выглядит на Zyxel USG 20:
настройка firewall:

настройка событий журнала syslog:

настройка адреса и типа сервера syslog:

Еще в настройках устройства обязательно выставить значимое имя хоста — для удобства сортировки.
Первые результаты
Если все оставить так, то на syslog сервере будет создан текстовый файл local5 со всеми событиями соответствующими последнему правилу файрволла — то есть атаки на этот маршрутизатор:
Sep 27 17:34:54 2017 GW18PUB src="5.188.203.30:54193" dst="X.X.X.X:8173" msg="invalid state detected, DROP" note="ACCESS BLOCK" user="unknown" devID="b0b2dcc63eec" cat="Firewall" class="Access Control" ob="0" ob_mac="000000000000" dir="ANY:ANY" protoID=6 proto="others" Sep 27 17:35:02 2017 GW36PUB src="212.83.176.116:51855" dst="Y.Y.Y.Y:4287" msg="invalid state detected, DROP" note="ACCESS BLOCK" user="unknown" devID="b0b2dcc63eed" cat="Firewall" class="Access Control" ob="0" ob_mac="000000000000" dir="ANY:ANY" protoID=6 proto="others" Sep 27 17:35:17 2017 GW18PUB src="212.83.176.116:51855" dst="X.X.X.X:3875" msg="invalid state detected, DROP" note="ACCESS BLOCK" user="unknown" devID="b0b2dcc63eec" cat="Firewall" class="Access Control" ob="0" ob_mac="000000000000" dir="ANY:ANY" protoID=6 proto="others"
Файл будет расти неограниченно, пока не съест весь логический раздел, поэтому надо настраивать ротацию журнала. Это можно сделать при помощи скриптов logrotate — разбивать журнал на дни, ежедневно архивировать и удалять старые архивы. Однако мне пришла идея получше — брать из записи только нужное, разбивать на поля и переносить их в базу данных.
База данных и обработчик журнала
В качестве СУБД я установил postgresql — потому что о работе с ней я ничего на тот момент не знал, а познакомиться очень хотелось.
sudo apt-get update sudo apt-get install postgresql-9.5
После установки, подключился к postgres:
sudo -u postgres psql
Cоздал нового пользователя:
CREATE USER pgmaxa WITH password ‘strongpass’;
И базу:
CREATE DATABASE blondemine; GRANT ALL ON DATABASE blondemine TO pgmaxa;
Первая таблица “wedro” должна содержать время события, имя маршрутизатора, адрес атакующего и атакованный порт. И все бы хорошо, но адрес атаковавшего хранить в текстовом виде мне показалось неправильным. В итоге я установил расширение IP4R — получилось не с первой попытки, но если будет интересно, расскажу. В результате таблицу создал так:
CREATE TABLE wedro ( rt timestamp without time zone, gw character varying(9), ad ip4, pt integer, dtst time without time zone NOT NULL DEFAULT now() )
Обработчик журнала
Теперь настало время создать обработчик журнала local5, который мою базу будет наполнять. Написан он был не за один день и даже претерпел пять редакций, так что приведу последнюю рабочую версию:
/usr/sbin/handmade/logtopostgres.sh
#!/bin/bash # FILES SECTION # parsed file log=/var/log/local5 # prepared query file tmp=/var/log/local5.ptmp # final query file sql=/var/log/local5.psql # debug file err=/var/log/badform5.log # CONSTANTS yr=`date +%Y` # DATABASE SECTION # syntax: {var name}={corresponding field in DB} # local (gateway) time of atack lt=rt # hostname of atacked gateway gw=gw # ip address of blocked atacker ad=ad # blocked port number pt=pt echo "INSERT INTO wedro ($lt,$gw,$ad,$pt) VALUES " > $tmp cat $log | grep "Access Control" | grep default | \ sed 's/ [0-9]\{4\} / /g;s/\([0-9]\): /\1:/' | \ awk '{ \ gsub(/"/,"",$5) \ sub(/src=/, "", $5 ) \ split($5,HA,":"); \ gsub(/"/,"",$6) \ sub(/dst=/, "", $6 ) \ split($6,OP,":"); \ if (length($4) > 9) \ print \ "GWNAME too long: \x27" $4 "\x27 | whole line: \x27" $0 "\x27 |" \ >> "'$err'"; \ else \ if (OP[2]=="") \ print \ "PORT is empty: \x27 \x27 | whole line: \x27" $0 "\x27 |" \ >> "'$err'"; \ else \ print \ "(to_timestamp( \x27"'$yr'$2$1$3"\x27,\x27YYYYDDMonHH24:MI:SS\x27)," \ "\x27"$4"\x27," \ "\x27" HA[1] "\x27," \ "\x27" OP[2] "\x27)," >> "'$tmp'" \ }' truncate --size=-2 $tmp echo ";" >> $tmp mv $tmp $sql export PGPASSWORD=strongpass && psql -h localhost -U pgmaxa -d blondemine -f $sql rm $sql # # awk afterprint section for debug local output # #" Day: " $2 \ #" Month: " $1 \ #" Time: " $3 \ #" Host: " $4 \ #" AtackerIP: " HA[1] \ #" MyPort: " OP[2] \ #
Суть данного “скрипта” такова:
- журнал обрабатываем построчно
- берем только строки с фразами «Access Control» и «default»
- (!) удаляем четырехзначный год и пробелы во времени события
- из пятого поля удаляем “src=”, а адрес пишем в переменную HA (HackerAddress)
- из шестого поля удаляем “dst=”, а порт пишем в переменную OP (OurPort)
- проверяем длину имени маршрутизатора и номер порта на корректность
- собираем полученые данные в строку для запроса на вставку
- подрезаем полученый файл с запросом на два символа и завершаем “;”
- переименовываем и вставляем в базу
Восклицательный знак я поставил, для отдельного комментария. Дело в том, что фо��мат журналов Zywall2Plus и USG20 отличаются тем, что один пишет год, а другой почему-то этого не делает. Чтобы привести эти записи к общему виду, я год удаляю из всех записей, а потом добавляю значение года из системы. Это вообще-то не очень правильно, и спустя некоторое время я понял, что сделать надо было по-другому. На каждый формат журнала надо было бы сделать некий шаблон обработки в виде модуля. Однако времени не было и писал как получится.
Чтобы база наполнялась автоматически, в настройке ротации журналов /etc/logrotate.d/ надо создать правило для local5, задающее ротацию по объему файла:
/var/log/local5 { rotate 1 prerotate /usr/sbin/handmade/logtopostgres.sh endscript postrotate invoke-rc.d rsyslog rotate > /dev/null endscript size 2190000 missingok notifempty compress }
Однако ротация почему-то срабатывала не всегда, и я сделал в планировщике cron принудительную ротацию каждые 5 минут:
*/5 * * * * root /usr/sbin/logrotate /etc/logrotate.d/loc5
Результат
В итоге база действительно наполняется автоматом — за два дня порядка четырех миллионов записей с нескольких маршрутизаторов.
Шаг второй: пассивный анализ
С самого начала стало ясно, что записей с 53-м портом слишком много, чтобы разглядеть что-то еще. В результате, ничего лучше, чем их удалять, я не пока придумал. На таблицу wedro был повешен триггер на AFTER INSERT:
CREATE OR REPLACE FUNCTION public.deldns() RETURNS trigger AS $BODY$ BEGIN DELETE FROM wedro WHERE pt=53 OR ad::text like '192.168%'; RETURN NEW; END; $BODY$ CREATE TRIGGER sweep AFTER INSERT ON public.wedro FOR EACH STATEMENT EXECUTE PROCEDURE public.deldns();
Результат
При такой регулярной чистке база перестала расти угрожающими темпами и появилась возможность в ней что-то найти. Правда также появилась необходимость в AUTOVACUUM-е для освобождения когда-то занятых ресурсов.
Уникальные адреса
Атаки с одних и тех же адресов достаточно часто повторяются. Поэтому я решил, что их надо собирать в отдельную таблицу. Так как данные обновляются каждые пять минут, смысла делать это в виде триггера нет — в результате пришлось освоить установку и работу планировщика pgagent.
Специально для этого были созданы еще две таблицы ipid и wedro_old:
CREATE TABLE ipid ( id bigint NOT NULL DEFAULT nextval('ipid_id_seq'::regclass), ad ip4 NOT NULL, dtst timestamp without time zone NOT NULL DEFAULT now() ); CREATE TABLE wedro_old ( rt timestamp without time zone, gw character varying(9), ad ip4, pt integer, dtst time without time zone NOT NULL DEFAULT now() );
А в pgagent-е было заведено ежечасное задание по наполнению этих таблиц:
INSERT INTO ipid (ad) SELECT DISTINCT ad FROM wedro WHERE dtst > (select max(dtst) from ipid)::time AND ad NOT IN (select ad from ipid); INSERT INTO wedro_old SELECT * FROM wedro; TRUNCATE table wedro;
Шаг третий: активный анализ
Камеры и DVR-ы Hikvision
В оставшихся записях оказалось довольно много атак на 23-й порт telnet. Причем ряд адресов делал это многократно и никаких других портов не сканировал. Если набрать такой адрес в браузере, откроется форма ввода учетных данных:

в некоторых случаях заводские admin/12345 пускают в интерфейс цифрового магнитофона Hikvision. Интересно также, что на ряде устройств имеется не задокументированный открытый порт 9527. Вот и пример для фазы активного анализа:
Ищем адреса атаковавших только 23-й порт:
SELECT ad INTO telnetz FROM wedro_old WHERE ad IN ( SELECT a.ad FROM (SELECT ad,pt from wedro_old group by ad,pt) AS a GROUP BY a.ad HAVING count(*)=1 ) AND pt=23 GROUP BY ad,pt;
Используем полученный список в Nmap-е для поиска уязвимых DVR-ов:
#!/bin/bash nmap \ -n -Pn -p9527 --open `\ echo "select * from telnetz limit $1 offset $2;" | \ psql -U pgmaxa -d blondemine -t -h syslog_IP` | \ grep for | sed 's/^.*for //g'
Выражения limit и offset использованы здесь из-за ограничений сканера на количество целей. На неограниченном списке он дает сбой.
Шаг четвертый: действие
Для найденных на предыдущем шаге узлов я также написал скрипт на expect для проверки заводских учетных данных и отключения сканера 23-го порта. Однако я достаточно далеко отошел от своей первоначальной цели — активного предотвращения несанкционированного доступа к моим маршрутизаторам. И думаю настало время подвести общий итог.
Заключение
Интернет это действительно «грязное» место и моя система позволяет это оценить количественно. Думаю, из того что я здесь привел, понятно что система далека от совершенства и нуждается в доработках: нужен интерфейс, шаблоны устройств, объединение всех модулей в единый установочный пакет и еще много чего. Но система уже работает, собрана из доступных и бесплатных компонентов. Я не исключаю, что системы подобные моей, существуют и в коммерческом исполнении. Однако я сомневаюсь, что все модули таких систем будут доступны для настройки и изменения.