[Из песочницы] Грязное место провайдера: проект blondemine

В нашей компании имеется распределенная сеть продаж. Связь офиса с магазином обеспечивает маршрутизатор, на котором настроен 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-у принимать сообщения извне:

/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:

image

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

image

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

image

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

Первые результаты


Если все оставить так, то на 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. Причем ряд адресов делал это многократно и никаких других портов не сканировал. Если набрать такой адрес в браузере, откроется форма ввода учетных данных:

59ccaf68a7733495074464.png

в некоторых случаях заводские 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

© Habrahabr.ru