모드시큐리티를 이용하여 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 172 – 19 – 11 – 6 /] # 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 172 – 19 – 11 – 6 /] # cd /usr/local/src/03_application
[root 172 – 19 – 11 – 6 03_application] # mkdir -p /etc/modsecurity
[root 172 – 19 – 11 – 6 03_application] # wget https://github.com/SpiderLabs/ModSecurity/releases/download/v3.0.8/modsecurity-v3.0.8.tar.gz
[root 172 – 19 – 11 – 6 03_application] # tar xvzf modsecurity-v3.0.8.tar.gz
[root 172 – 19 – 11 – 6 03_application] # cd modsecurity-v3.0.8
[root 172 – 19 – 11 – 6 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 172 – 19 – 11 – 6 modsecurity-v3.0. 8 ] # make -j $(nproc)
[root 172 – 19 – 11 – 6 modsecurity-v3.0. 8 ] # make install
[root 172 – 19 – 11 – 6 modsecurity-v3.0. 8 ] # ln -s /usr/local/modsecurity-3.0.8 /usr/local/modsecurity
[root 172 – 19 – 11 – 6 modsecurity-v3.0. 8 ] # cd /usr/local/modsecurity/include/
[root 172 – 19 – 11 – 6 include] # cp -rp modsecurity /usr/local/src/03_application/spoa-modsecurity/include/
spoa-modsecurity 설치
아래와 같이 sed를 이용하거나 vi 편집기를 이용하여 Makefile의 내용을 ModSec을 설치한 경로로 반드시 수정해 주셔야 합니다.
[root 172 – 19 – 11 – 6 /] # cd /usr/local/src/03_application
[root 172 – 19 – 11 – 6 03_application] # git clone https://github.com/FireBurn/spoa-modsecurity.git
[root 172 – 19 – 11 – 6 include] # cd /usr/local/src/03_application/spoa-modsecurity/
[root 172 – 19 – 11 – 6 spoa-modsecurity] # sed -i ‘s/ModSecurity-v3.0.5\/INSTALL\/usr\/local\/modsecurity\/include/\/usr\/local\/modsecurity\/include\/modsecurity/g’ Makefile
[root 172 – 19 – 11 – 6 spoa-modsecurity] # sed -i ‘s/ModSecurity-v3.0.5\/INSTALL\/usr\/local\/modsecurity\/lib/\/usr\/local\/modsecurity\/lib/g’ Makefile
[root 172 – 19 – 11 – 6 spoa-modsecurity] # make
[root 172 – 19 – 11 – 6 spoa-modsecurity] # make install
편집기를 이용해 modsec의 systemd 파일을 만들어 줍니다.
이때 반드시 아래와 같이 LD_LIBRARY_PATH에 modsec을 설치한 library 경로를 지정해 주어야 합니다.
: vi /usr/lib/systemd/system/modsecurity.service
Description = Modsecurity Standalone
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
WantedBy = multi-user.target
OWASP ModSecurity CRS 설치
OWASP ModSecurity CRS 또한 편의를 위해 링크를 거는 과정을 추가하였습니다.
[root 172 – 19 – 11 – 6 spoa-modsecurity] # cd /usr/local/src/03_application
[root 172 – 19 – 11 – 6 03_application] # wget -O coreruleset-3.3.4.tar.gz https://github.com/coreruleset/coreruleset/archive/refs/tags/v3.3.4.tar.gz
[root 172 – 19 – 11 – 6 03_application] # tar xvzf coreruleset-3.3.4.tar.gz
[root 172 – 19 – 11 – 6 03_application] # cp -rp coreruleset-3.3.4 /usr/local/
[root 172 – 19 – 11 – 6 03_application] # ln -s /usr/local/coreruleset-3.3.4/ /usr/local/coreruleset
[root 172 – 19 – 11 – 6 03_application] # cd /usr/local/coreruleset/
[root 172 – 19 – 11 – 6 coreruleset] # cp crs-setup.conf.example crs-setup.conf
[root 172 – 19 – 11 – 6 coreruleset] # cd rules/
[root 172 – 19 – 11 – 6 rules] # cp REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
[root 172 – 19 – 11 – 6 rules] # cp RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
[root 172 – 19 – 11 – 6 spoa-modsecurity] # cd /usr/local/src/03_application/modsecurity-v3.0.8/
[root 172 – 19 – 11 – 6 modsecurity-v3.0. 8 ] # cp unicode.mapping /etc/modsecurity/unicode.mapping
[root 172 – 19 – 11 – 6 modsecurity-v3.0. 8 ] # cp modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
[root 172 – 19 – 11 – 6 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 172 – 19 – 11 – 6 modsecurity-v3.0. 8 ] # cd /etc/haproxy/
modsecurity 관련 설정파일을 생성합니다.
: vi spoe-modsecurity.conf
spoe-agent modsecurity-agent
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
stats socket /run/haproxy-master.sock mode 660 level admin
ssl-default-bind-options ssl-min-ver TLSv1.2
ssl-default-server-options ssl-min-ver TLSv1.2
option forwardfor except 127.0 . 0.0 /8
retry- on all-retryable-errors
timeout http-request 180s
timeout http-keep-alive 10s
stats uri /haproxy_status
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
server modsec1 127.0 . 0.1 :12345
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
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 172 – 19 – 11 – 6 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.
Configuration file is valid
문제가 없으면 modsec 및 haproxy를 systemd에 등록하고 실행합니다.
[root 172 – 19 – 11 – 6 haproxy] # systemctl enable –now modsecurity
[root 172 – 19 – 11 – 6 haproxy] # systemctl enable –now haproxy
[root 172 – 19 – 11 – 6 haproxy] # systemctl status modsecurity
[root 172 – 19 – 11 – 6 haproxy] # systemctl status haproxy
WAF 동작 확인
ModSec 및 추가한 CRS가 정상 작동하는지 보기 위해서는 audit 로그를 확인하시면 됩니다. audit 로그는 modsecurity.conf 파일에서 확인 및 수정 가능합니다.
설정파일 수정 후에는 항상 ModSec 서비스를 재시작 해주어야 합니다.
: vi /etc/modsecurity/modsecurity.conf
SecAuditLog /var/log/modsecurity/audit.log
[root 172 – 19 – 11 – 6 haproxy] # systemctl restart modsecurity
audit 로그 확인을 위해 나쁜짓을 한번 해 보겠습니다.
[root 172 – 19 – 11 – 6 haproxy] # curl -I https://umount.net/?../etc/passwd
[root 172 – 19 – 11 – 6 haproxy] # cat /var/log/modsecurity/audit.log
Modsecurity_Audit 로그
일부를 모자이크 처리 하긴 했지만 로그가 정상적으로 나오는 것 같습니다. 하지만 json 형식으로 길게 한줄로 나오기 때문에 보기 불편할 수 있습니다. jq
패키지를 이용하면 이쁘게 보실 수 있습니다.
[root 172 – 19 – 11 – 6 haproxy] # dnf -y install jq
[root 172 – 19 – 11 – 6 haproxy] # cat /var/log/modsecurity/audit.log | jq
Modsecurity_Audit with jq
지금은 기본 설정으로 되어 있기 때문에 Detected Only 모드로 동작하고 있습니다. 다음에는 ModSecurity의 설정을 통해 탐지 모드에서 차단 모드로 변경하는 방법, 예외처리, 커스텀 룰셋 등에 대해 간략하게 정리해 보도록 하겠습니다.