В нашей компании имеется распределенная сеть продаж. Связь офиса с магазином обеспечивает маршрутизатор, на котором настроен VPN до центра. И вот, с определенного момента, эта связь стала крайне некачественной, из-за шквала пакетов по 53-му DNS порту. Связь хоть и улучшилась после введения блокирующих правил файрволла, но атаки не прекратились.
Я обратился к провайдеру, с просьбой решить проблему на его стороне. На что получил ответ вынесенный в заголовок: «Ну вы же понимаете, интернет — это грязное место». И тогда я решил бороться с этим явлением самостоятельно.
В результате была собрана система сбора и анализа несанкционированных сетевых пакетов.
А чтобы не утомлять читателя техническими подробностями, самое интересное я расскажу сразу, а тем кто пожелает ее повторить или улучшить рекомендую дочитать до конца.
В результате почти двух месяцев работы, в систему поступило почти 200 миллионов записей. Большая часть (98%) — это атаки типа DNS amplification, которые не раз обсуждались на хабре [1], [2], [3]. Причем атаки начинаются не сразу, а по прошествии некоторого времени, достаточного для попадания нового публичного адреса в базу сканирующих интернет ботов. В оставшейся части событий выделяется большой сегмент атак на 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-у принимать сообщения извне:
...
# 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, который мою базу будет наполнять. Написан он был не за один день и даже претерпел пять редакций, так что приведу последнюю рабочую версию:
#!/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:SSx27),"
"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-го порта. Однако я достаточно далеко отошел от своей первоначальной цели — активного предотвращения несанкционированного доступа к моим маршрутизаторам. И думаю настало время подвести общий итог.
Заключение
Интернет это действительно «грязное» место и моя система позволяет это оценить количественно. Думаю, из того что я здесь привел, понятно что система далека от совершенства и нуждается в доработках: нужен интерфейс, шаблоны устройств, объединение всех модулей в единый установочный пакет и еще много чего. Но система уже работает, собрана из доступных и бесплатных компонентов. Я не исключаю, что системы подобные моей, существуют и в коммерческом исполнении. Однако я сомневаюсь, что все модули таких систем будут доступны для настройки и изменения.
Ссылки
[1] habrahabr.ru/post/51574
[2] habrahabr.ru/post/51574
[3] habrahabr.ru/post/83202
Автор: max-himik