Protecting Asterisk with Fail2ban

Looking for attention from robots? Then run a service on the public Internet like Asterisk, the opensource PBX. Robots will prod and poke your newly minted installation looking for way to make calls to pay-for numbers at your expense. You’re probably reading this post after seeing a number of these attempts in your Asterisk CLI.

So what to do?

Fail2ban isn’t bulletproof but provides a final layer of defence and will calm your log files and CLI. Its operation is simple - Fail2ban watches your log files and identifies malicious attempts, then adds a firewall rule to block the offending host.

The problem? The fail2ban configuration for Asterisk hasn’t been updated in a while and I found a number of registration and outbound call attempts were not being detected. I spent a few days updating my configuration for Asterisk version 18 and wanted to share my configuration in case it helps you protect your Asterisk service, too.

There are two configuration files, both should be present when you install Fail2ban and just need updating. I’m using Ubuntu and have quoted the paths for my setup, however, you might find these files at a different path.

/etc/fail2ban/jail.d/asterisk-jail.conf

[asterisk]
# This needs to be true to tell Fail2ban to monitor and act on Asterisk logs
enabled = true

# Three attempts, and you're out!
maxretry  = 3 

# Count to three attempts over the period of one day
findtime  = 1d 

# Naughty robots are banned for a year. This is my preference and you might be more lenient
bantime   = 1y 

# A space sepated list of ip addressess that should never be blocked. I've made these up as an example and you should configure your own.
ignoreip  = 127.0.0.0/8 192.168.0.0/24 

# This line tells Fail2ban how to ban ip addressess. On Ubuntu it's likely iptables
banaction = iptables-allports

/etc/fail2ban/filter.d/asterisk-filter.conf

# Fail2Ban filter for Asterisk
#

[INCLUDES]

# Include macros from common.conf
before = common.conf

[Definition]
_daemon = asterisk
__pid_re = (?:\s*\[\d+\])
iso8601 = \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+[+-]\d{4}

# All Asterisk log messages begin like this:
log_prefix= (?:NOTICE|SECURITY|WARNING|ERROR)%(__pid_re)s:?(?:\[C-[\da-f]*\])?:? [^:]+:\d*(?:(?: in)? [^:]+:)?

prefregex = ^%(__prefix_line)s%(log_prefix)s <F-CONTENT>.+</F-CONTENT>$

failregex = ^Registration from '[^']*' failed for '<HOST>(:\d+)?' - (?:Wrong password|Username/auth name mismatch|No matching peer found|Not a local domain|Device does not match ACL|Peer is not supposed to register|ACL error \(permit/deny\)|Not a local domain)$
            ^Call from '[^']*' \(<HOST>:\d+\) to extension '[^']*' rejected because extension not found in context
            ^(?:Host )?<HOST> (?:failed (?:to authenticate\b|MD5 authentication\b)|tried to authenticate with nonexistent user\b)
            ^No registration for peer '[^']*' \(from <HOST>\)$
            ^hacking attempt detected '<HOST>'$
            ^SecurityEvent="(?:FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)"(?:(?:,(?!RemoteAddress=)\w+="[^"]*")*|.*?),RemoteAddress="IPV[46]/[^/"]+/<HOST>/\d+"(?:,(?!RemoteAddress=)\w+="[^"]*")*$
            ^"Rejecting unknown SIP connection from <HOST>(?::\d+)?"$
            ^Request (?:'[^']*' )?from '(?:[^']*|.*?)' failed for '<HOST>(?::\d+)?'\s\(callid: [^\)]*\) - (?:No matching endpoint found|Not match Endpoint(?: Contact)? ACL|(?:Failed|Error) to authenticate)\s*$
            ^Endpoint 'anonymous' \(<HOST>:\d+\) has no configured AORs$
            ^SecurityEvent="(?:InvalidPassword|FailedACL)".*,RemoteAddress="IPV[46]\/(?:UDP|TCP|TLS)\/<HOST>\/\d+
	        Call \((?:UDP|TCP|TLS):<HOST>:\d+\) to extension '\+?\d*' rejected because extension not found in context '.*'.$
            Error processing \d+ bytes packet from (?:UDP|TCP|TLS) <HOST>:\d+ : PJSIP syntax error

ignoreregex =

datepattern = {^LN-BEG}

journalmatch = _SYSTEMD_UNIT=asterisk.service

[lt_journal]

# asterisk can log timestamp if logs into systemd-journal (optional part matching this timestamp, gh-2383):
__extra_timestamp = (?:\[[^\]]+\]\s+)?
__prefix_line = %(known/__prefix_line)s%(__extra_timestamp)s

Not Bullet Proof?

Automated bots succeed because they have time on their hands. They slowly iterate through different configurations until they stumble on something that works. For example, they might try dial different numbers until one gets through a dial plan you perhaps configured to call mobile numbers in your local area. If your system is configured well, it’s unlikely a malicious bot will srike it lucky within 3 attempts and Fail2ban will block the automated bot before it discovers anything. However, Fail2ban has limitations that are important to understand:

  • It can’t protect against your permissive configuration. For example, if you configure Asterisk to allow anonymous calls through a pay-for service, an automated bot is going to strike gold on its first attempt and Fail2ban won’t detect and ban it, becuase Asterisk will report the event as a positive log entry, if at all.
  • Bots can get lucky and strike gold within 3 attempts, too. Make sure your configuration is secure and use Fail2ban as the last line of defence and to make your logs quieter.
  • A well-crafted malicious attempt can spoof a legitimate ip address and cause Fail2ban to block the legitimate ip address. This causes a Denial of Service (DoS) to the user(s) behind that address. This is unlikely but something to be aware of nonetheless.
  • A more likely scenario is false positives due to an incorrect client configuration. For example, a legitimate remote host with an incorrect password will try to register multiple times and be blocked by Fail2ban. This can cause immense fustration when you’re debugging the issue and don’t realise the remote ip address has been firewalled out. When debugging always check that Fail2ban hasn’t blocked the ip first.