Tại sao tôi lại chroot cho sandboxing để bảo mật nếu ứng dụng của tôi có thể ngay từ đầu chạy ở mức thấp hơn?


14

Tôi đang viết một daemon máy chủ HTTP bằng C (có lý do tại sao), quản lý nó với tệp đơn vị systemd.

Tôi đang viết lại một ứng dụng được thiết kế 20 năm trước, khoảng năm 1995. Và hệ thống họ sử dụng là họ chroot và sau đó setuid, và quy trình chuẩn.

Bây giờ trong công việc trước đây của tôi, chính sách thông thường là bạn không bao giờ chạy bất kỳ quy trình nào với quyền root. Bạn tạo một người dùng / nhóm cho nó và chạy từ đó. Tất nhiên, hệ thống đã chạy một số thứ như root, nhưng chúng ta có thể đạt được tất cả xử lý logic nghiệp vụ mà không cần root.

Bây giờ đối với trình nền HTTP, tôi có thể chạy nó mà không cần root nếu tôi không truy cập vào ứng dụng. Vì vậy, nó không an toàn hơn cho ứng dụng không bao giờ chạy như root?

Không phải nó an toàn hơn để chạy nó với tư cách là người dùng mydaemon ngay từ đầu sao? Thay vì bắt đầu với root, chroot, sau đó setuid cho người dùng mydaemon?


3
bạn cần được chạy bằng root để sử dụng cổng 80 hoặc 443. nếu không, bạn có thể làm những gì tomcat và phần mềm webapp / máy chủ web khác làm và chạy trên một cổng cao hơn (giả sử, 8080, 9090, v.v.) và sau đó sử dụng apache / nginx để ủy quyền kết nối với phần mềm máy chủ web của bạn hoặc sử dụng tường lửa của hệ thống để NAT / chuyển lưu lượng truy cập đến máy chủ web của bạn từ cổng 80. Nếu bạn không cần cổng 80 hoặc 443 hoặc có thể ủy quyền hoặc chuyển tiếp kết nối, sau đó bạn không có nhu cầu chạy như root, trong một chroot hay cách khác.
SnakeDoc

3
@SnakeDoc trên Linux không còn đúng nữa. Nhờ có capabilities(7).
0xC0000022L

@SnakeDoc bạn có thể sử dụng authbind cũng
Abdul Ahad

Câu trả lời:


27

Dường như những người khác đã bỏ lỡ quan điểm của bạn, đó không phải là lý do tại sao sử dụng gốc thay đổi, tất nhiên bạn đã biết rõ, cũng không phải làm gì khác để đặt giới hạn cho các mons, khi bạn cũng biết rõ về việc chạy theo aegide của tài khoản người dùng không có đặc quyền; Nhưng tại sao phải làm những thứ này trong ứng dụng . Thực sự có một ví dụ khá chính xác về lý do tại sao.

Hãy xem xét việc thiết kế httpdchương trình dmon trong gói công khai của Daniel J. Bernstein. Điều đầu tiên mà nó làm là thay đổi root thành thư mục gốc mà nó được yêu cầu sử dụng với một đối số lệnh, sau đó thả đặc quyền vào ID người dùng không được ưu tiên và ID nhóm được truyền trong hai biến môi trường.

Các bộ công cụ quản lý của D havemon có các công cụ chuyên dụng cho những việc như thay đổi thư mục gốc và chuyển sang ID người dùng và nhóm không được ưu tiên. Runit của Gerrit Pape có chpst. Bộ công cụ nosh của tôi có chrootsetuidgid-fromenv. S6 của Laurent Bercot có s6-chroots6-setuidgid. Perp của Wayne Marshall có runtoolrunuid. Và kể từ đó trở đi. Thật vậy, tất cả họ đều có bộ công cụ daemontools của M. Bernstein với setuidgidtư cách là một tiền lệ.

Người ta sẽ nghĩ rằng người ta có thể trích xuất các chức năng từ httpdvà sử dụng các công cụ chuyên dụng như vậy. Sau đó, như bạn hình dung, không có phần nào của chương trình máy chủ chạy với các đặc quyền siêu người dùng.

Vấn đề là một hậu quả trực tiếp phải thực hiện nhiều công việc hơn để thiết lập root đã thay đổi và điều này phơi bày những vấn đề mới.

Với Bernstein httpd, hiện tại, các tệp và thư mục duy nhất trong cây thư mục gốc là những tệp sẽ được xuất bản ra thế giới. Không có gì khác trong cây cả. Hơn nữa, không có lý do cho bất kỳ tệp hình ảnh chương trình thực thi nào tồn tại trong cây đó.

