From bc9aa78437d14cf26605f58a3c1e17caed8f05bc Mon Sep 17 00:00:00 2001 From: Imran Iqbal Date: Sat, 3 Apr 2021 22:39:56 +0100 Subject: [PATCH] fix(freebsd): fix `modsecurity` suite implementation and tests --- apache/files/FreeBSD/modsecurity.conf.jinja | 241 ++++++++++++++++++ apache/modsecurity.yaml | 7 + .../modules/controls/config_spec.rb | 12 +- .../modules/controls/mod_security_spec.rb | 14 +- .../modules/controls/server_status_spec.rb | 19 +- 5 files changed, 282 insertions(+), 11 deletions(-) create mode 100644 apache/files/FreeBSD/modsecurity.conf.jinja diff --git a/apache/files/FreeBSD/modsecurity.conf.jinja b/apache/files/FreeBSD/modsecurity.conf.jinja new file mode 100644 index 0000000..02cdead --- /dev/null +++ b/apache/files/FreeBSD/modsecurity.conf.jinja @@ -0,0 +1,241 @@ +{%- set apache = pillar.get('apache', {}) %} +{%- set modsec = apache.get('mod_security', {}) %} +{%- set sec_rule_engine = modsec.get('sec_rule_engine', 'DetectionOnly' ) -%} +{%- set sec_request_body_access = modsec.get('sec_request_body_access', 'On' ) -%} +{%- set sec_request_body_limit = modsec.get('sec_request_body_limit', 13107200 ) -%} +{%- set sec_request_body_no_files_limit = modsec.get('sec_request_body_no_files_limit', 131072 ) -%} +{%- set sec_request_body_in_memory_limit = modsec.get('sec_request_body_in_memory_limit', 131072 ) -%} +{%- set sec_request_body_limit_action = modsec.get('sec_request_body_limit_action', 'Reject' ) -%} +{%- set sec_pcre_match_limit = modsec.get('sec_pcre_match_limit', 1000 ) -%} +{%- set sec_pcre_match_limit_recursion = modsec.get('sec_pcre_match_limit_recursion', 1000 ) -%} +{%- set sec_debug_log_level = modsec.get('sec_debug_log_level', 0 ) -%} +# +# This file is managed by Salt! Do not edit by hand! +# Modify the salt pillar that generates this file instead +# +# -- Rule engine initialization ---------------------------------------------- + +# Enable ModSecurity, attaching it to every transaction. Use detection +# only to start with, because that minimises the chances of post-installation +# disruption. +# +SecRuleEngine {{ sec_rule_engine }} + + +# -- Request body handling --------------------------------------------------- + +# Allow ModSecurity to access request bodies. If you don't, ModSecurity +# won't be able to see any POST parameters, which opens a large security +# hole for attackers to exploit. +# +SecRequestBodyAccess {{ sec_request_body_access }} + + +# Enable XML request body parser. +# Initiate XML Processor in case of xml content-type +# +SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\+|/)|text/)xml" \ + "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + +# Enable JSON request body parser. +# Initiate JSON Processor in case of JSON content-type; change accordingly +# if your application does not use 'application/json' +# +SecRule REQUEST_HEADERS:Content-Type "application/json" \ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + +# Maximum request body size we will accept for buffering. If you support +# file uploads then the value given on the first line has to be as large +# as the largest file you are willing to accept. The second value refers +# to the size of data, with files excluded. You want to keep that value as +# low as practical. +# +SecRequestBodyLimit {{ sec_request_body_limit }} +SecRequestBodyNoFilesLimit {{ sec_request_body_no_files_limit }} + +# Store up to 128 KB of request body data in memory. When the multipart +# parser reaches this limit, it will start using your hard disk for +# storage. That is slow, but unavoidable. +# +SecRequestBodyInMemoryLimit {{ sec_request_body_in_memory_limit }} + +# What do do if the request body size is above our configured limit. +# Keep in mind that this setting will automatically be set to ProcessPartial +# when SecRuleEngine is set to DetectionOnly mode in order to minimize +# disruptions when initially deploying ModSecurity. +# +SecRequestBodyLimitAction {{ sec_request_body_limit_action }} + +# Verify that we've correctly processed the request body. +# As a rule of thumb, when failing to process a request body +# you should reject the request (when deployed in blocking mode) +# or log a high-severity alert (when deployed in detection-only mode). +# +SecRule REQBODY_ERROR "!@eq 0" \ +"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + +# By default be strict with what we accept in the multipart/form-data +# request body. If the rule below proves to be too strict for your +# environment consider changing it to detection-only. You are encouraged +# _not_ to remove it altogether. +# +SecRule MULTIPART_STRICT_ERROR "!@eq 0" \ +"id:'200003',phase:2,t:none,log,deny,status:400, \ +msg:'Multipart request body failed strict validation: \ +PE %{REQBODY_PROCESSOR_ERROR}, \ +BQ %{MULTIPART_BOUNDARY_QUOTED}, \ +BW %{MULTIPART_BOUNDARY_WHITESPACE}, \ +DB %{MULTIPART_DATA_BEFORE}, \ +DA %{MULTIPART_DATA_AFTER}, \ +HF %{MULTIPART_HEADER_FOLDING}, \ +LF %{MULTIPART_LF_LINE}, \ +SM %{MULTIPART_MISSING_SEMICOLON}, \ +IQ %{MULTIPART_INVALID_QUOTING}, \ +IP %{MULTIPART_INVALID_PART}, \ +IH %{MULTIPART_INVALID_HEADER_FOLDING}, \ +FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'" + +# Did we see anything that might be a boundary? +# +SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \ +"id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" + +# PCRE Tuning +# We want to avoid a potential RegEx DoS condition +# +SecPcreMatchLimit {{ sec_pcre_match_limit }} +SecPcreMatchLimitRecursion {{ sec_pcre_match_limit_recursion }} + +# Some internal errors will set flags in TX and we will need to look for these. +# All of these are prefixed with "MSC_". The following flags currently exist: +# +# MSC_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded. +# +SecRule TX:/^MSC_/ "!@streq 0" \ + "id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'" + + +# -- Response body handling -------------------------------------------------- + +# Allow ModSecurity to access response bodies. +# You should have this directive enabled in order to identify errors +# and data leakage issues. +# +# Do keep in mind that enabling this directive does increases both +# memory consumption and response latency. +# +SecResponseBodyAccess On + +# Which response MIME types do you want to inspect? You should adjust the +# configuration below to catch documents but avoid static files +# (e.g., images and archives). +# +SecResponseBodyMimeType text/plain text/html text/xml + +# Buffer response bodies of up to 512 KB in length. +SecResponseBodyLimit 524288 + +# What happens when we encounter a response body larger than the configured +# limit? By default, we process what we have and let the rest through. +# That's somewhat less secure, but does not break any legitimate pages. +# +SecResponseBodyLimitAction ProcessPartial + + +# -- Filesystem configuration ------------------------------------------------ + +# The location where ModSecurity stores temporary files (for example, when +# it needs to handle a file upload that is larger than the configured limit). +# +# This default setting is chosen due to all systems have /tmp available however, +# this is less than ideal. It is recommended that you specify a location that's private. +# +SecTmpDir /tmp/ + +# The location where ModSecurity will keep its persistent data. This default setting +# is chosen due to all systems have /tmp available however, it +# too should be updated to a place that other users can't access. +# +SecDataDir /tmp/ + + +# -- File uploads handling configuration ------------------------------------- + +# The location where ModSecurity stores intercepted uploaded files. This +# location must be private to ModSecurity. You don't want other users on +# the server to access the files, do you? +# +#SecUploadDir /opt/modsecurity/var/upload/ + +# By default, only keep the files that were determined to be unusual +# in some way (by an external inspection script). For this to work you +# will also need at least one file inspection rule. +# +#SecUploadKeepFiles RelevantOnly + +# Uploaded files are by default created with permissions that do not allow +# any other user to access them. You may need to relax that if you want to +# interface ModSecurity to an external program (e.g., an anti-virus). +# +#SecUploadFileMode 0600 + + +# -- Debug log configuration ------------------------------------------------- + +# The default debug log configuration is to duplicate the error, warning +# and notice messages from the error log. +# +#SecDebugLog /opt/modsecurity/var/log/debug.log +SecDebugLogLevel {{ sec_debug_log_level }} + + +# -- Audit log configuration ------------------------------------------------- + +# Log the transactions that are marked by a rule, as well as those that +# trigger a server error (determined by a 5xx or 4xx, excluding 404, +# level response status codes). +# +SecAuditEngine RelevantOnly +SecAuditLogRelevantStatus "^(?:5|4(?!04))" + +# Log everything we know about a transaction. +SecAuditLogParts ABIJDEFHZ + +# Use a single file for logging. This is much easier to look at, but +# assumes that you will use the audit log only ocassionally. +# +SecAuditLogType Serial +SecAuditLog /var/log/modsec_audit.log + +# Specify the path for concurrent audit logging. +#SecAuditLogStorageDir /opt/modsecurity/var/audit/ + + +# -- Miscellaneous ----------------------------------------------------------- + +# Use the most commonly used application/x-www-form-urlencoded parameter +# separator. There's probably only one application somewhere that uses +# something else so don't expect to change this value. +# +SecArgumentSeparator & + +# Settle on version 0 (zero) cookies, as that is what most applications +# use. Using an incorrect cookie version may open your installation to +# evasion attacks (against the rules that examine named cookies). +# +SecCookieFormat 0 + +# Specify your Unicode Code Point. +# This mapping is used by the t:urlDecodeUni transformation function +# to properly map encoded data to your language. Properly setting +# these directives helps to reduce false positives and negatives. +# +SecUnicodeMapFile unicode.mapping 20127 + +# Improve the quality of ModSecurity by sharing information about your +# current ModSecurity version and dependencies versions. +# The following information will be shared: ModSecurity version, +# Web Server version, APR version, PCRE version, Lua version, Libxml2 +# version, Anonymous unique id for host. +SecStatusEngine On + diff --git a/apache/modsecurity.yaml b/apache/modsecurity.yaml index 858d6ff..a3a9627 100644 --- a/apache/modsecurity.yaml +++ b/apache/modsecurity.yaml @@ -23,3 +23,10 @@ Suse: manage_config: false package: apache2-mod_security2 config_file: /etc/apache2/conf.d/mod_security2.conf + +FreeBSD: + mod_security: + crs_install: false + manage_config: false + package: ap24-mod_security + config_file: /usr/local/etc/modsecurity/modsecurity.conf diff --git a/test/integration/modules/controls/config_spec.rb b/test/integration/modules/controls/config_spec.rb index eb8443e..43d7eb0 100644 --- a/test/integration/modules/controls/config_spec.rb +++ b/test/integration/modules/controls/config_spec.rb @@ -34,9 +34,11 @@ control 'apache configuration' do when 'bsd' vhostdir = '/usr/local/etc/apache24/Includes' logdir = '/var/log' - # logrotatedir = ? - # moddir = '?' - # sitesdir = '?' + logrotatedir = '/usr/local/etc/logrotate.d/apache2' + moddir = '/usr/local/etc/apache24/modules.d' + # https://docs.freebsd.org/en/books/handbook/network-servers/#_virtual_hosting + # All done under `/usr/local/etc/apache24/httpd.conf` + sitesdir = '/usr/local/etc/apache24' end describe command(apachectl) do its('stdout') { should eq '' } @@ -72,6 +74,7 @@ end control 'apache configuration (unique)' do title 'should match desired lines' + config_file_group = 'root' case platform[:family] when 'debian' config_file = '/etc/apache2/apache2.conf' @@ -90,11 +93,12 @@ control 'apache configuration (unique)' do wwwdir = '/srv/http' when 'bsd' config_file = '/usr/local/etc/apache24/httpd.conf' + config_file_group = 'wheel' wwwdir = '/usr/local/www/apache24/' end describe file(config_file) do it { should be_file } - it { should be_grouped_into 'root' } + it { should be_grouped_into config_file_group } its('mode') { should cmp '0644' } its('content') do should include( diff --git a/test/integration/modules/controls/mod_security_spec.rb b/test/integration/modules/controls/mod_security_spec.rb index 825eb7d..115c00c 100644 --- a/test/integration/modules/controls/mod_security_spec.rb +++ b/test/integration/modules/controls/mod_security_spec.rb @@ -8,20 +8,30 @@ control 'apache mod_security configuration' do end modspec_file = - case platform[:family] + case system.platform[:family] when 'redhat', 'fedora' '/etc/httpd/conf.d/mod_security.conf' when 'debian' '/etc/modsecurity/modsecurity.conf-recommended' when 'suse' '/etc/apache2/conf.d/mod_security2.conf' + when 'bsd' + '/usr/local/etc/modsecurity/modsecurity.conf' + end + + modspec_file_group = + case system.platform[:family] + when 'bsd' + 'wheel' + else + 'root' end describe file(modspec_file) do it { should be_file } its('mode') { should cmp '0644' } its('owner') { should eq 'root' } - its('group') { should eq 'root' } + its('group') { should eq modspec_file_group } its('content') { should match(/SecRuleEngine On/) } its('content') { should match(/SecRequestBodyAccess On/) } its('content') { should match(/SecRequestBodyLimit 14000000/) } diff --git a/test/integration/modules/controls/server_status_spec.rb b/test/integration/modules/controls/server_status_spec.rb index 51f6802..de86ed0 100644 --- a/test/integration/modules/controls/server_status_spec.rb +++ b/test/integration/modules/controls/server_status_spec.rb @@ -13,22 +13,31 @@ control 'apache server_status configuration' do SS_STANZA confdir = - case platform[:family] + case system.platform[:family] when 'debian' '/etc/apache2/conf-available' when 'redhat', 'fedora' '/etc/httpd/conf.d' when 'suse' '/etc/apache2/conf.d' - # `linux` here is sufficient for `arch` - when 'linux' + when 'arch' '/etc/httpd/conf/extra' + when 'bsd' + '/usr/local/etc/apache24/extra' end - describe file("#{confdir}/server-status.conf") do + conffile, conffile_group = + case system.platform[:family] + when 'bsd' + %W[#{confdir}/server-status wheel] + else + %W[#{confdir}/server-status.conf root] + end + + describe file(conffile) do it { should be_file } it { should be_owned_by 'root' } - it { should be_grouped_into 'root' } + it { should be_grouped_into conffile_group } its('mode') { should cmp '0644' } its('content') { should include '# File managed by Salt' } its('content') { should include server_status_stanza }