Hiểu và Sử dụng Linux Capabilities: Phân Quyền Chi Tiết Không Cần Root Cho Ứng Dụng

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Giới Thiệu: Đừng Để Root Làm Quá Nhiều!

Làm việc với Linux, anh em DevOps mình chắc chắn không lạ gì với việc “chạy bằng quyền root cho tiện”. Một dòng sudo python app.py hay sudo ./start_service.sh là xong. Nhưng “tiện” ở đây đồng nghĩa với “nguy hiểm” đấy. Cấp đầy đủ quyền root cho một ứng dụng là con dao hai lưỡi. Điều này đặc biệt nguy hiểm nếu ứng dụng đó có lỗ hổng bảo mật. Vậy làm sao để một ứng dụng có thể làm những việc cần đặc quyền (ví dụ như lắng nghe trên cổng 80/443) mà không cần phải là root hoàn toàn?

Câu trả lời nằm ở Linux Capabilities. Đây là cơ chế phân quyền chi tiết. Nó cho phép bạn chia nhỏ các đặc quyền của tài khoản root thành những “khả năng” nhỏ hơn. Sau đó, bạn có thể gán riêng lẻ chúng cho từng tiến trình hoặc file thực thi. Nó giống như việc thay vì đưa cho thợ sửa ống nước cả chìa khóa nhà, mình chỉ đưa đúng chìa khóa phòng tắm thôi vậy.

Quick Start: Cấp Quyền Lắng Nghe Cổng 80 Cho Python Script Trong 5 Phút

Để anh em thấy ngay “sức mạnh” của Capabilities, mình sẽ hướng dẫn một ví dụ thực tế:

Vấn đề: Một script Python đơn giản muốn chạy HTTP server trên cổng 80 (cổng đặc quyền, yêu cầu quyền root) mà không cần chạy toàn bộ script dưới quyền root.

Bước 1: Chuẩn bị script Python

Tạo một file simple_server.py với nội dung sau:


import http.server
import socketserver

PORT = 80

Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print(f"Serving at port {PORT}")
    httpd.serve_forever()

Bước 2: Thử chạy (và thất bại)

Thử chạy script này với một user thường (không phải root):


python3 simple_server.py

Anh em sẽ thấy lỗi kiểu như Permission denied hoặc socket.error: [Errno 13] Permission denied. Đúng như dự đoán, cổng 80 là của “người lớn”.

Bước 3: Gán capability

Đây là lúc Capabilities “ra tay”. Chúng ta sẽ dùng lệnh setcap để gán khả năng lắng nghe trên các cổng đặc quyền (CAP_NET_BIND_SERVICE). Tốt nhất là gán cho trình thông dịch Python. Dù có thể gán trực tiếp cho script, nhưng cách này tiện hơn cho các script nhỏ.


sudo setcap 'cap_net_bind_service=+ep' $(eval readlink -f $(which python3))

Giải thích lệnh trên:

  • cap_net_bind_service: Là capability cho phép process bind tới các cổng có số hiệu nhỏ hơn 1024.
  • +ep: “e” (effective) nghĩa là capability này sẽ được kích hoạt, “p” (permitted) nghĩa là process được phép sử dụng capability này.
  • $(eval readlink -f $(which python3)): Tìm đường dẫn tuyệt đối của trình thông dịch python3 đang được sử dụng.

Bước 4: Chạy lại (và thành công!)

Bây giờ, thử chạy lại script với user thường:


python3 simple_server.py

Và “tada!” Anh em sẽ thấy output Serving at port 80. Mở trình duyệt và truy cập http://localhost (hoặc IP của server), anh em sẽ thấy server đang chạy và phục vụ file. Script Python của chúng ta giờ có thể lắng nghe trên cổng 80 mà không cần chạy với toàn bộ quyền root.

Bước 5: Dọn dẹp (quan trọng!)

Sau khi thử nghiệm, đừng quên gỡ bỏ capability để đảm bảo bảo mật. Gán capability vào trình thông dịch Python có thể không phải lúc nào cũng là ý hay, vì bất kỳ script Python nào cũng có thể lợi dụng nó.


sudo setcap -r $(eval readlink -f $(which python3))

Giải Thích Chi Tiết: Tại Sao Linux Capabilities Lại Quan Trọng?

