This article is almost identical to my previous post regarding HAProxy on CentOS, but as Centos 8 is going End Of Life at the end of this year I thought I would revisit it on AlmaLinux.
I used the 64-Bit minimal ISO that can be found at the appropriate mirror : https://mirrors.almalinux.org/isos/x86_64/8.4.html . By using the 8.4 version of AlmaLinux , which natively supports OpenSSL 1.1.1 and LUA5.3, there is less package compilation and ongoing updates should be easier to manage as well as supporting TLS 1.3. Going with a minimal install and only installing the components and services you need helps with your security posture and attack surface area.
As before, the first things to install are the EPEL Repo, Development tools and a bunch of pre-reqs for compiling HAProxy and Modsecurity:
dnf -y install epel-release
dnf config-manager --set-enabled powertools
dnf update
dnf groupinstall "Development Tools"
dnf -y install openssl-devel perl pcre-devel zlib-devel systemd-devel wget net-tools libxml2 libxml2-devel expat-devel httpd-devel curl-devel yajl-devel libevent libevent-devel readline-devel ssdeep ssdeep-devel lua lua-devel
Once that is done, change to the /usr/src directory:
cd /usr/src
Before we start on the HAProxy install, you need to make a patch file that makes some adjustments to the HAProxy Modsecurity Makefile so it works with AlmaLinux and Modsecurity 2.9.4.
Create a file in /tmp called spoa-modsecurity_Makefile.patch and put the following in it:
--- Makefile 2019-10-23 07:06:13.000000000 +0100
+++ /root/contrib_modsec_Makefile 2019-11-01 14:09:51.537626204 +0000
@@ -6,19 +6,19 @@
LD = $(CC)
ifeq ($(MODSEC_INC),)
-MODSEC_INC := modsecurity-2.9.1/INSTALL/include
+MODSEC_INC := ModSecurity/INSTALL/include
endif
ifeq ($(MODSEC_LIB),)
-MODSEC_LIB := modsecurity-2.9.1/INSTALL/lib
+MODSEC_LIB := ModSecurity/INSTALL/lib
endif
ifeq ($(APACHE2_INC),)
-APACHE2_INC := /usr/include/apache2
+APACHE2_INC := /usr/include/httpd
endif
ifeq ($(APR_INC),)
-APR_INC := /usr/include/apr-1.0
+APR_INC := /usr/include/apr-1
endif
ifeq ($(LIBXML_INC),)
@@ -35,7 +35,7 @@
CFLAGS += -g -Wall -pthread
INCS += -Iinclude -I$(MODSEC_INC) -I$(APACHE2_INC) -I$(APR_INC) -I$(LIBXML_INC) -I$(EVENT_INC)
-LIBS += -lpthread $(EVENT_LIB) -levent_pthreads -lcurl -lapr-1 -laprutil-1 -lxml2 -lpcre -lyajl
+LIBS += -lpthread $(EVENT_LIB) -levent_pthreads -lcurl -lapr-1 -laprutil-1 -lxml2 -lpcre -lyajl -llua-5.3 -lfuzzy
OBJS = spoa.o modsec_wrapper.o
Making sure your are in /usr/src, download the latest version of HAProxy, check the checksum and once that is done, extract it:
wget http://www.haproxy.org/download/2.4/src/haproxy-2.4.7.tar.gz
wget http://www.haproxy.org/download/2.4/src/haproxy-2.4.7.tar.gz.sha256
sha256sum -c haproxy-2.4.7.tar.gz.sha256
tar xfvz haproxy-2.4.7.tar.gz
Once that is done, change in to the haproxy directory you have just extracted and clone the spoa-modsecurity repo. This is necessary as contributions have been split out from the main package:
cd haproxy-2.4.7
git clone https://github.com/haproxy/spoa-modsecurity.git
Next you have to modify the spoa-modsecurity Makefile using the patch created earlier:
cd spoa-modsecurity
patch Makefile < /tmp/spoa-modsecurity_Makefile.patch
Once the Makefile has been patched clone the modsecurity 2.9.4 repo:
git clone --branch v2/master https://github.com/SpiderLabs/ModSecurity.git
Once the repo has been cloned, carry out the following to configure, build and install the standalone modsecurity module:
cd ModSecurity
./autogen.sh
./configure --prefix=$PWD/INSTALL --disable-apache2-module --enable-standalone-module --enable-pcre-study --enable-pcre-jit --enable-lua-cache
make
make -C standalone install
mkdir -p $PWD/INSTALL/include
cp standalone/*.h $PWD/INSTALL/include
cp apache2/*.h $PWD/INSTALL/include
cd ..
make
make install
Once the install has finished, it is time to install HAProxy:
cd ..
mkdir -p /etc/haproxy/cert
make TARGET=linux-glibc USE_PCRE=1 USE_OPENSSL=1 USE_ZLIB=1 USE_SYSTEMD=1
make install
id -u haproxy &> /dev/null || useradd -s /usr/sbin/nologin -r haproxy
Next thing to tackle is the OWASP Rules, so download and extract those then set up a symbolic link to help with updates:
cd /opt/
wget https://github.com/coreruleset/coreruleset/archive/refs/tags/v3.3.2.tar.gz
tar xfvz v3.3.2.tar.gz
ln -s coreruleset-3.3.2 /opt/owasp-modsecurity-crs
With the rules extracted you need to manipulate a few files:
cd owasp-modsecurity-crs
cp crs-setup.conf.example crs-setup.conf
cd rules
mv REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
mv RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
And copy out a few:
mkdir /opt/modsecurity
cp /usr/src/haproxy-2.4.7/spoa-modsecurity/ModSecurity/unicode.mapping /opt/modsecurity/unicode.mapping
cp /usr/src/haproxy-2.4.7/spoa-modsecurity/ModSecurity/modsecurity.conf-recommended /opt/modsecurity/modsecurity.conf
In order for modsecurity to know about the CRS config and rules (and avoid a potential issue mentioned in the contrib README) you need to append the following to the end of /opt/modsecurity/modsecurity.conf:
include /opt/owasp-modsecurity-crs/crs-setup.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-901-INITIALIZATION.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-905-COMMON-EXCEPTIONS.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-910-IP-REPUTATION.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-911-METHOD-ENFORCEMENT.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-912-DOS-PROTECTION.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-913-SCANNER-DETECTION.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-921-PROTOCOL-ATTACK.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-931-APPLICATION-ATTACK-RFI.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf
include /opt/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf
include /opt/owasp-modsecurity-crs/rules/RESPONSE-950-DATA-LEAKAGES.conf
include /opt/owasp-modsecurity-crs/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf
include /opt/owasp-modsecurity-crs/rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf
include /opt/owasp-modsecurity-crs/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
include /opt/owasp-modsecurity-crs/rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf
include /opt/owasp-modsecurity-crs/rules/RESPONSE-959-BLOCKING-EVALUATION.conf
include /opt/owasp-modsecurity-crs/rules/RESPONSE-980-CORRELATION.conf
include /opt/owasp-modsecurity-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
There is one other possible change needed to /opt/modsecurity/modsecurity.conf to ensure it can find the unicode.mapping file. Find the section below and ensure it points to the correct location:
# 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 /opt/modsecurity/unicode.mapping 20127
As the standalone modsecurity module is based on spoe you need to create a config file. Create /etc/haproxy/spoe-modsecurity.conf containing the following:
[modsecurity]
spoe-agent modsecurity-agent
messages check-request
option var-prefix modsec
timeout hello 100ms
timeout idle 30s
timeout processing 1s
use-backend spoe-modsecurity
spoe-message check-request
args unique-id method path query req.ver req.hdrs_bin req.body_size req.body
event on-frontend-http-request
The next file to create is your HAProxy config file, so create /etc/haproxy/haproxy.cfg containing your haproxy configuration, mine was:
global
maxconn 20480
ssl-dh-param-file /etc/haproxy/dhparam.pem
log 127.0.0.1 local0
stats socket 127.0.0.1:14567
tune.ssl.default-dh-param 2048
server-state-file /tmp/haproxy_server_state
ssl-default-bind-options ssl-min-ver TLSv1.2
ssl-default-server-options ssl-min-ver TLSv1.2
ssl-default-bind-ciphers TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:TLS13-CHACHA20-POLY1305-SHA256:EECDH+AESGCM:EECDH+CHACHA20:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256
ssl-default-server-ciphers TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:TLS13-CHACHA20-POLY1305-SHA256:EECDH+AESGCM:EECDH+CHACHA20:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256
defaults
log global
mode http
option httplog
timeout connect 5s
timeout client 50s
timeout server 50s
# Newly added timeouts
timeout http-request 10s
timeout http-keep-alive 2s
timeout queue 5s
timeout tunnel 2m
timeout client-fin 1s
timeout server-fin 1s
frontend myfrontend
# primary cert is /etc/haproxy/cert/haproxy.pem
bind *:443 ssl crt /etc/haproxy/cert/haproxy.pem alpn h2,http/1.1
option http-keep-alive
option forwardfor
acl https ssl_fc
filter spoe engine modsecurity config /etc/haproxy/spoe-modsecurity.conf
http-request deny if { var(txn.modsec.code) -m int gt 0 }
http-request set-header X-Forwarded-Proto http if !https
http-request set-header X-Forwarded-Proto https if https
timeout client 30000
acl ACL1 var(txn.txnhost) -m str -i haproxy.domain.tld
http-request set-var(txn.txnhost) hdr(host)
use_backend example1 if ACL1
backend spoe-modsecurity
mode tcp
balance roundrobin
timeout connect 5s
timeout server 3m
server modsec1 127.0.0.1:12345
backend example1
# a https backend
http-response set-header Strict-Transport-Security max-age=31536000;\ includeSubDomains;\ preload;
http-response set-header X-Frame-Options DENY
http-response set-header X-XSS-Protection 1;mode=block
http-response set-header X-Content-Type-Options nosniff
http-response set-header Referrer-Policy same-origin
http-response set-header Cache-Control private,\ no-cache,\ no-store,\ max-age=0,\ no-transform,\ must-revalidate
http-response set-header Pragma no-cache
http-response del-header Server
http-response del-header X-Powered-By
server backendhost backendhost.otherdomain.tld:443 ssl verify none
The important lines in this from a modsecurity point of view are:
Frontend
....
filter spoe engine modsecurity config /etc/haproxy/spoe-modsecurity.conf
http-request deny if { var(txn.modsec.code) -m int gt 0 }
....
backend spoe-modsecurity
mode tcp
balance roundrobin
timeout connect 5s
timeout server 3m
server modsec1 127.0.0.1:12345
These define the modsecurity spoe filter with associated config file and the backend referenced in that config file.
Once you have done that make sure your certificate for your listener exists as /etc/haproxy/cert/haproxy.pem and set up your Diffie-Hellman parameters file by running:
openssl dhparam -out /etc/haproxy/dhparam.pem 2048
With that completed , we are now in to the home straight. Ideally we need HAProxy and the standalone modsecurity module to run as services. So lets get that sorted.
Created a service file for HAProxy, /lib/systemd/system/haproxy.service , containing the following:
[Unit]
Description=HAProxy Load Balancer
After=network.target
[Service]
EnvironmentFile=-/etc/default/haproxy
EnvironmentFile=-/etc/sysconfig/haproxy
Environment="CONFIG=/etc/haproxy/haproxy.cfg" "PIDFILE=/run/haproxy.pid" "EXTRAOPTS=-S /run/haproxy-master.sock"
ExecStartPre=/usr/local/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS
ExecStart=/usr/local/sbin/haproxy -Ws -f $CONFIG -p $PIDFILE $EXTRAOPTS
ExecReload=/usr/local/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed
Restart=always
SuccessExitStatus=143
Type=notify
[Install]
WantedBy=multi-user.target
Then create a service file for modsecurity, /lib/systemd/system/modsecurity.service, containing the following:
[Unit]
Description=Modsecurity Standalone
After=network.target
[Service]
EnvironmentFile=-/etc/default/modsecurity
EnvironmentFile=-/etc/sysconfig/modsecurity
Environment="CONFIG=/opt/modsecurity/modsecurity.conf" "PIDFILE=/run/modesecurity.pid" "EXTRAOPTS=-d -n 1"
ExecStart=/usr/local/bin/modsecurity $EXTRAOPTS -f $CONFIG
ExecReload=/usr/local/bin/modsecurity $EXTRAOPTS -f $CONFIG
ExecReload=/bin/kill -USR2 $MAINPID
Restart=always
Type=simple
[Install]
WantedBy=multi-user.target
Enable and Start both services by running:
systemctl enable modsecurity.service
systemctl enable haproxy.service
systemctl start modsecurity.service
systemctl start haproxy.service
The HAProxy and modsecurity processes then launch and listen on their respective ports:
[root@haproxy opt]# netstat -natp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:14567 0.0.0.0:* LISTEN 8007/haproxy
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 889/sshd
tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN 7981/modsecurity
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 8007/haproxy
If you havent already allowed HTTPS in through the host firewall, make sure you do:
firewall-cmd --zone=public --add-service=https --permanent
firewall-cmd --reload
You can confirm the firewall has updated by listing the services:
[root@haproxy haproxy]# firewall-cmd --zone=public --list-services
cockpit dhcpv6-client https ssh
You can then test things work; The initial mode that is configured is Detection Only, which is why the Headers that get added by the backend get returned:
curl -I https://haproxy.domain.tld/?../etc/passwd
HTTP/2 302
date: Thu, 28 Oct 2021 10:50:53 GMT
content-security-policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' ; object-src 'self' ; worker-src 'self' blob:
content-type: text/html
last-modified: Thu, 28 Oct 2021 10:50:53 GMT
accept-ranges: bytes
location: /?../etc/passwd/
strict-transport-security: max-age=31536000; includeSubDomains; preload;
x-frame-options: DENY
x-xss-protection: 1;mode=block
x-content-type-options: nosniff
referrer-policy: same-origin
cache-control: private, no-cache, no-store, max-age=0, no-transform, must-revalidate
pragma: no-cache
But you can see in the modsecurity logs via journalctl, that the attempted “attack” is being detected:
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029525 [00] <17> New Client connection accepted and assigned to worker 01
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029564 [01] <17> read_frame_cb
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029573 [01] <17> New Frame of 129 bytes received
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029577 [01] <17> Decode HAProxy HELLO frame
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029580 [01] <17> Supported versions : 2.0
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029584 [01] <17> HAProxy maximum frame size : 16380
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029587 [01] <17> HAProxy capabilities : pipelining,async
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029590 [01] <17> HAProxy supports frame pipelining
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029592 [01] <17> HAProxy supports asynchronous frame
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029594 [01] <17> HAProxy engine id : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029597 [01] <17> Encode Agent HELLO frame
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029600 [01] <17> Agent version : 2.0
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029601 [01] <17> Agent maximum frame size : 16380
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029603 [01] <17> Agent capabilities :
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029611 [01] <17> write_frame_cb
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.029649 [01] <17> Frame of 54 bytes send
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.030394 [01] <17> read_frame_cb
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.030409 [01] <17> New Frame of 128 bytes received
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.030411 [01] <17> Decode HAProxy NOTIFY frame
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.030413 [01] <17> STREAM-ID=62 - FRAME-ID=1 - unfragmented frame received - frag_len=0 - len=128 - offset=7
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.030422 [01] Process frame messages : STREAM-ID=62 - FRAME-ID=1 - length=121 bytes
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.030425 [01] Process SPOE Message 'check-request'
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.031793 [00] [client 127.0.0.1] ModSecurity: Warning. Matched phrase "etc/passwd" at ARGS_NAMES:../etc/passwd. [fi>
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.031925 [00] [client 127.0.0.1] ModSecurity: Warning. Matched phrase "etc/passwd" at ARGS_NAMES:../etc/passwd. [fi>
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.032226 [00] [client 127.0.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:anomaly_score. [file "/opt/owasp>
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.032341 [00] [client 127.0.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:inbound_anomaly_score. [file "/o>
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.032486 [01] Encode Agent ACK frame
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.032493 [01] STREAM-ID=62 - FRAME-ID=1
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.032495 [01] Add action : set variable code=4294967195
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.032506 [01] <17> write_frame_cb
Oct 28 11:50:50 haproxy.domain.tld modsecurity[7981]: 1635418250.032542 [01] <17> Frame of 30 bytes send
Oct 28 11:50:51 haproxy.domain.tld modsecurity[7981]: 1635418251.433138 [01] 1 clients connected
In order for things to work correctly you need to enable the engine in /opt/modsecurity/modsecurity.conf:
SecRuleEngine On
And change the default action in /opt/owasp-modsecurity-crs/crs-setup.conf:
# Default: Anomaly Scoring mode, log to error log, log to ModSecurity audit log
# - By default, offending requests are blocked with an error 403 response.
# - To change the disruptive action, see RESPONSE-999-EXCEPTIONS.conf.example
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
#SecDefaultAction "phase:1,log,auditlog,pass"
#SecDefaultAction "phase:2,log,auditlog,pass"
# Example: Anomaly Scoring mode, log only to ModSecurity audit log
# - By default, offending requests are blocked with an error 403 response.
# - To change the disruptive action, see RESPONSE-999-EXCEPTIONS.conf.example
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
# SecDefaultAction "phase:1,nolog,auditlog,pass"
# SecDefaultAction "phase:2,nolog,auditlog,pass"
# Example: Self-contained mode, return error 403 on blocking
# - In this configuration the default disruptive action becomes 'deny'. After a
# rule triggers, it will stop processing the request and return an error 403.
# - You can also use a different error status, such as 404, 406, et cetera.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
SecDefaultAction "phase:1,log,auditlog,deny,status:403"
SecDefaultAction "phase:2,log,auditlog,deny,status:403"
Then restart modsecurity:
systemctl restart modsecurity.service
Testing now shows the block with HTTP 403:
curl -I https://haproxy.domain.tld/?../etc/passwd
HTTP/2 403
content-length: 93
cache-control: no-cache
content-type: text/html
And Journalctl also shows the block:
Oct 28 12:00:02 haproxy.domain.tld modsecurity[8101]: 1635418802.906768 [00] ModSecurity for nginx (STABLE)/2.9.4 (http://www.modsecurity.org/) configured.
Oct 28 12:00:02 haproxy.domain.tld modsecurity[8101]: 1635418802.906823 [00] ModSecurity: APR compiled version="1.6.3"; loaded version="1.6.3"
Oct 28 12:00:02 haproxy.domain.tld modsecurity[8101]: 1635418802.906831 [00] ModSecurity: PCRE compiled version="8.42 "; loaded version="8.42 2018-03-20"
Oct 28 12:00:02 haproxy.domain.tld modsecurity[8101]: 1635418802.906833 [00] ModSecurity: LUA compiled version="Lua 5.3"
Oct 28 12:00:02 haproxy.domain.tld modsecurity[8101]: 1635418802.906836 [00] ModSecurity: YAJL compiled version="2.1.0"
Oct 28 12:00:02 haproxy.domain.tld modsecurity[8101]: 1635418802.906838 [00] ModSecurity: LIBXML compiled version="2.9.7"
Oct 28 12:00:02 haproxy.domain.tld modsecurity[8101]: 1635418802.906895 [00] ModSecurity: StatusEngine call: "2.9.4,nginx,1.6.3/1.6.3,8.42/8.42 2018-03-20,Lua 5.3,2.9.7,964323a65>
Oct 28 12:00:03 haproxy.domain.tld modsecurity[8101]: 1635418803.028566 [00] ModSecurity: StatusEngine call successfully sent. For more information visit: http://status.modsecuri>
Oct 28 12:00:03 haproxy.domain.tld modsecurity[8101]: 1635418803.031965 [00] Worker 01 initialized
Oct 28 12:00:03 haproxy.domain.tld modsecurity[8101]: 1635418803.032009 [00] Server is ready [fragmentation=false - pipelining=false - async=false - debug=true - max-frame-size=1>
Oct 28 12:00:03 haproxy.domain.tld modsecurity[8101]: 1635418803.032051 [01] Worker ready to process client messages
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583223 [00] <1> New Client connection accepted and assigned to worker 01
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583263 [01] <1> read_frame_cb
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583307 [01] <1> New Frame of 129 bytes received
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583312 [01] <1> Decode HAProxy HELLO frame
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583319 [01] <1> Supported versions : 2.0
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583323 [01] <1> HAProxy maximum frame size : 16380
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583325 [01] <1> HAProxy capabilities : pipelining,async
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583329 [01] <1> HAProxy supports frame pipelining
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583332 [01] <1> HAProxy supports asynchronous frame
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583334 [01] <1> HAProxy engine id : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583338 [01] <1> Encode Agent HELLO frame
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583342 [01] <1> Agent version : 2.0
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583343 [01] <1> Agent maximum frame size : 16380
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583345 [01] <1> Agent capabilities :
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583355 [01] <1> write_frame_cb
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.583396 [01] <1> Frame of 54 bytes send
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.584082 [01] <1> read_frame_cb
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.584097 [01] <1> New Frame of 128 bytes received
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.584100 [01] <1> Decode HAProxy NOTIFY frame
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.584102 [01] <1> STREAM-ID=64 - FRAME-ID=1 - unfragmented frame received - frag_len=0 - len=128 - offset=7
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.584110 [01] Process frame messages : STREAM-ID=64 - FRAME-ID=1 - length=121 bytes
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.584113 [01] Process SPOE Message 'check-request'
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.585807 [00] [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 2). Matched phrase "etc/passwd" at>
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.586022 [01] Encode Agent ACK frame
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.586049 [01] STREAM-ID=64 - FRAME-ID=1
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.586055 [01] Add action : set variable code=403
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.586065 [01] <1> write_frame_cb
Oct 28 12:00:06 haproxy.domain.tld modsecurity[8101]: 1635418806.586099 [01] <1> Frame of 22 bytes send
Oct 28 12:00:08 haproxy.domain.tld modsecurity[8101]: 1635418808.032927 [01] 1 clients connected
One last thing to confirm is the supported ciphers, a quick nmap scan takes care of that:
Starting Nmap 7.92 ( https://nmap.org ) at 2021-10-28 12:26 GMT Summer Time
Nmap scan report for haproxy (xxx.xxx.xxx.xxx)
Host is up (0.0015s latency).
Not shown: 90 filtered tcp ports (no-response), 8 filtered tcp ports (admin-prohibited)
PORT STATE SERVICE
22/tcp open ssh
443/tcp open https
| ssl-enum-ciphers:
| TLSv1.2:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (ecdh_x25519) - A
| compressors:
| NULL
| cipher preference: server
| TLSv1.3:
| ciphers:
| TLS_AKE_WITH_AES_128_CCM_SHA256 (ecdh_x25519) - A
| TLS_AKE_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
| TLS_AKE_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
| TLS_AKE_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
| cipher preference: client
|_ least strength: A
That all looks good. Hopefully someone will find this of some use. Take Care.