8000 Anti-virus support without need to run external programs by azurit · Pull Request #1986 · coreruleset/coreruleset · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Anti-virus support without need to run external programs #1986

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions crs-setup.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,60 @@ SecDefaultAction "phase:2,log,auditlog,pass"
# setvar:'tx.dos_block_timeout=600'"


#
# -- [[ Anti-virus Protection ]] ------------------------------------
#
# Optional anti-virus protection for uploaded files.
#
# Currently, only ClamAV anti-virus is supported.
#
# Prerequisities for this feature:
# - Lua support must be enabled
# - Library lua-socket must be installed in the system
# - modsecurity 'SecTmpSaveUploadedFiles' directive is 'On' or
# the 'SecUploadKeepFiles' directive is set to either
# 'RelevantOnly' or 'On'
# - ngx_http_lua_module web server module (Nginx only, see
# https://github.com/openresty/lua-nginx-module )
#
# By default, only uploaded files are scanned for viruses. If you
# want to scan also data send by POST/PUT requests (REQUEST_BODY),
# set 'tx.crs_anti-virus_protection_scan_request_body' to 1.
#
# With setting 'tx.crs_anti-virus_clamav_connect_type' you can choose
# between two connection types:
# - tcp: connect using TCP/IP protocol
# - socket: connect using unix socket file
#
# Make sure that setting 'tx.crs_anti-virus_clamav_chunk_size' below
# does not exceed 'StreamMaxLength' as defined in ClamAV
# configuration file clamd.conf.
#
# After enabling, anti-virus scanning should be tested, for example,
# using:
# curl http://localhost --form "data=@eicar.com"
# Get eicar test file from https://www.eicar.org.
#
# Default: no
# Uncomment this rule to change the default.
#
#SecAction \
# "id:900800,\
# phase:1,\
# nolog,\
# pass,\
# t:none,\
# setvar:'tx.crs_anti-virus_protection_enable=1',\
# setvar:'tx.crs_anti-virus_protection_scan_request_body=0',\
# setvar:'tx.crs_anti-virus_clamav_connect_type=socket',\
# setvar:'tx.crs_anti-virus_clamav_socket_file=/var/run/clamav/clamd.ctl',\
# setvar:'tx.crs_anti-virus_clamav_address=127.0.0.1',\
# setvar:'tx.crs_anti-virus_clamav_port=3310',\
# setvar:'tx.crs_anti-virus_clamav_chunk_size=4096',\
# setvar:'tx.crs_anti-virus_clamav_network_timeout_seconds=2',\
# setvar:'tx.crs_anti-virus_clamav_max_file_size_bytes=1048576'"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid the FP's in case of libmodsecurity3 (because the REQUEST_BODY is always presented), we should add these actions:

  ctl:ruleRemoveById=920272,\
  ctl:ruleRemoveById=920273"

or we can combine these exceptions with checking of CT header.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not count on CT as it can be faked to bypass anti-virus check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be we should discuss this on monthly chat. I tried this rule in crs-setup.conf:

SecRule REQUEST_HEADERS:Content-Type "(?:multipart/form-data)" \
    "id:'900801',\
    phase:1,\
    t:none,t:lowercase,\
    pass,\
    nolog,\
    ctl:ruleRemoveTargetById=920272;REQUEST_BODY,\
    ctl:ruleRemoveTargetById=920273;REQUEST_BODY"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will keep this open, i think we need to discuss it with others.



#
# -- [[ Check UTF-8 encoding ]] ------------------------------------------------
#
Expand Down
95 changes: 95 additions & 0 deletions rules/REQUEST-925-ANTIVIRUS.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# ------------------------------------------------------------------------
# OWASP ModSecurity Core Rule Set ver.3.3.0
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
#
# The OWASP ModSecurity Core Rule Set is distributed under
# Apache Software License (ASL) version 2
# Please see the enclosed LICENSE file for full details.
# ------------------------------------------------------------------------

#
# -= Paranoia Level 0 (empty) =- (apply unconditionally)
#



SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 1" "id:925011,phase:1,pass,nolog,skipAfter:END-REQUEST-925-ANTIVIRUS"
SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 1" "id:925012,phase:2,pass,nolog,skipAfter:END-REQUEST-925-ANTIVIRUS"
#
# -= Paranoia Level 1 (default) =- (apply only when tx.executing_paranoia_level is sufficiently high: 1 or higher)
#

SecRule TX:CRS_ANTI-VIRUS_PROTECTION_ENABLE "!@eq 1" \
"id:925100,\
phase:2,\
pass,\
nolog,\
ver:'OWASP_CRS/3.3.0',\
skipAfter:ANTI-VIRUS_PROTECTION"

SecRule FILES_TMPNAMES "@inspectFile anti-virus.lua" \
"id:925110,\
phase:2,\
block,\
t:none,\
msg:'Virus detected in uploaded file: %{tx.virus_name}',\
logdata:'Virus detected in uploaded file: %{tx.virus_name}',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/262/441/442',\
ver:'OWASP_CRS/3.3.0',\
severity:'CRITICAL',\
chain"
SecRule TX:VIRUS_NAME "@streq %{tx.virus_name}" \
"setvar:'tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}'"

SecRule TX:CRS_ANTI-VIRUS_PROTECTION_SCAN_REQUEST_BODY "@eq 1" \
"id:925120,\
phase:2,\
block,\
t:none,\
msg:'Virus detected in request body: %{tx.virus_name}',\
logdata:'Virus detected in request body: %{tx.virus_name}',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/262/441/442',\
ver:'OWASP_CRS/3.3.0',\
severity:'CRITICAL',\
chain"
SecRule REQUEST_BODY "@inspectFile anti-virus.lua" "chain"
SecRule TX:VIRUS_NAME "@streq %{tx.virus_name}" \
"setvar:'tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}'"

SecMarker "ANTI-VIRUS_PROTECTION"


SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 2" "id:925013,phase:1,pass,nolog,skipAfter:END-REQUEST-925-ANTIVIRUS"
SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 2" "id:925014,phase:2,pass,nolog,skipAfter:END-REQUEST-925-ANTIVIRUS"
#
# -= Paranoia Level 2 =- (apply only when tx.executing_paranoia_level is sufficiently high: 2 or higher)
#




SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 3" "id:925015,phase:1,pass,nolog,skipAfter:END-REQUEST-925-ANTIVIRUS"
SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 3" "id:925016,phase:2,pass,nolog,skipAfter:END-REQUEST-925-ANTIVIRUS"
#
# -= Paranoia Level 3 =- (apply only when tx.executing_paranoia_level is sufficiently high: 3 or higher)
#
#



SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 4" "id:925017,phase:1,pass,nolog,skipAfter:END-REQUEST-925-ANTIVIRUS"
SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 4" "id:925018,phase:2,pass,nolog,skipAfter:END-REQUEST-925-ANTIVIRUS"
#
# -= Paranoia Level 4 =- (apply only when tx.executing_paranoia_level is sufficiently high: 4 or higher)
#



#
# -= Paranoia Levels Finished =-
#
SecMarker "END-REQUEST-925-ANTIVIRUS"
131 changes: 131 additions & 0 deletions rules/anti-virus.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
-- Binary packing of numbers into big-endian 4 byte integer (>I4),
-- added for compatibility with Lua version < 5.3.
function clamav_pack_chunk_length(x)
local bytes = {}
for j = 1, 4 do
table.insert(bytes, string.char(x % (2 ^ 8)))
x = math.floor(x / (2 ^ 8))
end
return string.reverse(table.concat(bytes))
end

function main(filename_or_data)
pcall(require, "m")
local chunk_size = tonumber(m.getvar("tx.crs_anti-virus_clamav_chunk_size", "none"))
local connect_type = m.getvar("tx.crs_anti-virus_clamav_connect_type")
if connect_type == "socket" then
module_name = "socket.unix"
elseif connect_type == "tcp" then
module_name = "socket"
else
m.log(2, string.format("ClamAV: invalid value '%s' for 'tx.crs_anti-virus_clamav_connect_type' in crs-setup.conf.", connect_type))
return nil
end
local ok, socket = pcall(require, module_name)
if not ok then
m.log(2, "ClamAV: lua-socket library not installed, please install it or disable 'tx.crs_anti-virus_protection_enable' in crs-setup.conf.")
return nil
end

local file_handle = io.open(filename_or_data, "r")
if file_handle ~= nil then
data_size = file_handle:seek("end")
file_handle:seek("set", 0)
else
data_size = string.len(filename_or_data)
position_from = 1
end
-- Empty data, nothing to scan.
if data_size == 0 then
return nil
elseif data_size > tonumber(m.getvar("tx.crs_anti-virus_clamav_max_file_size_bytes")) then
m.log(2, string.format("ClamAV: Scan aborted, data are too big (see 'tx.crs_anti-virus_clamav_max_file_size_bytes' in crs-setup.conf), data size: %s bytes", data_size))
return nil
end

if connect_type == "socket" then
sck = socket()
elseif connect_type == "tcp" then
sck = socket.tcp()
end
sck:settimeout(tonumber(m.getvar("tx.crs_anti-virus_clamav_network_timeout_seconds", "none")))
-- Connecting using unix socket file.
if connect_type == "socket" then
status, error = sck:connect(m.getvar("tx.crs_anti-virus_clamav_socket_file"))
-- Connecting using TCP/IP.
else
status, error = sck:connect(m.getvar("tx.crs_anti-virus_clamav_address", "none"), tonumber(m.getvar("tx.crs_anti-virus_clamav_port", "none")))
end
if not status then
m.log(2, string.format("ClamAV: Error connecting to antivirus: %s", error))
return nil
end

sck:send("nINSTREAM\n")

-- Data needs to be streamed in chunks prefixed by binary packed chunk size.
-- Streaming is terminated by sending a zero-length chunk.
-- Chunk size is configurable but must NOT exceed StreamMaxLength defined in ClamAV configuration file.
while true do
-- Scanning of uploaded file.
if file_handle ~= nil then
chunk = file_handle:read(chunk_size)
-- Scanning of REQUEST_BODY.
else
if position_from > data_size then
chunk = nil
else
local position_to = position_from + chunk_size
if position_to > data_size then
position_to = data_size
end
chunk = string.sub(filename_or_data, position_from, position_to)
position_from = position_to + 1
end
end
if chunk then
-- string.pack was introduced in Lua 5.3 but we need to support older versions.
--sck:send(string.pack(">I4", string.len(chunk)) .. chunk)
sck:send(clamav_pack_chunk_length(string.len(chunk)) .. chunk)
else
--sck:send(string.pack(">I4", 0))
sck:send(clamav_pack_chunk_length(0))
break
end
end

if file_handle ~= nil then
io.close(file_handle)
end

local output = ""
while true do
local s, status, partial = sck:receive()
if s then
output = output .. s
elseif partial then
output = output .. partial
end
if status == "closed" then
break
elseif status == "timeout" then
m.log(2, "ClamAV: Timeout while scanning file.")
return nil
end
end

sck:close()

if output == "stream: OK" then
return nil
else
local virus_name = string.match(output, "^stream: (.+) FOUND$")
if virus_name then
m.setvar("tx.virus_name", virus_name)
return string.format("ClamAV: Virus found in uploaded file: %s", virus_name)
else
m.log2(string.format("ClamAV: Unknown response from antivirus: %s", output))
return nil
end
end
end
0