Ngày xưa, trong Linux, một process hoặc là root (quyền “God mode”), hoặc là user thường (bị hạn chế rất nhiều). Không có đường ở giữa. Điều này dẫn đến hai vấn đề lớn:

  1. Nguy cơ bảo mật: Nếu một dịch vụ như Nginx, Apache, hoặc ứng dụng tùy chỉnh cần bind cổng 80, nó buộc phải khởi động dưới quyền root. Sau đó, ứng dụng thường “drop privileges” xuống một user ít quyền hơn. Tuy nhiên, nếu quá trình khởi động hoặc “drop privileges” gặp lỗi, toàn bộ ứng dụng vẫn chạy với quyền root. Điều này tạo ra lỗ hổng bảo mật cực kỳ lớn nếu bị khai thác.
  2. Hạn chế linh hoạt: Nhiều ứng dụng chỉ cần một hoặc hai đặc quyền nhỏ của root. Nhưng trước đây, chúng lại bị buộc phải có tất cả. Ví dụ, để thay đổi quyền sở hữu một file (chown) hay đọc thông tin mạng (net_admin), bạn phải có cả “núi” quyền root.

Linux Capabilities ra đời để giải quyết vấn đề này. Nó chia nhỏ các đặc quyền của tài khoản root thành khoảng 40-50 “khả năng” riêng biệt (ví dụ: CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_NET_ADMIN, CAP_SYS_ADMIN…). Mỗi khả năng cho phép một hành động cụ thể mà trước đây chỉ root mới làm được. Khi đó, anh em có thể gán chính xác những capability cần thiết cho một tiến trình, mà không phải trao cả “vương miện” root.

Một số capabilities phổ biến mà mình hay gặp:

  • CAP_NET_BIND_SERVICE: Cho phép bind tới các cổng có số hiệu nhỏ hơn 1024.
  • CAP_CHOWN: Cho phép thay đổi quyền sở hữu (owner) của file.
  • CAP_DAC_OVERRIDE: Bỏ qua quyền kiểm tra đọc/ghi/thực thi file.
  • CAP_NET_ADMIN: Cho phép thực hiện các thao tác quản lý mạng (thêm/xóa route, cấu hình interface…).
  • CAP_SYS_ADMIN: Một trong những capability “mạnh” nhất, bao gồm nhiều đặc quyền quản trị hệ thống. Cần cẩn trọng khi cấp cái này.

Mỗi process có một vài tập hợp capability:

  • Permitted (P): Tập hợp các capability mà process được phép sử dụng.
  • Effective (E): Tập hợp con của Permitted, là các capability hiện đang được kích hoạt và sử dụng bởi process.
  • Inheritable (I): Các capability được truyền lại cho process con khi gọi execve.
  • Bounding (B): Giới hạn “trần” của tất cả các capability mà một process có thể có.
  • Ambient (A): Tập hợp capability được giữ lại khi chuyển từ root sang non-root user mà không dùng SUID. Cái này hơi nâng cao, nhưng rất hữu ích trong container.

Nâng Cao: Quản Lý Capabilities Với getcap, setcap và systemd

1. Kiểm Tra Capabilities Hiện Tại

Để kiểm tra các capabilities được gán cho một file thực thi, anh em dùng getcap:


getcap /usr/bin/ping

Kết quả có thể trả về đại loại như /usr/bin/ping = cap_net_raw+ep, nghĩa là ping có capability CAP_NET_RAW (để tạo các gói tin ICMP thô) ở chế độ effective và permitted. Đây là lý do user thường vẫn có thể dùng ping.

Để kiểm tra capabilities của một tiến trình đang chạy, anh em có thể đọc file /proc/<pid>/status:


cat /proc/self/status | grep Cap

Anh em sẽ thấy các dòng như CapPrm (Permitted), CapEff (Effective), CapInh (Inheritable), CapBnd (Bounding), CapAmb (Ambient) được biểu diễn dưới dạng số hex. Để hiểu các số này là gì, anh em cần tra bảng mapping từ hex sang tên capability, hoặc dùng công cụ như capsh --decode=<hex_value>.

2. Gán và Gỡ Bỏ Capabilities

Cú pháp cơ bản của setcap:


sudo setcap 'cap_NAME1+ep cap_NAME2+ep' /path/to/executable
sudo setcap -r /path/to/executable # Gỡ bỏ tất cả capabilities

Lưu ý: Anh em chỉ nên gán capabilities cho các file thực thi đáng tin cậy. Nếu không, kẻ tấn công có thể lợi dụng file đó để leo thang đặc quyền.

3. Capabilities Trong systemd

