Здравствуйте коллеги! Сегодня, когда накал страстей вокруг «удалёнки» немного спал, большинство админов победило задачу удаленного доступа сотрудников к корпоративной сети, пришло время поделиться моей давней наработкой по повышению безопасности VPN. В этой статье не будет модных ныне IPSec IKEv2 и xAuth. Речь пойдет о построении системы двухфакторной аутентификации (2FA) пользователей VPN, когда MikroTik выступает в качестве VPN-сервера. А именно, когда используются «классические» протоколы типа PPP.



Сегодня я расскажу как защитить MikroTik PPP-VPN даже в случае «угона» пользовательской учётки. Когда эта схема была внедрена одному из моих заказчиков, он охарактеризовал его коротко «ну, теперь прямо как в банке!».

Метод не использует внешних сервисов-аутентификаторов. Задачи выполняются внутренними средствами самого маршрутизатора. Без затрат для подключаемого клиента. Способ работает как для клиентов PC, так и для мобильных устройств.

Общая схема защиты выглядит следующим образом:

  1. Внутренний IP-адрес пользователя успешно подключившегося к в VPN-серверу, автоматически попадает в «серый» список.
  2. Событие подключения автоматически генерирует одноразовый код, который пересылается пользователю одним из доступных способов.
  3. Адресам, находящимся в этом списке ограничен доступ к ресурсам локальной сети, за исключением сервиса «аутентификатора», ожидающего получение одноразового кода-пароля.
  4. После предъявления кода, пользователю открывается доступ к внутренним ресурсам сети.

Первая самая маленькая проблема с которой пришлось столкнуться, это хранение контактной информации о пользователе для пересылки ему кода 2FA. Поскольку, произвольных полей данных, соответствующих пользователям в микротике создать нельзя, было использовано существующее поле «comment»:

/ppp secrets add name=Petrov password=4M@ngr! comment=«89876543210»

Вторая проблема оказалась посерьёзнее — выбор пути и способа доставки кода. В текущий момент реализованы три схемы: а) SMS через USB-модем б) e-mail в) SMS via e-mail доступный для корпоративных клиентов красного сотового оператора.

Да, схемы с SMS приносят затраты. Но если разобраться, «безопасность это всегда про деньги» (с).
Схема с e-mail лично мне не нравится. Не потому, что требует доступности почтового сервера для аутентифицируемого клиента — это не проблема разделить трафик. Однако, если клиент беспечно сохранял пароли на и на vpn и на почту в браузере, а затем потерял свой ноутбук, злоумышленник получит с него полный доступ к корпоративной сети.

Итак, решено — доставляем одноразовый код при помощи SMS-сообщений.

Третья проблема была в том, где и как в MikroTik сгенерировать псевдослучайный код для 2FA. В скриптовом языке RouterOS нет аналога функции random() и прежде я видел несколько костыльных скриптовых генераторов псевдослучайных чисел. Ни один из них мне не понравился по разным причинам.

На самом деле, генератор псевдослучайных последовательностей в MikroTik ЕСТЬ! Припрятан он от поверхностного взгляда в контексте /certificates scep-server. Первый способ получить одноразовый пароль легок и прост — командой /certificates scep-server otp generate. Если выполнить простую операцию присвоения переменной, то мы получим значение типа array, которое можно использовать в дальнейшем в скриптах.

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

Код
:global rnd1 [:pick ([/tool fetch url="https://www.random.org/strings/?num=1&len=7&digits=on&unique=on&format=plain&rnd=new" as-value output=user ]->"da
ta") 1 6]
:put $rnd1


Запрос сформатированный для консоли (в теле скрипта потребуется экранирование спецсимволов) получает строку из шести символов-цифр в переменную $rnd1. Следующая команда «put» просто отображает переменную в консоли MikroTik.

Четвертая проблема, которую пришлось оперативно решать — это каким образом и куда подключенный клиент будет передавать свой одноразовый код на второй стадии аутентификации.



На роутере MikroTik должна существовать служба способная принять код и сопоставить его с конкретным клиентом. При совпадении предоставленного кода с ожидаемым адрес клиента должен попасть в некий «белый» список, адресам из которого разрешен доступ к внутренней сети компании.

Ввиду небогатого выбора служб, было принято решение принимать коды по http при помощи встроенного в микротик webproxy. А поскольку фаервол умеет работать с динамическими списками IP-адресов, то поиск кода, сопоставление его с клиентским IP и внесение в «белый» список выполняет именно фаервол посредством Layer7 regexp. Самому маршрутизатору присвоено условное DNS-имя «gw.local», на нем создана статическая А-запись для выдачи PPP-клиентам:

DNS
/ip dns static add name=gw.local address=172.31.1.1

Захват на прокси трафика непроверенных клиентов:
/ip firewall nat add chain=dstnat dst-port=80,443 in-interface=2fa protocol=tcp !src-address-list=2fa_approved action=redirect to-ports=3128


В данном случае у прокси две функции.

1. Открывать tcp-соединения с клиентами;

2. В случае успешной авторизации переадресовать клиентский браузер на страничку или картинку извещающую об успешном прохождении аутентификации:

Proxy config
/ip proxy
set enabled=yes port=3128
/ip proxy access
add action=deny disabled=no redirect-to=gw.local./mikrotik_logo.png src-address=0.0.0.0/0


Перечислю важные элементы конфигурации:

  1. interface-list «2fa» — динамический список клиентских интерфейсов, трафик с которых требует обработки в рамках 2FA;
  2. address-list «2fa_jailed» — «серый» список туннельных IP-адресов клиентов VPN;
  3. address_list «2fa_approved» — «белый» список туннельных IP-адресов клиентов VPN, успешно прошедших двухфакторную аутентификацию.
  4. цепочка фаервола «input_2fa» — в ней происходит проверка tcp-пакетов на наличие кода авторизации и совпадение IP-адреса отправителя кода с требуемым. Правила в цепочку добавляются и удаляются динамически.

Упрощенная блок-схема обработки пакета выглядит так:



Для попадания в проверку по Layer7 трафика от клиентов из «серого» списка еще не прошедших второй этап аутентификации, в стандартной цепочке «input» создано правило:

Код
/ip firewall filter add chain=input !src-address-list=2fa_approved action=jump jump-target=input_2fa

Теперь начнем прикручивать всё это богатство к сервису PPP. MikroTik позволяет использовать скрипты в профилях (ppp-profile) и назначать их на события установки и разрыва ppp-соединения. Настройки ppp-profile могут применяться как для PPP-сервера в целом, так и для отдельных пользователей. При этом назначенный пользователю profile имеет приоритет перекрывая своими заданными параметрами параметры profile выбранного для сервера в целом.

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

Во вновь созданном специальном профиле используем динамическое добавление адреса и интерфейса подключившегося пользователя в «серые» списки адресов и интерфейсов:

winbox


Код
/ppp profile add address-list=2fa_jailed change-tcp-mss=no local-address=192.0.2.254 name=2FA interface-list=2fa only-one=yes remote-address=dhcp_pool1 use-compression=no use-encryption= required use-mpls=no use-upnp=no dns-server=172.31.1.1

Использовать совместно списки «address-list» и «interface-list» необходимо, чтобы определять и захватывать трафик от не прошедших вторичную авторизацию VPN-клиентов в цепочке dstnat (prerouting).

Когда подготовка закончена, дополнительные цепочки фаервола и профиль созданы, напишем скрипт отвечающий за автогенерацию кода 2FA и отдельных правил фаервола.

Документация wiki.mikrotik.com на PPP-Profile обогащает нас информацией о переменных, связанных с событиями подключения-отключения PPP-клиента «Execute script on user login-event. These are available variables that are accessible for the event script: user, local-address, remote-address, caller-id, called-id, interface». Некоторые из них нам очень пригодятся.

Код, используемый в профиле для события подключения PPP on-up
#Логируем для отладки полученные переменные 
:log info ($"local-address")
:log info ($"remote-address")
:log info ($"caller-id")
:log info ($"called-id")
:log info ([/int pptp-server get ($"interface") name])
#Объявляем свои локальные переменные
:local listname "2fa_jailed"
:local viamodem false
:local modemport "usb2"
#ищем автоматически созданную запись в адрес-листе "2fa_jailed"
:local recnum1 [/ip fi address-list find address=($"remote-address") list=$listname]

#получаем псевдослучайный код через random.org 
#:local rnd1 [:pick ([/tool fetch url="https://www.random.org/strings/?num=1&len=7&digits=on&unique=on&format=plain&rnd=new" as-value output=user]->"data") 0 4]
#либо получаем псевдослучайный код через локальный генератор
#:local rnd1 [pick ([/cert scep-server otp generate as-value minutes-valid=1]->"password") 0 4 ]

#Ищем и обновляем коммент к записи в адрес-листе. Вносим искомый код для отладки  
/ip fir address-list set $recnum1 comment=$rnd1
#получаем номер телефона куда слать SMS
:local vphone [/ppp secret get [find name=$user] comment]

#Готовим тело сообщения. Если клиент подключается к VPN прямо с телефона ему достаточно
#будет перейти прямо по ссылке из полученного сообщения
:local msgboby ("Your code: ".$comm1."\n Or open link http://gw.local/otp/".$comm1."/")

# Отправляем SMS по выбранному каналу - USB-модем или email-to-sms
if $viamodem do={ /tool sms send phone-number=$vphone message=$msgboby port=$modemport } else={ /tool e-mail send server=a.b.c.d from=admin@mydomain.example to=mail2sms@mcommunicator.ru subject="@".$vphone body=$msgboby }

#Генерируем Layer7 regexp
local vregexp ("otp\\/".$comm1)
:local vcomment ("2fa_".($"remote-address"))
/ip firewall layer7-protocol add name=($"vcomment") comment=($"remote-address") regexp=($"vregexp")

#Генерируем правило проверяющее по Layer7 трафик клиента в поисках нужного кода
#и небольшой защитой от брутфорса кодов с помощью dst-limit
/ip firewall filter add action=add-src-to-address-list address-list=2fa_approved address-list-timeout=none-dynamic chain=input_2fa dst-port=80,443,3128 layer7-protocol=($"vcomment") protocol=tcp src-address=($"remote-address") dst-limit=1,1,src-address/1m40s


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

При отключении пользователя генерируется событие «On-Down» и вызывается соответствующий скрипт с параметрами. Задача этого скрипта — почистить за собой правила фаервола, созданные для отключившегося пользователя.

Код, используемый в профиле для события подключения PPP on-down
:local vcomment ("2fa_".($"remote-address"))
/ip firewall address-list remove [find address=($"remote-address") list=2fa_approved]
/ip firewall filter remove [find chain="input_2fa" src-address=($"remote-address") ]
/ip firewall layer7-protocol remove [find name=$vcomment]


После этого можно создавать пользователей и назначать всем или некоторым из них профиль с двухфакторной аутентификацией.

winbox


Код
/ppp secrets set [find name=Petrov] profile=2FA

Как это выглядит на стороне клиента.

При установке VPN-соединения, на телефон/планшет на андроид/iOS с сим-картой приходит SMS примерно такого вида:

SMSка


Если соединение установлено непосредственно с телефона/планшета, то можно пройти 2FA просто кликнув по ссылке из сообщения. Это удобно.

Если VPN-соединение устанавливается с PC, то для пользователя потребуется минимальная форма ввода пароля. Небольшая форма в виде файла HTML передается пользователю при настройке VPN. Файлик можно даже по почте переслать, чтобы пользователь сохранил его у себя и создал ярлык в удобном месте. Примерно так это выглядит:

Ярлык на столе



Пользователь щёлкает мышкой по ярлыку, открывается простая форма ввода кода, которая вставит код в открываемый URL:
Скрин формы


Форма самая примитивная, дана для примера. Желающие могут доработать под себя.

2fa_login_mini.html
<html>
<head> <title>SMS OTP login</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head>
<body>
<form name="login" action="location.href='http://gw.local/otp/'+document.getElementById(‘text').value"  method="post"
 <input id="text" type="text"/> 
<input type="button" value="Login" onclick="location.href='http://gw.local/otp/'+document.getElementById('text').value"/> 
</form>
</body>
</html>

Если авторизация прошла успешно, пользователь в браузере увидит лого MikroTik, что должно послужить сигналом об успешной аутентификации:



Замечу, что картинка возвращается из встроенного web-сервера MikroTik с помощью WebProxy Deny Redirect.

Полагаю, картинку можно кастомизировать, используя инструмент «hotspot», загрузив туда свой вариант и задав на него Deny Redirect URL с WebProxy.

Большая просьба к тем, кто пытается купив самый дешевый «игрушечный» микротик за $20, заменить им маршрутизатор за $500 — не надо так поступать. Устройства типа «hAP Lite»/«hAP mini» (home access point) имеют очень слабый CPU(smips), и вполне вероятно не справятся с нагрузкой в бизнес-сегменте.

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

P.S.: методы доставки кода клиенту могут быть расширены и дополнены насколько хватит ваших возможностей в программировании. Например, можно отправлять сообщения в telegram или… предлагайте варианты!

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