Recently we had a client application that allowed file uploads which was flooded with various viruses and malware that were uploaded to the server. The client's WAF requirements were to detect the malware before it hit the back-end server and blacklist the offenders. After all, if the client is uploading malware, you probably don't want to allow them access to the rest of your site either. This is, of course, a double edge sword, as it has the possibility of blocking valid IPs, such as those behind a NATted address. In this case, that was not a concern.
ClamAV was the obvious choice for virus detection. ModSecurity even comes with a Perl script to make the configuration easy. The setup is well documented in the ModSecurity Handbook. However running external Perl scripts from ModSecurity may not be the most efficient technique for high traffic sites, especially if the clamscan executable is used.
As I've mentioned before, the ModSecurity rules language is pretty easy to use but at the end of the day can be too rigid as its bound by the Apache configuration syntax. The good news is that ModSecurity can process Lua scripts internally and efficiently, thus we can have the flexibility of a complete scripting language and still have access to the request context from our code.
Before I show the solution created, there are a couple of points I want to note:
- We used clamdscan (and not clamscan) to avoid creating a new instance of the ClamAV engine on every file inspection.
- We added the clamav user to the apache group (assuming apache is the group that runs the httpd)
- We set the SecUploadFileMode direction to 0640 to allow group read.
- The script uses blacklist to blacklist IPs. The script is available here
The Lua script below runs clamdscan on all uploaded files. If a virus is detected, the request is denied, plus the remote IP address is blacklisted for one hour.
This script can be used to inspect uploaded files for viruses
via ClamAV. To implement, use with the following ModSecurity rule:
SecRule FILES_TMPNAMES "@inspectFile /opt/modsecurity/bin/modsec-clamscan.lua"
Author: Josh Amishav-Zlatin
Requires the clamav-daemon and liblua5.1-rex-pcre0 Debian packages (or other
local rex = require "rex_pcre"
local remote_addr = m.getvar("REMOTE_ADDR"); -- Retrieve remote IP address
-- Configure paths
local clamscan = "/usr/bin/clamdscan"
local blacklist = "/opt/modsecurity/bin/blacklist"
local duration = "3600"
-- The system command we want to call
local cmd = clamscan .. " --stdout --disable-summary"
-- Run the command and get the output
local f = io.popen(cmd .. " " .. filename)
local l = f:read("*a")
-- Check the output for the FOUND or ERROR strings which indicate
-- an issue we want to block access on
local isVuln = rex.match(l, "FOUND")
local isError = rex.match(l, "ERROR")
if isVuln ~= nil then
local b = io.popen(blacklist .. " block " .. remote_addr .. " " .. duration)
return "Virus Detected"
elseif isError ~= nil then
return "Error Detected"