Dynamic IP Denylisting dengan NGINX Plus dan fail2ban

Dynamic IP Denylisting dengan NGINX Plus dan fail2ban

Artikel ini berdasarkan postingan blog NGINX asli oleh Liam Crilly dari F5, diterbitkan 19 September 2017.

Mungkin kamu tidak menyadarinya, tapi website kamu terus-menerus berada di bawah ancaman. Jika menjalankan WordPress, bot sedang mencoba melakukan spam. Jika ada halaman login, ada serangan brute-force password. Kamu mungkin juga menganggap spider search engine sebagai pengunjung yang tidak diinginkan.

Mempertahankan situs dari aktivitas yang tidak diinginkan, mencurigakan, dan berbahaya bukanlah tugas yang mudah. Web application firewall adalah alat yang efektif dan harus dipertimbangkan sebagai bagian dari security stack kamu. Untuk sebagian besar lingkungan, tidak ada yang namanya terlalu banyak keamanan, dan pendekatan berlapis selalu menjadi yang paling efektif.

Dalam postingan ini, kita akan membahas penggunaan fail2ban sebagai lapisan lain dari web security stack. Fail2ban adalah sistem deteksi intrusi (IDS) yang terus memantau file log untuk aktivitas mencurigakan, lalu mengambil satu atau lebih tindakan yang telah dikonfigurasi. Biasanya fail2ban memantau percobaan login yang gagal kemudian memblokir (ban) alamat IP yang bermasalah untuk jangka waktu tertentu. Ini adalah pertahanan yang sederhana namun efektif terhadap serangan brute-force password.

Pada NGINX Plus R13, sebuah key-value store native dan NGINX Plus API baru diperkenalkan. Ini memungkinkan solusi konfigurasi dinamis di mana perilaku NGINX Plus dapat dikendalikan oleh sistem eksternal. Kita akan menunjukkan bagaimana fail2ban dapat digunakan untuk secara otomatis mengkonfigurasi ulang NGINX Plus agar mengabaikan permintaan dari alamat IP yang telah menyebabkan beberapa kegagalan autentikasi.

Catatan: Pada NGINX Plus R16 dan yang lebih baru, key-value store dapat disinkronisasi di semua instans NGINX Plus dalam sebuah cluster.

Read Also: Harderning Server with Fail2ban and Reporting to Telegram

Turnstile yang merepresentasikan IP denylisting

Menggunakan Key-Value Store untuk IP Denylisting

NGINX Plus key-value store adalah penyimpanan native berbasis memori dengan tiga karakteristik utama:

  • Pasangan key-value direpresentasikan sebagai objek JSON
  • Pasangan key-value dikelola sepenuhnya melalui API
  • Nilai tersedia untuk NGINX Plus sebagai variabel konfigurasi biasa

Key-value store didefinisikan dengan membuat shared memory zone yang dinamai oleh direktif keyval_zone. Direktif keyval kemudian mendefinisikan variabel yang akan digunakan sebagai lookup key ($remote_addr dalam contoh), dan nama variabel baru ($num_failures) yang akan dievaluasi dari nilai pasangan key tersebut.

Konfigurasi dan manajemen key-value store NGINX Plus

API diaktifkan dengan menentukan location block sebagai endpoint NGINX Plus API:

server {
    listen 1111;
    allow  127.0.0.1; # Hanya izinkan akses dari localhost,
    deny   all;       # dan cegah akses remote.

    location /api {
        api write=on; # Endpoint NGINX Plus API dalam mode read/write
    }
}

Sebelum kita menambahkan pasangan key-value, permintaan untuk isi denylist store mengembalikan objek JSON kosong:

$ curl http://localhost:1111/api/6/http/keyvals/denylist
{}

Key-value store kini dapat diisi dengan pasangan key-value awal menggunakan HTTP POST:

$ curl -iX POST -d '{"10.0.0.1":"1"}' http://localhost:1111/api/6/http/keyvals/denylist
HTTP/1.1 201 Created
...

Sebuah pasangan key-value dihapus dengan PATCH-ing key dengan nilai null:

$ curl -iX PATCH -d '{"10.0.0.1":null}' http://localhost:1111/api/6/http/keyvals/denylist
HTTP/1.1 204 No Content
...

Semua pasangan key-value dapat dihapus dari store dengan mengirimkan metode DELETE:

$ curl -iX DELETE http://localhost:1111/api/6/http/keyvals/denylist
HTTP/1.1 204 No Content
...

Implementasi sederhana IP denylisting kini dapat dikonfigurasi:

keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;

server {
    listen 80;

    location / {
        root /usr/share/nginx/html;
        if ($num_failures) {
            return 403;
        }
    }
}

Konfigurasi ini mengevaluasi $num_failures terhadap denylist key-value store menggunakan $remote_addr (IP client) sebagai key. Jika IP client telah di-POST ke store, semua permintaan akan mengembalikan HTTP 403 Forbidden.

Keunggulan pendekatan ini dibanding menggunakan blok map adalah denylist IP dapat dikendalikan oleh sistem eksternal tanpa memerlukan reload NGINX.


Menggunakan fail2ban untuk Mengelola IP Denylist Secara Dinamis

