Hak Istimewa Minimum

Pengguna layanan khusus dan pengurangan kapabilitas.

Daftar isi

Setiap proses di Linux berjalan dengan identitas tertentu, dan seluruh izin yang dimiliki proses itu mengikuti identitas tersebut. Topik ini membahas prinsip hak istimewa minimum (least privilege), yaitu layanan dijalankan oleh pengguna khusus tanpa kemampuan login, kemudian sisa haknya dipangkas sampai batas yang benar-benar dibutuhkan. Setelah membacanya, pengguna layanan dapat diperiksa di sistem, unit systemd dapat disetel berjalan tanpa hak istimewa berlebih, dan kapabilitas dapat dipahami sebagai pembagian wewenang root yang lebih halus.

Pengguna layanan#

Pengguna layanan (service user) adalah akun yang dibuat untuk menjalankan satu layanan dan tidak dimaksudkan untuk dipakai oleh orang. Cirinya terlihat pada berkas /etc/passwd: UID-nya berada pada rentang akun sistem, direktori home menunjuk ke lokasi aplikasi atau data, dan shell-nya adalah /usr/sbin/nologin, yaitu shell yang menolak sesi interaktif. Pengguna www-data adalah contohnya, dibuat otomatis oleh paket web server sebagai identitas untuk proses penyajian halaman.

bash
# identitas pengguna layanan www-data
id www-data
# baris www-data pada berkas /etc/passwd
grep www-data /etc/passwd
text Keluaran
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

Keluaran tersebut menunjukkan UID 33, grup utama yang sama, direktori home /var/www, dan shell nologin. Shell nologin hanya menutup sesi interaktif: proses layanan tetap dapat dijalankan oleh sistem atas nama akun itu, tetapi seseorang tidak dapat duduk di terminal sebagai www-data. Kolom kata sandi akun ini pada /etc/shadow juga berisi nilai yang tidak cocok dengan kata sandi apa pun, sehingga jalur autentikasi kata sandi tertutup sejak akun dibuat. Pembatasan ini meredam dampak pembajakan proses, karena pelaku tidak otomatis mendapatkan shell untuk berpindah-pindah di sistem.

Pembuatan pengguna layanan baru memerlukan hak root, sehingga tidak dapat dieksekusi di sandbox.

bash
# tidak-dijalankan
# pembuatan pengguna memerlukan hak root
# opsi -r membuat akun sistem dan -s menetapkan shell nologin
sudo useradd -r -s /usr/sbin/nologin app-catatan

Layanan tanpa hak istimewa berlebih#

Layanan yang berjalan sebagai root memindahkan seluruh wewenang root kepada program itu sendiri. Satu lubang keamanan pada aplikasi, misalnya kesalahan penanganan masukan, berarti pengambilalihan penuh atas mesin, karena proses yang dibajak mewarisi identitas root. Prinsip hak istimewa minimum membalik keadaan tersebut: layanan dijalankan sebagai pengguna khusus dan hanya boleh menulis pada direktori yang memang dipakainya.

systemd menampung ketentuan tersebut pada berkas unit melalui arahan User= beserta sejumlah arahan pengerasan lain. Unit berikut dijadikan contoh untuk aplikasi catatan sederhana yang mendengarkan pada port tinggi.

ini app-layanan.service
# /etc/systemd/system/app-catatan.service
[Unit]
Description=Layanan aplikasi catatan
After=network.target

[Service]
User=app-catatan
Group=app-catatan
WorkingDirectory=/opt/app-catatan
StateDirectory=app-catatan
ExecStart=/usr/bin/python3 /opt/app-catatan/server.py
Restart=on-failure

# Pembatasan hak istimewa
NoNewPrivileges=yes
CapabilityBoundingSet=
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target

Arahan pada bagian [Service] saling melengkapi. User= dan Group= menurunkan proses ke identitas app-catatan, sedangkan StateDirectory=app-catatan membuat /var/lib/app-catatan milik pengguna tersebut sekaligus menandainya sebagai lokasi yang boleh ditulis. NoNewPrivileges=yes melarang proses dan turunannya memperoleh hak baru, termasuk melalui biner setuid yang berjalan sebagai pemiliknya.

CapabilityBoundingSet= yang kosong mengosongkan himpunan kapabilitas yang boleh dipertahankan oleh proses. ProtectSystem=strict menjadikan seluruh hierarki sistem berkas hanya-baca bagi layanan, ProtectHome=yes menyembunyikan direktori home, dan PrivateTmp=yes memberikan /tmp yang terpisah. Aplikasi contoh mendengarkan pada port tinggi sehingga tidak membutuhkan wewenang apa pun untuk berjalan, sebab hanya port di bawah 1024 yang menuntut wewenang khusus.

Penerapan unit tersebut pada server berjalan sebagai prosedur berikut.

bash
# tidak-dijalankan
# systemd tidak berjalan di sandbox dan langkahnya memerlukan root
# kode aplikasi diserahkan kepada pengguna layanan
sudo chown -R app-catatan:app-catatan /opt/app-catatan
# unit dipasang lalu daftar unit systemd diperbarui
sudo cp app-layanan.service /etc/systemd/system/
sudo systemctl daemon-reload
# layanan diaktifkan dan dijalankan
sudo systemctl enable --now app-catatan.service

Kapabilitas: memecah wewenang root#

