Komunikasi antar-proses (IPC) berbasis Unix Domain Socket sering kali dianggap aman secara default karena berjalan secara lokal di dalam satu kernel host yang sama. Anggapan ini keliru. Jika socket file memiliki izin akses longgar atau protokol parsing data biner tidak divalidasi dengan ketat, penyerang lokal non-privilese dapat mengeksekusi aksi tanpa izin, memicu crash sistem melalui denial-of-service (DoS), atau mengeksploitasi celah memori.
Pendekatan hardening berikut mengadaptasi arsitektur keamanan X Window System (X11)—khususnya skema MIT-MAGIC-COOKIE-1—yang dipadukan dengan validasi framing biner berbatas ketat untuk memastikan integritas dan ketersediaan layanan IPC lokal.
1. Vektor Ancaman pada IPC Lokal
Sebelum menerapkan mitigasi, identifikasi titik eksploitasi umum pada soket domain Unix:
- Akses Socket Tanpa Izin: Socket file yang dibuat tanpa konfigurasi
umaskeksplisit sering mewarisi mode direktori permisif (misalnya0755atau0777), memungkinkan pengguna lokal lain di host yang sama untuk membuat koneksi langsung. - Timing Attack pada Handshake: Perbandingan token rahasia secara naif menggunakan operator kesetaraan string/byte standar (seperti
==ataumemcmp) berhenti pada byte pertama yang tidak cocok. Variasi waktu respons ini dapat dimanfaatkan penyerang untuk merekonstruksi token byte demi byte. - Eksploitasi Alokasi Memori (Unbounded Allocation): Protokol biner yang membaca field length dari header dan langsung mengalokasikan buffer memori sebesar nilai tersebut rentan terhadap DoS berbasis memori (OOM kill) atau integer overflow jika ukuran length melebihi batas wajar.
2. Arsitektur Autentikasi Magic Cookie
Mekanisme magic cookie bekerja dengan membuat rahasia bersama (shared secret) berbasis entropi tinggi saat daemon atau sesi pertama kali diinisialisasi. Rahasia ini disimpan ke dalam filesystem lokal dengan izin sangat terbatas, kemudian dibaca oleh klien sah sebelum melakukan koneksi ke soket.
Siklus Hidup Cookie
- Server membangkitkan 32 byte acak dari CSPRNG (Cryptographically Secure Pseudo-Random Number Generator).
- Server menuliskan representasi biner atau heksadesimal dari token tersebut ke berkas target (misal:
~/.app_cookie) dengan mode izin0600(hanya dapat dibaca dan ditulis oleh pemilik proses). - Klien yang berjalan di bawah UID yang sama membaca berkas
0600tersebut. - Saat inisiasi koneksi ke socket IPC, klien wajib mengirimkan handshake frame berisi token ini sebelum perintah lain diproses.
Peringatan: Selalu periksa nilaifstat()setelah membuka berkas cookie. Pastikanst_mode & 0077 == 0dan kepemilikan berkas (st_uid) identik dengan UID proses pemanggil untuk mencegah symlink race attack.
3. Protokol Framing Biner Aman
Protokol berbasis teks (seperti JSON/line-delimited) memiliki beban parsing tinggi dan rentan terhadap injection. Protokol biner dengan fixed-size header memberikan performa optimal dan batas parsing yang terukur.
Struktur frame standar yang aman diatur sebagai berikut:
- Magic Bytes (2 byte): Penanda identitas protokol (misal:
0x5A, 0x43). Menolak paket sampah non-protokol sejak byte pertama. - Frame Type (1 byte): Menentukan fungsi paket (misal:
0x01untuk Handshake Auth,0x02untuk Command Payload). - Reserved/Flags (1 byte): Untuk kebutuhan ekspansi mendatang, wajib bernilai
0x00. - Payload Length (4 byte, Big-Endian uint32): Ukuran payload aktual. Wajib dibatasi pada konstanta
MAX_PAYLOAD_SIZE(misal: 64 KB) sebelum dialokasikan ke memori.
4. Implementasi Server Socket (Go)
Implementasi berikut menunjukkan pembuatan Unix domain socket, validasi izin berkas, mitigasi timing-attack menggunakan crypto/subtle, serta validasi batas alokasi buffer:
package main
import (
"crypto/rand"
"crypto/subtle"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"os"
)
const (
SocketPath = "/tmp/hardened_ipc.sock"
CookiePath = "/tmp/hardened_ipc.cookie"
MagicByte1 = 0x5A
MagicByte2 = 0x43
TypeHandshake = 0x01
TypeData = 0x02
CookieLength = 32
MaxPayloadSize = 64 * 1024 // Batas ketat: 64KB
)
type Server struct {
expectedCookie []byte
listener net.Listener
}
func generateCookieFile(path string) ([]byte, error) {
cookie := make([]byte, CookieLength)
if _, err := rand.Read(cookie); err != nil {
return nil, err
}
// Buat berkas dengan mode izin ketat 0600
flags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
f, err := os.OpenFile(path, flags, 0600)
if err != nil {
return nil, err
}
defer f.Close()
if _, err := f.Write(cookie); err != nil {
return nil, err
}
return cookie, nil
}
func readFrame(r io.Reader) (byte, []byte, error) {
header := make([]byte, 8)
if _, err := io.ReadFull(r, header); err != nil {
return 0, nil, err
}
// 1. Verifikasi magic bytes
if header[0] != MagicByte1 || header[1] != MagicByte2 {
return 0, nil, errors.New("invalid magic header")
}
frameType := header[2]
payloadLen := binary.BigEndian.Uint32(header[4:8])
// 2. Proteksi alokasi: cegah alokasi memori berlebih sebelum alokasi buffer
if payloadLen > MaxPayloadSize {
return 0, nil, fmt.Errorf("payload length %d exceeds max allowed %d", payloadLen, MaxPayloadSize)
}
payload := make([]byte, payloadLen)
if _, err := io.ReadFull(r, payload); err != nil {
return 0, nil, err
}
return frameType, payload, nil
}
func (s *Server) handleConnection(conn net.Conn) {
defer conn.Close()
// Langkah 1: Wajib Handshake Frame terlebih dahulu
frameType, payload, err := readFrame(conn)
if err != nil || frameType != TypeHandshake {
return
}
// Langkah 2: Constant-time comparison untuk mitigasi timing attack
if subtle.ConstantTimeCompare(payload, s.expectedCookie) != 1 {
return // Autentikasi gagal, tutup koneksi langsung tanpa info detail
}
// Handshake valid, lanjutkan pemrosesan payload data
for {
fType, data, err := readFrame(conn)
if err != nil {
break
}
if fType == TypeData {
// Tangani perintah aplikasi
_ = data
}
}
}
func main() {
_ = os.Remove(SocketPath)
cookie, err := generateCookieFile(CookiePath)
if err != nil {
panic(err)
}
l, err := net.Listen("unix", SocketPath)
if err != nil {
panic(err)
}
defer l.Close()
defer os.Remove(SocketPath)
defer os.Remove(CookiePath)
// Pastikan socket file memiliki izin yang sesuai (0600)
if err := os.Chmod(SocketPath, 0600); err != nil {
panic(err)
}
srv := &Server{expectedCookie: cookie, listener: l}
for {
conn, err := srv.listener.Accept()
if err != nil {
break
}
go srv.handleConnection(conn)
}
}
5. Detail Analisis Keamanan
Mitigasi Timing Attack
Fungsi subtle.ConstantTimeCompare memastikan waktu komputasi independen dari jumlah byte yang cocok. Hal ini krusial untuk token biner acak, di mana penyerang dengan latensi rendah di lingkungan localhost dapat mengukur selisih nanodetik jika pembandingan byte berhenti di tengah jalan (early exit).
Pemeriksaan Alokasi Sebelum Buffer Dibuat
Pada fungsi readFrame, periksa variabel payloadLen sebelum pemanggilan make([]byte, payloadLen). Mengabaikan validasi ini memungkinkan penyerang mengirimkan header dengan nilai length 0xFFFFFFFF (4 GB), yang dapat memicu panic alokasi memori atau kehabisan RAM instan pada host.
6. Alternatif dan Pertimbangan Desain
- SO_PEERCRED vs Magic Cookie: Pada Linux, mekanisme alternatif adalah memeriksa kredensial soket menggunakan
getsockoptdengan levelSOL_SOCKETdan opsiSO_PEERCREDuntuk memverifikasi PID, UID, dan GID klien secara langsung dari kernel. Pola magic cookie dipilih ketika portabilitas antar-OS (BSD, macOS, Linux) dibutuhkan, atau saat kontrol akses berbasis token per-sesi lebih disukai daripada pembatasan UID level-sistem. - Penanganan Koneksi Gagal: Jangan kembalikan pesan error yang spesifik (misal: "Cookie mismatch" vs "Malformed header"). Putuskan koneksi secara diam-diam (silent drop) untuk mempersulit pemetaan logika server oleh pihak ketiga.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!