Using WAF on HAProxy with ModSecurity

모드시큐리티를 이용하여 HAProxy에 WAF 달기

ModSecurity

WAF(Web Application Firewall)는 웹 어플리케이션을 보호하기 위해 필수라고 볼 수 있는 꽤나 비싼 장비 혹은 솔루션입니다. ModSecurity(이하 ModSec)은 꽤나 오래전부터 알려져 있는 오픈소스 WAF 입니다.

오픈소스이긴 하지만 상용 WAF에 비해 기능이 떨어지지도 않을 뿐더러, 오히려 많은 상용 솔루션에도 ModSec 기반으로 제작되었거나 룰셋 등 일부 시스템을 차용하기도 합니다.

초기부터 Apache를 기반으로 한 WAF로 유명했던 ModSec 외에 Nginx를 기반으로 한 NAXSI(Nginx Anti XSS & SQL Injection)라는 오픈소스 WAF도 있습니다.

ModSec은 v3 부터 아파치와의 종속성을 없애고 독립적인 모듈로써 어떤 플랫폼과도 지원할 수 있도록 변경되었습니다.

Spoa-ModSecurity

HAProxy의 공식 깃허브에서 제공되고 있는 spoa-modsecurity는 ModSec과 HAProxy를 연동할 수 있도록 개발된 모듈입니다. HAProxy에서는 외부 프로그램의 통신을 위해 SPOE(Stream Processing Offload Engine) 모듈을 개발하였습니다.

spoa-modsecurity는 바로 SPOE를 이용한 모듈이라고 보시면 됩니다.

spoa-modsecurity는 ModSec v2 기반으로 개발되었습니다. 또한 설치 및 설정 가이드는 매우 불친절하게 되어 있으며, 잘못된 내용이 많았습니다. 어렵게 모든 설정을 마무리하고 테스트를 진행했지만 remote address가 127.0.0.1로만 출력 된다거나, ModSec 룰셋 적용이 제대로 안된다거나 하는 문제점들이 있었습니다.

이러한 문제들로 인해 직접 모듈 소스를 수정해서 몇 가지 이슈는 해결되었지만  ModSec v2 기반이라는 점도 마음에 썩 들진 않았었습니다.

그러던 중 fork 된 버전이 있는지 확인하게 되었고, 그 중에서 가장 많은 커밋량과 현재까지도 커밋이 진행되고 있는 FireBurn fork를 찾게 되었습니다. 이 fork에는 제가 발견한 문제점들이 모두 수정되었으며, ModSec v3 버전으로 포팅까지 완료되어 있었습니다.

이 fork 버전 또한 설치 및 설정 가이드는 매우 불친절하고 잘못된 내용이 많았지만 결국 설치에 성공하였고 현재 테스트 중에 있습니다.

이 가이드에서는 HAProxy 설치는 다루지 않으며, spoa-haproxy의 FireBurn fork버전을 이용하여 HAProxy에 ModSec v3를 설치하여 WAF를 구성하는 방법에 대해 다루도록 하겠습니다.

HAProxy 설치는 Using HTTP/3 over QUIC on HAProxy – Umount Blog 또는 Source Install HAProxy 2.0 with TLS 1.3 on CentOS 7 – Umount Blog 문서를 참고해 주시기 바랍니다.

Enviroment

OS: Rocky Linux 9.1
HAProxy 2.6.7
ModSecurity 3.0.8
OWASP ModSecurity CRS 3.3.4

전체 종속성 패키지 설치

[root 17219116 /]# dnf -y install openssl-devel perl pcre-devel zlib-devel systemd-devel net-tools libxml2 libxml2-devel expat-devel curl-devel yajl-devel libevent libevent-devel readline-devel ssdeep ssdeep-devel lua lua-devel libtool autoconf automake pcre2-devel lmdb-devel geolite2-country geolite2-city libmaxminddb-devel libasan

ModSecurity 설치

개인적인 편의를 위해 기본적인 작업 디렉토리를 /usr/local/src/03_application/ 경로로 사용하고 있습니다.

