Quản trị VPS

Phân quyền Docker an toàn: Ngăn container root leo thang chiếm quyền VPS

Hướng dẫn phân quyền Docker an toàn: rủi ro UID 0 mặc định, nguy cơ từ --privileged và docker.sock, cách thiết lập user non-root, cap-drop và userns-remap.

Phân quyền Docker an toàn ngăn container root leo thang chiếm quyền VPS
MỤC LỤC

Docker container chia sẻ chung nhân Linux với máy chủ host. Nếu cấu hình mặc định không được thắt chặt, tiến trình chạy dưới quyền root (UID 0) bên trong container cũng chính là UID 0 trên hệ điều hành của VPS. Khi ứng dụng web bị khai thác lỗ hổng thực thi mã từ xa, sự kết hợp giữa quyền root mặc định và các cấu hình lỏng lẻo như gắn cờ --privileged hoặc chia sẻ socket /var/run/docker.sock sẽ tạo điều kiện để kẻ tấn công thoát khỏi rào chắn namespace và kiểm soát toàn bộ máy chủ. Đây cũng là lý do vì sao trong chiến lược tối ưu hóa và làm sạch Docker trên VPS, việc quản lý vòng đời container luôn cần đi đôi với kỷ luật phân quyền chặt chẽ ngay từ đầu.

Việc thiết lập cơ chế phòng thủ nhiều lớp cho Docker—từ chỉ thị USER non-root, thu hồi Linux Capabilities đến ánh xạ không gian định danh người dùng (userns-remap)—giúp cô lập triệt để tiến trình container khỏi tài nguyên cốt lõi của máy chủ.

So sánh cấu hình mặc định và User Namespace Remapping trong Docker

1. Bản chất phân quyền trong Docker và rủi ro UID 0 mặc định

Khác với máy ảo phần cứng (KVM, VMware) sở hữu nhân hệ điều hành và lớp ảo hóa hypervisor tách biệt hoàn toàn, container chỉ là các tiến trình được cô lập bằng các tính năng của nhân Linux: Namespaces (phân vùng tầm nhìn mạng, tiến trình, mount point) và Control Groups (giới hạn CPU, RAM).

Khi bạn tạo một container bằng lệnh:

bash
docker run -d --name web-test nginx

Tiến trình Nginx bên trong container sẽ chạy với người dùng root (UID 0). Nếu bạn mở một cửa sổ dòng lệnh khác trên máy chủ VPS và kiểm tra tiến trình bằng lệnh của hệ điều hành host:

bash
ps -ef | grep nginx

Bạn sẽ thấy tiến trình nginx: master process hiển thị trên máy chủ host với cột UID là root (hoặc số 0).

Về mặt kỹ thuật, nhân Linux không phân biệt "root của container" với "root của VPS host". Quyền hạn của tiến trình trong container chỉ bị kiềm chế bởi các rào chắn namespace và danh sách capabilities bị cắt bớt của Docker. Nếu kẻ tấn công tìm được lỗ hổng tràn bộ đệm trong nhân Linux (kernel vulnerability) hoặc cấu hình container bị mở rộng đặc quyền, chúng sẽ có đầy đủ quyền root trên toàn bộ máy chủ VPS.

2. Hai sai lầm cấu hình nghiêm trọng dẫn đến chiếm quyền VPS

Trong quá trình triển khai thực tế, nhiều hệ thống bị chiếm quyền điều khiển không phải do lỗi của bản thân Docker, mà do sử dụng các tùy chọn cấu hình nguy hiểm nhằm giải quyết nhanh các vấn đề về phân quyền hoặc tiện ích quản trị.

Cờ --privileged: Vô hiệu hóa toàn bộ rào chắn bảo vệ

Cờ --privileged thường được sử dụng khi người vận hành muốn chạy Docker bên trong Docker (DinD), can thiệp vào card mạng ảo hoặc mount phân vùng ổ đĩa từ bên trong container.

