Vì sao control panel hosting không bao giờ nên chạy bằng root

Tách quyền, sandbox cho từng tài khoản, user namespace cho Docker và phòng thủ nhiều lớp: thiết kế panel hosting để một lỗi không trao cả server cho kẻ tấn công.

Kiến trúc panel hosting: panel không có root và agent root

Ý chính

  • Panel chạy bằng root biến một lỗi web thành toàn quyền trên server.
  • Tách quyền giữ panel web ở quyền thường và để một agent root nhỏ thực hiện danh sách hành động cố định đã kiểm tra.
  • Công việc của khách chạy dưới quyền của khách, trong sandbox riêng từng tài khoản.
  • Container Docker nên chạy trong user namespace để root trong container không phải root trên máy chủ.

Control panel hosting là một trong những mục tiêu hấp dẫn nhất trên internet. Nó truy cập được từ bất kỳ đâu, nhận dữ liệu từ hàng trăm khách hàng không phải ai cũng cẩn thận (hay trung thực), và nắm quyền thay đổi mọi thứ trên server: mọi website, mọi database, mọi hộp thư.

Vì lý do lịch sử, nhiều panel trao cho giao diện web quyền root, hoặc một cách rộng rãi để mượn quyền đó. Điều này tiện lợi vì panel làm được mọi thứ nó cần. Nhưng nó cũng có nghĩa là chỉ một lỗi trong đoạn mã phân tích yêu cầu web cũng có thể trở thành toàn quyền trên server và trên mọi khách hàng ở đó.

Bài viết này trình bày một cách tiếp cận khác, cách chúng tôi chọn cho ZoPanel, cùng các nguyên tắc thiết kế phía sau. Không ý tưởng nào ở đây là mới; chúng đến từ hàng chục năm thực hành bảo mật hệ điều hành và mạng. Điều quan trọng là áp dụng chúng một cách nhất quán.

Giả định rằng panel sẽ có lỗi

Mọi thiết kế bảo mật nghiêm túc đều bắt đầu từ một giả định khó chịu: sẽ có phần mã nào đó chứa lỗ hổng. Một panel web có hàng nghìn hàm xử lý yêu cầu, upload file, giải nén, render template và tích hợp với hàng chục công cụ hệ thống. Ở đâu đó trong số ấy, sớm hay muộn, sẽ có sai sót.

Vì vậy câu hỏi không chỉ là "làm sao tránh lỗi?" mà còn là "kẻ tấn công được gì khi tìm thấy một lỗi?" Câu trả lời nên là: càng ít càng tốt.

Từ đó có ba nguyên tắc:

  1. Đặc quyền tối thiểu: mỗi thành phần chỉ chạy với đúng quyền nó cần.
  2. Tách quyền: phần mã giao tiếp với internet không phải là phần mã nắm quyền lực.
  3. Phòng thủ nhiều lớp: nhiều lớp độc lập, để một lớp thất bại là chưa đủ.

Tách quyền: panel không có root

Ý tưởng này đã cũ và được hiểu rõ: mỗi thành phần chỉ nhận quyền tối thiểu nó cần, không hơn. Trong ZoPanel, panel web (server HTTPS, API, giao diện, database, tác vụ nền) chạy bằng một user hệ thống riêng, không có đặc quyền. Service systemd của nó còn thêm các hạn chế: không thể nâng quyền, thấy phần lớn hệ thống file ở chế độ chỉ đọc, không đọc được thư mục home, và không giữ capability đặc biệt nào của kernel.

Dĩ nhiên công việc hosting cần root: tạo user hệ thống, ghi cấu hình web server, cấp chứng chỉ, reload dịch vụ. Những việc đó do một agent root riêng đảm nhận, panel giao tiếp với nó qua một socket cục bộ. Agent được thiết kế hẹp một cách có chủ đích:

  • Chỉ nhận một danh sách thao tác cố định. Không có thao tác "chạy lệnh này". Thao tác nào không có trong danh sách thì không tồn tại.
  • Kiểm tra lại mọi tham số. Agent không tin panel. Username, tên miền, đường dẫn và giới hạn đều được kiểm tra ở phía root, dù panel đã kiểm tra trước đó.
  • Biết ai đang gọi. Socket chỉ nhận kết nối từ user của panel hoặc root, do kernel xác nhận chứ không dựa vào mật khẩu.
  • Không bao giờ ghép lệnh shell từ dữ liệu người dùng. Chương trình được chạy trực tiếp với danh sách tham số cố định, PATH cố định và biến môi trường cố định, nên tấn công chèn lệnh shell không có chỗ để chèn.
  • Từ chối dữ liệu bất thường. Yêu cầu có trường lạ bị từ chối ngay.

Kết quả: nếu kẻ tấn công chiếm hoàn toàn panel web, thứ họ có là một tiến trình không đặc quyền, chỉ có thể nhờ agent làm những việc panel vốn đã được phép làm, với tham số mà agent sẽ kiểm tra lại. Đó vẫn là một ngày tồi tệ, nhưng khác xa việc mất quyền root trên server của mọi khách hàng.

Làm việc của khách dưới quyền của khách

Ngay cả mã chạy bằng root cũng không nên dùng quyền root khi chạm vào dữ liệu của khách. Một kiểu tấn công kinh điển vào panel hosting là thủ thuật symlink: khách tạo một liên kết trong thư mục của mình trỏ tới file của khách khác hoặc file hệ thống, rồi chờ một tác vụ backup, khôi phục hay sửa quyền chạy bằng root đi theo liên kết đó.

Cách phòng thủ là hạ quyền trước khi chạm vào file của khách. Trong ZoPanel, thao tác file trong thư mục home của khách chạy dưới quyền chính khách đó, nên kernel tự từ chối truy cập vào bất cứ thứ gì khách không được phép chạm tới. Backup và dump database được đọc dưới quyền tài khoản, việc đổi quyền sở hữu duyệt thư mục mà không đi theo liên kết, và web server được cấu hình không đi theo liên kết thuộc về chủ sở hữu khác. Khi hệ điều hành giữ ranh giới, một sai sót trong mã của chúng tôi cũng không mở được nó.

Sandbox cho từng tài khoản

Tách quyền bảo vệ server khỏi panel. Khách hàng cũng cần được bảo vệ khỏi nhau: trên shared hosting, kẻ tấn công nhiều khả năng nhất là một website bị xâm nhập ngay bên cạnh.

Mỗi tài khoản ZoPanel chạy trong sandbox systemd riêng:

  • user Linux riêng, với thư mục home mà tài khoản khác không đọc được;
  • pool PHP riêng, chạy dưới user đó;
  • slice control group riêng với giới hạn CPU, bộ nhớ, số tiến trình và I/O đĩa, để một website không làm đói các website còn lại;
  • tiến trình được ẩn khỏi các tài khoản khác;
  • quota đĩa.

Tác vụ build và ứng dụng Node.js hay Python chạy trong cùng kiểu sandbox, với phần còn lại của hệ thống ở chế độ chỉ đọc. Database theo cùng nguyên tắc: mỗi database có user hoặc role riêng, và trong Redis dùng chung, mỗi tài khoản chỉ thấy key của mình và không chạy được script. Giới hạn gửi mail được áp dụng tại một cổng mà tiến trình của tài khoản không thể lách qua, nên một website bị xâm nhập không thể đẩy IP của server vào blacklist.

User namespace cho Docker

Container Docker rất tiện cho các ứng dụng như n8n hay Uptime Kuma, nhưng container khởi chạy theo cách thông thường vẫn là root trên máy chủ dưới góc nhìn của kernel. Khi đó thoát khỏi container đồng nghĩa với có root trên máy chủ.

ZoPanel chạy container của App Store trong user namespace: root bên trong container được ánh xạ thành một user không đặc quyền trên máy chủ. Tiến trình thoát được khỏi container sẽ thấy mình không có quyền đặc biệt nào trên server. Dữ liệu ứng dụng nằm ngoài tầm với của tài khoản hosting, và việc đổi quyền sở hữu trên volume của container không bao giờ đi theo liên kết, nên container không thể lừa máy chủ sửa file nằm ngoài dữ liệu của chính nó.

Phòng thủ nhiều lớp: các lớp bao quanh lõi

Tách quyền và sandbox là phần lõi. Bao quanh là các lớp độc lập khiến mỗi bước tấn công khó hơn:

  • Xác thực: băm mật khẩu mạnh, xác thực hai lớp có mã khôi phục và giới hạn số lần thử trước khi kiểm tra mã, mã không thể dùng lại, và danh sách IP cho phép tuỳ chọn, áp dụng cả với kết nối từ chính server.
  • API token có phạm vi: token cấp phát cho hệ thống billing chỉ tạo và tạm khoá tài khoản, không hơn. Token bị thu hồi khi chủ sở hữu đổi mật khẩu hoặc 2FA.
  • Ranh giới đại lý: đại lý không thể cấp cho khách nhiều hơn gói của chính đại lý.
  • Danh sách cho phép trong cấu hình: chỉ thị web server tuỳ chỉnh được phân tích theo danh sách cho phép thay vì chép nguyên vào.
  • Dữ liệu bí mật được mã hoá khi lưu trong database của panel.
  • Bản cập nhật có chữ ký: agent root tự xác minh chữ ký bản phát hành, không tin kết quả kiểm tra của panel, cài đặt nguyên tử và rollback nếu phiên bản mới không ổn định.
  • Nhật ký hoạt động chống sửa đổi, liên kết bằng chuỗi băm và có thể gửi ra syslog bên ngoài, để kẻ xâm nhập không thể lặng lẽ sửa lịch sử.
  • Tường lửa ứng dụng web và trình quét mã độc, file bị cách ly được chuyển vào thư mục chỉ root đọc được.

Không lớp nào hoàn hảo. Cùng nhau, chúng biến "một lỗi bằng mất tất cả" thành "kẻ tấn công cần nhiều thất bại không liên quan xảy ra liên tiếp".

Cái giá phải trả

Tách quyền không miễn phí. Mỗi tính năng mới cần root phải được thiết kế thành một thao tác hẹp với tham số được kiểm tra, thay vì một lệnh shell viết vội. Làm việc của khách dưới quyền khách đòi hỏi mã xử lý file và tiến trình cẩn thận hơn. Sandbox cho từng tài khoản đòi hỏi chú ý tới hiệu năng, và đó là lý do pool PHP trong ZoPanel khởi động theo nhu cầu và ngủ khi rảnh.

Chúng tôi tin sự đánh đổi này hoàn toàn xứng đáng. Công sức bỏ thêm chỉ diễn ra một lần, trong lúc phát triển. Lợi ích thì có mỗi ngày, trên mọi server, cho mọi khách hàng.

Câu hỏi nên đặt cho bất kỳ panel nào

Dù bạn có dùng ZoPanel hay không, những câu hỏi sau cho biết rất nhiều về mức độ an toàn của một panel:

  1. Tiến trình nào chạy bằng root, và tiến trình nào trong số đó xử lý dữ liệu từ mạng?
  2. Giao diện web yêu cầu thao tác đặc quyền bằng cách nào? Có danh sách cố định không?
  3. Tham số có được kiểm tra lại ở phía có đặc quyền không?
  4. Mã chạy bằng root có bao giờ đi theo liên kết trong thư mục của khách không?
  5. Khách hàng được cách ly với nhau thế nào: file, tiến trình, bộ nhớ, CPU, mail, database?
  6. Container được cách ly với máy chủ thế nào?
  7. Bản cập nhật được ký và xác minh ra sao, và chuyện gì xảy ra nếu cập nhật thất bại?
  8. Có chính sách bảo mật công khai và kênh báo cáo lỗ hổng không?

Bạn có thể xem cách chúng tôi xử lý báo cáo trong Chính sách bảo mật, và cách cách ly ảnh hưởng tới mật độ trong kết quả đo năng lực của chúng tôi.

Câu hỏi thường gặp

Vì sao control panel hosting không nên chạy bằng root?

Vì giao diện web xử lý dữ liệu từ internet và từ mọi khách hàng. Nếu chạy bằng root, bất kỳ lỗi nào trong phần mã đó cũng có thể trao cho kẻ tấn công toàn quyền trên server và mọi website.

Tách quyền trong control panel là gì?

Panel web chạy bằng người dùng thường. Việc cần quyền root được gửi tới một agent riêng qua socket cục bộ, và agent chỉ thực hiện một tập hành động cố định sau khi kiểm tra lại mọi tham số.

ZoPanel cách ly tài khoản khách hàng như thế nào?

Mỗi tài khoản chạy trong sandbox riêng với tiến trình PHP, quyền file và giới hạn tài nguyên riêng, còn container của khách chạy trong user namespace.