Black list for failed login to IPSec VPN

For sure, for one thing I know more than you, you certainly know another thing more than me… :astonished:

Duel of titans :smiley:

Thank you both for teaching us so many useful things.

BR.

Added some functions here, can be useful:
http://forum.mikrotik.com/t/rextended-fragments-of-snippets/151033/15

Could you guys include OVPN failed login into this Script, please? I tried to replicate it from your Script. However, I was not able to. Added IP is 0.0.0.0 at the created address list.
Thank you :d.

TLS Error: unknown opcode received (1)
<64.62.197.21>: disconnected <bad packet received>

ovpn-1.jpg
ovpn-2.jpg

I have another script to share:

# Created by Anton BORODA Borodyuk 2023
#
# This script adds users who end up with "parsing packet failed, possible cause: wrong password" message during
# VPN connection more then $maxTryCount times to the $listName for 7 days,
# This should be a good riddance for VPN password guessers.
#
# Losely based on Jotne && rextended 2022 v1.5 script.

:local listName "IPSEC"
:local maxTryCount 21;

:local offenders ({})

/log
:foreach i in=[find where message~"possible cause: wrong password"] do={
    :local logMessage [get $i message]

    :local ipString [:pick $logMessage 0 [:find $logMessage " "]]

    :if ($offenders->$ipString != nil) do={
        :set ($offenders->$ipString) ([:tonum ($offenders->$ipString)] + 1)
    } else={
        :set ($offenders->$ipString) 1
    }
}
:foreach key,value in=$offenders do {
    :local tryCount [:tonum ($value)]
    :if ($tryCount > $maxTryCount) do={
        :local logIp [:toip $key]
        :if ([:len [/ip fire addr find where list=$listName address=$logIp]] < 1) do={
            /ip fire addr add address=$logIp list=$listName timeout=7d
            :log info message="script=IPSEC_failed src_ip=$logIp why=Password guesser"
        }
    }
}

This works a little differently.
I had a lot of false-positives with the scripts above, so I modified this to include only actual failed password attempts.
This counts the number of incorrect password messages and if it’s more than 21 (iOS client retries 7 times before throwing an “unable to connect message”, so it’s giving a user 3 attempts in total), the user is added to the list.

Do not use ({}) for define empty array, the correct way is :local arrayvar [:toarray ""]
I myself used that method once suggested by another user, but it backfired because, although I don't quite remember now how,
it had unwanted effects that backfired on me in programming.
IT SEEMS to work, then it messes up...