Tuy nhiên, khi gắn cờ này:

  • Docker cấp toàn bộ 41 Linux Capabilities cho container, bao gồm CAP_SYS_ADMIN và CAP_SYS_RAWIO.
  • Bỏ qua toàn bộ bộ lọc bảo mật Seccomp và AppArmor mặc định.
  • Cho phép container nhìn thấy và truy cập trực tiếp vào toàn bộ các tệp tin thiết bị trong thư mục /dev của máy chủ host (như /dev/vda, /dev/sda).

Với cờ --privileged, nếu ứng dụng bên trong container bị chèn webshell, kẻ tấn công chỉ cần chạy một lệnh mount đơn giản là có thể đọc và ghi đè toàn bộ hệ điều hành của VPS:

bash
# Lệnh chạy từ bên trong container có gắn cờ --privileged
mkdir /mnt/host
mount /dev/vda1 /mnt/host

# Ghi thêm khóa SSH của kẻ tấn công thẳng vào VPS host
echo "ssh-ed25519 AAAAC3NzaC... attacker@c2" >> /mnt/host/root/.ssh/authorized_keys

Sau thao tác này, kẻ tấn công đăng nhập thẳng vào VPS qua cổng SSH với quyền root mà không gặp bất kỳ trở ngại nào. Kịch bản này vô hiệu hóa hoàn toàn mọi nỗ lực thiết lập SSH key truy cập VPS an toàn mà bạn đã cấu hình trước đó trên máy chủ.

Mount UNIX Socket /var/run/docker.sock vào container

File /var/run/docker.sock là giao diện IPC (Inter-Process Communication) cho phép giao tiếp trực tiếp với dịch vụ nền Docker Daemon (dockerd). Dịch vụ này luôn chạy dưới quyền root trên host.

Cấu hình mount socket này thường gặp khi cài đặt các công cụ quản lý giao diện như Portainer, reverse proxy tự động phát hiện container như Traefik, hoặc các agent CI/CD (GitLab Runner, Jenkins):

yaml
volumes:
  - /var/run/docker.sock:/var/run/docker.sock

Khi ứng dụng chạy trong container đó bị chiếm quyền, kẻ tấn công không cần khai thác lỗ hổng kernel phức tạp. Chúng chỉ cần cài đặt công cụ dòng lệnh docker hoặc gửi request HTTP thuần vào UNIX socket thông qua lệnh curl để ra lệnh cho Docker daemon trên host khởi tạo một container mới có quyền root:

bash
# Gửi lệnh trực tiếp vào docker.sock từ bên trong container bị chiếm quyền
curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" \
  -d '{"Image": "alpine", "Cmd": ["chroot", "/host", "sh", "-c", "echo pwned > /tmp/hacked"], "Binds": ["/:/host"]}' \
  -X POST http://localhost/v1.41/containers/create

Biện pháp thay thế an toàn:

  1. Tránh chia sẻ socket trực tiếp cho các container công khai ra Internet.
  2. Nếu bắt buộc phải dùng (như trường hợp của Traefik hoặc Portainer), hãy đặt một proxy trung gian chuyên dụng như docker-socket-proxy (của Tecativa). Proxy này chỉ cho phép các lệnh đọc GET /containers/json và chặn hoàn toàn các phương thức POST, DELETE có khả năng tạo hoặc xóa container.

3. Cấu hình container với quyền tối thiểu (Least Privilege)

Để bảo vệ máy chủ, quy trình đóng gói và khởi chạy container cần tuân thủ nguyên tắc cấp đặc quyền tối thiểu.

Thiết lập người dùng non-root trong Dockerfile

Không bao giờ để ứng dụng chạy với quyền root mặc định của base image. Hãy chủ động tạo một nhóm và người dùng chuyên dụng ở các dòng cuối của Dockerfile:

dockerfile
FROM node:20-alpine

WORKDIR /app

# Tạo group và user với UID cụ thể không có quyền root
RUN addgroup -g 10001 -S appgroup && \
    adduser -u 10001 -S appuser -G appgroup

COPY package*.json ./
RUN npm ci --only=production

COPY --chown=appuser:appgroup . .

# Chuyển quyền thực thi sang user không đặc quyền
USER appuser:appgroup

EXPOSE 3000
CMD ["node", "server.js"]

Khi khởi chạy container trong môi trường sản xuất, bạn có thể bổ sung cờ --security-opt=no-new-privileges:true để ngăn chặn các tiến trình con bên trong container nhận thêm quyền thông qua các nhị phân có cờ setuid hoặc setgid:

bash
docker run -d \
  --security-opt=no-new-privileges:true \
  --name web-app \
  my-app:1.0

Thu hồi Linux Capabilities không cần thiết

Theo mặc định, Docker cấp khoảng 14 capabilities cho container (như CAP_CHOWN, CAP_FOWNER, CAP_KILL, CAP_NET_BIND_SERVICE). Hầu hết các ứng dụng web backend (Node.js, Python FastAPI, Go, PHP-FPM) hoàn toàn không cần đến các quyền này trong suốt quá trình xử lý request.

Cấu hình an toàn nhất là thu hồi toàn bộ capabilities bằng --cap-drop=ALL và chỉ cấp lại đúng capability tối thiểu nếu ứng dụng yêu cầu:

bash
docker run -d \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --name web-app \
  my-app:1.0

Trong ví dụ trên, --cap-add=NET_BIND_SERVICE chỉ cần thiết nếu tiến trình của bạn cần lắng nghe trực tiếp trên các cổng mạng đặc quyền dưới 1024 (như cổng 80 hoặc 443). Nếu ứng dụng chạy cổng 3000 hoặc 8080, bạn thậm chí không cần cấp thêm bất kỳ capability nào.

Khóa hệ thống tệp chỉ đọc (Read-only Root Filesystem)

Phần lớn mã độc khi xâm nhập vào ứng dụng web thông qua lỗ hổng upload file sẽ cố gắng ghi các đoạn mã webshell hoặc tải tệp thực thi vào các thư mục như /tmp hoặc mã nguồn ứng dụng.

Bạn có thể kích hoạt chế độ chỉ đọc cho toàn bộ hệ thống tệp của container bằng cờ --read-only, kết hợp mount ramdisk tmpfs cho các thư mục bắt buộc phải ghi dữ liệu tạm:

bash
docker run -d \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --tmpfs /run:rw,noexec,nosuid,size=16m \
  --name secure-web \
  my-app:1.0

Với cấu hình này, ngay cả khi kẻ tấn công có quyền thực thi lệnh từ xa (RCE), mọi nỗ lực tải file nhị phân độc hại về container đều sẽ bị hệ điều hành từ chối với lỗi Read-only file system.

4. Triệt tiêu rủi ro UID 0 với User Namespace Remapping (userns-remap)

Trong các môi trường phức tạp mà một số container bên thứ ba bắt buộc phải chạy dưới quyền root bên trong (ví dụ các dịch vụ khởi động cần bind port hoặc quản lý quyền file nội bộ), cơ chế User Namespace Remapping (userns-remap) là giải pháp bảo vệ toàn diện ở cấp độ nhân Linux.

Nguyên lý hoạt động của userns-remap

Tính năng User Namespaces của Linux cho phép ánh xạ một dải UID/GID bên trong container sang một dải UID/GID hoàn toàn khác trên máy chủ host:

  • Tiến trình trong container tin rằng nó đang chạy với UID 0 (root), do đó mọi thao tác cấu hình bên trong container vẫn diễn ra bình thường.
  • Tuy nhiên, nhân Linux trên máy chủ VPS sẽ dịch (remap) UID 0 đó thành một người dùng thông thường không có đặc quyền trên host, ví dụ UID 165536.

Nếu kẻ tấn công thoát khỏi rào chắn container, hệ điều hành VPS chỉ nhìn nhận kẻ tấn công là tiến trình của user 165536. Kẻ tấn công không thể đọc file /etc/shadow, không thể chỉnh sửa thư mục /root và không thể can thiệp vào các dịch vụ khác của hệ thống.

Hướng dẫn kích hoạt userns-remap trên VPS

Kiểm tra xem hệ thống Linux của bạn đã cấu hình dải SubUID và SubGID hay chưa:

bash
cat /etc/subuid
cat /etc/subgid

Nếu chưa có dòng cấu hình cho Docker, thêm vào cuối hai file trên:

text
# Cấp dải 65536 UID/GID bắt đầu từ 165536 cho người dùng dockremap
dockremap:165536:65536

Tiếp theo, chỉnh sửa tệp cấu hình trung tâm của Docker Daemon tại /etc/docker/daemon.json (tạo mới nếu chưa có):

json
{
  "userns-remap": "default"
}

Khởi động lại dịch vụ Docker để áp dụng cấu hình:

bash
systemctl restart docker

Sau khi kích hoạt, kiểm tra thư mục lưu trữ của Docker tại /var/lib/docker/: bạn sẽ thấy một thư mục mới có định dạng <UID>.<GID> (ví dụ /var/lib/docker/165536.165536/). Toàn bộ dữ liệu image và container từ thời điểm này sẽ được cô lập hoàn toàn dưới quyền của UID phụ đó.

Lưu ý quan trọng khi vận hành userns-remap

  1. Khả năng tương thích của Images cũ: Khi bật userns-remap, Docker sẽ quản lý layer lưu trữ ở thư mục mới. Các container và image đã tải trước đó sẽ không hiển thị (chúng vẫn nằm ở thư mục cũ và không bị xóa). Bạn sẽ cần tải lại các image cần thiết.
  2. Quyền sở hữu Volume Mount: Nếu bạn mount một thư mục từ máy chủ host vào container bằng -v /data/web:/var/www, thư mục /data/web trên máy chủ host phải được cấp quyền đọc/ghi cho UID remapped (chown -R 165536:165536 /data/web), nếu không ứng dụng trong container sẽ báo lỗi quyền truy cập Permission denied.

5. Checklist kiểm tra an toàn phân quyền Docker trên VPS

Trước khi đưa một dịch vụ Docker vào vận hành chính thức trên VPS, hãy rà soát cấu hình theo bảng đối chiếu sau:

Hạng mục kiểm traNguy cơ tiềm ẩnCấu hình khuyến nghị
User trong ContainerChạy root (UID 0) trùng với root của máy chủ hostKhai báo USER non-root trong Dockerfile; gán cờ --security-opt=no-new-privileges:true
Cờ --privilegedVô hiệu hóa bảo vệ, truy cập toàn bộ thiết bị /dev của hostTuyệt đối không dùng trên môi trường production
Mount /var/run/docker.sockChiếm quyền điều khiển Docker daemon để tạo container rootKhông mount ra Internet; sử dụng docker-socket-proxy có giới hạn quyền đọc
Linux CapabilitiesGiữ lại các capabilities quản trị mạng và kernelKhởi chạy với --cap-drop=ALL, chỉ mở thêm quyền tối thiểu (--cap-add)
Hệ thống tệp (Rootfs)Mã độc tải và thực thi binary độc hạiBật cờ --read-only, mount thư mục ghi tạm qua tmpfs
User Namespace RemapLỗ hổng kernel dẫn đến leo thang quyền hostBật "userns-remap": "default" trong /etc/docker/daemon.json

Việc áp dụng đồng bộ các nguyên tắc trên giúp biến Docker thành một lớp phòng thủ tin cậy, ngăn chặn nguy cơ một ứng dụng web bị xâm nhập làm ảnh hưởng đến tính an toàn của toàn bộ máy chủ VPS. Trong trường hợp nghi ngờ một container từng bị khai thác hoặc xuất hiện các tiến trình lạ trên host, bạn nên lập tức thực hiện quy trình rà soát mã độc và backdoor trên Linux VPS để kiểm tra toàn diện các socket mạng và cơ chế persistence trước khi đưa hệ thống trở lại hoạt động.

Nội dung hướng dẫn này có hữu ích với hệ thống của bạn không?

Chia sẻ bài viết hữu ích

Gửi bài viết này cho đồng nghiệp

Đọc tiếp

Bài nên đọc tiếp

Xem tất cả bài về Quản trị VPS