Bali, Indonesia
Insights

Cara Cek Rule ModSecurity Mana yang Memblokir Form Kontak Pelanggan

October 28, 2026
0:00
Pelanggan bilang form kontak di situsnya gagal kirim. Halaman lain terbuka normal. Tapi begitu tombol Kirim ditekan, yang muncul halaman 403 Forbidden. Tidak ada serangan dan tidak ada perubahan kode. Kasus seperti ini sering berujung di satu tempat: ModSecurity menganggap isi form sebagai serangan, padahal isinya cuma pesan biasa. ModSecurity adalah WAF (web application firewall) yang berjalan di web server. Ia memeriksa setiap request dengan sekumpulan rule, dan yang paling umum dipakai adalah OWASP Core Rule Set (CRS). Artikel ini disusun seperti post-mortem. Kita mulai dari dampak, lanjut ke urutan investigasi, momen ketika penyebabnya ketemu, mitigasi sementara, lalu perbaikan yang lebih rapi. Tujuannya satu: WAF tetap menyala, hanya satu rule yang dilonggarkan di tempat yang tepat.
Pola gejalanya cukup khas. Halaman form bisa dibuka (GET berhasil), tapi pengiriman form (POST) dibalas 403. Kadang hanya sebagian pengunjung yang kena. Penyebabnya bukan IP mereka, melainkan isi pesan yang mereka ketik. Pesan yang menyebut kata seperti 'select' dan 'from' dalam satu kalimat, menempelkan potongan HTML, atau memakai banyak tanda kutip dan titik koma bisa terlihat mirip pola SQL injection atau XSS di mata rule. Hal yang sering membingungkan pelanggan: mereka mencoba lagi dengan pesan pendek dan berhasil. Mereka lalu menyimpulkan masalahnya acak. Sebenarnya perilakunya konsisten. Isi yang sama akan selalu diblokir dengan cara yang sama. Sebelum menyalahkan ModSecurity, pastikan dulu 403 itu memang dari server. Kalau situs ada di belakang CDN atau reverse proxy dengan WAF sendiri, atau memakai plugin keamanan, 403 bisa datang dari lapisan lain. Halaman error yang tampil dan header respons biasanya memberi petunjuk. Konfirmasi paling pasti tetap ada di log server: kalau di sana tidak ada jejak ModSecurity pada jam dan URI yang sama, cari di lapisan lain dulu.
Langkah pertama adalah mengumpulkan tiga hal dari pelanggan: URL form, perkiraan waktu kejadian, dan kalau bisa, isi pesan yang gagal. Dengan tiga hal itu kita bisa mencari entri log yang cocok tanpa menebak. ModSecurity menulis ke dua tempat. Error log web server berisi ringkasan satu baris per rule yang cocok. Audit log berisi detail transaksi lengkap, termasuk header dan, tergantung konfigurasi SecAuditLogParts, isi request body. Lokasi kedua file ini beda-beda per distro dan panel. Cara paling aman adalah mencari direktif SecAuditLog dan lokasi error log di konfigurasi server yang sedang kamu tangani. Untuk triase awal, error log biasanya sudah cukup:
BASH
# Cari entri ModSecurity yang menyebut path form (sesuaikan path log dan URI)
grep 'ModSecurity' /var/log/apache2/error.log | grep '/kontak' | tail -n 20