Nhưng di chuyển thay đổi thư mục gốc ra vào một chương trình chuỗi nạp (hoặc systemd), và đột nhiên các tập tin hình ảnh chương trình httpd, bất kỳ thư viện chia sẻ rằng nó tải, và bất kỳ tập tin đặc biệt trong /etc, /run/devrằng bộ nạp chương trình hoặc C Runtime truy cập thư viện trong quá trình khởi tạo chương trình (điều mà bạn có thể thấy khá ngạc nhiên nếu bạn truss/ stracemột chương trình C hoặc C ++), cũng phải có mặt trong thư mục gốc đã thay đổi. Nếu không thì httpdkhông thể bị xích và không tải / chạy.

Hãy nhớ rằng đây là máy chủ nội dung HTTP (S). Nó có khả năng có thể phục vụ bất kỳ tệp nào (có thể đọc được trên thế giới) trong thư mục gốc đã thay đổi. Điều này hiện bao gồm những thứ như thư viện dùng chung, trình tải chương trình của bạn và các bản sao của các tệp cấu hình bộ nạp / CRTL khác nhau cho hệ điều hành của bạn. Và nếu bởi một số (tình cờ) có nghĩa là máy chủ nội dung có quyền truy cập để ghi nội dung, thì máy chủ bị xâm nhập có thể có quyền truy cập ghi vào hình ảnh chương trình cho httpdchính nó, hoặc thậm chí là trình tải chương trình của hệ thống. (Hãy nhớ rằng bây giờ bạn có hai bộ song song /usr, /lib, /etc, /run, và /devthư mục để giữ an toàn.)

Không ai trong số này là trường hợp httpdthay đổi root và giảm đặc quyền.

Vì vậy, bạn đã giao dịch có một lượng nhỏ mã đặc quyền, khá dễ kiểm toán và chạy ngay khi bắt đầu httpdchương trình, chạy với các đặc quyền siêu người dùng; để có một bề mặt tấn công được mở rộng đáng kể của các tệp và thư mục trong thư mục gốc đã thay đổi.

Đây là lý do tại sao nó không đơn giản như làm mọi thứ bên ngoài cho chương trình dịch vụ.

Lưu ý rằng đây dù sao cũng chỉ là một chức năng tối thiểu httpd. Tất cả các mã thực hiện những việc như tìm trong cơ sở dữ liệu tài khoản của hệ điều hành cho ID người dùng và ID nhóm để đặt vào các biến môi trường đó ở vị trí đầu tiên bên ngoài httpdchương trình, trong các lệnh có thể kiểm tra độc lập đơn giản như envuidgid. (Và tất nhiên nó là một công cụ UCSPI, vì vậy nó chứa không ai trong số các mã để lắng nghe trên cổng TCP có liên quan (s) hoặc để chấp nhận các kết nối, những người đang được các miền của lệnh như tcpserver, tcp-socket-listen, tcp-socket-accept, s6-tcpserver4-socketbinder, s6-tcpserver4d, và vân vân.)

đọc thêm


+1, có tội như bị buộc tội. Tôi tìm thấy tiêu đề và đoạn cuối mơ hồ, và nếu bạn đúng tôi đã bỏ lỡ điểm. Câu trả lời này đưa ra một giải thích rất thực tế. Cá nhân tôi sẽ lưu ý rõ ràng rằng việc phải xây dựng môi trường chroot như thế này là một nỗ lực thêm, mà hầu hết mọi người sẽ muốn tránh. Nhưng 2 điểm bảo mật ở đây đã được thực hiện tốt.
sourcejedi

Một điểm khác cần nhớ là nếu máy chủ giảm đặc quyền trước khi xử lý bất kỳ lưu lượng mạng nào, thì mã đặc quyền sẽ không được hiển thị với bất kỳ khai thác từ xa nào.
kasperd

5

Tôi nghĩ rằng nhiều chi tiết của câu hỏi của bạn có thể áp dụng như nhau avahi-daemon, mà tôi đã xem xét gần đây. (Tôi có thể đã bỏ lỡ một chi tiết khác mà mặc dù). Chạy avahi-daemon trong chroot có nhiều lợi thế, trong trường hợp avahi-daemon bị xâm phạm. Bao gồm các:

  1. nó không thể đọc bất kỳ thư mục nhà của người dùng và lọc thông tin cá nhân.
  2. nó không thể khai thác lỗi trong các chương trình khác bằng cách viết vào / tmp. Có ít nhất một loại toàn bộ các lỗi như vậy. Ví dụ: https://www.google.co.uk/search?q=tmp+race+security+orms
  3. nó không thể mở bất kỳ tập tin ổ cắm unix nào bên ngoài chroot, mà các trình tiện ích khác có thể đang nghe và đọc tin nhắn trên đó.