Wewenang root pada kenyataannya adalah kumpulan kapabilitas terpisah, seperti CAP_NET_BIND_SERVICE untuk mendengarkan port di bawah 1024 dan CAP_NET_RAW untuk membuat soket mentah. Model ini menggantikan pertanyaan tumpul “root atau bukan root” dengan pembagian yang lebih rinci, misalnya sebuah proses yang hanya boleh mengikat port rendah tanpa mewarisi wewenang lain. Kapabilitas tersebut dapat dilekatkan pada berkas eksekusi lewat atribut diperluas (extended attribute) sistem berkas, dan getcap membacanya kembali.

bash
# menghitung berkas berkapabilitas pada direktori biner sandbox
getcap -r /usr/bin /usr/sbin 2>/dev/null | wc -l
text Keluaran
0

Hasil nol berarti tidak ada satu pun berkas di dua direktori tersebut yang membawa kapabilitas berkas pada sandbox latihan ini. Pada pemasangan Debian standar, daftar yang sama biasanya memuat /usr/bin/ping dengan cap_net_raw, karena utilitas ping membutuhkan soket mentah tanpa harus berjalan sebagai root. Pemberiannya menggunakan setcap, yang memerlukan hak root dan karenanya tidak dieksekusi di sandbox.

bash
# tidak-dijalankan
# setcap memerlukan hak root
# aplikasi contoh diberi wewenang mengikat port rendah saja
sudo setcap cap_net_bind_service=+ep /opt/app-catatan/server

Akhiran =+ep memberi kapabilitas pada himpunan efektif dan diizinkan, sehingga berkas tersebut dapat langsung memakai wewenangnya saat dieksekusi. Hubungan setcap dengan unit pada bagian sebelumnya bersifat saling melengkapi: CapabilityBoundingSet= yang kosong memastikan proses layanan tidak dapat mempertahankan kapabilitas apa pun, sekalipun berkas yang dieksekusi oleh proses tersebut membawa kapabilitas berkas.

Mengaudit hak istimewa yang tersebar#

Bit setuid adalah mekanisme eskalasi tertua di Unix: biner yang membawa bit ini berjalan dengan identitas pemilik berkasnya, dan pemilik itu hampir selalu root. Mekanisme ini sah dan diperlukan untuk perintah tertentu, tetapi setiap entri pada daftar berikut adalah permukaan eskalasi lokal yang layak diperiksa secara berkala.

bash
# berkas setuid atau setgid pada direktori biner sistem
find /usr/bin /usr/sbin -xdev -perm /6000 -type f 2>/dev/null | sort
text Keluaran
/usr/bin/chage
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/expiry
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/su
/usr/bin/sudo
/usr/bin/umount
/usr/sbin/unix_chkpwd

Daftar pada sandbox ini seluruhnya berasal dari paket dasar sistem, dan perannya mudah dijelaskan. passwd membutuhkan setuid karena menulis ke /etc/shadow milik root, sedangkan su dan sudo memang perintah eskalasi itu sendiri. Bagi layanan yang sudah dikeraskan pada bagian sebelumnya, biner-biner pada daftar ini tidak terjangkau, karena NoNewPrivileges=yes menutup jalan proses layanan untuk memanfaatkan biner setuid apa pun. Audit sederhananya sebagai berikut: daftar ini dibaca berkala, setiap entri dihubungkan dengan paket yang memasangnya melalui dpkg -S, dan biner asing yang muncul tanpa penjelasan diselidiki terlebih dahulu. Aturan yang sama berlaku untuk getcap, karena daftar yang kosong hari ini bukan jaminan untuk selamanya, dan satu paket baru yang membawa kapabilitas cukup untuk mengubah keadaannya.

Kesalahan umum#

Kesalahan berikut paling sering terjadi saat menerapkan prinsip ini.

  • Layanan berhenti setelah pengerasan diterapkan. ProtectSystem=strict menjadikan sistem berkas hanya-baca, dan lokasi yang memang perlu ditulis belum dimasukkan, sehingga StateDirectory= atau ReadWritePaths= perlu dilengkapi. Pesan error pada journalctl -u app-catatan biasanya menunjuk langsung jalur yang ditolak.
  • Aplikasi gagal mengikat port rendah meskipun sudah bukan root. Port di bawah 1024 membutuhkan CAP_NET_BIND_SERVICE, dan wewenang itu diberikan melalui setcap atau kapabilitas pada unit, bukan dengan mengembalikan layanan ke root. Cara menghindarinya yang paling sederhana adalah menjaga aplikasi pada port tinggi seperti contoh unit pada bagian sebelumnya, sehingga setcap tersisa hanya untuk kasus yang benar-benar membutuhkannya.
  • Pengguna layanan dibuat tanpa shell nologin. Akun dengan shell biasa membuka pintu login interaktif yang justru ingin ditutup, dan -s /usr/sbin/nologin pada useradd menutupnya sejak awal.
  • Kapabilitas menghilang setelah pembaruan aplikasi. Berkas biner diganti berkas baru oleh paket, sedangkan atribut kapabilitasnya tidak ikut terbawa, sehingga setcap perlu diulang setelah setiap pembaruan berkas tersebut.

Ringkasan#

  • Pengguna layanan adalah akun tanpa login interaktif, ditandai shell /usr/sbin/nologin pada /etc/passwd, yang menjadi identitas proses layanan.
  • Unit systemd menurunkan layanan ke akun tersebut lewat User= dan memangkas sisanya dengan NoNewPrivileges=, CapabilityBoundingSet=, serta arahan Protect*.
  • Kapabilitas memecah wewenang root menjadi wewenang yang lebih kecil, dan dapat dilekatkan pada berkas dengan setcap lalu dibaca dengan getcap.
  • Daftar biner setuid hasil find -perm /6000 adalah permukaan eskalasi lokal yang diaudit secara berkala.

Referensi#