# Ambil semua baris dengan unique_id yang sama untuk melihat rule penyumbang skor
grep 'unique_id "CONTOH_UNIQUE_ID"' /var/log/apache2/error.log
Setiap transaksi punya unique_id. Field inilah yang menyatukan beberapa baris log menjadi satu cerita, dan juga yang menghubungkan error log dengan entri audit log yang sama.
Di sini banyak orang salah langkah. Saat membaca log, baris yang paling mencolok biasanya berbunyi 'Access denied with code 403' dengan rule ID 949110. Refleks pertama adalah menonaktifkan 949110. Jangan. CRS versi modern secara default memakai anomaly scoring. Rule-rule deteksi tidak langsung memblokir. Masing-masing hanya menambah skor ketika cocok. Di akhir fase pemeriksaan request, rule 949110 menjumlahkan skor itu. Kalau totalnya melewati ambang batas, request diblokir. Jadi 949110 hanya hakim yang mengetuk palu. Rule yang memberi 'bukti' adalah baris Warning dengan unique_id yang sama. Contoh format output (disederhanakan, bukan dari insiden nyata):
TEXT
ModSecurity: Warning. ... [id "942100"] [msg "SQL Injection Attack Detected via libinjection"] [data "Matched Data: ... found within ARGS:pesan: ..."] [uri "/kontak/"] [unique_id "CONTOH_UNIQUE_ID"]
ModSecurity: Access denied with code 403 (phase 2). ... [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [uri "/kontak/"] [unique_id "CONTOH_UNIQUE_ID"]
Dari baris Warning ada tiga informasi penting. Pertama, rule ID penyumbang skor (di contoh ini 942100). Kedua, parameter yang memicunya (ARGS:pesan). Ketiga, potongan data yang cocok dengan pola. Bagian terakhir ini yang menentukan apakah ini false positive. Untuk memastikannya, baca isi yang cocok itu dengan akal sehat. Kalau isinya kalimat wajar dari pelanggan, misalnya pertanyaan yang kebetulan mengandung kata-kata mirip SQL, itu false positive. Kalau isinya payload aneh dari IP yang juga mencoba path lain seperti wp-login.php atau file konfigurasi, itu mungkin serangan sungguhan dan rule bekerja sebagaimana mestinya. Cek juga apakah pola blokir yang sama muncul dari banyak IP berbeda yang semuanya mengirim pesan normal. Itu tanda kuat rule-nya terlalu ketat untuk form tersebut. Satu catatan keamanan. Audit log bisa berisi isi form lengkap: nama, email, nomor telepon, isi pesan. Batasi siapa yang bisa membacanya, dan jangan menempelkan potongan mentahnya ke tiket atau chat. Cukup kutip rule ID, nama parameter, dan unique_id.
Kalau form harus segera jalan lagi, godaannya adalah mematikan ModSecurity untuk satu domain. Itu memang cepat, tapi seluruh situs jadi tidak terlindungi, termasuk halaman login dan endpoint lain yang justru sering jadi target. Mitigasi yang lebih masuk akal adalah menonaktifkan rule penyumbang skor hanya untuk path form. Di Apache bisa dengan SecRuleRemoveById di dalam blok LocationMatch yang cocok dengan path form. Panel seperti WHM juga punya menu untuk melihat hit ModSecurity dan menonaktifkan rule. Periksa dulu apakah cakupannya global untuk semua akun atau per domain, karena menonaktifkan rule secara global demi satu form berarti melonggarkan semua situs di server itu. Setelah perubahan apa pun, jalankan apachectl configtest (atau padanannya di web server kamu) sebelum reload. Lalu minta pelanggan mencoba lagi dengan isi pesan yang sama persis. Kalau berhasil dan tidak ada baris Warning baru dari rule itu, mitigasinya bekerja.
Mitigasi tadi masih melepas satu rule untuk seluruh isi request ke path itu. Versi yang lebih rapi hanya mengecualikan parameter yang bermasalah dari pemeriksaan rule tersebut. Rule lain tetap memeriksa parameter itu, dan rule itu tetap memeriksa parameter lain. CRS menyediakan action ctl:ruleRemoveTargetById untuk keperluan ini:
APACHE
# Muat SEBELUM file rule CRS. ID 1000100 hanya contoh; pakai ID lokal yang belum terpakai.
# Untuk request ke /kontak/, rule 942100 berhenti memeriksa parameter 'pesan' saja.
SecRule REQUEST_FILENAME "@beginsWith /kontak/" \
    "id:1000100,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942100;ARGS:pesan"
Ada beberapa hal yang membuat ini lebih tahan lama dibanding mitigasi cepat. Pertama, cakupannya sempit: satu path, satu rule, satu parameter. Kedua, urutan pemuatan penting. Action ctl di fase awal harus dievaluasi sebelum rule CRS berjalan, jadi exclusion seperti ini biasanya ditaruh di file yang di-include sebelum CRS. Ketiga, simpan di file custom milikmu sendiri, bukan di file rule bawaan. File bawaan bisa tertimpa saat rule set diperbarui, dan perbaikanmu akan hilang tanpa ada yang sadar. Di server dengan control panel, lokasi file custom dan cara memuatnya tergantung panel dan versinya. Cek dokumentasinya supaya perubahan tidak hilang saat panel membangun ulang konfigurasi web server. Terakhir, catat exclusion ini di dokumentasi atau komentar file: rule ID, path, parameter, alasan, dan unique_id contoh yang jadi dasarnya. Beberapa bulan kemudian, ketika ada yang bertanya kenapa rule itu dikecualikan, jawabannya sudah ada dan tidak perlu diselidiki ulang. Jadi, setiap ada laporan 403 di form, ambil unique_id dari baris 949110, cari baris Warning dengan ID yang sama, lalu baca Matched Data untuk memutuskan apakah itu false positive. Kalau memang false positive, buat exclusion sesempit mungkin dan simpan di tempat yang tidak tertimpa update. WAF tetap menyala, dan form pelanggan kembali bisa dipakai.