Securing Submisssion on Exim

I noticed a significant increase in the number of hosts attempting to authenticate to my Exim submission ports (465 and 587). As a result, I have implemented a number of security measures. These measures have effectively blocked the attempts. This post outlines the main measures that I have implemented. This includes ACLs and fail2ban blacklisting.

Background

My implementation uses the Ubuntu split configuration. This makes it easy to drop in new ACLs and other configuration changes. The ACLs below can be added to other configuration file formats.

I have an Exim Perl module that looks up the country from which the connection originates. This is used for a number of purposes, including logging. The ACL variable “acl_c_ipcountry” is populated when at connection time. This variable can be removed if you don’t have a way to populate it.

My tcpwrappers configuration tracks IP addresses allowed to authenticate to IMAP. Addresses that do not come from a restricted list of geographic locations are blocked. The path to this file is configured in the variable “ACCESS_ALLOW_FILE”. This is effectively an IMAP before Submission setup.

Configuration variables

When acting as a submission server, there are a number of settings that need to be set. These are added to the “main” section of the configuration file. The ACLs are not required but are used to block unwanted authentication requests. When blocking at connect time, the “acl_smtp_auth” ACL can be omitted.

# Enable submission(s) ports; require tls and correct port
auth_advertise_hosts = ${if eq{$tls_in_cipher}{}{}\
                        {${if eq {$interface_port}{25}{}{*}}}}
# Listen on submission ports
daemon_smtp_ports = 25 : 465 : 587
tls_on_connect_ports = 465
# Local ACLS
acl_smtp_connect = acl_local_connect
acl_smtp_auth = acl_local_auth

The “tls_in_cipher” was named “tls_cipher” in earlier versions. Recently, the TLS variables have be qualified into “tls_in” and “tls_out” variables.

Investigation

When I first noticed the traffic increase, it appeared likely that these were password cracking attempts. An investigation confirmed that it was. My initial approach was to force authentication failures while gathering the authentication data. This was done by creating an authentication ACL. The log message uses “nixed” because “rejected” doubled the counts reported by the “eximstats” utility. The password data is also reported in the rejection log message but is more difficult to parse. The “nixed” message is also used to increase the hit count for fail2ban.

 # acl/acl_25_local-config_check_auth            --sh--
 # This access control list is used after an auth command.
 acl_local_auth:
   # Accept from local and relay hosts quietly - implicitly trusted
   accept
     hosts = : +relay_from_hosts
   # Accept and log submissions from external addresses
   # Require encrypted submission - should alreaday be verified
   # Require submission not be on the SMTP port
   # Require host to be listed in access allowed file
   accept
     encrypted = *
     !condition = ${if eq {$interface_port}{25}}
     hosts = ${if exists{ACCESS_ALLOW_FILE}\
                         {ACCESS_ALLOW_FILE}{}}
     logwrite = AUTH accepted ${sender_host_address} ${received_port} \\
              ${received_protocol} ${acl_c_ipcountry}
   # Default drop connection
   drop
     message = AUTH not accepted
     logwrite = AUTH nixed: ${sender_host_address} ${received_port} \
              ${received_protocol} ${acl_c_ipcountry} \
              ${escape:$smtp_command_argument}
 # EOF

Extracting the base64 encrypted authentication data showed attempts to validate against a number of possible accounts. Many hosts tried many users and passwords.

For a period of time authentication was enabled on all ports and TLS was not required. The log files were monitored to ensure no authentications succeeded on unwanted ports. During this testing, I noticed a large number of attempts to use “LOGIN” authentication on port 25. This method is neither advertised nor supported by my configuration.

Blacklisting with fail2ban

I run fail2ban to dynamically blacklist hosts. The Exim Filter was already catching many of the more persistent hosts. However many hosts were not being caught. I identified an issue with a couple of additional TLS fields being logged. I resolved the issue by creating an “exim-common.local” file with a corrected “host_info_suf” definition.

# Fail2Ban  filter file for common exim expressions
# Local overrides
[Definition]
host_info_suf = (?::\d+)?(?: I=[\S+](:\d+)?)?(?: U=\S+)?(?: P=e?smtp)?(:? X=\S+)?(?: CV=\w+)?(?: F=(?:<>|[^@]+@\S+))?\s
host_info = %(host_info_pre)s[]%(host_info_suf)s
# EOF