Điểm 3 có thể đặc biệt tốt khi bạn không sử dụng dbus hoặc tương tự ... Tôi nghĩ avahi-daemon sử dụng dbus, vì vậy nó đảm bảo giữ quyền truy cập vào dbus hệ thống ngay cả từ bên trong chroot. Nếu bạn không cần khả năng gửi tin nhắn trên hệ thống dbus, việc từ chối khả năng đó có thể là một tính năng bảo mật khá hay.

quản lý nó với tập tin đơn vị systemd

Lưu ý rằng nếu avahi-daemon được viết lại, nó có khả năng có thể chọn dựa vào systemd để bảo mật và sử dụng, vd ProtectHome. Tôi đã đề xuất một thay đổi cho avahi-daemon để thêm các biện pháp bảo vệ này như một lớp bổ sung, cùng với một số biện pháp bảo vệ bổ sung không được bảo đảm bởi chroot. Bạn có thể xem danh sách đầy đủ các tùy chọn tôi đề xuất ở đây:

https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a

Có vẻ như có nhiều hạn chế hơn mà tôi có thể đã sử dụng nếu avahi-daemon không sử dụng chroot, một số trong đó được đề cập trong thông điệp cam kết. Tôi không chắc chắn bao nhiêu điều này áp dụng mặc dù.

Lưu ý, các biện pháp bảo vệ mà tôi đã sử dụng sẽ không giới hạn trình nền mở các tệp ổ cắm unix (điểm 3 ở trên).

Một cách tiếp cận khác là sử dụng SELinux. Tuy nhiên, bạn sẽ buộc ứng dụng của mình vào tập hợp phân phối Linux đó. Lý do tôi nghĩ về SELinux một cách tích cực ở đây, đó là vì Selinux hạn chế quyền truy cập mà các quy trình có trên dbus, theo cách chi tiết. Ví dụ: tôi nghĩ bạn thường có thể mong đợi rằng nó systemdsẽ không nằm trong danh sách tên xe buýt mà bạn cần để có thể gửi tin nhắn đến :-).

"Tôi đã tự hỏi, nếu sử dụng systemd sandbox an toàn hơn chroot / setuid / umask / ..."

Tóm tắt: tại sao không phải cả hai? Hãy giải mã những điều trên một chút :-).

Nếu bạn nghĩ về điểm 3, sử dụng chroot cung cấp nhiều hạn chế hơn. ProtectHome = và bạn bè của nó thậm chí không cố gắng hạn chế như chroot. (Ví dụ, không có danh sách đen tùy chọn systemd nào được đặt tên /run, trong đó chúng tôi có xu hướng đặt các tệp ổ cắm unix).

chroot cho thấy rằng việc hạn chế quyền truy cập hệ thống tệp có thể rất mạnh mẽ, nhưng không phải mọi thứ trên Linux đều là một tệp :-). Có các tùy chọn systemd có thể hạn chế những thứ khác, đó không phải là tập tin. Điều này hữu ích nếu chương trình bị xâm phạm, bạn có thể giảm các tính năng kernel có sẵn cho nó, chương trình có thể cố gắng khai thác lỗ hổng. Ví dụ: avahi-daemon không cần ổ cắm bluetooth và tôi đoán máy chủ web của bạn cũng không :-). Vì vậy, đừng cấp cho nó quyền truy cập vào họ địa chỉ AF_BLUETOOTH. Chỉ cần liệt kê danh sách trắng AF_INET, AF_INET6 và có thể AF_UNIX bằng cách sử dụng RestrictAddressFamilies=tùy chọn.

Vui lòng đọc tài liệu cho từng tùy chọn bạn sử dụng. Một số tùy chọn có hiệu quả hơn khi kết hợp với các tùy chọn khác và một số tùy chọn không khả dụng trên tất cả các kiến ​​trúc CPU. (Không phải vì CPU kém, mà vì cổng Linux cho CPU đó không được thiết kế độc đáo. Tôi nghĩ vậy).

