- Add YARA guide, WAF integration docs, and webshell rules - Ignore *.lock files in .gitignore Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
30 KiB
30 KiB
HAProxy WAF 통합 가이드
ModSecurity와 CrowdSec을 HAProxy와 연동하여 웹 애플리케이션 방화벽(WAF)을 구성하는 방법을 설명합니다.
목차
개요
ModSecurity vs CrowdSec
| 구분 | ModSecurity | CrowdSec |
|---|---|---|
| 역할 | WAF (웹 애플리케이션 방화벽) | 침입 탐지/차단 시스템 (IDS/IPS) |
| 분석 대상 | HTTP 요청/응답 내용 | IP 기반 행위 패턴 |
| 탐지 방식 | 시그니처 기반 (OWASP CRS) | 행위 기반 + 커뮤니티 위협 인텔리전스 |
| 차단 범위 | 개별 악성 요청 | IP 단위 (반복 공격자) |
| 주요 탐지 | SQL Injection, XSS, RCE, LFI/RFI | 브루트포스, 스캐닝, DDoS, 알려진 악성 IP |
| 성능 영향 | 높음 (모든 요청 검사) | 낮음 (IP 조회만) |
권장 조합
CrowdSec (먼저) → ModSecurity (나중)
- CrowdSec: 알려진 악성 IP를 빠르게 차단 (낮은 지연)
- ModSecurity: 나머지 요청에 대해 상세 분석 (깊은 검사)
아키텍처
동일 서버 구성 (권장)
┌─────────────────────────────────────────────────────────────────────────┐
│ Host Server │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ HAProxy (host network) │ │
│ │ TCP 80/443 + UDP 443 │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ SPOE Agent │ │ SPOE Agent │ │ │
│ │ │ (CrowdSec) │ │(ModSecurity)│ │ │
│ │ └──────┬──────┘ └──────┬──────┘ │ │
│ └───────────┼───────────────────┼──────────────────────────────────┘ │
│ │ │ │
│ │ TCP :8888 │ TCP :12345 │
│ ▼ ▼ │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ CrowdSec Bouncer │ │ ModSecurity SPOA │ │ CrowdSec Engine │ │
│ │ (haproxy bouncer)│ │ (modsecurity-spoa)│ │ (crowdsec) │ │
│ │ Podman Container │ │ Podman Container │ │ Podman Container │ │
│ │ │ │ │ │ │ │
│ │ - API Key 검증 │ │ - OWASP CRS 3.x │ │ - 로그 분석 │ │
│ │ - LAPI 연동 │ │ - 요청 검사 │ │ - 시나리오 탐지 │ │
│ └─────────┬─────────┘ └───────────────────┘ │ - 알림 발송 │ │
│ │ └─────────┬─────────┘ │
│ │ TCP :8080 │ │
│ └──────────────────────────────────────────────┘ │
│ (LAPI 연동) │
└─────────────────────────────────────────────────────────────────────────┘
별도 서버 구성
┌──────────────────────┐ ┌──────────────────────┐
│ HAProxy Server │ │ Security Server │
│ │ │ │
│ ┌────────────────┐ │ │ ┌────────────────┐ │
│ │ HAProxy │ │ TCP │ │ CrowdSec │ │
│ │ (host network)│──┼──:8888───┼─▶│ Bouncer │ │
│ │ │ │ │ └───────┬────────┘ │
│ │ │──┼─:12345───┼─▶│ │ │
│ └────────────────┘ │ │ │ ModSecurity │ │
│ │ │ │ SPOA │ │
│ │ │ └────────────────┘ │
│ │ │ │
│ │ │ ┌────────────────┐ │
│ │ │ │ CrowdSec │ │
│ │ │ │ Engine │ │
│ │ │ └────────────────┘ │
└──────────────────────┘ └──────────────────────┘
ModSecurity 구성
디렉토리 구조
mkdir -p /opt/haproxy/modsecurity/{conf,rules,logs}
/opt/haproxy/modsecurity/
├── conf/
│ ├── modsecurity.conf # ModSecurity 메인 설정
│ └── crs-setup.conf # OWASP CRS 설정
├── rules/
│ └── (OWASP CRS 룰셋)
├── logs/
│ └── modsec_audit.log
└── Dockerfile
Dockerfile
# /opt/haproxy/modsecurity/Dockerfile
FROM alpine:3.19
# 빌드 의존성 설치
RUN apk add --no-cache \
git \
build-base \
autoconf \
automake \
libtool \
pcre2-dev \
yajl-dev \
curl-dev \
libxml2-dev \
lmdb-dev \
lua5.4-dev \
geoip-dev \
libmaxminddb-dev \
linux-headers \
haproxy
# ModSecurity v3 빌드
WORKDIR /build
RUN git clone --depth 1 https://github.com/owasp-modsecurity/ModSecurity.git && \
cd ModSecurity && \
git submodule init && \
git submodule update && \
./build.sh && \
./configure --with-pcre2 --with-lmdb && \
make -j$(nproc) && \
make install
# SPOA 에이전트 빌드
RUN git clone --depth 1 https://github.com/haproxy/spoa-modsecurity.git && \
cd spoa-modsecurity && \
make MODSEC_INC=/usr/local/modsecurity/include \
MODSEC_LIB=/usr/local/modsecurity/lib \
APACHE2_INC=/usr/include/apache2
# OWASP CRS 다운로드
RUN git clone --depth 1 https://github.com/coreruleset/coreruleset.git /opt/owasp-crs && \
cp /opt/owasp-crs/crs-setup.conf.example /opt/owasp-crs/crs-setup.conf
# 런타임 이미지
FROM alpine:3.19
RUN apk add --no-cache \
pcre2 \
yajl \
libcurl \
libxml2 \
lmdb \
lua5.4-libs \
geoip \
libmaxminddb
# 빌드 결과 복사
COPY --from=0 /usr/local/modsecurity /usr/local/modsecurity
COPY --from=0 /build/spoa-modsecurity/modsecurity /usr/local/bin/modsecurity-spoa
COPY --from=0 /opt/owasp-crs /opt/owasp-crs
# 라이브러리 경로 설정
ENV LD_LIBRARY_PATH=/usr/local/modsecurity/lib
# 설정 디렉토리
VOLUME ["/etc/modsecurity", "/var/log/modsecurity"]
# SPOA 포트
EXPOSE 12345
# 실행
ENTRYPOINT ["/usr/local/bin/modsecurity-spoa"]
CMD ["-f", "/etc/modsecurity/modsecurity.conf", "-a", "0.0.0.0", "-p", "12345", "-n", "4"]
ModSecurity 설정 파일
# /opt/haproxy/modsecurity/conf/modsecurity.conf
# 기본 설정
SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess Off
# 요청 본문 설정
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject
# 임시 파일 경로
SecTmpDir /tmp/
SecDataDir /tmp/
# 로깅 설정
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABIJDEFHZ
SecAuditLogType Serial
SecAuditLog /var/log/modsecurity/modsec_audit.log
# 디버그 로그 (문제 해결시 활성화)
# SecDebugLog /var/log/modsecurity/debug.log
# SecDebugLogLevel 3
# 응답 상태
SecStatusEngine On
# 유니코드 매핑
SecUnicodeMapFile unicode.mapping 20127
# PCRE 설정
SecPcreMatchLimit 100000
SecPcreMatchLimitRecursion 100000
# OWASP CRS 포함
Include /opt/owasp-crs/crs-setup.conf
Include /opt/owasp-crs/rules/*.conf
OWASP CRS 설정
# /opt/haproxy/modsecurity/conf/crs-setup.conf
# Paranoia Level (1-4, 높을수록 엄격)
SecAction \
"id:900000,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.blocking_paranoia_level=1"
# 탐지 Paranoia Level
SecAction \
"id:900001,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.detection_paranoia_level=1"
# 차단 임계값 설정
SecAction \
"id:900110,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.inbound_anomaly_score_threshold=5,\
setvar:tx.outbound_anomaly_score_threshold=4"
# 특정 규칙 제외 (오탐 방지)
# SecRuleRemoveById 920350
# SecRuleRemoveById 942100
# 특정 경로 제외
SecRule REQUEST_URI "@beginsWith /api/upload" \
"id:1001,\
phase:1,\
pass,\
t:none,\
nolog,\
ctl:ruleRemoveById=920420"
# 허용 IP (내부망)
SecRule REMOTE_ADDR "@ipMatch 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10" \
"id:1002,\
phase:1,\
pass,\
t:none,\
nolog,\
ctl:ruleEngine=DetectionOnly"
ModSecurity Quadlet
# /etc/containers/systemd/modsecurity.container
[Unit]
Description=ModSecurity SPOA Agent
After=network-online.target
Wants=network-online.target
[Container]
Image=localhost/modsecurity-spoa:latest
ContainerName=modsecurity
AutoUpdate=local
# 네트워크 설정
PublishPort=12345:12345
# 볼륨 마운트
Volume=/opt/haproxy/modsecurity/conf:/etc/modsecurity:ro,Z
Volume=/opt/haproxy/modsecurity/logs:/var/log/modsecurity:Z
# 환경 변수
Environment=LD_LIBRARY_PATH=/usr/local/modsecurity/lib
# 리소스 제한
Memory=512M
CPUQuota=100%
[Service]
Restart=always
RestartSec=5
TimeoutStartSec=60
[Install]
WantedBy=multi-user.target
ModSecurity 이미지 빌드
# 이미지 빌드
cd /opt/haproxy/modsecurity
podman build -t localhost/modsecurity-spoa:latest .
# Quadlet 활성화
systemctl daemon-reload
systemctl start modsecurity
systemctl enable modsecurity
# 상태 확인
systemctl status modsecurity
podman logs -f modsecurity
CrowdSec 구성
디렉토리 구조
mkdir -p /opt/haproxy/crowdsec/{config,data,hub}
mkdir -p /opt/haproxy/crowdsec-bouncer
/opt/haproxy/crowdsec/
├── config/
│ ├── config.yaml # CrowdSec 메인 설정
│ ├── acquis.yaml # 로그 수집 설정
│ └── local_api_credentials.yaml
├── data/
│ └── (데이터베이스)
└── hub/
└── (시나리오, 파서)
/opt/haproxy/crowdsec-bouncer/
└── config.yaml # HAProxy Bouncer 설정
CrowdSec Engine Quadlet
# /etc/containers/systemd/crowdsec.container
[Unit]
Description=CrowdSec Security Engine
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/crowdsecurity/crowdsec:latest
ContainerName=crowdsec
AutoUpdate=registry
# 네트워크 설정
PublishPort=8080:8080
PublishPort=6060:6060
# 볼륨 마운트
Volume=/opt/haproxy/crowdsec/config:/etc/crowdsec:Z
Volume=/opt/haproxy/crowdsec/data:/var/lib/crowdsec/data:Z
Volume=/opt/haproxy/crowdsec/hub:/var/lib/crowdsec/hub:Z
# HAProxy 로그 접근 (호스트 로그)
Volume=/var/log:/var/log:ro
# 환경 변수
Environment=COLLECTIONS="crowdsecurity/haproxy crowdsecurity/http-cve crowdsecurity/linux"
Environment=GID=1000
[Service]
Restart=always
RestartSec=10
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target
CrowdSec 설정 파일
# /opt/haproxy/crowdsec/config/config.yaml
common:
daemonize: false
log_media: stdout
log_level: info
log_dir: /var/log/
working_dir: /var/lib/crowdsec/data/
config_paths:
config_dir: /etc/crowdsec/
data_dir: /var/lib/crowdsec/data/
simulation_path: /etc/crowdsec/simulation.yaml
hub_dir: /var/lib/crowdsec/hub/
index_path: /var/lib/crowdsec/hub/.index.json
notification_dir: /etc/crowdsec/notifications/
plugin_dir: /usr/local/lib/crowdsec/plugins/
crowdsec_service:
acquisition_path: /etc/crowdsec/acquis.yaml
parser_routines: 1
cscli:
output: human
db_config:
log_level: info
type: sqlite
db_path: /var/lib/crowdsec/data/crowdsec.db
api:
client:
insecure_skip_verify: false
credentials_path: /etc/crowdsec/local_api_credentials.yaml
server:
log_level: info
listen_uri: 0.0.0.0:8080
profiles_path: /etc/crowdsec/profiles.yaml
online_client:
credentials_path: /etc/crowdsec/online_api_credentials.yaml
prometheus:
enabled: true
level: full
listen_addr: 0.0.0.0
listen_port: 6060
로그 수집 설정
# /opt/haproxy/crowdsec/config/acquis.yaml
# HAProxy 로그 (호스트에서 rsyslog/journald로 수집)
filenames:
- /var/log/haproxy.log
labels:
type: haproxy
---
# syslog에서 HAProxy 로그 수집
filenames:
- /var/log/syslog
- /var/log/messages
labels:
type: syslog
---
# HAProxy 컨테이너 로그 (journald)
source: journalctl
journalctl_filter:
- "_SYSTEMD_UNIT=haproxy.service"
labels:
type: haproxy
HAProxy Bouncer Quadlet
# /etc/containers/systemd/crowdsec-bouncer.container
[Unit]
Description=CrowdSec HAProxy Bouncer
After=crowdsec.service
Requires=crowdsec.service
[Container]
Image=docker.io/crowdsecurity/crowdsec-haproxy-bouncer:latest
ContainerName=crowdsec-bouncer
AutoUpdate=registry
# 네트워크 설정 (HAProxy가 연결할 포트)
PublishPort=8888:8888
# 설정 파일
Volume=/opt/haproxy/crowdsec-bouncer:/etc/crowdsec:Z
# 환경 변수 (LAPI 연결 정보)
Environment=CROWDSEC_LAPI_URL=http://host.containers.internal:8080
Environment=CROWDSEC_LAPI_KEY=
[Service]
Restart=always
RestartSec=5
TimeoutStartSec=30
# ExecStartPre에서 API 키 설정
ExecStartPre=/bin/sh -c 'podman exec crowdsec cscli bouncers add haproxy-bouncer -o raw > /opt/haproxy/crowdsec-bouncer/api_key.txt 2>/dev/null || true'
[Install]
WantedBy=multi-user.target
Bouncer 설정 파일
# /opt/haproxy/crowdsec-bouncer/config.yaml
# SPOE 서버 설정
listen_addr: 0.0.0.0:8888
# CrowdSec LAPI 연결
crowdsec_lapi_url: http://host.containers.internal:8080
crowdsec_lapi_key: "${API_KEY}" # 자동 생성됨
# 캐시 설정 (성능 최적화)
cache:
enabled: true
ttl: 60s
size: 10000
# 로깅
log_level: info
log_mode: stdout
# 차단 응답
ban_response_code: 403
ban_response_msg: "Access Denied by CrowdSec"
# Captcha (선택)
captcha:
enabled: false
CrowdSec 초기 설정
# CrowdSec 시작
systemctl daemon-reload
systemctl start crowdsec
systemctl enable crowdsec
# 컬렉션 설치 확인
podman exec crowdsec cscli collections list
# HAProxy 컬렉션 설치 (필요시)
podman exec crowdsec cscli collections install crowdsecurity/haproxy
podman exec crowdsec cscli collections install crowdsecurity/http-cve
# Bouncer API 키 생성
podman exec crowdsec cscli bouncers add haproxy-bouncer -o raw
# 생성된 키를 bouncer 설정에 추가
# /opt/haproxy/crowdsec-bouncer/config.yaml의 crowdsec_lapi_key에 설정
# Bouncer 시작
systemctl start crowdsec-bouncer
systemctl enable crowdsec-bouncer
# 연결 확인
podman exec crowdsec cscli bouncers list
HAProxy 연동
SPOE 설정 파일
CrowdSec SPOE
# /opt/haproxy/conf/crowdsec-spoe.conf
[crowdsec]
spoe-agent crowdsec-agent
messages check-client-ip
option var-prefix crowdsec
timeout hello 2s
timeout idle 2m
timeout processing 10ms
use-backend crowdsec-backend
spoe-message check-client-ip
args src=src dst=dst method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
event on-frontend-http-request
ModSecurity SPOE
# /opt/haproxy/conf/modsecurity-spoe.conf
[modsecurity]
spoe-agent modsecurity-agent
messages check-request
option var-prefix modsec
timeout hello 100ms
timeout idle 30s
timeout processing 1s
use-backend modsecurity-backend
spoe-message check-request
args unique-id=unique-id src-ip=src dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
event on-frontend-http-request
HAProxy 설정 수정
아래 내용을 /opt/haproxy/conf/haproxy.cfg에 추가합니다.
방법 1: CrowdSec만 사용
# haproxy.cfg에 추가 (global 섹션 아래)
# =============================================================================
# CrowdSec 연동 (IP 기반 차단)
# =============================================================================
# CrowdSec Bouncer 백엔드
backend crowdsec-backend
mode tcp
server crowdsec 127.0.0.1:8888 check
# HTTPS Frontend에 SPOE 필터 추가
frontend https_front
# ... 기존 설정 유지 ...
# CrowdSec SPOE 필터
filter spoe engine crowdsec config /usr/local/etc/haproxy/crowdsec-spoe.conf
# CrowdSec 차단 (crowdsec.action이 설정되면 차단)
http-request deny deny_status 403 if { var(sess.crowdsec.action) -m str ban }
http-request deny deny_status 403 if { var(sess.crowdsec.action) -m str captcha }
방법 2: ModSecurity만 사용
# haproxy.cfg에 추가 (global 섹션 아래)
# =============================================================================
# ModSecurity 연동 (WAF)
# =============================================================================
# ModSecurity SPOA 백엔드
backend modsecurity-backend
mode tcp
server modsec 127.0.0.1:12345 check
# HTTPS Frontend에 SPOE 필터 추가
frontend https_front
# ... 기존 설정 유지 ...
# 유니크 ID 생성 (ModSecurity 트랜잭션 추적용)
unique-id-format %{+X}o\ %ci:%cp_%fi:%fp_%Ts_%rt:%pid
unique-id-header X-Unique-ID
# ModSecurity SPOE 필터
filter spoe engine modsecurity config /usr/local/etc/haproxy/modsecurity-spoe.conf
# ModSecurity 차단 (modsec.code가 0이 아니면 차단)
http-request deny deny_status 403 if { var(txn.modsec.code) -m int gt 0 }
방법 3: CrowdSec + ModSecurity 함께 사용 (권장)
# /opt/haproxy/conf/haproxy.cfg
global
# ... 기존 global 설정 유지 ...
log stdout format raw local0
defaults
# ... 기존 defaults 설정 유지 ...
# =============================================================================
# WAF 백엔드 (CrowdSec + ModSecurity)
# =============================================================================
# CrowdSec Bouncer 백엔드
backend crowdsec-backend
mode tcp
server crowdsec 127.0.0.1:8888 check
# ModSecurity SPOA 백엔드
backend modsecurity-backend
mode tcp
server modsec 127.0.0.1:12345 check
# =============================================================================
# HTTPS Frontend (WAF 통합)
# =============================================================================
frontend https_front
bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
bind quic4@:443 ssl crt /etc/haproxy/certs/ alpn h3
http-response set-header alt-svc "h3=\":443\"; ma=86400"
# 유니크 ID 생성
unique-id-format %{+X}o\ %ci:%cp_%fi:%fp_%Ts_%rt:%pid
unique-id-header X-Unique-ID
# =========================================================================
# WAF 필터 (순서 중요: CrowdSec 먼저, ModSecurity 나중)
# =========================================================================
# 1. CrowdSec SPOE (IP 기반 빠른 차단)
filter spoe engine crowdsec config /usr/local/etc/haproxy/crowdsec-spoe.conf
# 2. ModSecurity SPOE (상세 요청 분석)
filter spoe engine modsecurity config /usr/local/etc/haproxy/modsecurity-spoe.conf
# =========================================================================
# 차단 규칙 (순서 중요)
# =========================================================================
# CrowdSec 차단 (알려진 악성 IP)
http-request deny deny_status 403 content-type "text/plain" string "Access denied by CrowdSec" if { var(sess.crowdsec.action) -m str ban }
http-request deny deny_status 403 if { var(sess.crowdsec.action) -m str captcha }
# ModSecurity 차단 (WAF 규칙 위반)
http-request deny deny_status 403 content-type "text/plain" string "Access denied by WAF" if { var(txn.modsec.code) -m int gt 0 }
# =========================================================================
# 내부 IP 우회 (선택사항)
# =========================================================================
# Tailscale IP는 WAF 우회
acl is_tailscale src 100.64.0.0/10
# http-request set-var(txn.skip_waf) bool(true) if is_tailscale
# =========================================================================
# 기존 라우팅 설정 (변경 없음)
# =========================================================================
# MCP 인증
acl is_mcp hdr(host) -i mcp.inouter.com
acl valid_token req.hdr(Authorization) -m str "Bearer dcb7963ab3ef705f6b780818f78942a100efa3b55e3d2f99c4560b65da64c426"
http-request deny deny_status 401 if is_mcp !valid_token !is_tailscale
# Map 기반 라우팅
use_backend %[req.hdr(host),lower,map_dom(/usr/local/etc/haproxy/domains.map)] if { req.hdr(host),lower,map_dom(/usr/local/etc/haproxy/domains.map) -m found }
default_backend default_backend
# ... 나머지 backend 설정 유지 ...
HAProxy 설정 적용
# SPOE 설정 파일 복사 (이미 conf 디렉토리에 있다면 생략)
# HAProxy 컨테이너에서 /usr/local/etc/haproxy/로 마운트됨
# 설정 검증
podman exec haproxy haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg
# HAProxy 리로드 (무중단)
systemctl reload haproxy
# 또는 완전 재시작
systemctl restart haproxy
별도 서버 구성
대규모 트래픽이나 보안 격리가 필요한 경우, CrowdSec과 ModSecurity를 별도 서버에서 운영할 수 있습니다.
HAProxy 서버 설정
# haproxy.cfg (HAProxy 서버)
# CrowdSec Bouncer (원격 서버)
backend crowdsec-backend
mode tcp
server crowdsec 10.0.0.100:8888 check
# ModSecurity SPOA (원격 서버)
backend modsecurity-backend
mode tcp
server modsec 10.0.0.100:12345 check
보안 서버 Quadlet
# /etc/containers/systemd/crowdsec-bouncer.container (보안 서버)
[Container]
# ...
# 모든 인터페이스에서 수신
PublishPort=0.0.0.0:8888:8888
방화벽 설정
# 보안 서버 방화벽
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="HAProxy서버IP" port port="8888" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="HAProxy서버IP" port port="12345" protocol="tcp" accept'
firewall-cmd --reload
테스트 방법
CrowdSec 테스트
# 1. Bouncer 연결 확인
podman exec crowdsec cscli bouncers list
# 2. 현재 차단 목록 확인
podman exec crowdsec cscli decisions list
# 3. 테스트 IP 수동 차단
podman exec crowdsec cscli decisions add --ip 1.2.3.4 --reason "test ban" --duration 1h
# 4. 차단 확인 (403 응답)
curl -I https://yourdomain.com --resolve yourdomain.com:443:1.2.3.4
# 5. 테스트 차단 해제
podman exec crowdsec cscli decisions delete --ip 1.2.3.4
# 6. HAProxy SPOE 통계 확인
echo "show stat" | nc localhost 9999 | grep crowdsec
ModSecurity 테스트
# 1. SQL Injection 테스트
curl -I "https://yourdomain.com/?id=1' OR '1'='1"
# 예상: 403 Forbidden
# 2. XSS 테스트
curl -I "https://yourdomain.com/?q=<script>alert(1)</script>"
# 예상: 403 Forbidden
# 3. Path Traversal 테스트
curl -I "https://yourdomain.com/../../../etc/passwd"
# 예상: 403 Forbidden
# 4. 로그 확인
tail -f /opt/haproxy/modsecurity/logs/modsec_audit.log
# 5. HAProxy SPOE 통계 확인
echo "show stat" | nc localhost 9999 | grep modsec
통합 테스트
# 1. 정상 요청 테스트
curl -I https://yourdomain.com
# 예상: 200 OK
# 2. nikto 스캔 시뮬레이션 (CrowdSec 탐지)
for i in {1..100}; do
curl -s -o /dev/null "https://yourdomain.com/admin" &
done
# 3. 차단 확인
podman exec crowdsec cscli decisions list
# 4. 메트릭 확인
curl http://localhost:6060/metrics | grep crowdsec
운영 명령어
CrowdSec 관리
# =====================
# 차단 목록 관리
# =====================
# 현재 차단 목록 조회
podman exec crowdsec cscli decisions list
# 특정 IP 차단 정보 조회
podman exec crowdsec cscli decisions list --ip 1.2.3.4
# IP 수동 차단
podman exec crowdsec cscli decisions add --ip 1.2.3.4 --reason "manual ban" --duration 24h
# IP 대역 차단
podman exec crowdsec cscli decisions add --range 1.2.3.0/24 --reason "subnet ban" --duration 24h
# 차단 해제
podman exec crowdsec cscli decisions delete --ip 1.2.3.4
# 모든 차단 해제
podman exec crowdsec cscli decisions delete --all
# =====================
# 알림 및 시나리오
# =====================
# 최근 알림 조회
podman exec crowdsec cscli alerts list
# 시나리오 목록
podman exec crowdsec cscli scenarios list
# 파서 목록
podman exec crowdsec cscli parsers list
# =====================
# Hub 관리 (컬렉션/시나리오)
# =====================
# 업데이트 확인
podman exec crowdsec cscli hub update
# 모든 업그레이드
podman exec crowdsec cscli hub upgrade
# 컬렉션 설치
podman exec crowdsec cscli collections install crowdsecurity/nginx
podman exec crowdsec cscli collections install crowdsecurity/http-cve
# =====================
# Bouncer 관리
# =====================
# Bouncer 목록
podman exec crowdsec cscli bouncers list
# Bouncer 추가
podman exec crowdsec cscli bouncers add my-bouncer
# Bouncer 삭제
podman exec crowdsec cscli bouncers delete my-bouncer
# =====================
# 메트릭 및 상태
# =====================
# 엔진 상태
podman exec crowdsec cscli metrics
# Prometheus 메트릭
curl http://localhost:6060/metrics
ModSecurity 관리
# =====================
# 로그 확인
# =====================
# 감사 로그 실시간 확인
tail -f /opt/haproxy/modsecurity/logs/modsec_audit.log
# 차단된 요청만 필터링
grep -A 20 "403" /opt/haproxy/modsecurity/logs/modsec_audit.log
# 특정 규칙 ID로 필터링
grep "942100" /opt/haproxy/modsecurity/logs/modsec_audit.log
# =====================
# 규칙 관리
# =====================
# OWASP CRS 업데이트
cd /opt/haproxy/modsecurity
podman exec modsecurity git -C /opt/owasp-crs pull
# 규칙 비활성화 (crs-setup.conf 또는 별도 파일에 추가)
# SecRuleRemoveById 942100
# 특정 경로 제외
# SecRule REQUEST_URI "@beginsWith /api/webhook" "id:1003,phase:1,pass,nolog,ctl:ruleEngine=Off"
# =====================
# 서비스 관리
# =====================
# 컨테이너 재시작
systemctl restart modsecurity
# 로그 확인
podman logs -f modsecurity
HAProxy WAF 상태 확인
# SPOE 에이전트 상태
echo "show stat" | nc localhost 9999 | grep -E "(crowdsec|modsec)"
# 백엔드 상태
echo "show servers state" | nc localhost 9999 | grep -E "(crowdsec|modsec)"
# 필터 통계
echo "show stat" | nc localhost 9999 | column -t -s ','
화이트리스트 관리
# CrowdSec 화이트리스트 추가
podman exec crowdsec cscli decisions add --ip 10.0.0.1 --type whitelist --duration 87600h --reason "Internal server"
# ModSecurity 화이트리스트 (modsecurity.conf에 추가)
# SecRule REMOTE_ADDR "@ipMatch 10.0.0.1" "id:1,phase:1,pass,nolog,ctl:ruleEngine=Off"
문제 해결
CrowdSec 연결 오류
# Bouncer 연결 확인
podman exec crowdsec cscli bouncers list
# API 키 재생성
podman exec crowdsec cscli bouncers delete haproxy-bouncer
podman exec crowdsec cscli bouncers add haproxy-bouncer -o raw
# Bouncer 재시작
systemctl restart crowdsec-bouncer
ModSecurity 오탐 처리
# 1. 감사 로그에서 규칙 ID 확인
grep "id \"" /opt/haproxy/modsecurity/logs/modsec_audit.log | tail -20
# 2. 규칙 비활성화 (crs-setup.conf)
# SecRuleRemoveById 942100
# 3. 또는 특정 경로만 제외
# SecRule REQUEST_URI "@beginsWith /api/" "id:1,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
# 4. 컨테이너 재시작
systemctl restart modsecurity
HAProxy SPOE 타임아웃
# SPOE 타임아웃 조정 (haproxy.cfg)
spoe-agent crowdsec-agent
timeout processing 100ms # 기본 10ms에서 증가
성능 튜닝
CrowdSec Bouncer 캐시
# /opt/haproxy/crowdsec-bouncer/config.yaml
cache:
enabled: true
ttl: 60s # 캐시 TTL
size: 50000 # 캐시 크기 (IP 수)
ModSecurity 최적화
# modsecurity.conf
SecRequestBodyLimit 1048576 # 1MB로 제한
SecRequestBodyNoFilesLimit 65536 # 64KB
SecPcreMatchLimit 50000 # PCRE 매칭 제한
SecPcreMatchLimitRecursion 50000
HAProxy SPOE 워커
# SPOE 설정
spoe-agent modsecurity-agent
option async # 비동기 처리
option pipelining # 파이프라이닝
max-frame-size 16384 # 프레임 크기