ReDoS — regex 30 karakter yang bisa bikin server kena 100% CPU
Isi
Regex engine kebanyakan (PCRE, JavaScript, Python re, Java) pakai backtracking. Artinya kalau satu jalur gagal, dia mundur dan coba jalur lain. Masalahnya: kalau pola punya nested quantifier, jumlah jalur yang dicoba tumbuh eksponensial — bukan linear — terhadap panjang input.
Contoh klasik:
^(\w+\s?)*$
Coba input normal "hello world" → cepat. Coba input jahat: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!" (30 a lalu !). Engine bakal coba semua cara membagi 30 a ke dalam grup berulang. Jumlah kombinasinya ~2³⁰ ≈ 1 miliar. Server hang beberapa detik sampai menit, dari satu request. Ini DoS tanpa perlu botnet.
Kenapa (\w+\s?)* berbahaya: \w+ dan \s? bisa sama-sama match karakter yang sama dalam banyak cara berbeda, jadi ada banyak "pembagian" valid yang harus dicoba ulang saat $ gagal.
Pola yang perlu dicurigai (bukan otomatis salah, tapi cek):
| Pola | Kenapa rawan |
|---|---|
(a+)+ |
nested quantifier, klasik |
(a\|aa)+ |
alternasi dengan prefix sama |
(\w+\s?)* |
dua quantifier yang overlap |
(.*),(.*) di string panjang tanpa koma |
dua .* saling rebut |
Kasus nyata (terdokumentasi, bukan karangan): - Cloudflare, 2 Juli 2019 — outage global ~27 menit. Penyebab: satu rule WAF berisi regex yang backtracking-nya meledak, CPU seluruh edge node kena 100%. - Stack Overflow, 2016 — outage karena regex di parser komentar yang catastrophic di input tertentu.
Dua-duanya bukan serangan. Itu bug di kode sendiri yang baru ketahuan saat input kebetulan pas.
Cara mitigasi, urut dari paling murah:
- Batasi panjang input sebelum regex jalan. Kalau field cuma butuh 50 karakter, tolak 10.000 karakter di depan. Ini menutup 80% kasus.
- Sederhanakan pola.
^(\w+\s?)*$bisa diganti^\w+(\s\w+)*$— tidak ada overlap, tidak ada backtracking eksplosif. - Pakai engine linear-time kalau regex-nya dari user/config: RE2 (Go,
google/re2untuk C++),re2npm package, atau Rustregexcrate. Engine ini tidak punya backtracking sama sekali, jadi ReDoS mustahil — trade-off: tidak support backreference dan lookaround. - Kasih timeout / jalankan di worker terpisah. Node tidak punya regex timeout native; solusinya worker thread dengan
terminate(), atau pindah kere2. - Validasi manual untuk hal sederhana. Email, nomor telepon, URL — sering lebih aman pakai parser/library daripada regex buatan sendiri.
Satu hal yang bisa langsung dipakai
Tes regex production-mu sekarang, sebelum ada yang nemuin:
# pola + input jahat, ukur waktunya
time node -e "/(\w+\s?)+\$/.test('a'.repeat(28)+'!')"
Kalau selesai > 1 detik, itu sudah lampu merah. Untuk Python:
python3 -c "import re,time;t=time.time();re.match(r'(\w+\s?)+$','a'*28+'!');print(time.time()-t)"
Naikkan angka 28 sedikit demi sedikit. Kalau waktunya dobel tiap +1 karakter, itu tanda eksponensial murni — bukan cuma "lambat".
Takeaway
Regex yang lambat bukan regex yang jelek — regex yang lambat adalah bug keamanan. Kalau quantifier-nya bersarang, panjang input adalah remote control ke CPU server-mu.