Titik Kritis Arsitektur ModSecurity v3
libmodsecurity) yang dipisahkan dari Nginx connector. Secara konseptual, pemisahan ini menjanjikan isolasi proses yang bersih, namun pada beban konkurensi masif, pola alokasi memori internalnya menjadi liabilitas.
Dalam ModSecurity v3, setiap request HTTP menginstansiasi objek transaksi terisolasi (msc_new_transaction). Alokasi memori untuk evaluasi variabel (seperti ARGS_NAMES, REQUEST_HEADERS, REQUEST_BODY) mengandalkan std::string dan container standar C++ yang memicu alokasi heap mikro ribuan kali per transaksi. Pada sistem multithreaded atau multiproses Nginx pekerja tinggi, alokator glibc standar menciptakan ratusan memori arena (malloc arenas) untuk meminimalkan lock contention global.
TEXT
+-------------------------------------------------------------+
| NGINX WORKER |
| |
| +------------------+ +--------------------------+ |
| | HTTP Request Buf | ------> | Nginx ModSec Connector | |
| +------------------+ +--------------------------+ |
| | |
| v |
| +--------------------------+ |
| | libmodsecurity C++ API | |
| | - msc_process_headers() | |
| | - msc_process_body() | |
| +--------------------------+ |
| | |
| +-------------------------------+----------------+ |
| | | |
| v v |
| +------------------+ +------------------+ |
| | PCRE2 Regex JIT | | glibc Heap Arena | |
| | Backtracking | | Fragmentation | |
| +------------------+ +------------------+ |
+-------------------------------------------------------------+Pergeseran Paradigma: Coraza Engine dan Alokasi Zero-Copy
sync.Pool untuk pooling struktur internal transaksi dan buffer evaluasi regex. Alih-alih mendelegasikan alokasi jutaan pointer kecil ke glibc, Coraza mendaur ulang objek transaksi yang sudah selesai. Hal ini menekan aktivitas Go Garbage Collector (GC) secara radikal, membatasi siklus mark-and-sweep bahkan ketika konkurensi melonjak tinggi.
GO
// cuplikan abstraksi pooling transaksi Coraza internal
type TransactionPool struct {
pool sync.Pool
}
func NewTransactionPool(waf WAF) *TransactionPool {
return &TransactionPool{
pool: sync.Pool{
New: func() any {
return waf.NewTransaction()
},
},
}
}
func (p *TransactionPool) Acquire() *Transaction {
tx := p.pool.Get().(*Transaction)
tx.Reset()
return tx
}
func (p *TransactionPool) Release(tx *Transaction) {
p.pool.Put(tx)
}Metodologi Benchmarking dan Profiling
- CPU: AMD EPYC 9654 (128 Cores, 256 Threads)
- RAM: 256 GB DDR5 ECC
- OS: Ubuntu 24.04 LTS (Kernel 6.8.0-38-generic)
- Trafik Generator: k6 cluster running on 4 dedicated bare-metal nodes via 100GbE Mellanox ConnectX-6
- 70% GET benign traffic dengan variasi query parameters.
- 20% POST payload JSON (5 KB - 25 KB) dengan sanitasi field dasar.
- 10% Malicious payload yang memicu signature SQL Injection dan Cross-Site Scripting (XSS).
Profiling ModSecurity v3 dengan Perf
perf pada proses Nginx yang menjalankan ModSecurity v3:
BASH
perf record -F 99 -p $(pgrep -d',' -f 'nginx: worker') -g -- sleep 30
perf report --stdio --no-childrenTEXT
# Overhead Symbol
# ........ .......................................................
24.12% [libpcre2-8.so.0.11.2] pcre2_match_8
18.45% [libc.so.6] _int_malloc
14.20% [libmodsecurity.so.3.0.10] modsecurity::actions::transformations::...
11.83% [libc.so.6] _int_free
8.15% [libmodsecurity.so.3.0.10] modsecurity::Transaction::processBody
5.41% [libpthread.so.0] pthread_mutex_lock_int_malloc dan _int_free) di dalam glibc, memicu lock contention pada arena memori saat multi-thread/multi-core mencoba mengklaim bucket memori yang sama.
Profiling Coraza Engine dengan Go pprof
net/http/pprof:
BASH
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30- 48.2% runtime dialokasikan langsung untuk pencocokan regex dan transformasi rule.
- 8.1% untuk decoding JSON dan multipart parser.
- Alokasi runtime GC (
runtime.gcWork,runtime.mallocgc) terkendali secara konsisten di bawah 7.4% dari total CPU footprint.
Perbandingan Performa Empiris
| Metrik Evaluasi | Nginx + ModSecurity v3 | Nginx / Caddy + Coraza Engine | Delta Relatif |
|---|---|---|---|
| Throughput Stabil | 38.400 RPS (Bottlenecked) | 59.850 RPS | +55.8% |
| P50 Latency | 4.8 ms | 1.2 ms | -75.0% |
| P95 Latency | 38.1 ms | 4.9 ms | -87.1% |
| P99 Latency | 412.0 ms | 12.8 ms | -96.8% |
| Resident Set Size (RSS) | 38.4 GB (Terfragmentasi) | 4.2 GB | -89.0% |
| CPU Utilization (Avg) | 98.2% | 46.5% | -52.6% |
| Context Switches/sec | ~840.000 | ~112.000 | -86.6% |
Konfigurasi Penyetelan untuk Beban Produksi
jemalloc guna mengurangi arena lock contention.
1. Hardening Environment ModSecurity v3 dengan jemalloc
jemalloc ke dalam systemd unit file Nginx:
INI
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=1048576
LimitMEMLOCK=infinity
Environment="LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
Environment="MALLOC_CONF=background_thread:true,metadata_thp:auto,dirty_decay_ms:5000,muzzy_decay_ms:5000"modsecurity.conf untuk membatasi konsumsi memori parsing body:
NGINX
# Batasi alokasi memory buffer untuk request body
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyInMemoryLimit 131072
# Matikan inspeksi response body jika tidak krusial untuk mitigasi kebocoran data
SecResponseBodyAccess Off
# Tuning engine PCRE2 match limits untuk mencegah catastrophic backtracking
SecPcreMatchLimit 150000
SecPcreMatchLimitRecursion 1500002. Implementasi Pipeline Coraza WAF Modern
CADDYFILE
:443 {
bind 10.0.4.15
# TLS Offloading
tls /etc/ssl/certs/edge.crt /etc/ssl/certs/edge.key {
protocols tls1.3
}
coraza_waf {
load_owasp_crs
directives `
Include /etc/coraza/coraza.conf
SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=1"
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 10485760
SecRequestBodyInMemoryLimit 262144
# Konfigurasi tuning performa internal Coraza
SecArgumentsLimit 250
`
}
reverse_proxy 10.0.5.0/24 {
transport http {
keepalive 300s
keepalive_idle_conns 1024
}
}
}BASH
cat << 'EOF' > /etc/sysctl.d/99-waf-performance.conf
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 100000
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65