/usr/local/modsecurity 경로에 링크를 거는 이유는 나중에 최신 버전을 설치할때 경로상 복잡함을 없애기 위함입니다. 즉, 최신버전을 설치 후 링크만 변경해 주면 되기 때문입니다.

[root 17219116 /]# cd /usr/local/src/03_application
[root 17219116 03_application]# mkdir -p /etc/modsecurity
[root 17219116 03_application]# wget https://github.com/SpiderLabs/ModSecurity/releases/download/v3.0.8/modsecurity-v3.0.8.tar.gz
[root 17219116 03_application]# tar xvzf modsecurity-v3.0.8.tar.gz
[root 17219116 03_application]# cd modsecurity-v3.0.8
[root 17219116 modsecurity-v3.0.8]# ./configure –prefix=/usr/local/modsecurity-3.0.8 –without-lua –enable-pcre-jit –with-lua –enable-lua-cache -with-pcre2 –with-lmdb
[root 17219116 modsecurity-v3.0.8]# make -j $(nproc)
[root 17219116 modsecurity-v3.0.8]# make install
[root 17219116 modsecurity-v3.0.8]# ln -s /usr/local/modsecurity-3.0.8 /usr/local/modsecurity
[root 17219116 modsecurity-v3.0.8]# cd /usr/local/modsecurity/include/
[root 17219116 include]# cp -rp modsecurity /usr/local/src/03_application/spoa-modsecurity/include/

spoa-modsecurity 설치

아래와 같이 sed를 이용하거나 vi 편집기를 이용하여 Makefile의 내용을 ModSec을 설치한 경로로 반드시 수정해 주셔야 합니다.

[root 17219116 /]# cd /usr/local/src/03_application
[root 17219116 03_application]# git clone https://github.com/FireBurn/spoa-modsecurity.git
[root 17219116 include]# cd /usr/local/src/03_application/spoa-modsecurity/
[root 17219116 spoa-modsecurity]# sed -i ‘s/ModSecurity-v3.0.5\/INSTALL\/usr\/local\/modsecurity\/include/\/usr\/local\/modsecurity\/include\/modsecurity/g’ Makefile
[root 17219116 spoa-modsecurity]# sed -i ‘s/ModSecurity-v3.0.5\/INSTALL\/usr\/local\/modsecurity\/lib/\/usr\/local\/modsecurity\/lib/g’ Makefile
[root 17219116 spoa-modsecurity]# make
[root 17219116 spoa-modsecurity]# make install

편집기를 이용해 modsec의 systemd 파일을 만들어 줍니다.

이때 반드시 아래와 같이 LD_LIBRARY_PATH에 modsec을 설치한 library 경로를 지정해 주어야 합니다.

: vi /usr/lib/systemd/system/modsecurity.service

[Unit]
Description=Modsecurity Standalone
After=network.target
[Service]
Environment=LD_LIBRARY_PATH=/usr/local/modsecurity/lib/
Environment=“CONFIG=/etc/modsecurity/modsecurity.conf” “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

OWASP ModSecurity CRS 설치

OWASP ModSecurity CRS 또한 편의를 위해 링크를 거는 과정을 추가하였습니다.

[root 17219116 spoa-modsecurity]# cd /usr/local/src/03_application
[root 17219116 03_application]# wget -O coreruleset-3.3.4.tar.gz https://github.com/coreruleset/coreruleset/archive/refs/tags/v3.3.4.tar.gz
[root 17219116 03_application]# tar xvzf coreruleset-3.3.4.tar.gz
[root 17219116 03_application]# cp -rp coreruleset-3.3.4 /usr/local/
[root 17219116 03_application]# ln -s /usr/local/coreruleset-3.3.4/ /usr/local/coreruleset
[root 17219116 03_application]# cd /usr/local/coreruleset/
[root 17219116 coreruleset]# cp crs-setup.conf.example crs-setup.conf
[root 17219116 coreruleset]# cd rules/
[root 17219116 rules]# cp REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
[root 17219116 rules]# cp RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
[root 17219116 spoa-modsecurity]# cd /usr/local/src/03_application/modsecurity-v3.0.8/
[root 17219116 modsecurity-v3.0.8]# cp unicode.mapping /etc/modsecurity/unicode.mapping
[root 17219116 modsecurity-v3.0.8]# cp modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
[root 17219116 modsecurity-v3.0.8]# sed -i ‘s/unicode.mapping/\/etc\/modsecurity\/unicode.mapping/g’ /etc/modsecurity/modsecurity.conf

