Panel pengelolaan di server hampir selalu menabrak dinding yang sama: proses web perlu melakukan sesuatu yang tidak menjadi haknya. Memblokir sebuah alamat, membuka sebuah porta, membaca jurnal. Jawaban yang langsung terpikir adalah memberi satu baris sempit di sudoers, dan tidak lebih dari itu:

www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client set * banip *

Baris itu terbaca sebagai «hanya memblokir». Kenyataannya ia berarti «apa saja, sebagai root». Berikut ini: mengapa jadinya demikian, bagaimana memeriksanya di server Anda sendiri dalam lima menit, dan dengan apa aturan semacam itu digantikan.

Apa yang sebenarnya dilakukan *

Rincian yang menentukan: sudo tidak membandingkan argumen satu per satu, melainkan seluruh baris perintah, dan * dalam pola melintasi spasi dengan mudah — artinya melintasi batas antar-argumen. Karena itu kata harfiah banip yang berdiri di tengah aturan tidak membatasi apa pun: cukup kata banip muncul di suatu tempat dalam perintah, sementara sebelum dan sesudahnya apa saja boleh dilewatkan.

Selanjutnya dicari program yang kepadanya bisa diserahkan sebuah perintah untuk dijalankan. Dalam kasus kami, program itu ada di dalam aturannya sendiri. Di fail2ban, tindakan pemblokiran ditetapkan sebagai teks, dan teks itu dapat didefinisikan ulang saat berjalan:

fail2ban-client get portscan actions
sudo fail2ban-client set portscan action nftables-multiport actionban "<perintah>" banip 192.0.2.10
sudo fail2ban-client set portscan banip 192.0.2.11

Panggilan pertama menukar teks tindakan pada sebuah tindakan nyata, panggilan kedua memaksa jail bekerja — dan fail2ban, yang berjalan sebagai root, menjalankan apa yang telah disisipkan. Kami melakukannya pada 4 Juli di server kami sendiri: sebuah berkas di /root tercipta dengan hak root, artinya proses web memperoleh hak penuh atas mesin dari aturan yang tampak sempit.

Di sepanjang jalan muncul tiga rincian yang biasanya tidak ada dalam uraian orang lain:

  • opsi -c sebelum set dipotong oleh sudo: dalam pola, set menempel langsung pada nama berkas binernya dan tidak ada yang bisa disisipkan di depannya;
  • addaction dan action tanpa kata banip juga tidak lolos — tetapi semua yang diperlukan termuat dalam satu perintah set … banip …, sehingga polanya tetap terpenuhi;
  • nama tindakan harus nama yang nyata, kalau tidak, tak ada yang bisa ditukar: fail2ban-client get <jail> actions menampilkannya.

Siapa lagi yang ada di daftar yang sama

fail2ban di sini tidak bersalah dan bukan kasus tunggal. Yang berbahaya adalah perpaduan NOPASSWD dengan tanda bintang itu sendiri. Apa yang kami temukan di sebelahnya pada server kami:

  • journalctl * — jurnal terbuka lewat pager, dan dari pager bisa dijalankan sebuah shell. Aturannya seolah tentang membaca log; dalam praktiknya ia adalah shell root. Obatnya bukan pola yang lebih sempit, melainkan grup systemd-journal: dengan itu journalctl bekerja tanpa sudo sama sekali;
  • grep * /var/log/fail2ban.log — argumen pertama grep adalah pola, tetapi tanda bintang memungkinkan diberikan pula jalur kedua, dan grep berjalan sebagai root. Membaca /etc/shadow dengan aturan itu hanya satu perintah. Penggantinya sama: grup adm untuk membaca log;
  • ufw --force * dan ufw allow/deny/delete * yang gamblang — proses web dapat mematikan firewall sepenuhnya. Yang terutama tidak menyenangkan: pada kedua kasus panel sama sekali tidak memakai aturan itu, yakni izin mati yang tertinggal dari versi-versi awal pemasang;
  • lynis-scan.sh * — pembungkus yang meneruskan "$@" ke proses root setara dengan aturan tanpa batasan apa pun.

Bagaimana melihat apa yang ada di tempat Anda

Yang perlu dilihat bukan berkasnya, melainkan hak efektif seorang pengguna tertentu — pengguna yang benar-benar menjalankan pool PHP-FPM (tidak selalu www-data; pada salah satu mesin kami panel berjalan sebagai admin):

ps -o user= -C php-fpm | sort -u
sudo -l -U www-data
sudo -l -U admin

Selanjutnya daftar berkasnya sendiri, dan di sini ada dua jebakan yang memakan waktu kami:

sudo grep -rn 'NOPASSWD' /etc/sudoers /etc/sudoers.d/
ls -la /etc/sudoers.d/

Yang pertama: aturan hidup di dua tempat sekaligus. Pada kami baris berbahaya itu ada baik di /etc/sudoers.d/monitor maupun di /etc/sudoers utama — di berkas utama dalam sekitar delapan salinan, yang ditimbun oleh berbagai versi pemasang. Membersihkan satu tempat lalu merasa tenang adalah cara yang biasa untuk membiarkan lubangnya tetap terbuka.

