owasp-modsecurity / ModSecurity

ModSecurity is an open source, cross platform web application firewall (WAF) engine for Apache, IIS and Nginx. It has a robust event-based programming language which provides protection from a range of attacks against web applications and allows for HTTP traffic monitoring, logging and real-time analysis.
https://www.modsecurity.org
Apache License 2.0
8.15k stars 1.6k forks source link

ClamAV with mod security #1610

Closed intelbg closed 6 years ago

intelbg commented 6 years ago

Hello, I wondered if this is the right place where to ask and I am sorry if it's not. I would like to use clamAV scanning with mod security in nginx and found the following guide:

https://malware.expert/scan-every-file-clam-antivirus-scanner-modsecurity/

Although I receive the following error on the rule:

SecRule FILES_TMPNAMES "@inspectFile /usr/local/bin/runav.pl" \ "phase:2,t:none,block,msg:'Virus found in uploaded file',id:'399999'"

Operator: InspectFile is not yet supported. Is it possible to use another operator to accomplish this task or is it planned to be implemented? I am using libmodsecurity version 3 with nginx connector and cars v3 too.

Thank you in advance.

zimmerle commented 6 years ago

Hi @intelbg,

Please check the most recent version of v3/master. The code was recently implemented there.

intelbg commented 6 years ago

I did git clone -b v3/master https://github.com/SpiderLabs/ModSecurity and recompiled it again, but still the operator is missing. I a wrong in doing the git clone or I should use the branch v3/dev/inspectFile. Are this branches merged?

intelbg commented 6 years ago

Can you please provide me md5 sum of the specific file?

zimmerle commented 6 years ago

@intelbg make sure you are cloning the repository into an empty directory.

intelbg commented 6 years ago

@zimmerle I am sure it's cloned in empty directory. Is my command for git clone right?

victorhora commented 6 years ago

@intelbg inspecFile support was added at https://github.com/SpiderLabs/ModSecurity/commit/1866a3a9eb59e293b12a5ca37513d3763b8bd5bb. See https://github.com/SpiderLabs/ModSecurity/blob/v3/master/src/operators/inspect_file.cc for reference.

"$ git clone -b v3/master https://github.com/SpiderLabs/ModSecurity" should work to clone to a new directory. To update your local repository with the current master "$git pull" should do the trick.

defanator commented 6 years ago

Hi @intelbg, @zimmerle, @victorhora,

I've been digging into libmodsecurity + clamav integration, and googled to this issue, so I decided to post some of my results here instead of creating another one.

The v3/master branch, as of https://github.com/SpiderLabs/ModSecurity/commit/480a2f89d786956bd98e41941c001e6afbd22728, does have working support for the @inspectFile operator.

Test env:

root@vagrant:/# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.4 LTS
Release:    16.04
Codename:   xenial
root@vagrant:/# dpkg -l | fgrep clamav
ii  clamav                              0.99.4+addedllvm-0ubuntu0.16.04.1          amd64        anti-virus utility for Unix - command-line interface
ii  clamav-base                         0.99.4+addedllvm-0ubuntu0.16.04.1          all          anti-virus utility for Unix - base package
ii  clamav-daemon                       0.99.4+addedllvm-0ubuntu0.16.04.1          amd64        anti-virus utility for Unix - scanner daemon
ii  clamav-freshclam                    0.99.4+addedllvm-0ubuntu0.16.04.1          amd64        anti-virus utility for Unix - virus database update utility
ii  libclamav7                          0.99.4+addedllvm-0ubuntu0.16.04.1          amd64        anti-virus utility for Unix - library

ModSecurity config part:

root@vagrant:/# cat /etc/nginx/modsec/main.conf 
Include /etc/nginx/modsec/modsecurity.conf

SecRule FILES_TMPNAMES "@inspectFile /usr/local/bin/runav.pl" \
"phase:2,t:none,deny,msg:'Virus found in uploaded file',id:'399999'"

The /usr/local/bin/runav.pl script - note that it was modified ("1" and "0" in output have been swapped around, see below) and a bit differ from the "official" one at https://github.com/SpiderLabs/owasp-modsecurity-crs/blob/v3.0/master/util/av-scanning/runav.pl:

