
我正在用 C 語言編寫一個 HTTP 伺服器守護程序(這是有原因的),並使用 systemd 單元檔案對其進行管理。
我正在重寫一個 20 年前設計的應用程序,大約在 1995 年。
在我之前的工作中,通常的政策是永遠不要以 root 身分執行任何進程。您為其創建一個用戶/群組並從那裡運行。當然,系統確實以root身分運行了一些東西,但是我們可以在不root的情況下實現所有業務邏輯處理。
現在對於 HTTP 守護進程,如果我不在應用程式內 chroot,我可以在沒有 root 的情況下運行它。那麼應用程式永遠不要以 root 身分運行不是更安全嗎?
從一開始就以 mydaemon-user 身份運行不是更安全嗎?而不是使用 root 啟動它,chrooting,然後 setuid 到 mydaemon-user?
答案1
似乎其他人沒有理解你的觀點,這不是為什麼要使用改變的根的原因,這當然你已經清楚地知道了,也不是你還能做些什麼來限制守護進程,當你也清楚地知道在守護進程的幫助下運行時非特權用戶帳戶;但為什麼要做這些事在應用程式內部。實際上有一個相當恰當的例子來說明原因。
httpd
考慮Daniel J. Bernstein 的 publicfile 包中守護程序的設計。它所做的第一件事是將 root 更改為被告知與命令參數一起使用的根目錄,然後將特權授予在兩個環境變數中傳遞的非特權使用者 ID 和群組 ID。
守護程式管理工具集具有專用工具,用於更改根目錄以及刪除非特權使用者和群組 ID 等操作。 Gerrit Pape 的 runit 有chpst
。我的 nosh 工具集有chroot
和setuidgid-fromenv
。 Laurent Bercot 的 s6 有s6-chroot
和s6-setuidgid
。韋恩馬歇爾的 Perp 有runtool
和runuid
。等等。事實上,他們都有 M. Bernstein 自己的 daemontools 工具集setuidgid
作為前因。
人們可能會認為可以從此類專用工具中提取功能httpd
並使用這些專用工具。然後,正如你想像的那樣,不伺服器程式的一部分曾經以超級使用者權限運行。
問題是,直接後果是必須做更多的工作來建立更改後的根,這暴露了新的問題。
就伯恩斯坦httpd
而言,僅有的根目錄樹中的文件和目錄是要發佈到全世界的文件和目錄。有沒有其他的根本就在樹上。而且,沒有理由任何可執行程式映像檔存在於該樹中。
但是將根目錄更改為鍊式加載程序(或 systemd),突然出現 的程序映像文件httpd
、它加載的任何共享庫以及 、 、 和/etc
程序/run
加載/dev
器或 C 運行時庫訪問的任何特殊文件在程序初始化期間(如果您truss
/ strace
C 或 C++ 程序,您可能會感到非常驚訝),也必須存在於更改的根中。否則httpd
無法連結並且不會載入/運行。
請記住,這是一個 HTTP(S) 內容伺服器。它有可能在更改後的根目錄中提供任何(世界可讀的)檔案。現在,這包括共用程式庫、程式載入器以及作業系統的各種載入器/CRTL 設定檔的副本。如果透過某種方式(偶然)意味著內容伺服器可以訪問寫東西,受感染的伺服器可能會獲得對httpd
自身程式映像的寫入權限,甚至是您系統的程式載入器的寫入權限。 (請記住,您現在有兩組並行的/usr
、/lib
、/etc
、/run
和/dev
目錄來保證安全。)
httpd
這一切都不是更改 root 並刪除權限本身的情況。
因此,您已經擁有少量特權程式碼,這些程式碼相當容易審計,並且在程式啟動時httpd
以超級用戶權限運行;在更改後的根目錄中大幅擴展了檔案和目錄的攻擊面。
這就是為什麼它不像在服務程式外部執行所有操作那麼簡單。
請注意,這仍然是其本身的最低限度的功能httpd
。所有執行諸如在作業系統的帳戶資料庫中尋找使用者 ID 和群組 ID 並首先放入這些環境變數等操作的程式碼是在程式外部httpd
,在簡單的獨立可審核命令中,例如envuidgid
. (當然,它是一個 UCSPI 工具,因此它不包含偵聽相關 TCP 連接埠或接受連接的程式碼,這些程式碼屬於命令域,例如tcpserver
,tcp-socket-listen
,tcp-socket-accept
,s6-tcpserver4-socketbinder
,s6-tcpserver4d
, 等等。
進一步閱讀
- 丹尼爾伯恩斯坦 (1996)。
httpd
。公共文件。 cr.yp.to。 httpd
。Daniel J. Bernstein 的軟體合一。軟體.喬納森·德博因·波拉德。 2016年。gopherd
。Daniel J. Bernstein 的軟體合一。軟體.喬納森·德博因·波拉德。 2017年。- https://unix.stackexchange.com/a/353698/5132
- https://github.com/janmojzis/httpfile/blob/master/droproot.c
答案2
我認為你的問題的許多細節同樣適用於avahi-daemon
我最近看過的。 (不過我可能錯過了另一個不同的細節)。在 chroot 中運行 avahi-daemon 有很多優點,以防 avahi-daemon 受到損害。這些包括:
- 它無法讀取任何使用者的主目錄並竊取私人資訊。
- 它不能透過寫入 /tmp 來利用其他程式中的錯誤。此類錯誤至少有一整類。例如https://www.google.co.uk/search?q=tmp+race+security+bug
- 它無法開啟 chroot 以外的任何 UNIX 套接字文件,其他守護程式可能正在偵聽和讀取該文件上的消息。
當你不是使用 dbus 或類似的...我認為 avahi-daemon 使用 dbus,因此它確保即使從 chroot 內部也能存取系統 dbus。如果您不需要在系統 dbus 上發送訊息的能力,則拒絕該能力可能是一個非常好的安全功能。
使用 systemd 單元檔案管理它
請注意,如果重寫 avahi-daemon,它可能會選擇依賴 systemd 來確保安全,並使用例如ProtectHome
.我建議對 avahi-daemon 進行更改,將這些保護添加為額外層,以及 chroot 無法保證的一些附加保護。您可以在此處查看我提出的選項的完整清單:
https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a
看起來如果 avahi-daemon 這樣做的話我可以使用更多的限制不是使用 chroot 本身,其中一些在提交訊息中提到。我不確定這在多大程度上適用。
請注意,我使用的保護不會限制守護程序開啟 unix 套接字檔案(上面的第 3 點)。
另一種方法是使用 SELinux。然而,您可能會將您的應用程式與 Linux 發行版的子集連結起來。我在這裡積極看待 SELinux 的原因是 SELinux 以細粒度的方式限制進程對 dbus 的存取。例如,我認為您通常會期望它systemd
不會出現在您需要能夠向其發送訊息的總線名稱清單中:-)。
“我想知道,使用 systemd 沙箱是否比 chroot/setuid/umask/...更安全”
摘要:為什麼不能兩者兼具呢?讓我們稍微解碼一下上面的內容:-)。
如果您考慮第 3 點,那麼使用 chroot 會提供更多限制。 ProtectHome= 及其朋友甚至不嘗試像 chroot 那樣限制。 (例如,沒有指定的 systemd 選項 blacklists /run
,我們傾向於將 unix 套接字檔案放在其中)。
chroot 表明限製檔案系統存取可以是非常強大的,但不是一切在 Linux 上是一個檔案:-)。有一些 systemd 選項可以限製文件以外的其他內容。如果程式受到損害,這很有用,您可以減少可用的核心功能,它可能會嘗試利用其中的漏洞。 。因此,請勿授予其存取 AF_BLUETOOTH 位址系列的權限。只需使用該選項將 AF_INET、AF_INET6 甚至 AF_UNIX 列入白名單即可RestrictAddressFamilies=
。
請閱讀您使用的每個選項的文件。有些選項與其他選項結合使用會更有效,而有些選項並非在所有 CPU 架構上都可用。 (不是因為 CPU 不好,而是因為該 CPU 的 Linux 端口設計得不夠好。我認為)。
(這裡有一個一般原則。如果您可以編寫您想要允許的內容的列表,而不是您想要拒絕的內容,那麼它會更安全。就像定義chroot 為您提供允許訪問的文件列表一樣,這更可靠而不是說你想阻止/home
)。
原則上,您可以在 setuid() 之前自行套用所有相同的限制。這只是您可以從 systemd 複製的程式碼。然而,systemd 單元選項應該更容易編寫,並且由於它們採用標準格式,因此應該更容易閱讀和審查。
因此,我強烈建議您閱讀man systemd.exec
目標平台上的沙箱部分。但如果您想要最安全的設計,我不會害怕在您的程式中嘗試chroot
(然後放棄root
特權)還有。這裡需要權衡。使用chroot
會對您的整體設計施加一些限制。如果您已經有一個使用 chroot 的設計,而且它似乎可以滿足您的需要,那麼聽起來非常棒。
答案3
如果您可以依賴 systemd,那麼將沙箱留給 systemd 確實會更安全(也更簡單!)。 (當然,應用程式還可以檢測它是否已由 systemd 啟動沙箱,如果它仍然是 root,則沙箱本身。)您描述的服務的等效項是:
[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...
但我們不必就此止步。 systemd 還可以為您執行許多其他沙箱操作 - 以下是一些範例:
[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
請參閱man 5 systemd.exec
參考資料 以取得更多指示和更詳細的描述。如果你讓你的守護程式socket-activatable ( man 5 systemd.socket
),你甚至可以使用與網路相關的選項:該服務與外界的唯一連結將是它從systemd 接收的網路套接字,它將無法連接到其他任何東西。如果它是一個簡單的伺服器,僅偵聽某些連接埠並且不需要連接到其他伺服器,那麼這可能很有用。 (在我看來,與檔案系統相關的選項也可能會過時RootDirectory
,因此也許您不再需要費心設定包含所有必需的二進位檔案和庫的新根目錄。)
較新的 systemd 版本(自 v232 起)也支持DynamicUser=yes
,其中 systemd 將僅在服務運行時自動為您分配服務用戶。這意味著您不必為該服務註冊永久用戶,並且只要該服務不寫入除StateDirectory
、LogsDirectory
和之外的任何文件系統位置CacheDirectory
(您也可以在單元文件中聲明 –再次參見man 5 systemd.exec
- 然後哪個systemd 將管理,注意將它們正確分配給動態使用者)。