이제 편집기를 이용해 실질적인 룰셋 파일들을 맨 아래라인에 include 해줍니다.

: vi /etc/modsecurity/modsecurity.conf

include /usr/local/coreruleset/crs-setup.conf
include /usr/local/coreruleset/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
include /usr/local/coreruleset/rules/REQUEST-901-INITIALIZATION.conf
include /usr/local/coreruleset/rules/REQUEST-905-COMMON-EXCEPTIONS.conf
include /usr/local/coreruleset/rules/REQUEST-910-IP-REPUTATION.conf
include /usr/local/coreruleset/rules/REQUEST-911-METHOD-ENFORCEMENT.conf
include /usr/local/coreruleset/rules/REQUEST-912-DOS-PROTECTION.conf
include /usr/local/coreruleset/rules/REQUEST-913-SCANNER-DETECTION.conf
include /usr/local/coreruleset/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf
include /usr/local/coreruleset/rules/REQUEST-921-PROTOCOL-ATTACK.conf
include /usr/local/coreruleset/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf
include /usr/local/coreruleset/rules/REQUEST-931-APPLICATION-ATTACK-RFI.conf
include /usr/local/coreruleset/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf
include /usr/local/coreruleset/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf
include /usr/local/coreruleset/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf
include /usr/local/coreruleset/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
include /usr/local/coreruleset/rules/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf
include /usr/local/coreruleset/rules/REQUEST-949-BLOCKING-EVALUATION.conf
include /usr/local/coreruleset/rules/RESPONSE-950-DATA-LEAKAGES.conf
include /usr/local/coreruleset/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf
include /usr/local/coreruleset/rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf
include /usr/local/coreruleset/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
include /usr/local/coreruleset/rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf
include /usr/local/coreruleset/rules/RESPONSE-959-BLOCKING-EVALUATION.conf
include /usr/local/coreruleset/rules/RESPONSE-980-CORRELATION.conf
include /usr/local/coreruleset/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf

HAProxy 설정

[root 17219116 modsecurity-v3.0.8]# cd /etc/haproxy/

modsecurity 관련 설정파일을 생성합니다.

: vi spoe-modsecurity.conf

[modsecurity]
spoe-agent modsecurity-agent
messages check-request
option var-prefix modsec
timeout hello 100ms
timeout idle 30s
timeout processing 15ms
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

haproxy 구성파일을 편집합니다. 아래는 아주 기본적인 haproxy.cfg 파일의 내용이며, modsecurity와 관련한 주요 설정에 하이라이트를 하였습니다.

: vi haproxy.cfg