Yang kedua: sudo mengabaikan berkas yang namanya memuat titik. Berkas www-data.bak3 di /etc/sudoers.d/ tampak seperti aturan yang berlaku dan terbaca seperti aturan yang berlaku, tetapi tidak berdaya guna. Ini bekerja ke dua arah: «aturannya ada, haknya tidak» dan rasa tenang palsu bahwa salinan konfigurasi «terletak persis di sebelahnya».

Diganti dengan apa

Mempersempit pola tidak ada gunanya — tanda bintang di mana pun pada baris itu mengembalikan soalnya ke titik awal. Hanya satu pendekatan yang bertahan: pembungkus root yang menerima argumen posisi tetap, memvalidasinya sendiri, dan memanggil program tanpa sedikit pun peluang untuk menambahkan sesuatu.

#!/bin/sh
# /usr/local/sbin/arciveo-f2b — ban|unban <jail> <ip>, dan tidak lebih
set -eu
action="$1"; jail="$2"; ip="$3"
case "$action" in ban|unban) ;; *) echo "invalid action"; exit 1 ;; esac
case "$jail" in *[!a-zA-Z0-9_-]*|'') echo "invalid jail"; exit 1 ;; esac
printf '%s' "$ip" | grep -Eq '^[0-9a-fA-F:.]+$' || { echo "invalid ip"; exit 1; }

case "$action" in
  ban)   exec /usr/bin/fail2ban-client set "$jail" banip   "$ip" ;;
  unban) exec /usr/bin/fail2ban-client set "$jail" unbanip "$ip" ;;
esac

Pembungkus itu milik root, izin 755, dan — hal ini tidak bersifat pilihan — ia tidak boleh dapat ditulisi dari web, kalau tidak seluruh bangunan ini kehilangan makna. Di sudoers hanya ia yang tinggal:

www-data ALL=(root) NOPASSWD: /usr/local/sbin/arciveo-f2b

Argumen tidak didaftar dalam aturan: skripnya sendiri yang memeriksanya, dan posisi di dalamnya tetap, jadi tidak ada tempat untuk menyelipkan teks tindakan. Aturan yang hanya membaca — status, ss, ipset list — ditaruh pada baris terpisah, juga tanpa tanda bintang di tempat yang memungkinkan.

Secara terpisah, tentang menggantikan sudo dengan grup. Pendekatannya benar: systemd-journal untuk jurnal dan adm untuk log lebih murah dan lebih aman daripada aturan sudo apa pun. Tetapi menarik pengguna web keluar dari grup secara membuta tidak bisa. Kami mengeluarkan www-data dari satu grup pada sebuah host berpanel dan langsung mendapat 403 di semua situs sekaligus: di sana Apache berjalan sebagai www-data, berkas situs-situs itu milik pengguna lain, dan hak baca datang justru dari keanggotaan grup tersebut. Pemulihannya menuntut mulai ulang penuh — bukan muat ulang — Apache dan PHP-FPM, sebab pekerja lama mempertahankan himpunan grup sebelumnya dan menghasilkan keadaan «jalan, lalu 403».

Pemeriksaan setelah perubahan

Sebelum berkas sudoers diganti, sintaksisnya harus diperiksa — kalau tidak, bisa berakhir tanpa sudo sama sekali:

sudo visudo -cf /etc/sudoers.d/monitor

Sesudahnya perlu dipastikan bahwa jalur lama sudah mati, dan itu atas nama pengguna yang sama:

sudo -k
sudo -u www-data sudo -n fail2ban-client set portscan banip 192.0.2.10

Jawabannya harus a password is required, sedangkan memanggil pembungkus dengan sampah sebagai ganti alamat harus memberi invalid ip. Di sini mengintai jebakan tersendiri: sudo -v menyimpan konfirmasi selama lima belas menit, dan sesudah itu setiap pemeriksaan dengan sudo -n lolos «dengan sukses». Tepat begitulah kami pernah memastikan sebuah aturan yang sama sekali tidak ada di server. Karena itu sudo -k sebelum pemeriksaan bersifat wajib.

Untuk apa mengamati

Aturan sudo jarang berubah, tetapi berubah tanpa suara: pemasang menambahi, panel hosting menyisipkan miliknya, paket yang dihapus meninggalkan baris-barisnya. Peninjauan sekali jalan menutup apa yang ada hari ini dan tidak mengatakan apa pun tentang apa yang akan dibawa pembaruan berikutnya. Kesimpulan praktisnya sederhana: daftar siapa yang dapat menjadi root lebih baik ada di depan mata, berdampingan dengan keadaan server lainnya, daripada dalam ingatan sesudah sebuah insiden. Seperti apa semua itu bila dirangkai, halaman demo di bawah memperlihatkannya.