The existing Exim filter rules did not match the AUTH rejections. Also, one rule was failing if the TLS data was missing. This was resolved by creating an “exim.local” file containing additional patterns for the AUTH failures. It also contains a fix for the broken “no MAIL in SMTP” pattern that failed on the TLS fields. Catching the “nixed” line doubles the count for rejected authentications. The final match is for connections to the submission ports done in the connect ACL.

# Fail2Ban filter for exim
#Local overrides
[Definition]
failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|Unrouteable address|all relevant MX records point to non-existent hosts)\s$
            ^%(pid)s \w+ authenticator failed for (?:[^[( ] )?(?:(\S) )?[](?::\d+)?(?: I=[\S+](:\d+)?)?: 535 Incorrect authentication data( (set_id=.)|: \d+ Time(s))?\s$
            ^%(pid)s %(host_info)srejected RCPT [^@]+@\S+: (?:relay not permitted|Sender verify failed|Unknown user|Unrouteable address)\s$
            ^%(pid)s SMTP protocol synchronization error ([^)]): rejected (?:connection from|"\S+") %(host_info)s(?:next )?input="."\s$
            ^%(pid)s SMTP call from (?:[^[( ] )?%(host_info)sdropped: too many (?:nonmail commands|syntax or protocol errors) (last (?:command )?was "[^"]")\s$
            ^%(pid)s SMTP protocol error in "[^"]+(?:"+[^"](?="))?" %(host_info)sAUTH command used when not advertised\s$
            ^%(pid)s no MAIL in SMTP connection from (?:[^[( ] )?(?:(\S) )?%(host_info)s(?:D=\d\Ss)?( X=\S+)?(?: CV=\w+)?(?: C=\S)?\s$
            ^%(pid)s (?:[\w-]+ )?SMTP connection from (?:[^[( ]* )?(?:(\S) )?%(host_info)sclosed by DROP in ACL\s$
            ^%(pid)s %(host_info)srejected AUTH \w+: AUTH not (accepted|permitted)
            ^%(pid)s AUTH nixed:? [A-Za-z0-9]+
            ^%(pid)s %(host_info)s(?:temporarily )?rejected connection.*ACL: Submission not permitted
            <mrde-mode>>
 # EOF
 

These patterns quickly blacklist any unwanted hosts attempting to authenticate. The fail2ban Exim filter is usually blocking around 30 addresses at any time.

Blocking at connection time

Once I had completed my investigations, I decided to block unwelcome IP addresses on the authentication port. This involved modifying the connect ACL to block connections on the submission ports. This uses the authentication ACL that can be applied at connect time.

# acl/25_local-config_check_connect                  --sh--
# This access control list is used at the start of an incoming connection.
acl_local_connect:
  # Set some local ACL variables
  warn
    set acl_c_ctod = $tod_log
    set acl_c_ipcountry = ${perl{get_geo}{$sender_host_address}}
  #### MTA
  # Accept connections received over a local interface, 
  # and from hosts for which we relay mail.
  accept
    hosts = : +relay_from_hosts
  #### Submission
  # Defer if source ip is not in allowed access file
  # Only if connection is received on a submission port.
  defer
    log_message = Submission not permitted on ${received_port}
    !condition = ${if eq {$interface_port}{25}}
    !hosts = ${if exists{ACCESS_ALLOW_FILE}\
                         {ACCESS_ALLOW_FILE}{}
    logwrite = AUTH nixed ${sender_host_address} ${received_port} \
                          ${acl_c_ipcountry}
  # Accept connections submission ports if not defered above 
  accept
    !condition = ${if eq {$interface_port}{25}}
    logwrite = Submission from ${sender_host_address} on port ${received_port} \
              ${acl_c_ipcountry}
#### MX - configuration omitted.

Connections are deferred so that they are appropriately counted by eximstats. The effect is the same. My connection ACL also runs a number of tests when functioning as an MX server. The MX tests are used for spam avoidance measures.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Cookie Consent with Real Cookie Banner