Server của mình từng bị brute-force SSH và mình phải xử lý gấp lúc nửa đêm — từ đó mình bắt đầu rà soát lại toàn bộ lớp bảo mật, không chỉ firewall hay fail2ban mà cả những thứ sâu hơn ở kernel level. Một trong những thứ mình tìm thấy và áp dụng ngay là Seccomp (Secure Computing Mode).
Nhiều anh em chạy Docker container với cấu hình mặc định mà không biết rằng container đó vẫn có thể gọi hàng trăm system call lên kernel host. Nếu một syscall nguy hiểm như ptrace hay mount bị khai thác, attacker có thể leo thang đặc quyền từ trong container ra ngoài host. Seccomp profile đóng cửa những syscall đó lại trước khi chúng bị lợi dụng.
Seccomp là gì và tại sao cần quan tâm?
Nói đơn giản: Seccomp là bộ lọc cấp kernel, quyết định syscall nào một process được phép gọi — và chặn tất cả cái còn lại. Linux 5.x có hơn 335 syscall, nhưng một web app bình thường chỉ thực sự cần khoảng 50–70 cái. Số còn lại — kexec_load, create_module, mount — không ứng dụng nào cần đến, nhưng lại là những thứ attacker rất muốn gọi được.
Docker đã có default Seccomp profile chặn sẵn khoảng 44 syscall nguy hiểm nhất. Nghe nhiều, nhưng 44/335 vẫn để ngỏ hơn 290 cái. Với service xử lý dữ liệu nhạy cảm hoặc tiếp xúc internet trực tiếp, nên tự viết custom profile riêng thay vì tin vào default.
Hai chế độ Seccomp
- SECCOMP_MODE_STRICT: Chỉ cho phép
read,write,exit,sigreturn— cực kỳ hạn chế, hầu như không dùng thực tế - SECCOMP_MODE_FILTER: Dùng BPF (Berkeley Packet Filter) để định nghĩa rule linh hoạt — đây là cái Docker và systemd dùng
Cấu hình Seccomp Profile cho Docker Container
Tạo custom Seccomp profile
Profile viết dưới dạng JSON. Trường defaultAction quyết định điều gì xảy ra với syscall không có trong danh sách — thường dùng SCMP_ACT_ERRNO (trả lỗi EPERM) hoặc SCMP_ACT_KILL (kill process ngay). Tạo file custom-seccomp.json:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
"syscalls": [
{
"names": [
"accept", "accept4", "access", "arch_prctl",
"bind", "brk", "capget", "capset",
"chdir", "clock_gettime", "clone", "close",
"connect", "dup", "dup2", "dup3",
"epoll_create1", "epoll_ctl", "epoll_pwait", "epoll_wait",
"execve", "exit", "exit_group",
"fchmod", "fchown", "fcntl", "fstat", "fsync",
"futex", "getcwd", "getdents64", "getegid",
"geteuid", "getgid", "getpeername", "getpid",
"getppid", "getrandom", "getrlimit", "getsockname",
"getsockopt", "gettid", "gettimeofday", "getuid",
"ioctl", "kill", "listen", "lseek", "lstat",
"madvise", "mmap", "mprotect", "munmap",
"nanosleep", "open", "openat", "pipe", "pipe2",
"poll", "ppoll", "prctl", "pread64", "prlimit64",
"pwrite64", "read", "readlink", "readv",
"recvfrom", "recvmsg", "rename", "renameat2", "rmdir",
"rt_sigaction", "rt_sigprocmask", "rt_sigreturn",
"sched_yield", "select", "sendfile", "sendmsg", "sendto",
"set_robust_list", "set_tid_address",
"setgid", "setuid", "setsockopt",
"sigaltstack", "socket", "stat", "statfs",
"symlink", "tgkill", "unlink", "unlinkat",
"uname", "wait4", "write", "writev"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
Chạy container với custom profile
# Chạy với custom Seccomp profile
docker run --security-opt seccomp=./custom-seccomp.json \
-p 3000:3000 \
my-nodejs-app
# Chạy unconfined — CHỈ dùng khi debug, không dùng production
docker run --security-opt seccomp=unconfined my-nodejs-app
Cấu hình trong docker-compose.yml
version: '3.8'
services:
webapp:
image: my-nodejs-app
security_opt:
- seccomp:./custom-seccomp.json
ports:
- "3000:3000"
Debug: Tìm ra syscall nào ứng dụng thực sự cần
Câu hỏi thực tế là: làm sao biết app cần syscall gì để không whitelist thiếu? Dùng strace để trace trong lúc app chạy bình thường:
# Trace và lọc danh sách unique syscall khi khởi động app
strace -f -o /tmp/syscalls.log node server.js
grep -oP '^[a-z_]+' /tmp/syscalls.log | sort -u
# Trace một process đang chạy (PID cụ thể)
strace -f -e trace=all -p 1234 2>&1 | awk -F'(' '{print $1}' | sort -u
Đã áp profile nhưng app báo lỗi không rõ nguyên nhân? Đổi defaultAction thành SCMP_ACT_LOG để test — kernel sẽ log syscall bị chặn thay vì kill process, giúp debug nhanh hơn nhiều:
dmesg | grep -i seccomp
# hoặc
journalctl -k | grep seccomp
Seccomp cho Linux Service qua systemd
Chạy app thẳng trên host Linux mà không qua Docker? systemd có cách apply Seccomp còn tiện hơn — có sẵn các syscall group phân loại theo nhóm chức năng, không cần liệt kê từng syscall một:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Web Application
After=network.target
[Service]
User=appuser
ExecStart=/usr/local/bin/myapp
Restart=on-failure
# Seccomp: chỉ cho phép syscall phù hợp với web service
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
# Kết hợp thêm các hardening khác
NoNewPrivileges=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.target
Xem danh sách syscall trong từng group và reload service:
# Xem syscall nào thuộc group @system-service
systemd-analyze syscall-filter @system-service
# Có các group hữu ích như: @network-io, @file-system, @process, @io-event
systemd-analyze syscall-filter @network-io
# Reload và kiểm tra violations
systemctl daemon-reload
systemctl restart myapp
journalctl -u myapp | grep -i seccomp
Apply Seccomp từ trong code Python
Muốn nhúng Seccomp thẳng vào code Python, không phụ thuộc vào Docker hay systemd? Thư viện python-libseccomp làm được — cài đặt đơn giản:
pip install seccomp
import seccomp
# Default action: KILL nếu gọi syscall không được phép
filt = seccomp.SyscallFilter(defaction=seccomp.KILL)
allowed = [
"read", "write", "open", "openat", "close",
"stat", "fstat", "mmap", "mprotect", "munmap",
"brk", "rt_sigaction", "rt_sigreturn",
"ioctl", "pread64", "pwrite64",
"socket", "connect", "accept", "bind", "listen",
"send", "recv", "sendto", "recvfrom",
"exit", "exit_group", "futex", "gettimeofday",
]
for syscall in allowed:
try:
filt.add_rule(seccomp.ALLOW, syscall)
except Exception:
pass
# Áp dụng ngay — code phía dưới sẽ bị giới hạn syscall
filt.load()
print("Seccomp filter đã được áp dụng")
Kết luận
Seccomp không thay được firewall hay AppArmor — mỗi lớp bảo mật có vai trò riêng. Điểm mạnh của nó là chặn ngay ở tầng thấp nhất: trước khi syscall chạm tới kernel. Ghép thêm --cap-drop ALL, --read-only, và --security-opt no-new-privileges là container đó khóa theo ba lớp độc lập nhau.
Checklist nhanh khi triển khai:
seccomp=unconfinedtrên production là tối kị — kể cả khi chỉ debug tạm thời- Dùng
stracehoặcSCMP_ACT_LOGđể profile syscall thực tế trước khi viết whitelist — đừng đoán mò @system-servicetrong systemd là điểm khởi đầu ổn — customize thêm tùy nhu cầu từng service- Test kỹ sau khi áp profile, đặc biệt edge case — một số app gọi syscall không ngờ khi xử lý file lớn hoặc mất kết nối network
Từ sau cái đêm xử lý sự cố brute-force đó, Seccomp profile trở thành một trong những thứ đầu tiên mình setup mỗi khi deploy service mới lên production — đặc biệt với bất kỳ container nào tiếp xúc trực tiếp với internet.