#!/usr/bin/env perl
#
# runav.pl
# Copyright (c) 2004-2011 Trustwave
#
# This script is an interface between ModSecurity and its
# ability to intercept files being uploaded through the
# web server and ClamAV.

$CLAMDSCAN = "clamdscan";

if ($#ARGV != 0) {
    print "Usage: runav.pl <filename>\n";
    exit;
}

my ($FILE) = shift @ARGV;

$cmd = "$CLAMDSCAN --stdout --no-summary $FILE";

$input = `$cmd`;
$input =~ m/^(.+)/;
$error_message = $1;

$output = "1 Unable to parse clamscan output [$1]";

if ($error_message =~ m/: Empty file\.?$/) {
    $output = "0 empty file";
}
elsif ($error_message =~ m/: (.+) ERROR$/) {
    $output = "1 clamscan: $1";
}
elsif ($error_message =~ m/: (.+) FOUND$/) {
    $output = "1 clamscan: $1";
}
elsif ($error_message =~ m/: OK$/) {
    $output = "0 clamscan: OK";
}

print "$output\n";

Test request for good file:

root@vagrant:/# curl -i -F 'upload=@"/tmp/configs.tar.gz"' http://localhost/modsec-full/upload/a/b/c/
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Server: nginx/1.13.9
Date: Tue, 20 Mar 2018 11:49:43 GMT
Content-Type: text/plain
Content-Length: 38
Connection: keep-alive

5522:4d3d67931da1f3de75c5b156140b174b

Test request for bad file (http://www.eicar.org/86-0-Intended-use.html):

root@vagrant:/# curl -i -F 'upload=@"/tmp/eicar.com"' http://localhost/modsec-full/upload/a/b/c/
HTTP/1.1 100 Continue

HTTP/1.1 403 Forbidden
Server: nginx/1.13.9
Date: Tue, 20 Mar 2018 11:50:34 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive

<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.13.9</center>
</body>
</html>

Log for bad file:

2018/03/20 04:50:34 [info] 18473#18473: *6 ModSecurity: Access denied with code %d (phase 2). Matched "Operator `InspectFile' with parameter `/usr/local/bin/runav.pl' against variable `FILES_TMPNAMES:eicar.com' (Value: `eicar.com' ) [file "/etc/nginx/modsec/main.conf"] [line "31"] [id "399999"] [rev ""] [msg "Virus found in uploaded file"] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/modsec-full/upload/a/b/c/"] [unique_id "152154663498.971791"] [ref "v321,9"], client: 127.0.0.1, server: , request: "POST /modsec-full/upload/a/b/c/ HTTP/1.1", host: "localhost"
2018/03/20 04:50:34 [warn] 18473#18473: *6 [client 127.0.0.1] ModSecurity: Warning. Matched "Operator `InspectFile' with parameter `/usr/local/bin/runav.pl' against variable `FILES_TMPNAMES:eicar.com' (Value: `eicar.com' ) [file "/etc/nginx/modsec/main.conf"] [line "31"] [id "399999"] [rev ""] [msg "Virus found in uploaded file"] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/modsec-full/upload/a/b/c/"] [unique_id "152154663498.971791"] [ref "v321,9"], client: 127.0.0.1, server: , request: "POST /modsec-full/upload/a/b/c/ HTTP/1.1", host: "localhost"

Also, every run of external script from @inspectFile operator produces two log lines in nginx error log (level "notice"):

2018/03/20 05:26:10 [notice] 18473#18473: signal 17 (SIGCHLD) received from 18603
2018/03/20 05:26:10 [notice] 18473#18473: unknown process 18603 exited with code 0

The 18473 in the above example is a pid of nginx worker process - so there is a fork/exec every time @inspectFile is being iterated, and obviously this blocks nginx worker during the execution time.

Finally, I had to modify runav.pl to swap around "1" and "0" in output in order to make it work as expected:

Corresponding parts of ModSecurity debug log, good case:

[4] Starting phase REQUEST_BODY. (SecRules 2)
[9] Multipart: Boundary: ------------------------de27167d9e098491
[9] Multipart: Added part header "Content-Disposition" "form-data; name="upload"; filename="configs.tar.gz"".
[9] Multipart: Added part header "Content-Type" "application/octet-stream".
[9] Multipart: Content-Disposition name: upload.
[9] Multipart: Content-Disposition filename: configs.tar.gz.
[9] Multipart: Added file part to the list: name "upload" file name "configs.tar.gz" (offset 160, length 5314)
[4] Multipart: Cleanup started (remove files Not set)
[9] This phase consists of 5 rule(s).
[4] (Rule: 200002) Executing operator "Eq" with param "0" against REQBODY_ERROR.
[9] Target value: "0" (Variable: REQBODY_ERROR)
[4] Rule returned 0.
[9] Matched vars cleaned.
[4] (Rule: 200003) Executing operator "Eq" with param "0" against MULTIPART_STRICT_ERROR.
[9] Target value: "0" (Variable: MULTIPART_STRICT_ERROR)
[4] Rule returned 0.
[9] Matched vars cleaned.
[4] (Rule: 200004) Executing operator "Eq" with param "0" against MULTIPART_UNMATCHED_BOUNDARY.
[9] Target value: "0" (Variable: MULTIPART_UNMATCHED_BOUNDARY)
[4] Rule returned 0.
[9] Matched vars cleaned.
[4] (Rule: 200005) Executing operator "StrEq" with param "0" against TX:regex(^MSC_).
[4] Rule returned 0.
[9] Matched vars cleaned.
[4] (Rule: 399999) Executing operator "InspectFile" with param "/usr/local/bin/runav.pl" against FILES_TMPNAMES.
[9] Target value: "configs.tar.gz" (Variable: FILES_TMPNAMES:configs.tar.gz)
[4] Rule returned 0.
[9] Matched vars cleaned.

Bad case:

[4] Starting phase REQUEST_BODY. (SecRules 2)
[9] Multipart: Boundary: ------------------------7379d86185e7c6b7
[9] Multipart: Added part header "Content-Disposition" "form-data; name="upload"; filename="eicar.com"".
[9] Multipart: Added part header "Content-Type" "application/octet-stream".
[9] Multipart: Content-Disposition name: upload.
[9] Multipart: Content-Disposition filename: eicar.com.
[9] Multipart: Added file part to the list: name "upload" file name "eicar.com" (offset 155, length 68)
[4] Multipart: Cleanup started (remove files Not set)
[9] This phase consists of 5 rule(s).
[4] (Rule: 200002) Executing operator "Eq" with param "0" against REQBODY_ERROR.
[9] Target value: "0" (Variable: REQBODY_ERROR)
[4] Rule returned 0.
[9] Matched vars cleaned.
[4] (Rule: 200003) Executing operator "Eq" with param "0" against MULTIPART_STRICT_ERROR.
[9] Target value: "0" (Variable: MULTIPART_STRICT_ERROR)
[4] Rule returned 0.
[9] Matched vars cleaned.
[4] (Rule: 200004) Executing operator "Eq" with param "0" against MULTIPART_UNMATCHED_BOUNDARY.
[9] Target value: "0" (Variable: MULTIPART_UNMATCHED_BOUNDARY)
[4] Rule returned 0.
[9] Matched vars cleaned.
[4] (Rule: 200005) Executing operator "StrEq" with param "0" against TX:regex(^MSC_).
[4] Rule returned 0.
[9] Matched vars cleaned.
[4] (Rule: 399999) Executing operator "InspectFile" with param "/usr/local/bin/runav.pl" against FILES_TMPNAMES.
[9] Target value: "eicar.com" (Variable: FILES_TMPNAMES:eicar.com)
[9] Matched vars updated.
[4] Running [independent] (non-disruptive) action: msg
[9] Saving msg: Virus found in uploaded file
[4] Rule returned 1.

That makes me think that there is an error in https://raw.githubusercontent.com/SpiderLabs/owasp-modsecurity-crs/v3.0/master/util/av-scanning/runav.pl - but I'm not sure as I don't have any knowledge on how @inspectFile is working in ModSecurity 2.x - are there any differences in parsing stdout, or in something else?

Hope this helps someone.

victorhora commented 6 years ago

For future reference: https://github.com/SpiderLabs/ModSecurity/issues/1646#issuecomment-382914522