Fail2ban umumnya digunakan untuk mendeteksi aktivitas berbahaya dalam file log dan kemudian mengambil tindakan. Tindakan default mengonfigurasi iptables untuk menjatuhkan semua paket dari IP yang bermasalah — efektif, tetapi memberikan pengalaman buruk bagi pengguna asli yang mungkin hanya lupa password mereka. Bagi pengguna tersebut, website terlihat seolah-olah tidak tersedia sama sekali.

Tindakan fail2ban berikut ini justru mengirimkan IP yang bermasalah ke NGINX Plus denylist key-value store. NGINX Plus kemudian menampilkan halaman error yang informatif dan sekaligus menerapkan rate limit pada IP tersebut, sehingga jika tindakan tersebut merupakan bagian dari serangan, serangan tersebut secara efektif dinetralkan.

Aksi fail2ban: nginx-plus-denylist.conf

Tempatkan file ini di /etc/fail2ban/action.d/:

[Definition]
actionban   = curl -s -o /dev/null -d '{"<ip>":"<failures>"}' http://localhost:1111/api/6/http/keyvals/denylist
actionunban = curl -s -o /dev/null -X PATCH -d '{"<ip>":null}' http://localhost:1111/api/6/http/keyvals/denylist

Jail fail2ban: jail.local

Aktifkan filter bawaan untuk kegagalan NGINX HTTP Basic Auth:

[DEFAULT]
bantime   = 120
banaction = nginx-plus-denylist

[nginx-http-auth]
enabled = true

Konfigurasi Lengkap NGINX Plus: password_site.conf

Tempatkan file ini di /etc/nginx/conf.d/:

server {
    listen 1111;
    allow  127.0.0.1; # Hanya izinkan akses dari localhost,
    deny   all;       # dan cegah akses remote.

    location /api {
        api write=on; # Endpoint NGINX Plus API dalam mode read/write
    }
}

keyval_zone zone=denylist:1M state=denylist.json;
keyval $remote_addr $num_failures zone=denylist;

limit_req_zone $binary_remote_addr zone=20permin:10M rate=20r/m;

server {
    listen 80;
    root /usr/share/nginx/html;

    location / {
        auth_basic "closed site";
        auth_basic_user_file users.htpasswd;

        if ($num_failures) {
            rewrite ^.* /banned.html;
        }
    }

    location = /banned.html {
        limit_req zone=20permin burst=100;
    }
}

Poin penting dalam konfigurasi ini:

  • Parameter state=denylist.json menyimpan state key-value store agar bertahan saat NGINX Plus dihentikan dan dijalankan kembali
  • limit_req_zone membuat rate limit 20 permintaan/menit per IP (20permin)
  • Ketika IP masuk denylist, permintaan ditulis ulang ke /banned.html alih-alih mengembalikan 403
  • Location /banned.html menerapkan rate limit untuk mencegah konsumsi sumber daya yang berlebihan dari traffic serangan
Halaman yang dilihat pengguna ketika alamat IP mereka masuk denylist

Menguji Setup

Simulasikan percobaan login yang gagal secara berulang:

$ curl -i http://admin:[email protected]/
HTTP/1.1 401 Unauthorized
...
$ curl -i http://admin:[email protected]/
HTTP/1.1 401 Unauthorized
...
# (ulangi 5 kali)

$ curl http://admin:[email protected]/
<!DOCTYPE html>
<html>
<head>
<title>Banned</title>
...

$ tail -f /var/log/fail2ban.log
2017-09-15 13:55:18,903 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:55:28,836 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:49,228 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:50,286 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,015 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,088 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Ban 172.16.52.1
2017-09-15 13:59:52,379 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Unban 172.16.52.1

Setelah 120 detik (bantime di jail.local), fail2ban secara otomatis menghapus IP dari denylist menggunakan NGINX Plus API, dan percobaan login kembali diterima dari alamat tersebut.


Cara Kerja Keseluruhan

Solusi dynamic IP denylisting ini berjalan tanpa perubahan konfigurasi lebih lanjut setelah setup selesai:

  1. fail2ban memantau log NGINX untuk kegagalan autentikasi
  2. Saat threshold terlampaui, fail2ban memanggil NGINX Plus API untuk menambahkan IP ke key-value store
  3. NGINX Plus langsung mulai menyajikan halaman /banned.html ke IP tersebut dengan rate limiting aktif
  4. Setelah bantime berakhir, fail2ban memanggil API lagi untuk menghapus IP dari denylist

Tidak perlu mengedit file konfigurasi NGINX. Tidak perlu nginx -s reload. Cukup denylist yang hidup dan dinamis, dikendalikan sepenuhnya melalui API.


Ringkasan

Komponen Peran
fail2ban Mendeteksi percobaan brute-force di file log
NGINX Plus API Menerima perintah ban/unban dari fail2ban
Key-value store Menyimpan denylist aktif di memori
Direktif keyval Memetakan IP client ke status denylist
limit_req_zone Membatasi rate IP yang di-ban untuk melindungi sumber daya

NGINX Plus API dan key-value store membuat integrasi seperti ini menjadi mungkin — konfigurasi dinamis tanpa reload, dikendalikan sepenuhnya oleh sistem eksternal.