modsecurity snippets

Brute-Force Authentication Protection with ModSecurity

Tagged authentication, modsecurity, brute-force  Languages apacheconf

IP-Based Blocking

The following ModSecurity script protects from brute-force authentication attacks by blocking IPs. It does this by checking the response code sent by the login page (/sessions). HTTP status 200 means a failed authentication attempt. After 3 attempts the IP is blocked.

<LocationMatch /sessions>
         # Uncomment to troubleshoot
        #SecDebugLogLevel 9
        #SecDebugLog /tmp/troubleshooting.log

        # Enforce an existing IP address block
        SecRule IP:bf_block "@eq 1" \
                "phase:2,deny,\
                msg:'IP address blocked because of suspected brute-force attack'"

        # Check that this is a POST
        SecRule REQUEST_METHOD "@streq POST" "phase:5,chain,t:none,nolog,pass"
                # AND Check for authentication failure and increment counters
                # NOTE this is for a Rails application, you probably need to customize this
                SecRule RESPONSE_STATUS "^200" \
                        "setvar:IP.bf_counter=+1"

        # Check for too many failures from a single IP address. Block for 10 minutes.
        SecRule IP:bf_counter "@ge 3" \
                "phase:5,pass,t:none, \
                setvar:IP.bf_block,\
                setvar:!IP.bf_counter,\
                expirevar:IP.bf_block=600"
</LocationMatch>

Username-based Blocking

A serious hacker will have billions of IPs, yes billions in the near future, so it's better to block by username. To block usernames, use this script:

<LocationMatch /sessions>
        # Retrieve the username
        SecAction phase:2,nolog,pass,initcol:USER=%{ARGS.username}

        # Enforce an existing username block
        SecRule USER:bf_block "@eq 1" \
                "phase:2,deny,\
                msg:'Username \"%{ARGS.username}\" blocked because of suspected brute-force attack'"

        # Check that this is a POST
        SecRule REQUEST_METHOD "@streq POST" "phase:5,chain,t:none,nolog,pass"
                # AND Check for authentication failure and increment counters
                # NOTE this is for a Rails application, you probably need to customize this
                SecRule RESPONSE_STATUS "^200" \
                        "setvar:IP.bf_counter=+1"

        # Check for too many failures for a single username
        SecRule USER:bf_counter "@ge 3" \
                "phase:5,t:none,pass,\
                setvar:USER.bf_block,\
                setvar:!USER.bf_counter,\
                expirevar:USER.bf_block=600"
</LocationMatch>

Password-based Blocking

Hackers might want to try a reverse brute-force attack on passwords, so you could also block multiple failed login attemps that use the same password. Just modify the script to read the password parameter:

# Retrieve the password parameter
        SecAction phase:2,nolog,pass,initcol:USER=%{ARGS.password}

Note, you might want to use the RESOURCE collection instead of USER, if you're blocking both usernames and passwords.

On Learning ModSecurity

* Buy the ModSecurity Handbook * Use Lua when possible. ModSecurity rules are severely limited by the Apache configuration language. * Use chain for controlling flow. chain = AND operator. OR operator is |. * There are 5 collections: GLOBAL, IP, RESOURCE, SESSION, USER. Future versions might give you unlimited collections... * Use LocationMatch and SecDebugLog when troubleshooting:

<LocationMatch /sessions>
        SecDebugLogLevel 9
        SecDebugLog /tmp/troubleshooting.log

How to set up email alerts for ModSecurity

Tagged email, modsecurity, alerts  Languages lua

Sending email alerts/notifications from ModSecurity is easy with Lua. Just put this script somewhere:

from = "hell@hell.com"
to = "heaven@heaven.com"

-- We use the mail command to send emails. You could also use the lua socket library:
-- http://w3.impa.br/~diego/software/luasocket/smtp.html
function main()
  -- create email
  local subject = m.getvar("TX.email_subject")
  local body = m.getvar("TX.email_body")

  if subject == nil then
    return nil
  end

  if body == nil then
    body = subject .. ":\n" ..
     "IP:\t " .. m.getvar("REMOTE_ADDR") .. "\n " ..
     "HOST:\t " .. m.getvar("REMOTE_HOST") .. "\n " ..
     "URI:\t " .. m.getvar("REQUEST_URI")
  end

  m.log(4, "Sending email " .. subject)
  m.log(5, body)

  -- use the mail command to send emails
  local cmd = "echo -e \"" .. body .. "\" | mail -s \"" .. subject .. "\" ".. from .." -- -r \"" .. to .. "\""

  -- remove variables
  m.setvar('tx.email_subject', nil)
  m.setvar('tx.email_body', nil)

  -- execute command
  local f = io.popen(cmd)

  -- read result
  local l = f:read("*a")
  m.log(5, "mail output: " .. l)
  f:close()
  --print(l)
  return nil
end

Next set up a rule that executes the script after a user has been blocked:

SecRule IP:bf_ip_counter "@gt 3" \
                "phase:5,pass,t:none, \
                setvar:'tx.email_subject=Blocking IP. Too many authentication failures.',exec:/xxx/email.lua, \
        ...

Now verify that it works. Note that this script uses the mail command, so make sure sending mails with Postfix and mail works.