題 允許非root進程綁定到端口80和443?


是否可以調整內核參數以允許用戶程序綁定到端口80和443?

我問的原因是我認為允許特權進程打開套接字並聽取它是愚蠢的。任何打開套接字並監聽的東西都是高風險的,高風險的應用程序不應該以root身份運行。

我寧願嘗試弄清楚端口80上沒有特權的進程正在偵聽,而不是試圖刪除使用root權限挖掘的惡意軟件。


74
2018-02-02 05:48


起源


看到 serverfault.com/questions/268099 和 stackoverflow.com/questions/413807 。最簡潔的答案是不。 - Sami Laine
答案很長,所以答案是肯定的。所以簡短的回答也應該是肯定的。 - B T
簡短的回答 是 是。 - Jason C


答案:


我不確定這裡的其他答案和評論是指什麼。這很容易實現。有兩個選項,它們都允許訪問低編號端口,而無需將進程提升為root:

選項1:使用 CAP_NET_BIND_SERVICE 授予進程的低編號端口訪問權限:

通過這種方式,您可以授予對特定二進製文件的永久訪問權限,以通過 setcap 命令:

sudo setcap CAP_NET_BIND_SERVICE=+eip /path/to/binary

有關e / i / p部分的更多詳細信息,請參閱 cap_from_text

這樣做之後 /path/to/binary 將能夠綁定到低編號的端口。請注意,您必須使用 setcap 在二進製本身而不是符號鏈接。

選項2:使用 authbind 通過更精細的用戶/組/端口控制授予一次性訪問權限: 

authbind (手冊頁)工具正是為此而存在的。

  1. 安裝 authbind 使用您最喜歡的包管理器。

  2. 配置它以授予對相關端口的訪問權限,例如允許來自所有用戶和組的80和443:

    sudo touch /etc/authbind/byport/80
    sudo touch /etc/authbind/byport/443
    sudo chmod 777 /etc/authbind/byport/80
    sudo chmod 777 /etc/authbind/byport/443
    
  3. 現在執行命令 authbind (可選擇指定 --deep 或其他參數,請參見手冊頁):

    authbind --deep /path/to/binary command line args
    

    例如。

    authbind --deep java -jar SomeServer.jar
    

上述兩者都有好處和缺點。選項1授予信任 二進制 但不提供對每端口訪問的控制。選項2授予信任 用戶/組 並提供對每端口訪問的控制,但AFAIK僅支持IPv4。


110
2018-03-21 21:12



謝謝杰森。這似乎是問題的最佳答案,但有點晚了。很抱歉沒有盡快看到它。 - jww
再次感謝所有信息。我認為這個超級用戶的問題和答案擊敗了服務器故障答案的鼻涕。超級用戶的人們更加友好和平易近人。 - jww
請注意,使用setcap,如果覆蓋您授予特權的可執行文件(例如:執行重建),那麼它將丟失其特權端口狀態,您必須再次授予它特權: - rogerdpack
我必須擺弄的東西;我試圖運行一個運行使用ruby的ruby可執行文件的sysv服務。你需要給予 setcap 許可 特定於版本的ruby可執行文件,例如 /usr/bin/ruby1.9.1 - Christian Rondeau
我對此表示懷疑 chmod到777年 byport 文件是最好的主意。我已經看到了給予的分配 500 至 744。我會堅持最適合你的限制性問題。 - Pere


Dale Hagglund就是現場。所以我只是以不同的方式說同樣的話,有一些細節和例子。 ☺

在Unix和Linux世界中正確的做法是:

  • 有一個小的,簡單的,易於審計的程序,以超級用戶身份運行並綁定偵聽套接字;
  • 還有一個小的,簡單的,易於審計的程序,它可以降低第一個程序產生的權限;
  • 擁有服務的肉,在一個單獨的 第三 程序,在非超級用戶帳戶下運行,並由第二個程序加載鏈,期望簡單地繼承套接字的打開文件描述符。

你對高風險在哪裡有錯誤的想法。高風險在於 從網絡中讀取並根據所讀取的內容採取行動 不是在打開套接字,將其綁定到端口和調用的簡單行為 listen()。它是服務的一部分,可以進行高風險的實際通信。打開的部分, bind(),和 listen(),甚至(在某種程度上)那部分 accepts(),不是高風險,可以在超級用戶的支持下運行。他們不使用和採取行動(除了源中的IP地址) accept() case)通過網絡受不受信任的陌生人控制的數據。

有很多方法可以做到這一點。

inetd

正如Dale Hagglund所說,舊的“網絡超級服務員” inetd 做這個。運行服務進程的帳戶是其中一列 inetd.conf。它不會將偵聽部分和刪除權限部分分成兩個獨立的程序,小而易於審計,但它確實將主服務代碼分離為一個單獨的程序, exec()在一個服務進程中編寫,它使用一個打開的套接字文件描述符生成。

審計的難度並不是一個問題,因為只需審計一個程序。 inetd與更新的工具相比,主要的問題不是審計那麼多,而是它不提供簡單的細粒度運行時服務控制。

UCSPI-TCP和daemontools

丹尼爾J.伯恩斯坦的 UCSPI-TCP 和 daemontools的 軟件包旨在結合使用。或者可以使用Bruce Guenter的大部分內容 daemontools的,安可 工具集。

打開套接字文件描述符並綁定到特權本地端口的程序是 tcpserver,來自UCSPI-TCP。它同時做到了 listen() 和 accept()

tcpserver 然後生成一個自動刪除root權限的服務程序(因為所服務的協議涉及以超級用戶身份開始然後“登錄”,例如,FTP或SSH守護程序)或者 setuidgid 這是一個獨立的小型且易於審核的程序,它只能刪除特權,然後將負載鏈接到服務程序(因此,其中任何一部分都不會以超級用戶權限運行,例如, qmail-smtpd)。

一項服務 run 因此腳本將是例如(這個為 dummyidentd 提供null IDENT服務):

#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl

NOSH

我的小包 旨在做到這一點。它有一個小 setuidgid 實用程序,就像其他人一樣。一個細微的差別是它可用於 systemd風格的“LISTEN_FDS”服務以及UCSPI-TCP服務,所以傳統 tcpserver 程序被兩個獨立的程序取代: tcp-socket-listen 和 tcp-socket-accept

同樣,單用途實用程序會相互產生和鍊式加載。設計的一個有趣的怪癖是,之後可以刪除超級用戶權限 listen() 但甚至在之前 accept()。這是一個 run 腳本 qmail-smtpd 確實如此:

#!/bin/nosh
fdmove -c 2 1
clearenv --keep-path --keep-locale
envdir env/
softlimit -m 70000000
tcp-socket-listen --combine4and6 --backlog 2 ::0 smtp
setuidgid qmaild
sh -c 'exec \
tcp-socket-accept -v -l "${LOCAL:-0}" -c "${MAXSMTPD:-1}" \
ucspi-socket-rules-check \
qmail-smtpd \
'

在超級用戶的支持下運行的程序是與服務無關的小型鍊式加載工具 fdmoveclearenvenvdirsoftlimittcp-socket-listen,和 setuidgid。到了那一點 sh 啟動後,套接字打開並綁定到 smtp 端口,並且該進程不再具有超級用戶權限。

s6,s6-networking和execline

Laurent Bercot的 S6 和 S6聯網 軟件包旨在結合使用。這些命令在結構上非常類似於 daemontools 和UCSPI-TCP。

run 腳本將大致相同,除了替換 s6-tcpserver 對於 tcpserver 和 s6-setuidgid 對於 setuidgid。但是,人們也可以選擇使用M. Bercot的 execline 工具集同時。

這是一個FTP服務的示例,輕微修改 韋恩馬歇爾的原創,使用execline,s6,s6-networking和FTP服務器程序 publicfile

#!/command/execlineb -PW
multisubstitute {
    define CONLIMIT 41
    define FTP_ARCHIVE "/var/public/ftp"
}
fdmove -c 2 1
s6-envuidgid pubftp 
s6-softlimit -o25 -d250000 
s6-tcpserver -vDRH -l0 -b50 -c ${CONLIMIT} -B '220 Features: a p .' 0 21 
ftpd ${FTP_ARCHIVE}

ipsvd

Gerrit Pape的 ipsvd 是另一個與ucspi-tcp和s6-networking相同的工具集。工具是 chpst 和 tcpsvd 這一次,但是他們做同樣的事情,並且由不受信任的客戶端通過網絡發送的事物的讀取,處理和寫入的高風險代碼仍然在一個單獨的程序中。

這裡的 M. Pape的例子 跑步 fnord 在一個 run 腳本:

#!/bin/sh
exec 2>&1
cd /public/10.0.5.4
exec \
chpst -m300000 -Uwwwuser \
tcpsvd -v 10.0.5.4 443 sslio -v -unobody -//etc/fnord/jail -C./cert.pem \
fnord

systemd

systemd,一些Linux發行版中可以找到的新服務監督和初始化系統, 是為了做什麼 inetd 可以做。但是,它不使用一套小型自包含程序。一個人必須審計 systemd 不幸的是,完整的。

systemd 一個創建配置文件來定義一個套接字 systemd 傾聽,並提供服務 systemd 開始。服務“單元”文件具有允許對服務進程進行大量控制的設置,包括其運行的用戶。

將該用戶設置為非超級用戶, systemd 完成打開套接字,將其綁定到端口和調用的所有工作 listen() (如果需要, accept())在進程#1中作為超級用戶,並且它生成的服務進程在沒有超級用戶權限的情況下運行。


22
2018-02-02 16:21



謝謝你的稱讚。這是一個很好的具體建議集合。 +1。 - Dale Hagglund
並不是說這不是一般的建議,而是 CAP_NET_BIND_SERVICE 和 authbind 兩者都存在於此目的;允許非root應用程序訪問低編號端口。 - Jason C
再次感謝所有信息。我認為這個超級用戶的問題和答案擊敗了服務器故障答案的鼻涕。超級用戶的人們更加友好和平易近人。 - jww


你的直覺是完全正確的:以root身份運行大型複雜程序是一個壞主意,因為它們的複雜性使它們難以信任。

但是,允許常規用戶綁定到特權端口也是一個壞主意,因為這些端口通常代表重要的系統服務。

解決這一明顯矛盾的標準方法是 特權分離。基本思想是將程序分成兩個(或更多)部分,每個部分都是整個應用程序中明確定義的部分,並通過簡單的有限接口進行通信。

在您給出的示例中,您希望將程序分成兩部分。一個以root身份運行並打開並綁定到特權套接字的一個,然後以某種方式將其交給另一個以常規用戶身份運行的部分。

這兩種主要方式實現了這種分離。

  1. 以root身份啟動的單個程序。它做的第一件事就是創建必要的套接字,盡可能簡單有限。然後,它會刪除權限,也就是說,它會將自身轉換為常規用戶模式進程,並執行所有其他工作。正確刪除權限很棘手,所以請花時間研究正確的方法。

  2. 一對通過父進程創建的套接字對進行通信的程序。非特權驅動程序接收初始參數,並可能進行一些基本的參數驗證。它通過socketpair()創建一對連接的套接字,然後分叉和執行另外兩個將完成實際工作的程序,並通過套接字對進行通信。其中一個是特權,將創建服務器套接字和任何其他特權操作,另一個將執行更複雜,因此不太可靠的應用程序執行。

[1] http://en.m.wikipedia.org/wiki/Privilege_separation


4
2018-02-02 06:49



謝謝戴爾。我來自Windows世界,具有強大的SDLC生命週期經驗,所以我理解你在說什麼。我不想做(1)因為它的風險很高。另外,在任何情況下都要正確地做到這一點(即, Setuid神秘化了)。我不想做(2)因為它增加了複雜性。我只想允許非特權用戶綁定到端口80和443.這是最安全,最簡單的方法。 - jww
您的建議不被視為最佳實踐。您可以查看inetd,它可以偵聽特權套接字,然後將該套接字交給非特權程序。 - Dale Hagglund
再次感謝所有信息。我認為這個超級用戶的問題和答案擊敗了服務器故障答案的鼻涕。超級用戶的人們更加友好和平易近人。 - jww


我有一個相當不同的方法。我想將端口80用於node.js服務器。自從為非sudo用戶安裝Node.js後,我無法做到這一點。我嘗試使用符號鏈接,但它對我不起作用。

然後我知道我可以將連接從一個端口轉發到另一個端口。所以我在端口3000上啟動了服務器,並設置了一個從端口80前進到端口3000的端口。

這個鏈接 提供可用於執行此操作的實際命令。這是命令 -

本地主機/回環

sudo iptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 80 -j REDIRECT --to-ports 3000

外部

sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000

我使用了第二個命令,它對我有用。所以我認為這是一個中間立場,不允許用戶進程直接訪問較低端口,但使用端口轉發授予它們訪問權限。


1
2018-06-27 07:00



+1開箱即用 - Richard Wiseman