Với anh em DevOps, việc quản lý dịch vụ bằng systemd là điều hiển nhiên. systemd cung cấp các directive mạnh mẽ để kiểm soát capabilities của service:

  • AmbientCapabilities: Gán Ambient capabilities cho service. Rất hữu ích cho các ứng dụng chạy non-root nhưng cần một số quyền đặc biệt.
  • CapabilityBoundingSet: Giới hạn tập hợp các capabilities mà service có thể có. Đây là một cơ chế phòng thủ mạnh mẽ, đảm bảo rằng ngay cả khi ứng dụng cố gắng có thêm quyền, nó cũng không thể vượt quá giới hạn này. Mặc định, nó bao gồm tất cả các capabilities. Mình khuyên anh em nên giảm thiểu cái này.
  • NoNewPrivileges=true: Đảm bảo tiến trình không thể leo thang đặc quyền (ví dụ thông qua SUID/SGID bit). Luôn luôn nên bật cái này cho các dịch vụ!

Ví dụ một file service của Nginx có thể trông như thế này (dù Nginx đã được tối ưu bảo mật khá tốt):


[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
PrivateTmp=true
NoNewPrivileges=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Trong ví dụ trên, mình giới hạn CapabilityBoundingSet chỉ còn CAP_NET_BIND_SERVICECAP_DAC_OVERRIDE (chỉ là ví dụ, không nên áp dụng bừa bãi). Điều này có nghĩa là Nginx không thể có bất kỳ capability nào khác ngoài hai cái đó, dù nó có cố gắng yêu cầu. Đồng thời, NoNewPrivileges=true đảm bảo nó không thể có thêm quyền mới.

Tips Thực Tế: Kinh Nghiệm Xương Máu Với Capabilities

1. Nguyên Tắc Ít Đặc Quyền Nhất (Principle of Least Privilege)

Đây là kim chỉ nam. Chỉ gán những capability tối thiểu mà ứng dụng cần để hoạt động. Đừng bao giờ cấp CAP_SYS_ADMIN nếu không thực sự hiểu rõ hậu quả. Nó gần như tương đương với quyền root.

2. Debugging Là Cả Một Nghệ Thuật

Hồi mới bắt đầu làm sysadmin, mình từng mất cả buổi chiều debug vấn đề này chỉ vì không đọc kỹ log, cứ nghĩ là lỗi code hay config firewall. Ai dè, chỉ thiếu mỗi cái CAP_NET_BIND_SERVICE mà thôi! Khi ứng dụng của anh em không hoạt động như mong đợi và anh em nghi ngờ về quyền, hãy dùng strace.

strace -e capability python3 simple_server.py

Lệnh này sẽ hiển thị tất cả các syscall liên quan đến capability mà tiến trình thực hiện. Nó sẽ giúp anh em thấy được ứng dụng đang cố gắng làm gì và có bị từ chối capability nào không. Nếu thấy lỗi EPERM liên quan đến một syscall nào đó (ví dụ bind()), thì rất có thể là do thiếu capability tương ứng.

3. Cẩn Thận Với setcap Trên Các File Chung

Việc gán capability cho trình thông dịch (như Python) hoặc các tiện ích hệ thống chung (như /usr/bin/curl) cần được xem xét rất kỹ. Bởi vì nếu file đó được cấp quá nhiều quyền, bất kỳ ai cũng có thể lợi dụng nó. Tốt nhất là tạo một bản sao của binary hoặc script, gán capability cho bản sao đó, và chỉ chạy bản sao đó.

4. Capabilities và Container

Trong môi trường container (Docker, Kubernetes), Capabilities càng trở nên quan trọng. Mặc định, Docker sẽ thả (drop) hầu hết các capabilities không cần thiết và chỉ giữ lại một tập con an toàn. Anh em có thể tùy chỉnh tập hợp này bằng --cap-add--cap-drop khi chạy container. Việc này giúp giảm bề mặt tấn công của container đáng kể.


docker run --cap-add=NET_ADMIN --cap-drop=CHOWN my_image command

Luôn review các capabilities mặc định mà container runtime của anh em giữ lại và điều chỉnh cho phù hợp với nhu cầu ứng dụng.

Kết Luận

Linux Capabilities là một công cụ cực kỳ mạnh mẽ để tăng cường bảo mật và kiểm soát hệ thống của anh em. Việc hiểu và áp dụng đúng cách cơ chế này giúp anh em xây dựng các ứng dụng an toàn hơn, giảm thiểu rủi ro từ các lỗ hổng bảo mật và tuân thủ nguyên tắc “ít đặc quyền nhất”. Hãy bắt đầu khám phá và tích hợp Capabilities vào quy trình DevOps của mình ngay hôm nay nhé!

Share: