Quản trị SELinux thực tế: Quản lý Context, Boolean và khắc phục sự cố với audit2allow

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Vấn đề: Những lỗi Permission Denied bí ẩn

Bạn triển khai một virtual host Nginx mới trỏ đến thư mục document root tùy chỉnh như /srv/www/site. Phân quyền thư mục trông hoàn toàn chuẩn xác: lệnh ls -l xác nhận quyền sở hữu thuộc về nginx:nginx và quyền truy cập được đặt là 755. Thế nhưng mọi yêu cầu kiểm tra đều lập tức trả về lỗi 403 Forbidden.

Kiểm tra file /var/log/nginx/error.log xuất hiện một dòng thông báo quá đỗi quen thuộc: open() "/srv/www/site/index.html" failed (13: Permission denied). Quyền UNIX tiêu chuẩn rõ ràng cho phép tiến trình worker đọc file, nhưng kernel vẫn từ chối thao tác đọc này.

Khi đối mặt với rào cản này, nhiều quản trị viên thường chọn giải pháp tình thế: chạy setenforce 0 hoặc đặt SELINUX=disabled trong /etc/selinux/config. Thao tác này giúp xóa lỗi ngay tức khắc. Tuy nhiên, việc vô hiệu hóa Security-Enhanced Linux (SELinux) sẽ loại bỏ lớp phòng thủ theo chiều sâu (defense-in-depth) mạnh mẽ nhất của bạn, khiến máy chủ dễ bị tấn công nếu một tiến trình daemon ứng dụng nào đó bị khai thác.

Nguyên nhân cốt lõi: Discretionary vs. Mandatory Access Control

Cơ chế bảo mật file truyền thống trên Linux dựa vào Discretionary Access Control (DAC). Quyền truy cập phụ thuộc hoàn toàn vào ID chủ sở hữu, nhóm (group) và các bit rwx tiêu chuẩn. Nếu kẻ tấn công khai thác được lỗ hổng thực thi mã từ xa (RCE) trong Nginx, chúng sẽ chiếm quyền của UID 998 (nginx). Chúng có thể đọc ngay bất kỳ file nào trên hệ thống mà user hoặc group đó có quyền truy cập—bao gồm cả các bản dump cơ sở dữ liệu tạm thời hay các cấu hình nội bộ nhạy cảm.

SELinux thực thi Mandatory Access Control (MAC) ngay bên trong Linux kernel. Mọi tiến trình, file descriptor, thư mục và network socket đều mang một nhãn security context rõ ràng. SELinux tham chiếu bộ quy tắc policy engine đang hoạt động trước khi cấp quyền truy cập. Ngay cả khi quyền DAC cho phép rwxrwxrwx (0777), SELinux vẫn sẽ chặn yêu cầu nếu chính sách bảo mật không cho phép tương tác giữa domain và type cụ thể đó.

Khái niệm cốt lõi: Context, Mode và Boolean

Quản trị SELinux hiệu quả xoay quanh ba thành phần chính:

1. Security Context

Mỗi nhãn của file và tiến trình bao gồm bốn trường phân tách bằng dấu hai chấm: user:role:type:level. Trường thứ ba—type (hoặc domain khi gán cho một tiến trình đang chạy)—quyết định các quy tắc Type Enforcement.

Kiểm tra context của file bằng cách thêm cờ -Z vào lệnh ls:

ls -lZ /var/www/html/index.html
# Kết quả:
# -rw-r--r--. 1 root root system_u:object_r:httpd_sys_content_t:s0 242 Oct 12 10:14 index.html

Kiểm tra context của tiến trình đang chạy bằng lệnh ps:

ps -eZ | grep nginx
# Kết quả:
# system_u:system_r:httpd_t:s0         1842 ?        00:00:01 nginx

Trong quy tắc policy có định hướng (targeted policy) này, kernel cho phép bất kỳ tiến trình nào bị giới hạn trong domain httpd_t được đọc các file được gán nhãn type là httpd_sys_content_t.

2. Các chế độ hoạt động (Operating Modes)

SELinux hoạt động ở một trong ba trạng thái:

  • Enforcing: Các quy tắc được áp dụng nghiêm ngặt. Những thao tác bị từ chối sẽ bị chặn ngay lập tức và ghi lại vào audit log.
  • Permissive: Các hành động bị từ chối chỉ được ghi log để gỡ lỗi nhưng thao tác vẫn được phép thực hiện. Chế độ này lý tưởng cho các máy chủ staging.
  • Disabled: Phân hệ SELinux trong kernel bị tắt hoàn toàn. Việc kích hoạt lại đòi hỏi hệ thống phải dán lại nhãn (relabel) toàn bộ filesystem khi khởi động lại.

Chuyển đổi chế độ trong runtime mà không cần khởi động lại máy:

# Kiểm tra chế độ hoạt động hiện tại
getenforce

# Chuyển sang chế độ Permissive để khắc phục sự cố
sudo setenforce 0

# Chuyển lại về chế độ Enforcing sau khi hoàn tất gỡ lỗi
sudo setenforce 1

3. SELinux Booleans

Boolean là các cờ tính năng runtime được biên dịch sẵn vào trong chính sách bảo mật. Thay vì phải viết mã policy tùy chỉnh, bạn chỉ cần bật/tắt các công tắc có sẵn này để cho phép các hoạt động tiêu chuẩn, chẳng hạn như cho phép web daemon khởi tạo kết nối mạng hoặc chạy các tiến trình gửi mail.

Thực hành: Sửa nhãn và xử lý các lỗi từ chối truy cập

Bước 1: Cài đặt các công cụ quản trị cốt lõi

Trên RHEL 9, Rocky Linux, AlmaLinux hoặc Fedora, hãy cài đặt các tiện ích quản lý chính sách và daemon phân tích audit:

sudo dnf install -y policycoreutils-python-utils setroubleshoot-server

Bước 2: Khắc phục File Context vĩnh viễn

Khi bạn tạo một thư mục web tùy chỉnh như /srv/www/site, filesystem sẽ gán cho nó một nhãn mặc định dự phòng—thường là default_t hoặc var_t. Tiến trình domain httpd_t không thể truy cập các generic type này.

Tránh sử dụng chcon. Lệnh này chỉ thay đổi nhãn tạm thời, và các chỉnh sửa thủ công đó sẽ bị ghi đè trong bất kỳ đợt relabel bảo trì nào. Luôn ghi quy tắc context vào cơ sở dữ liệu chính sách của hệ thống bằng lệnh semanage, sau đó áp dụng bằng restorecon:

# 1. Đăng ký quy tắc gán nhãn vĩnh viễn vào cơ sở dữ liệu policy
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www/site(/.*)?"

# 2. Áp dụng đệ quy context đã đăng ký lên ổ đĩa
sudo restorecon -Rv /srv/www/site

Nếu ứng dụng của bạn cần xử lý file upload của người dùng hoặc ghi các file cache (chẳng hạn như cache WordPress trong /srv/www/site/uploads), hãy áp dụng type cho phép đọc-ghi httpd_sys_rw_content_t:

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/srv/www/site/uploads(/.*)?"
sudo restorecon -Rv /srv/www/site/uploads

Bước 3: Bật/tắt quyền truy cập mạng bằng Boolean

Giả sử ứng dụng web Nginx của bạn cần truy vấn cơ sở dữ liệu PostgreSQL chạy trên cổng 5432 hoặc gọi một API upstream qua HTTPS. Theo mặc định, targeted policy sẽ ngăn chặn các HTTP daemon thiết lập các kết nối mạng outbound.

Tìm kiếm các boolean liên quan bằng lệnh getsebool:

getsebool -a | grep httpd_can_network

Bật chính xác tính năng bạn cần. Cờ -P giúp lưu thay đổi vĩnh viễn qua các lần khởi động lại hệ thống:

# Cho phép ứng dụng web kết nối riêng đến các cơ sở dữ liệu quan hệ
sudo setsebool -P httpd_can_network_connect_db on

# Hoặc cho phép thiết lập kết nối mạng outbound nói chung
sudo setsebool -P httpd_can_network_connect on

Bước 4: Phân tích các lỗi từ chối bằng ausearch, sealert và audit2allow

Khi kernel chặn một system call, nó sẽ ghi lại một bản ghi Access Vector Cache (AVC) vào /var/log/audit/audit.log:

type=AVC msg=audit(1710328912.421:842): avc:  denied  { read } for  pid=1842 comm="nginx" name="index.html" dev="nvme0n1p2" ino=2097156 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=0

Lọc các cảnh báo từ chối gần đây bằng lệnh ausearch:

sudo ausearch -m AVC -ts recent

Để nhận giải thích dễ hiểu bằng ngôn ngữ tự nhiên về lý do hành động thất bại và cách giải quyết, hãy chuyển log vào sealert:

sudo sealert -a /var/log/audit/audit.log

Khi làm việc với các daemon nội bộ tùy chỉnh hoặc các binary agent của bên thứ ba không có sẵn boolean, hãy tạo module Type Enforcement riêng bằng audit2allow:

# 1. Xem trước bản thảo policy dễ đọc được tạo từ log từ chối
sudo grep "mydaemon" /var/log/audit/audit.log | audit2allow -m mydaemon_custom

# 2. Biên dịch các quy tắc thành file mã nguồn (.te) và gói policy nhị phân (.pp)
sudo grep "mydaemon" /var/log/audit/audit.log | audit2allow -M mydaemon_custom

# 3. Luôn kiểm tra mã nguồn .te vừa được tạo trước khi kích hoạt
cat mydaemon_custom.te

# 4. Tải gói policy đã biên dịch vào kernel
sudo semodule -i mydaemon_custom.pp

Tuyệt đối không cài đặt các module policy được tạo tự động một cách mù quáng. Nếu lỗi từ chối của web daemon xuất phát từ mã độc đang cố gắng đọc /etc/shadow, việc biên dịch lỗi từ chối đó bằng audit2allow sẽ vô tình cấp cho kẻ tấn công quyền truy cập vĩnh viễn vào các chuỗi băm mật khẩu của bạn.

Quy tắc thực tế cần nhớ

Làm việc với SELinux thực sự rất mạch lạc một khi bạn tuân thủ đúng trình tự. Kiểm tra nhãn file bằng ls -Z. Đăng ký đường dẫn thư mục vĩnh viễn bằng semanage fcontext. Bật các quyền hoạt động cần thiết với setsebool -P, và chỉ dành audit2allow cho các dịch vụ nội bộ đặc thù. Hãy xem mỗi lỗi từ chối AVC như một manh mối chẩn đoán thay vì một rào cản. Bằng cách luôn giữ SELinux ở chế độ Enforcing, dịch vụ web khi bị xâm nhập sẽ bị cô lập trong chính domain của nó thay vì chiếm quyền kiểm soát toàn bộ máy chủ của bạn.

Share: