Help with LF_MODSEC

drsprite
Junior Member
Posts: 28
Joined: 21 Jun 2008, 18:39

Help with LF_MODSEC

Post by drsprite »

Hi there,

I have setup a modsec script to help protect my wp-login.php file. Essentially the script that I've found will block access for the offending IP address for 5 minutes upon 10 failed login attempts over a 3 minute duration.

I'd like to utilize the LF_MODSEC portion of CSF to add them to the iptables firewall so that they're blocked right at the front door.

In testing, modsec is kicking in like it should, but I'm having a bit of trouble getting my test IP blocked at the firewall. I know this because after I am modec denied, I try to refresh for 3-5 more times and I can still get the apache error document. csf.deny also does not show my test IP. I would have expected that after 3 refreshes, I wouldn't get access to anything on the server.

Here are my settings in csf.conf, any tips?

LF_TRIGGER: 0
LF_TRIGGER_PERM: 1
LF_MODSEC: 3
LF_MODSEC_PERM: 3600
websavers
Junior Member
Posts: 17
Joined: 04 Sep 2013, 13:46

Re: Help with LF_MODSEC

Post by websavers »

Hi there,

I'm trying to do the same thing. The modsec rules are working as they should be, but in my case I'm pretty confident the problem is the way Plesk handles apache logging.

Plesk is logging to a separate log file for each domain (good for client access to logs, not good for lfd parsing). LFD is trying to read the apache error log, /var/log/httpd/error_log, which only includes accesses to the default vhost (which is largely irrelevant).

I then set csf.conf to read from the audit_log which contains all modsec entries, however I don't think the default regex will work on that log file. As a result of this, modsec is doing its job, but LFD can't understand the log file and thusly is unable to detect and create blocks at the firewall level.

User londoh seems to have the same issue -- search for his name (as I can't post links).
websavers
Junior Member
Posts: 17
Joined: 04 Sep 2013, 13:46

Re: Help with LF_MODSEC

Post by websavers »

Here's the regex that worked for me! Enter this in /etc/csf/regex.custom.pm

Code: Select all

#mod_security v2 (audit_log)
        if (($config{LF_MODSEC}) and ($lgfile eq $config{MODSEC_LOG}) and ($line =~ /^\[modsecurity\] \[client (\S+)\] (.*) Access denied with (code|connection)/)) {
                $ip = $1; $acc = ""; $ip =~ s/^::ffff://;
                                if (&checkip($ip)) {return ("mod_security triggered by","$ip|$acc","mod_security")} else {return}
        }
Then make sure that your /etc/csf/csf.conf has the following settings:

LF_MODSEC = "8"
LF_MODSEC_PERM = "1"

You can set those to how you like them, just make sure it's enabled.

MODSEC_LOG = "/var/log/httpd/audit_log"

Assuming that's the location of your audit log.
drsprite
Junior Member
Posts: 28
Joined: 21 Jun 2008, 18:39

Re: Help with LF_MODSEC

Post by drsprite »

Oh, great explanation. LFD doesn't scan ALL the apache logs, only the main one. Just like you, my apache config is setup to have separate log files for each domain as well. (but I am not running Plesk, which shouldn't matter).

I will try out the regex you posted and will try it from my test IP to see if it works or not.

Thank you!
drsprite
Junior Member
Posts: 28
Joined: 21 Jun 2008, 18:39

Re: Help with LF_MODSEC

Post by drsprite »

Hmm, quick question. How did you change modsec's logging to go to audit_log? Right now it's logging into each domain's separate error_log, but I haven't found the setting to make it all funnel into 1 log.

I'll keep digging around.
drsprite
Junior Member
Posts: 28
Joined: 21 Jun 2008, 18:39

Re: Help with LF_MODSEC

Post by drsprite »

Okay, found it. For anyone in the future looking for it, check out your settings in /etc/httpd/conf.d/mod_security.conf, then look for SecAuditLog /var/log/httpd/modsec_audit.log. That's the audit log.

I'm still having trouble though. I can get modsec to block me, but CSF still doesn't block my IP.

Here's my modsec rules:

Code: Select all

#These rules will block access for the offending IP address for 5 minutes upon 10 failed login attempts over a 3 minute duration.
SecAction phase:1,nolog,pass,initcol:ip=%{REMOTE_ADDR},initcol:user=%{REMOTE_ADDR},id:5000134
<Locationmatch "/wp-login.php">
    # Setup brute force detection.

    # React if block flag has been set.
    SecRule user:bf_block "@gt 0" "deny,status:401,log,id:5000135,msg:'Blocked! More than 10 failed login attempts in the alloted time.'"

    # Setup Tracking.  On a successful login, a 302 redirect is performed, a 200 indicates login failed.
    SecRule RESPONSE_STATUS "^302" "phase:5,t:none,nolog,pass,setvar:ip.bf_counter=0,id:5000136"
    SecRule RESPONSE_STATUS "^200" "phase:5,chain,t:none,nolog,pass,setvar:ip.bf_counter=+1,deprecatevar:ip.bf_counter=1/180,id:5000137"
    SecRule ip:bf_counter "@gt 10" "t:none,setvar:user.bf_block=1,expirevar:user.bf_block=300,setvar:ip.bf_counter=0"
</locationmatch>
Which puts an entry into my /var/log/httpd/modsec_audit.log like this:

Code: Select all

--3e448f7d-A--
[04/Sep/2013:09:33:36 --0400] 2kFLR0rQ9gIAADp8azQAAAAH IP_REMOVED_1.2.3.4 2216 IP_REMOVED_5.6.7.8 80
--3e448f7d-B--
GET /wp-login.php HTTP/1.1
Host: mydomain.com
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: PHPSESSID=1murvpb9ft0gagcmfucojqf217; wordpress_test_cookie=WP+Cookie+check; __utma=106233565.80301593.1350401214.1377788147.1378301721.3; __utmb=10$
AlexaToolbar-ALX_NS_PH: AlexaToolbar/alxg-3.2

--3e448f7d-F--
HTTP/1.1 401 Authorization Required
Content-Length: 34
Connection: close
Content-Type: text/html; charset=iso-8859-1

--3e448f7d-H--
Message: Access denied with code 401 (phase 2). Operator GT matched 0 at USER:bf_block. [file "/etc/httpd/conf/httpd.conf"] [line "1363"] [id "5000135"] [ms$
Action: Intercepted (phase 2)
Apache-Handler: php5-script
Stopwatch: 1378301616671559 952 (- - -)
Stopwatch2: 1378301616671559 952; combined=209, p1=162, p2=20, p3=0, p4=0, p5=26, sr=133, sw=1, l=0, gc=0
Producer: ModSecurity for Apache/2.6.8 (http://www.modsecurity.org/).
Server: Apache/2.2.3 (CentOS)

Perhaps my log entry isn't matching your regex?
drsprite
Junior Member
Posts: 28
Joined: 21 Jun 2008, 18:39

Re: Help with LF_MODSEC

Post by drsprite »

I think the problem is that the custom regex is looking to parse entries from the error_log, but the layout of the data in audit_log is quite different.

Is there any other way to do this?
drsprite
Junior Member
Posts: 28
Joined: 21 Jun 2008, 18:39

Re: Help with LF_MODSEC

Post by drsprite »

I think I've solved this.

Since CSF can't scan 10 separate domain error_log files, and the custom regex you provided needs the entry from the error_log and NOT the audit_log, the trick was to get all domains to report errors to 1 file.

So what I did was in each VirtualHost domain entry in httpd.conf, I added yet another error_log entry. Something to the effect of

Code: Select all

ErrorLog logs/unified_mod_security-error_log
So I have each virtualhost domain reporting errors into 2 log files now. This way, I can still have an error log separate for each domain, AND a "unified" one for CSF to look at for modsecurity violations.

Then I used the bit of RegEx code you provided:

Code: Select all

#mod_security v2 (audit_log)
        if (($config{LF_MODSEC}) and ($lgfile eq $config{MODSEC_LOG}) and ($line =~ /^\[modsecurity\] \[client (\S+)\] (.*) Access denied with (code|connection)/)) {
                $ip = $1; $acc = ""; $ip =~ s/^::ffff://;
                                if (&checkip($ip)) {return ("mod_security triggered by","$ip|$acc","mod_security")} else {return}
        }
Updated my csf.conf to:

Code: Select all

LF_MODSEC = "3" // 3 failed attempts in 300 seconds
LF_MODSEC_PERM = "1" // 1 = permanent ban

MODSEC_LOG = "/var/log/httpd/unified_mod_security-error_log"
Using my test IP, I went after wp-admin and wp-login. After 10 failed attempts, mod_security blocked me and gave me a 401. After 3 more attempts of receiving the 401, CSF kicked in and permanently blocked me. Sweet!

Only downside I can think of is the log sizes. It should be fine, but I'll have to keep an eye on it to make sure it doesn't go out of control.
websavers
Junior Member
Posts: 17
Joined: 04 Sep 2013, 13:46

Re: Help with LF_MODSEC

Post by websavers »

Sorry I didn't keep up with this until now!

The reason I said Plesk is simply that Plesk automatically separates all apache logs into per-domain logging for better access to end-users (if you have clients that want to see their own log files basically). But as you've mentioned this will be the same if you've also set up apache in the same manner without using Plesk.

Mod sec can be configured to write to its own Audit Log which uses its own format for logging, rather than the standard Apache format -- this is why the regex I posted works for me. You can configure mod security to do the same using the following mod sec configuration directives:

Code: Select all

 SecAuditEngine RelevantOnly
 SecAuditLogRelevantStatus "^(?:5|4(?!04))"
 SecAuditLogType Concurrent
 SecAuditLog logs/audit_log
 SecAuditLogParts ABIFHZ
Using that, the regex will match the audit log values. This method might be a bit more efficient as only mod security logs wind up in the audit log and you can control it from one central place rather than each vhost.

It's also possible your audit log format is different with mod security 2.6. I'm using version 2.7, but that's just a guess.

Hope this helps!
drsprite
Junior Member
Posts: 28
Joined: 21 Jun 2008, 18:39

Re: Help with LF_MODSEC

Post by drsprite »

Thanks for that! I'll give it a try. As I found out, apache can't log to 2 ErrorLogs at once, so I ended up doing this in httpd.conf:

Code: Select all

ErrorLog "|/usr/bin/tee -a /var/log/httpd/mydomain.net-error_log /var/log/httpd/unified_mod_security-error_log"
That works great to log to 2 ErrorLog files, but now we're utilizing Tee a lot (especially for 20 log files (2 for 10 domains)).

I will give your method a try to see if I can get away from using /usr/bin/tee...

Thanks again
Post Reply