Two-factor authentication of VPN users via MikroTik and SMS

Hello colleagues! Today, when the intensity of passions around “remote work” subsided a little, the majority of admins won the task of remote access of employees to the corporate network, it's time to share my long-standing experience in improving VPN security. This article will not be fashionable now IPSec IKEv2 and xAuth. It's about building a system. two-factor authentication (2FA) VPN users when MikroTik acts as a VPN server. Namely, when "classic" protocols such as PPP are used.

Two-factor authentication of VPN users via MikroTik and SMS

Today I will tell you how to protect MikroTik PPP-VPN even if the user account is "hijacked". When this scheme was introduced to one of my customers, he briefly described it as “well, now it’s just like in a bank!”.

The method does not use external authenticator services. The tasks are performed internally by the router itself. No cost for the connecting client. The method works for both PC clients and mobile devices.

The general protection scheme is as follows:

  1. The internal IP address of a user who has successfully connected to the VPN server is automatically greylisted.
  2. The connection event automatically generates a one-time code that is sent to the user using one of the available methods.
  3. Addresses in this list have limited access to local network resources, with the exception of the “authenticator” service, which is waiting to receive a one-time passcode.
  4. After presenting the code, the user has access to the internal resources of the network.

First the smallest problem I had to face was storing contact information about the user to send him the 2FA code. Since it is impossible to create arbitrary data fields corresponding to users in Mikrotik, the existing “comment” field was used:

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

The second the problem turned out to be more serious - the choice of the path and method of delivering the code. Three schemes are currently implemented: a) SMS via USB-modem b) e-mail c) SMS via e-mail available for corporate clients of the red cellular operator.

Yes, SMS schemes bring costs. But if you look, "security is always about money" (c).
I personally don't like the scheme with e-mail. Not because it requires the mail server to be available for the client being authenticated - it's not a problem to split the traffic. However, if a client carelessly saved both vpn and email passwords in a browser and then lost their laptop, the attacker would gain full access to the corporate network from it.

So, it's decided - we deliver a one-time code using SMS messages.

The third The problem was where how to generate a pseudo-random code for 2FA in MikroTik. There is no analogue of the random() function in the RouterOS scripting language, and I have seen several crutch script pseudo-random number generators before. I didn't like any of them for various reasons.

In fact, there is a pseudo-random sequence generator in MikroTik! It is hidden from a superficial glance in the context of /certificates scep-server. The first method getting a one-time password is easy and simple - with the command /certificates scep-server otp generate. If we perform a simple variable assignment operation, we will get an array value that can be used later in scripts.

second method obtaining a one-time password which is also easy to apply - using an external service random.org to generate the desired kind of sequence of pseudo-random numbers. Here is a simplified cantilevered example of getting data into a variable:

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

A request formatted for the console (escaping special characters will be required in the script body) receives a string of six digits into the $rnd1 variable. The following "put" command simply displays the variable in the MikroTik console.

The fourth problem which had to be quickly resolved - this is how and where the connected client will transfer its one-time code at the second stage of authentication.

Two-factor authentication of VPN users via MikroTik and SMS

There must be a service on the MikroTik router that can accept the code and match it with a specific client. If the provided code matches the expected one, the client's address should be included in a certain "white" list, addresses from which are allowed access to the company's internal network.

Due to the poor choice of services, it was decided to accept codes via http using the webproxy built into Mikrotik. And since the firewall can work with dynamic lists of IP addresses, it is the firewall that performs the search for the code, matching it with the client IP and adding it to the “white” list using Layer7 regexp. The router itself has been assigned a conditional DNS name "gw.local", a static A-record has been created on it for issuing to PPP clients:

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

Capturing traffic of unverified clients on the proxy:
/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

In this case, the proxy has two functions.

1. Open tcp connections with clients;

2. In case of successful authorization, redirect the client browser to a page or picture notifying about successful authentication:

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

I will list the important configuration elements:

  1. interface-list "2fa" - a dynamic list of client interfaces, traffic from which requires processing within 2FA;
  2. address-list "2fa_jailed" - "gray" list of tunnel IP addresses of VPN clients;
  3. address_list "2fa_approved" - "white" list of tunnel IP addresses of VPN clients that have successfully passed two-factor authentication.
  4. firewall chain "input_2fa" - it checks tcp packets for the presence of an authorization code and matches the IP address of the code sender with the required one. Rules in the chain are added and removed dynamically.

A simplified flowchart of packet processing looks like this:

Two-factor authentication of VPN users via MikroTik and SMS

To get into the Layer7 check of traffic from clients from the "gray" list that have not yet passed the second stage of authentication, a rule has been created in the standard "input" chain:

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

Now let's start to fasten all this wealth to the PPP service. MikroTik allows you to use scripts in profiles (ppp-profile) and assign them to the events of establishing and breaking a ppp connection. The ppp-profile settings can be applied to the PPP server as a whole or to individual users. At the same time, the profile assigned to the user has priority, overriding the parameters of the profile selected for the server as a whole with its specified parameters.

As a result of this approach, we can create a special profile for two-factor authentication and assign it not to all users, but only to those who consider it necessary to do so. This may be relevant if you use PPP services not only to connect end users, but at the same time to build site-to-site connections.

In the newly created special profile, we use the dynamic addition of the address and interface of the connected user to the "gray" lists of addresses and interfaces:

winbox
Two-factor authentication of VPN users via MikroTik and SMS

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

It is necessary to use both "address-list" and "interface-list" lists to detect and capture traffic from non-secondary VPN clients in the dstnat (prerouting) chain.

When the preparation is completed, additional firewall chains and a profile are created, we will write a script responsible for auto-generation of the 2FA code and individual firewall rules.

Documentation wiki.mikrotik.com on PPP-Profile enriches us with information about variables associated with PPP client connect-disconnect events "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". Some of them are very useful to us.

Code used in profile for PPP on-up connection event

#Логируем для отладки полученные переменные 
:log info (

quot;local-address")
:log info (


quot;remote-address")
:log info (


quot;caller-id")
:log info (


quot;called-id")
:log info ([/int pptp-server get (


quot;interface") name])
#Объявляем свои локальные переменные
:local listname "2fa_jailed"
:local viamodem false
:local modemport "usb2"
#ищем автоматически созданную запись в адрес-листе "2fa_jailed"
:local recnum1 [/ip fi address-list find address=(


quot;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 [email protected] [email protected] subject="@".$vphone body=$msgboby }

#Генерируем Layer7 regexp
local vregexp ("otp\/".$comm1)
:local vcomment ("2fa_".(


quot;remote-address"))
/ip firewall layer7-protocol add name=(


quot;vcomment") comment=(


quot;remote-address") regexp=(


quot;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=(


quot;vcomment") protocol=tcp src-address=(


quot;remote-address") dst-limit=1,1,src-address/1m40s

Especially for those who like to mindlessly copy-paste, I warn you - the code is taken from the test version and may contain minor typos. It will not be difficult for an understanding person to figure out exactly where.

When a user disconnects, an “On-Down” event is generated and the corresponding script with parameters is called. The purpose of this script is to clean up the firewall rules created for the disconnected user.

Code used in profile for PPP on-down connection event

:local vcomment ("2fa_".(

quot;remote-address"))
/ip firewall address-list remove [find address=(


quot;remote-address") list=2fa_approved] /ip firewall filter remove [find chain="input_2fa" src-address=(


quot;remote-address") ] /ip firewall layer7-protocol remove [find name=$vcomment]
You can then create users and assign all or some of them to a two-factor authentication profile.

winbox
Two-factor authentication of VPN users via MikroTik and SMS

Code
/ppp secrets set [find name=Petrov] profile=2FA

How it looks on the client side.

When a VPN connection is established, an Android/iOS phone/tablet with a SIM card receives an SMS like this:

SMS
Two-factor authentication of VPN users via MikroTik and SMS

If the connection is established directly from the phone / tablet, then you can go through 2FA simply by clicking on the link from the message. It's comfortable.

If the VPN connection is established from a PC, then the user will be required to enter a minimal password form. A small form in the form of an HTML file is given to the user when setting up the VPN. The file can even be sent by mail so that the user saves it and creates a shortcut in a convenient place. It looks like this:

Label on the table
Two-factor authentication of VPN users via MikroTik and SMS

The user clicks on the shortcut, a simple code entry form opens, which will paste the code into the opened URL:

Screen form
Two-factor authentication of VPN users via MikroTik and SMS

The most primitive form is given as an example. Those who wish can modify for themselves.

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>

If the authorization was successful, the user will see the MikroTik logo in the browser, which should signal successful authentication:

Two-factor authentication of VPN users via MikroTik and SMS

Note that the image is returned from the built-in MikroTik web server using WebProxy Deny Redirect.

I suppose the image can be customized using the "hotspot" tool, uploading your own version there and setting the Deny Redirect URL to it with WebProxy.

A big request to those who are trying to buy the cheapest "toy" Mikrotik for $20 and replace a $500 router with it - don't do that. Devices like "hAP Lite" / "hAP mini" (home access point) have a very weak CPU (smips), and most likely will not cope with the load in the business segment.

Warning! This solution has one disadvantage: when clients connect or disconnect, configuration changes occur, which the router tries to save in its non-volatile memory. With a large number of clients and frequent connections and disconnections, this can lead to degradation of the internal storage in the router.

PS: Methods for delivering code to the client can be expanded and supplemented as far as your programming capabilities are sufficient. For example, you can send messages to telegram or ... suggest options!

I hope the article will be useful to you and will help to make the networks of small and medium-sized businesses a little more secure.

Source: habr.com