Menggabungkan seccomp, Linux capabilities, namespaces, dan root filesystem read-only menjadi pertahanan berlapis yang saling menutupi kelemahan. Membedah prasyarat no_new_privs dan studi kasus pencekikan capset, serta merangkai cap-drop dan profile seccomp dalam satu perintah docker run.

Setelah di episode 11 sebelumnya kita membahas seccomp untuk aplikasi dan daemon — bagaimana memasang filter pada proses yang berjalan lama, menguji profile sebelum dipakai, dan menjaga agar filter tidak merusak workload — kalian sekarang bisa memasang seccomp hampir di mana pun. Tetapi izinkan saya mengajukan satu pertanyaan yang sedikit mengganggu: seberapa aman sebenarnya sebuah sistem yang hanya mengandalkan seccomp?
Jawabannya: belum cukup. Seccomp hanya mengatur syscall mana yang boleh dipanggil. Ia tidak mencegah proses yang sah memanfaatkan haknya sendiri secara berlebihan. Ambil contoh proses root di dalam container: jika filter mengizinkan mount, proses itu bisa memasang filesystem baru seolah-olah ia admin host. Seccomp tidak pernah bertanya "siapa kamu dan apa hakmu" — menjawab pertanyaan itu adalah urusan lapisan lain.
Pikirkan sebuah gedung bank. Lapisan pertama adalah pagar keliling yang menentukan siapa yang boleh masuk kompleks — itulah namespaces. Lapisan kedua adalah resepsionis yang memeriksa hak akses — itulah Linux capabilities. Lapisan ketiga adalah pintu brankas yang hanya terbuka untuk operasi tertentu — itulah seccomp. Dan lapisan keempat adalah petugas keamanan di dalam yang menjaga isi brankas tetap utuh — itulah root filesystem read-only. Satu lapisan bisa didobrak, tetapi mendobrak empat lapisan sekaligus jauh lebih sulit.
Pada episode ini kita akan membedah kombinasi keempatnya: apa yang dilindungi setiap lapisan, mengapa no_new_privs menjadi prasyarat yang tidak bisa ditawar, bagaimana mencekik penyalahgunaan capset dan capsh, dan bagaimana merangkai semuanya dalam satu perintah docker run. Mari mulai.
Istilah defense in depth berarti banyak lapisan pertahanan yang masing-masing menutupi kelemahan lapisan lain. Tidak ada satu pun mekanisme keamanan Linux yang menyelesaikan semuanya sendirian:
Keempatnya seperti kepingan puzzle: sendirian tidak berarti, bersama-sama membentuk gambar yang utuh. Tabel berikut merangkum peran masing-masing.
| Lapisan | Mekanisme | Yang dilindungi | Contoh kelemahan tanpa lapisan ini |
|---|---|---|---|
| Namespaces | Isolasi visibilitas sumber daya | Proses tidak melihat atau mengganggu proses dan resource host | Proses bisa melihat dan menyerang semua proses host |
| Capabilities | Hak istimewa granular per proses | Kerusakan dibatasi meskipun berjalan sebagai root | Proses root bisa mount, mengubah network, atau mengganti owner file |
| Seccomp | Gerbang syscall | Permukaan syscall yang bisa dipanggil | Proses bisa memanggil syscall eksploitasi seperti ptrace |
| Root read-only | Filesystem sistem terkunci | Integritas binary, konfigurasi, dan library | File sistem bisa ditimpa oleh penyerang yang sudah masuk |
Namespaces mengisolasi sumber daya: proses di namespace PID yang berbeda tidak melihat proses lain, namespace mount menentukan filesystem mana yang terlihat, dan namespace network memberi stack jaringan sendiri. Inilah alasan utama container "terasa" seperti sistem yang terpisah.
Namun perlu kalian ingat: namespaces membatasi visibilitas dan sumber daya, bukan perilaku. Proses di dalam namespace tetap berbagi kernel yang sama dan tetap bisa memanggil syscall yang sama. Inilah celah yang ditutup oleh lapisan berikutnya.
Linux capabilities adalah mekanisme yang memecah hak istimewa root menjadi potongan-potongan granular. Daripada satu user root yang bisa melakukan segalanya, proses kini memegang daftar capability tertentu: CAP_NET_ADMIN untuk mengelola network, CAP_SYS_ADMIN untuk administrasi sistem, CAP_NET_BIND_SERVICE untuk membuka port di bawah 1024, dan puluhan lainnya.
Bayangkan root di dalam container sebagai penjaga gedung yang diberi gantungan kunci. Dengan capabilities, kalian yang memutuskan kunci mana yang benar-benar ada di gantungan itu — bukan sekadar nama yang tertera di ID card. Menjalankan proses sebagai root tanpa CAP_SYS_ADMIN membuat proses itu masih bisa melakukan banyak hal root, tetapi tidak bisa memasang filesystem atau memuat kernel module.
capsh --print
# Dalam container, cek bounding set setelah cap-drop
docker run --rm --cap-drop ALL alpine capsh --print | head -20Baris Current: = cap_chown,cap_net_bind_service+p pada output capsh --print menunjukkan capability mana yang aktif dan terbatas pada permitted (p). Inilah "kunci" yang dibawa proses — dan semakin sedikit, semakin baik.
Seccomp, seperti yang kalian pelajari di episode-episode sebelumnya, adalah gerbang syscall: filter BPF yang dieksekusi kernel setiap kali proses memanggil syscall. Ia menjawab pertanyaan "operasi kernel apa yang boleh dipanggil proses ini?" — ptrace boleh atau tidak, mount boleh atau tidak, dan seterusnya.
Kekuatannya ada di presisi: aturan bisa menyasar syscall tertentu, argumen tertentu, hingga return action tertentu. Kelemahannya juga ada di sana: seccomp tidak tahu siapa yang memanggil syscall. Proses root yang sah dan proses penyerang yang menyamar sama-sama lolos jika syscall-nya diizinkan. Itulah mengapa seccomp butuh capabilities — dan sebaliknya.
Lapisan terakhir menjaga isi brankas: filesystem. Dengan root yang di-mount read-only, proses tidak bisa menimpa binary, konfigurasi, atau library sistem. Penyerang yang sudah masuk tidak bisa memperdalam pijakan — tidak bisa menanam binary backdoor di /usr/bin, tidak bisa mengganti /etc/passwd, tidak bisa menyisipkan library di /lib.
Di Docker, root read-only dikombinasikan dengan --tmpfs agar aplikasi tetap punya tempat menulis file sementara:
docker run --rm -it \
--read-only \
--tmpfs /tmp \
alpine shPerhatikan: --read-only menahan seluruh filesystem dari penulisan, sedangkan --tmpfs /tmp menyediakan satu area tulis di memori yang otomatis hilang ketika container berhenti. Pola ini membuat persistensi penyerang nyaris mustahil — apa pun yang ia tulis akan lenyap bersamaan dengan container.
Sekarang bagian yang paling sering diabaikan pemula: no_new_privs. Ini adalah flag proses yang membuat proses — dan semua turunannya — tidak akan pernah mendapatkan hak istimewa baru, apa pun yang mereka eksekusi. Setuid binary, file capabilities, dan mekanisme kenaikan privilege lainnya semuanya dinonaktifkan.
Mengapa ini penting untuk seccomp? Dua alasan. Pertama, kernel menuntut no_new_privs (atau CAP_SYS_ADMIN) sebagai prasyarat untuk memasang filter — inilah pintu yang mencegah proses unprivileged memasang filter dengan semena-mena. Kedua, no_new_privs mencegah skenario paling jahat: proses yang sudah dibatasi seccomp mengeksekusi binary setuid untuk mengangkat dirinya menjadi lebih berkuasa, lalu memanfaatkan hak baru untuk melewati filter. Dengan flag ini, jalur kenaikan hak ditutup permanen.
docker run --rm -it \
--security-opt no-new-privileges \
alpine sh -c 'grep NoNewPrivs /proc/self/status'Output NoNewPrivs: 1 pada /proc/self/status menandakan flag aktif. Selalu pasang flag ini bersamaan dengan filter seccomp — tanpa itu, filter hanyalah gerbang tanpa pengunci.
Salah satu penyalahgunaan capabilities yang paling sering muncul adalah self-escalation: proses yang berhasil dikendalikan penyerang mencoba menambahkan capability baru ke dirinya sendiri lewat syscall capset. Jika proses memegang CAP_SETPCAP, panggilan ini akan berhasil — dan penyerang bisa memberi dirinya CAP_SYS_ADMIN lalu bebas lepas.
Tool baris perintah favorit para penyerang adalah capsh — yang secara internal memanggil capset dan setuid untuk mengubah kemampuan proses. Memblokir syscall-syscall itu di seccomp berarti menutup seluruh kelas serangan ini dalam satu aturan:
{
"defaultAction": "SCMP_ACT_ALLOW",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": [
"capset",
"setuid",
"setgid",
"setresuid",
"setresgid",
"setreuid",
"setregid"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1
}
]
}Perhatikan dua hal. Pertama, defaultAction tetap SCMP_ACT_ALLOW — profile ini adalah deny-list, dirancang sebagai lapisan defense in depth di atas policy utama. Kedua, errnoRet diset ke 1 (EPERM) sehingga panggilan yang diblokir gagal dengan pesan yang jelas dan mudah di-audit. Kombinasi --cap-drop ALL di level capability dan blokir capset di level seccomp membuat self-escalation praktis mustahil.
Sekarang saatnya menggabungkan keempat lapisan yang kita bahas — namespaces (ditangani otomatis oleh Docker), capabilities, seccomp, dan read-only root — dalam satu perintah:
docker run --rm -it \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges \
--security-opt seccomp=./profile.json \
--read-only \
--tmpfs /tmp \
nginx:latestBedah per barisnya:
--cap-drop ALL — buang seluruh capabilities, termasuk CAP_SYS_ADMIN, CAP_NET_ADMIN, dan CAP_SETPCAP. Proses root di dalam container kini nyaris tanpa kunci.--cap-add NET_BIND_SERVICE — kembalikan hanya satu kunci yang benar-benar dibutuhkan Nginx: kemampuan membuka port 80. Ini contoh least privilege.--security-opt no-new-privileges — kunci permanen terhadap kenaikan hak.--security-opt seccomp=./profile.json — pasang filter seccomp yang mencekik capset, setuid, dan teman-temannya.--read-only dan --tmpfs /tmp — root filesystem terkunci; hanya /tmp yang bisa ditulis, dan hanya sementara.Keempat lapisan tidak saling menggantikan — mereka saling menutup. Seccomp memblokir syscall berbahaya yang tidak bisa dijangkau cap-drop; cap-drop mencabut hak yang tidak bisa dijangkau seccomp; read-only root mengunci file yang tidak bisa dijangkau keduanya.
Tip
Jadikan perintah di atas template kalian. Tiga opsi — --cap-drop ALL, --security-opt no-new-privileges, dan --read-only — hampir selalu aman ditambahkan ke container apa pun dan langsung menaikkan postur keamanannya. Profile seccomp yang kalian sesuaikan bertugas memangkas sisanya.
1. Seccomp tanpa cap-drop. Filter yang mengizinkan mount tetapi berharap "tidak akan dipakai" adalah harapan yang rapuh. Selalu kombinasikan dengan --cap-drop ALL agar proses tidak punya hak untuk memanfaatkan syscall yang diizinkan.
2. Cap-drop tanpa seccomp. Sebaliknya, banyak syscall berbahaya tidak membutuhkan capability sama sekali. Proses tanpa CAP_SYS_PTRACE tetap bisa dieksploitasi lewat bug kernel di syscall userfaultfd atau perf_event_open — kita bedah detailnya di episode 13. Capabilities dan seccomp bekerja pada dua sumbu yang berbeda.
3. Lupa no-new-privileges. Memasang filter tanpa flag ini sama dengan mengunci pintu depan sambil membiarkan jendela atap terbuka — setuid masih bisa mengangkat hak proses.
4. Membiarkan root filesystem bisa ditulis. Penyerang yang sudah masuk akan menanam persistensi di binary atau library. Read-only root memotong pijakan itu sejak awal.
Pada episode 12 ini kalian melihat seccomp dalam konteks yang lebih luas: bukan satu-satunya benteng, melainkan salah satu dari empat lapisan — namespaces, capabilities, seccomp, dan root read-only — yang saling menutupi kelemahan. Kalian juga memahami mengapa no_new_privs adalah prasyarat yang tidak bisa ditawar, bagaimana mencekik penyalahgunaan capset dan capsh dengan deny-list, dan bagaimana merangkai semuanya dalam satu perintah docker run.
Poin kunci yang perlu kalian bawa:
no_new_privs adalah prasyarat filter sekaligus penutup jalur kenaikan hak.capset dan setuid di seccomp menutup kelas serangan self-escalation.--cap-drop ALL lalu --cap-add hanya yang dibutuhkan adalah pola least privilege yang benar.Sekarang kombinasi lapisan sudah di tangan. Namun ada satu pertanyaan yang belum terjawab: syscall mana saja yang seharusnya diblokir? Di episode 13 berikutnya kita akan membedah blocking attack surfaces — daftar syscall berbahaya seperti mount, ptrace, process_vm_writev, kexec_load, dan userfaultfd, mengapa masing-masing menjadi pintu masuk eksploitasi kernel dan container escape, serta bagaimana memblokirnya dengan benar. Sampai jumpa!