(Có một nguyên tắc chung ở đây. An toàn hơn nếu bạn có thể viết danh sách những gì bạn muốn cho phép, không phải những gì bạn muốn từ chối. Giống như xác định một chroot cung cấp cho bạn một danh sách các tệp bạn được phép truy cập, và điều này mạnh mẽ hơn hơn là nói bạn muốn chặn /home).

Về nguyên tắc, bạn có thể tự áp dụng tất cả các hạn chế tương tự trước khi setuid (). Tất cả chỉ là mã mà bạn có thể sao chép từ systemd. Tuy nhiên, các tùy chọn đơn vị systemd nên dễ viết hơn đáng kể và vì chúng ở định dạng chuẩn nên sẽ dễ đọc và xem lại hơn.

Vì vậy, tôi rất có thể khuyên bạn chỉ nên đọc qua phần hộp cát man systemd.exectrên nền tảng mục tiêu của bạn. Nhưng nếu bạn muốn thiết kế an toàn nhất có thể, tôi sẽ không ngại thử chroot(và sau đó thả rootvào) trong chương trình của bạn cũng . Có một sự đánh đổi ở đây. Sử dụng chrootáp đặt một số hạn chế trên thiết kế tổng thể của bạn. Nếu bạn đã có một thiết kế sử dụng chroot, và nó dường như làm những gì bạn cần, điều đó nghe có vẻ khá tuyệt vời.


+1 đặc biệt cho các đề xuất systemd.
mattdm

tôi đã học được khá nhiều từ câu trả lời của bạn, nếu stack over Flow cho phép nhiều câu trả lời tôi cũng sẽ chấp nhận ur. Tôi đã tự hỏi, nếu sử dụng systemd sandboxing an toàn hơn chroot / setuid / umask / ...
mur

@mur rất vui vì bạn thích nó :). Đó là một phản ứng rất tự nhiên cho câu trả lời của tôi. Vì vậy, tôi đã cập nhật lại, để thử và trả lời câu hỏi của bạn.
sourcejedi

1

Nếu bạn có thể dựa vào systemd, thì thực sự an toàn hơn (và đơn giản hơn!) Để bỏ hộp cát vào systemd. .

[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...

Nhưng chúng ta không phải dừng lại ở đó. systemd cũng có thể làm rất nhiều hộp cát khác cho bạn - đây là một số ví dụ:

[Service]
# allocate separate /tmp and /var/tmp for the service
PrivateTmp=yes
# mount / (except for some subdirectories) read-only
ProtectSystem=strict
# empty /home, /root
ProtectHome=yes
# disable setuid and other privilege escalation mechanisms
NoNewPrivileges=yes
# separate network namespace with only loopback device
PrivateNetwork=yes
# only unix domain sockets (no inet, inet6, netlink, …)
RestrictAddressFamilies=AF_UNIX

Xem man 5 systemd.execđể biết thêm nhiều chỉ thị và mô tả chi tiết hơn. Nếu bạn làm cho daemon của man 5 systemd.socketbạn có thể kích hoạt ( ), bạn thậm chí có thể sử dụng các tùy chọn liên quan đến mạng: liên kết duy nhất của dịch vụ với thế giới bên ngoài sẽ là ổ cắm mạng mà nó nhận được từ systemd, nó sẽ không thể kết nối với bất kỳ thứ gì khác. Nếu đó là một máy chủ đơn giản chỉ nghe trên một số cổng và không cần kết nối với các máy chủ khác, thì điều này có thể hữu ích. (Theo tôi, các tùy chọn liên quan đến hệ thống tệp cũng có thể khiến RootDirectorylỗi thời, vì vậy có lẽ bạn không cần phải thiết lập thư mục gốc mới với tất cả các thư mục và thư viện cần thiết nữa.)

Các phiên bản systemd mới hơn (kể từ phiên bản 232) cũng hỗ trợ DynamicUser=yes, trong đó systemd sẽ tự động phân bổ người dùng dịch vụ cho bạn chỉ trong thời gian chạy dịch vụ. Điều này có nghĩa bạn không cần phải đăng ký một người sử dụng vĩnh viễn cho dịch vụ và hoạt động tốt miễn là dịch vụ không viết thư cho bất kỳ địa điểm hệ thống tập tin khác hơn các sản phẩm StateDirectory, LogsDirectoryCacheDirectory(mà bạn cũng có thể khai báo trong file đơn vị - xem man 5 systemd.exec, một lần nữa - và systemd nào sau đó sẽ quản lý, chú ý gán chúng chính xác cho người dùng động).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.