global
daemon
user haproxy
group haproxy
chroot /var/lib/haproxy
maxconn 4096
log localhost local0
log-send-hostname
stats socket /run/haproxy-master.sock mode 660 level admin
stats timeout 60s
ssl-load-extra-del-ext
ssl-load-extra-files key
ssl-default-bind-options ssl-min-ver TLSv1.2
ssl-default-server-options ssl-min-ver TLSv1.2
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch 1
retries 3
retry-on all-retryable-errors
timeout http-request 180s
timeout client 300s
timeout queue 60s
timeout connect 300s
timeout server 300s
timeout check 10s
timeout http-keep-alive 10s
listen stats
bind 0.0.0.0:14098
stats enable
stats refresh 60s
stats uri /haproxy_status
frontend https-in
bind 0.0.0.0:80
bind 0.0.0.0:443 ssl crt /etc/haproxy/secure/haproxy.umount.net.pem alpn h2
unique-id-format %{+X}o\ %ci:%cp_%fi:%fp_%Ts_%rt:%pid
unique-id-header X-Unique-ID
log-format “%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r %[unique-id]”
http-request redirect scheme https unless { ssl_fc }
http-request set-header X-Forwarded-Proto https if { 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-response set-header server umount
http-response set-header X-Frame-Options SAMEORIGIN;
http-response set-header X-Content-Type-Options nosniff;
http-response set-header X-XSS-Protection “1; mode=block”;
acl umount.net var(txn.txnhost) -m str -i umount.net
http-request set-var(txn.txnhost) hdr(host)
use_backend umount.net if umount.net
backend spoe-modsecurity
mode tcp
balance roundrobin
timeout connect 5s
timeout server 3m
server modsec1 127.0.0.1:12345
backend umount.net
option httpchk
compression algo gzip
compression type text/html text/plain text/css
http-check send meth HEAD uri /health.txt ver HTTP/1.1 hdr Host umount.net
http-check expect status 200
balance roundrobin
http-request disable-l7-retry if METH_POST
cookie SERVERID insert indirect nocache
default-server inter 5s fastinter 3s rise 3 fall 3
server web01 10.19.11.7:8088 cookie web01 check maxconn 2048
server web02 10.19.11.8:8088 cookie web02 check maxconn 2048

파일에 문제가 없는지 테스트 해봅니다. spoe-modsecurity 백엔드에 option forwardfor 관련 경고는 무시하셔도 됩니다.

[root 17219116 haproxy]# haproxy -c -f haproxy.cfg
[NOTICE] (123986) : haproxy version is 2.6.7-c55bfdb
[NOTICE] (123986) : path to executable is /usr/local/sbin/haproxy
[WARNING] (123986) : config : ‘option forwardfor’ ignored for backend ‘spoe-modsecurity’ as it requires HTTP mode.
Warnings were found.
Configuration file is valid

문제가 없으면 modsec 및 haproxy를 systemd에 등록하고 실행합니다.

[root 17219116 haproxy]# systemctl enable –now modsecurity
[root 17219116 haproxy]# systemctl enable –now haproxy
[root 17219116 haproxy]# systemctl status modsecurity
[root 17219116 haproxy]# systemctl status haproxy

WAF 동작 확인

ModSec 및 추가한 CRS가 정상 작동하는지 보기 위해서는 audit 로그를 확인하시면 됩니다. audit 로그는 modsecurity.conf 파일에서 확인 및 수정 가능합니다.

설정파일 수정 후에는 항상 ModSec 서비스를 재시작 해주어야 합니다.

: vi /etc/modsecurity/modsecurity.conf

SecAuditLogType Serial
SecAuditLog /var/log/modsecurity/audit.log
SecAuditLogFormat JSON
[root 17219116 haproxy]# systemctl restart modsecurity

audit 로그 확인을 위해 나쁜짓을 한번 해 보겠습니다.

[root 17219116 haproxy]# curl -I https://umount.net/?../etc/passwd
[root 17219116 haproxy]# cat /var/log/modsecurity/audit.log
modsec_auditlog
Modsecurity_Audit 로그

일부를 모자이크 처리 하긴 했지만 로그가 정상적으로 나오는 것 같습니다. 하지만 json 형식으로 길게 한줄로 나오기 때문에 보기 불편할 수 있습니다. jq 패키지를 이용하면 이쁘게 보실 수 있습니다.

[root 17219116 haproxy]# dnf -y install jq
[root 17219116 haproxy]# cat /var/log/modsecurity/audit.log | jq

지금은 기본 설정으로 되어 있기 때문에 Detected Only 모드로 동작하고 있습니다. 다음에는 ModSecurity의 설정을 통해 탐지 모드에서 차단 모드로 변경하는 방법, 예외처리, 커스텀 룰셋 등에 대해 간략하게 정리해 보도록 하겠습니다.

5 1 vote
Article Rating
Subscribe
Notify of
guest

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.

0 Comments
Inline Feedbacks
View all comments
Scroll to top
0
Would love your thoughts, please comment.x
()
x