Двухфакторная аутентификация пользователей VPN посредством MikroTik и SMS

    Здравствуйте коллеги! Сегодня, когда накал страстей вокруг «удалёнки» немного спал, большинство админов победило задачу удаленного доступа сотрудников к корпоративной сети, пришло время поделиться моей давней наработкой по повышению безопасности 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 или… предлагайте варианты!

    Надеюсь статья окажется вам полезной и поможет сделать сети малого и среднего бизнеса еще чуть безопаснее.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 6

      0
      Интересная схема, плюс в том что все делается на Tike. Минус анти-юзер френдли. С мобилки подключаться «для не айтишников» очень неудобно будет.

      Сам внедрял 2FA на Tike по следующей схеме:
      1. Tik — VPN сервер
      2. Коммерческий RADIUS (для проверки одноразовых кодов [SMS]) + сервер и NPS сервер Microsoft (для проверки по AD)

      Схема подключения была следующая:
      1. У пользователя была SMS-ка с одноразовым паролем. Она высылалась при первоначальном подключении или при не успешной попытке входа.
      2. При авторизации в VPN пользователь указывал свой AD логин, а в поле пароль указывал <АD пароль><одноразовый код>

      Минус моей схемы в том, что тут есть платные элементы — коммерческий RADIUS (100k руб. в год на 500 пользователей).

      Как вариант развития можете посмотреть в сторону генерации одноразовых кодов с помощью Google Auth их тоже можно скрестить с Tikом
        0
        Минус моей схемы в том, что тут есть платные элементы — коммерческий RADIUS (100k руб. в год на 500 пользователей).

        FreeRADIUS+LinOTP
        0

        ИМХО как выше заметили лучше использовать radius. Решение сильное, но тот кто пойдет по этой инфраструктуре после вас будет рвать волосы на всех местах.

          0

          Человек не способный понять 25 строк конфига, вряд ли должен вообще совать ручки к сетевому оборудованию.
          К тому же, радиус на 2FA настроить нисколько не проще. Там тоже есть где убиться без подготовки с тупой копипасты.
          Да и отделтная дадача "поднять радиус" некоторыз ставит в тупик. Даже со встроенным в Windoa Server вариантом многие разобраться не способны.
          Здесь же как раз решение "не плодить лишние сущности". Потому оно оказывается пригодным для сетей малого и среднего бизнеса.

            0
            Проблема ещё в том, что RADIUS клиент в Mikrotik умеет только MS-CHAPv2. Соответственно RADIUS должен или в себе хранить учётки или LDAP иметь NT-hash.

            И вот если у вас линуксы, ldap построен не на MS AD, то получается, что всякие OTP не прикрутить. Из RADIUS в Mikrotik идёт неизменный NT-hash, в котором OTP не содержится.

            Я вот хотел реализовать связку FreeIPA (с нативным OTP, оч удобно) + FreeRADIUS + Mikrotik (с L2TP/IPSec сервером на борту), но видимо придётся придумывать что-то другое.
          0
          Спасибо. Использовал вашу статью как стартовую идею для настройки 2FA через telegram bot (в телеграм отправляется код, при клике на кнопку с кодом попадаешь в вайтлист)

          Only users with full accounts can post comments. Log in, please.