Missing a space between do and {

:foreach key,value in=$offenders do {

The script is wrong on this line:

:if ($offenders->$ipString != nil) do={

You can not compare one array field with the string "nil".
Nil is not a reserved word usable for compare something on this way.
Nil is just a string, you can write anything, and for just a coincidence work.
Instead the "nothing" keyword exist, but you can not compare two nothing or two nil....
:global test [:toarray ""]
:global isnil [:pick "" 0 1]
:global notnil ":)"

:put [:typeof $test]
:put [:typeof $isnil]
:put [:typeof $notnil]

:if ($isnil != nil) do={:put "not nil"} else={:put "is nil"}
:if ($notnil != nil) do={:put "not nil"} else={:put "is nil"}

:if ($test->$isnil != nil) do={:put "not nil"} else={:put "is nil"}
:if ($test->$notnil != nil) do={:put "not nil"} else={:put "is nil"}

:set ($test->$isnil) "is-nil"
:set ($test->$notnil) "not-nil"

:put [:typeof $test]
:put [:typeof $isnil]
:put [:typeof $notnil]

:if ($test->$isnil != nil) do={:put "not nil"} else={:put "is nil"}
:if ($test->$notnil != nil) do={:put "not nil"} else={:put "is nil"}

:put [:typeof ($test->$isnil)]
:put [:typeof ($test->$notnil)]

:if ($test->$isnil != anystring) do={:put "not nil"} else={:put "is nil"}
:if ($test->$notnil != anystring) do={:put "not nil"} else={:put "is nil"}

:if (($test->$isnil) != [:nothing]) do={:put "not nothing"} else={:put "is nothing"}
:if (($test->$notnil) != [:nothing]) do={:put "not nothing"} else={:put "is nothing"}
The correct way is with ( ) and use :typeof, and is nothing the undefined "nil" on array:

:if ([:typeof ($offenders->$ipString)] != "nothing") do={

Created by Anton BORODA Borodyuk 2023

v1.0.r

fixed and revised by rextended

This script adds users who end up with "parsing packet failed, possible cause: wrong password" message during

VPN connection more then $maxTryCount times to the $listName for 7 days,

This should be a good riddance for VPN password guessers.

Losely based on Jotne && rextended 2022 v1.5 script.

:local listName "IPSEC"
:local maxTryCount 21
:local offenders [:toarray ""]

/log
:foreach i in=[find where message~"possible cause: wrong password"] do={
:local logMessage [get $i message]
:local ipString [:pick $logMessage 0 [:find $logMessage " "]]

:if ([:typeof ($offenders->$ipString)] = "nothing") do={
    :set ($offenders->$ipString) 1
} else={
    :set ($offenders->$ipString) (($offenders->$ipString) + 1)
}

}
/ip firewall address-list
:foreach key,value in=$offenders do={
:local tryCount [:tonum ($value)]
:if ($tryCount > $maxTryCount) do={
:local logIp [:toip $key]
:if ([:len [find where list=$listName and address=$logIp]] < 1) do={
add address=$logIp list=$listName timeout=7d
:log info "script=IPSEC_failed src_ip=$logIp why=Password guesser"
}
}
}

Testing in Ros 6.49.7 some scripts (written using functions stored as array members), I have found that using ({}) to initialize a variable (even local) with an empty array, sometimes doesn't clear it and persists the value assigned in the previous execution of the function, so if each execution adds 3 items to the (in theory) empty array, the first execution will return 3 items, the second will duplicate it and return 6 items, the third will return 9, ...

Those scripts worked well in Ros 7.7, so it seems that some script bugs where solved in v7.

I now use exclusively [:toarray ""]

Thanks, I do not remember precisely why, but I remember that updating one array defined with ({}) cause later a mess with other arrays defined on same way…

My script!

add chain=input action=drop
add chain=forward action=drop

That was easy.

You forget to add first the admin access :laughing: :laughing: :laughing:

Thanks for all the improvements and corrections.

I'm far from good in writing mikrotik scripts tbh, so the corrections are most welcome.

Hello, I modified the code but I have problems with “phase1 negotiation failed\.”

# Created Jotne && rextended 2022 v1.5
#
# This script add ip of user who with "IPSEC negotiation failed", "SPI* not registered" and "Invalid exchange" to a block list for 7 days
# Schedule the script to run every 5 min
# It should run on all routerOS version
# 1.3 added "Invalid exchange"
# 1.4 added dot behind "negotiation failed" to get only lines with IP
# 1.4 made all inn to one loop, based on idea by rextended
# 1.5 Fixed typo

:local logMessage ""
:local logIp ""
:local cont1 0
:local cont2 0
:local cont3 0
/log

:foreach i in=[find where message~"phase1 negotiation failed\\." or message~"SPI.*not regist" or message~"Invalid exchange"] do={
    :set logMessage [get $i message]

    :if ($logMessage~"phase1 negotiation failed\\.") do={
        :set $cont1 ($cont1 + 1)
        }
    :if ($logMessage~"SPI.*not regist") do={
        :set $cont2 ($cont2 + 1)
        }
    :if ($logMessage~"Invalid exchange") do={
        :set $cont3 ($cont3 + 1)
        }


    :if (($logMessage~"phase1 negotiation failed\\.") && ($cont1 > 2)) do={
        :set logIp [:toip [:pick $logMessage -1 [:find $logMessage " "]]]
        :if ([:len [/ip fire addr find where list=IPSEC address=$logIp]] < 1) do={
            /ip fire addr add address=$logIp list=IPSEC timeout=7d
            :log info message="Script=IPSEC - src_ip=$logIp Motivo=negotiation_failed"
        }
    }

    :if (($logMessage~"SPI .* not registered for") && ($cont2 > 2)) do={
        :set logIp [:toip [:pick $logMessage ([:find $logMessage "for "]+4) [:find $logMessage "["]]]
        :if ([:len [/ip fire addr find where list=IPSEC address=$logIp]] < 1) do={
            /ip fire addr add address=$logIp list=IPSEC timeout=7d
	:log info message="Script=IPSEC - src_ip=$logIp Motivo=SPI.*not regist"
        }
    }
#:log info message="ipsec changed script settings by gleguizamon."
    :if (($logMessage~"Invalid exchange") && ($cont3 > 2)) do={
        :set logIp [:toip [:pick $logMessage ([:find $logMessage "from "]+5) [:find $logMessage "["]]]
        :if ([:len [/ip fire addr find where list=IPSEC address=$logIp]] < 1) do={
            /ip fire addr add address=$logIp list=IPSEC timeout=7d
	:log info message="Script=IPSEC - src_ip=$logIp Motivo=Invalid exchange"
               
        }
    }
}

Here is my variant that also reports abuse

:local listName "portscanner"
:local logMessage ""
:local logIp ""
/log
:foreach i in=[find where message~"phase1 negotiation failed\\." or message~"phase1 negotiation failed due to time up "] do={
    :set logMessage [get $i message]

    :if ($logMessage~"phase1 negotiation failed\\.") do={
        :set logIp [:toip [:pick $logMessage -1 [:find $logMessage " "]]]       
    }   

    :if ($logMessage~"phase1 negotiation failed due to time up ") do={
       :local temp [:pick $logMessage ([:find $logMessage ">"]+1) 999] 
       :set logIp [:pick $temp 0 [:find $temp "["]] 
    }   


   :if ([:len [/ip fire addr find where list=$listName address=$logIp]] < 1) do={   
      /ip fire addr add address=$logIp list=$listName timeout=7d

     
      :local data "ip=$logIp&comment=Port%20scanning&categories=14%2C15"
      :local result ([/tool fetch mode=https url="https://api.abuseipdb.com/api/v2/report" http-method=post http-data=$data http-header-field="key: [API_KEY],Accept: application/json,Content-Type: application/x-www-form-urlencoded" as-value output=user])
   }
}

Hello,
this line dont work
can you help me pls.

I added this so that it only looks at todays logs. Otherwise it will report old logs after timeout period. I use the fact that todays logs only have time not date

:if ([:len [get $i time]] = 8 and [:len [/ip fire addr find where list=$listName address=$logIp]] < 1) do={

Be advised, that assumption might become wrong.
It looks like they are moving towards standardized date/time formatting.
See latest change log on 7.10b5.

The time format alone is not changed,
this still valid until do not fix the “friendly” time on logs.

its a shame mikrotik log doesnt have a proper datetime data type

i have 6.48.6

My code you are trying to execute on your router was written on 7.8 - 7.9