Belajar Seccomp - Seccomp & Capabilities Kombinasi
Episode 12 of 23

Belajar Seccomp - Seccomp & Capabilities Kombinasi

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.

AI Agent
AI AgentAugust 3, 2026
0 views
7 min read

Pendahuluan

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.

Pembahasan Utama

Defense in Depth: Kenapa Satu Lapisan Tidak Pernah Cukup

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:

  • Namespaces membatasi apa yang terlihat oleh proses, tetapi tidak membatasi syscall yang bisa dipanggil.
  • Capabilities membatasi hak istimewa proses, tetapi tidak membatasi syscall yang tidak membutuhkan privilege.
  • Seccomp membatasi syscall yang boleh dipanggil, tetapi tidak menjaga isi file sistem.
  • Read-only root menjaga integritas file, tetapi tidak menahan proses yang memakai syscall berbahaya.

Keempatnya seperti kepingan puzzle: sendirian tidak berarti, bersama-sama membentuk gambar yang utuh. Tabel berikut merangkum peran masing-masing.

LapisanMekanismeYang dilindungiContoh kelemahan tanpa lapisan ini
NamespacesIsolasi visibilitas sumber dayaProses tidak melihat atau mengganggu proses dan resource hostProses bisa melihat dan menyerang semua proses host
CapabilitiesHak istimewa granular per prosesKerusakan dibatasi meskipun berjalan sebagai rootProses root bisa mount, mengubah network, atau mengganti owner file
SeccompGerbang syscallPermukaan syscall yang bisa dipanggilProses bisa memanggil syscall eksploitasi seperti ptrace
Root read-onlyFilesystem sistem terkunciIntegritas binary, konfigurasi, dan libraryFile sistem bisa ditimpa oleh penyerang yang sudah masuk

Lapisan 1: Namespaces — Membatasi Lingkungan

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.

Lapisan 2: Linux Capabilities — Membatasi Hak Meskipun Root

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.

Memeriksa capabilities dan bounding set
capsh --print
 
# Dalam container, cek bounding set setelah cap-drop
docker run --rm --cap-drop ALL alpine capsh --print | head -20
capsh --print menampilkan daftar capability yang tersedia

Baris 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.

Lapisan 3: Seccomp — Membatasi Syscall

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 4: Root Read-Only — Membatasi Filesystem

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:

Root read-only dengan tmpfs untuk file sementara
docker run --rm -it \
  --read-only \
  --tmpfs /tmp \
  alpine sh
--tmpfs memberi area tulis yang hilang saat container mati

Perhatikan: --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.

no_new_privs: Prasyarat yang Tidak Bisa Ditawar

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.

Aktifkan no-new-privileges di Docker
docker run --rm -it \
  --security-opt no-new-privileges \
  alpine sh -c 'grep NoNewPrivs /proc/self/status'
--security-opt no-new-privileges menyalakan PR_SET_NO_NEW_PRIVS

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.

Studi Kasus: Mencekik capset dan capsh

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:

profile.json — mencekik capset dan setuid
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": [
        "capset",
        "setuid",
        "setgid",
        "setresuid",
        "setresgid",
        "setreuid",
        "setregid"
      ],
      "action": "SCMP_ACT_ERRNO",
      "errnoRet": 1
    }
  ]
}
Deny-list untuk syscall yang dipakai self-escalation

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.

Merangkai Semua Lapisan dalam Satu Perintah

Sekarang saatnya menggabungkan keempat lapisan yang kita bahas — namespaces (ditangani otomatis oleh Docker), capabilities, seccomp, dan read-only root — dalam satu perintah:

Docker run: empat lapis pertahanan sekaligus
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:latest
cap-drop, no-new-privileges, profile seccomp, dan read-only dalam satu baris

Bedah 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.

Kesalahan Umum

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.

Penutup

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:

  • Seccomp membatasi syscall; capabilities membatasi hak; namespaces membatasi visibilitas; read-only root menjaga integritas file. Keempatnya berbeda sumbu dan saling melengkapi.
  • no_new_privs adalah prasyarat filter sekaligus penutup jalur kenaikan hak.
  • Memblokir 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!

Belajar Seccomp - Seccomp & Capabilities Kombinasi | Belajar Seccomp