允许非 root 进程绑定到端口 80 和 443?

jww*_*jww 167 linux privileges kernel sockets

是否可以调整内核参数以允许用户程序绑定到端口 80 和 443?

我问的原因是我认为允许特权进程打开套接字并侦听是愚蠢的。任何打开套接字并侦听的东西都是高风险的,高风险的应用程序不应以 root 身份运行。

我更愿意尝试找出哪些非特权进程正在侦听端口 80,而不是尝试删除以 root 权限潜入的恶意软件。

Jas*_*n C 237

我不确定这里的其他答案和评论指的是什么。这很容易实现。有两个选项,它们都允许访问低编号端口,而无需将进程提升为 root:

选项 1:CAP_NET_BIND_SERVICE用于授予对进程的低编号端口访问权限:

有了这个,您可以通过以下setcap命令授予对特定二进制文件的永久访问权限以绑定到低编号端口:

sudo setcap CAP_NET_BIND_SERVICE=+eip /path/to/binary
Run Code Online (Sandbox Code Playgroud)

有关 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
    
    Run Code Online (Sandbox Code Playgroud)
  3. 现在通过authbind(可选地指定--deep或其他参数,请参阅手册页)执行您的命令:

    authbind --deep /path/to/binary command line args
    
    Run Code Online (Sandbox Code Playgroud)

    例如

    authbind --deep java -jar SomeServer.jar
    
    Run Code Online (Sandbox Code Playgroud)

以上两者都有优点和缺点。选项 1 授予对二进制文件的信任,但不提供对每个端口访问的控制。选项 2 授予用户/组信任并提供对每个端口访问的控制,但旧版本仅支持 IPv4(因为我最初编写此内容,因此发布了支持 IPv6 的新版本)。

  • 请注意,使用 setcap,如果您覆盖了您授予权限的可执行文件(例如:进行重建),那么它会失去其特权端口状态,您必须再次授予它权限:| (11认同)
  • 我怀疑 `chmod` 到 777 `byport` 文件是最好的主意。我已经看到了从“500”到“744”的权限。我会坚持最适合你的限制性措施。 (7认同)
  • 它真的需要“rwx”权限吗? (3认同)
  • 我不得不摆弄的东西;我试图运行一个 sysv 服务,它运行一个使用 ruby​​ 的 ruby​​ 可执行文件。您需要为*版本特定的 ruby​​ 可执行文件*授予`setcap`权限,例如`/usr/bin/ruby1.9.1` (2认同)
  • 对于“setcap”,不需要授予继承的 (i) 权限,而且您可能不应该这样做。如果你正在写一个应用程序,那么最好是有能力感知,那么就没有必要设置有效(e)。 (2认同)
  • @chmike `e` 是不需要的,如果使用功能的程序是功能感知的。也就是说,编写它是为了使用它们,因此将允许的功能复制为有效(需要时)。所有遗留程序都需要“e”。 (2认同)
  • IMPO 您真的不应该授予“所有用户和组”的访问权限。相反,您应该选择一个需要运行它的受信任用户,然后由该用户 chown /etc/authbind/byport/80 和 443 文件并对其进行 chmod,以便该用户可以执行它们,而其他人则无法执行。否则,您会增加而不是降低安全风险。 (2认同)

Jde*_*eBP 34

戴尔·哈格伦德 (Dale Hagglund) 就在现场。所以我只是要说同样的事情,但以不同的方式,以及一些细节和例子。☺

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

  • 拥有一个小巧、简单、易于审核的程序,以超级用户身份运行并绑定侦听套接字;
  • 拥有另一个小的、简单的、易于审计的、由第一个程序产生的放弃特权的程序;
  • 为了获得服务的核心,在单独的第三个程序中,在非超级用户帐户下运行,并由第二个程序加载链,期望简单地继承套接字的打开文件描述符。

您对高风险的位置有错误的认识。高风险在于从网络读取并根据读取的内容采取行动,而不是打开套接字、将其绑定到端口和调用listen(). 进行实际通信的服务的一部分是高风险的。打开 ,bind()和的部分,listen()甚至(在一定程度上) , 的部分accepts()不是高风险的部分,可以在超级用户的支持下运行。他们不使用和操作(在这种情况下源 IP 地址除外accept())由网络上不受信任的陌生人控制的数据。

有很多方法可以做到这一点。

inetd

正如 Dale Hagglund 所说,旧的“网络超级服务器”就是inetd这样做的。运行服务进程的帐户是 中的列之一inetd.conf。它没有将侦听部分和删除权限部分分成两个独立的程序,小且易于审计,但它确实将主服务代码分离到一个单独的程序中,exec()在它生成的服务进程中使用打开的文件描述符为插座。

审计的难度不是什么大问题,因为一个人只需要审计一个程序。 inetd与最近的工具相比,它的主要问题不是审计,而是它没有提供简单的细粒度运行时服务控制。

UCSPI-TCP 和 daemontools

Daniel J. Bernstein 的UCSPI-TCPdaemontools包被设计为结合使用。也可以使用 Bruce Guenter 的基本等效的daemontools-encore工具集。

打开套接字文件描述符并绑定到特权本地端口的程序是tcpserver,来自 UCSPI-TCP。它同时执行listen()accept()

tcpserver然后生成一个服务程序,它本身放弃 root 权限(因为所服务的协议涉及以超级用户身份开始,然后“登录”,例如 FTP 或 SSH 守护程序的情况),或者setuidgid是一个自包含的小型且易于审核的程序,它仅删除权限,然后将负载链接到适当的服务程序(因此,其中任何部分都不会以超级用户权限运行,例如,就是这种情况qmail-smtpd)。

例如,服务run脚本将是(此脚本用于dummyidentd以提供空 IDENT 服务):

#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
Run Code Online (Sandbox Code Playgroud)

小吃

我的 nosh 包就是为此而设计的。它有一个小的setuidgid实用程序,就像其他的一样。一个细微的区别是它可以与systemd-style“LISTEN_FDS”服务以及UCSPI-TCP服务一起使用,因此传统tcpserver程序被两个单独的程序取代:tcp-socket-listentcp-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 \
'
Run Code Online (Sandbox Code Playgroud)

该超级用户的支持下运行的程序是小服务无关的链装载工具fdmoveclearenvenvdirsoftlimittcp-socket-listen,和setuidgid。到sh启动时,套接字已打开并绑定到smtp端口,进程不再具有超级用户权限。

s6、s6-networking 和 execline

Laurent Bercot 的s6s6-networking软件包旨在结合使用此功能。这些命令在结构上daemontools与 UCSPI-TCP的命令非常相似。

run脚本大致相同,除了替换为s6-tcpserverfortcpservers6-setuidgidfor setuidgid。但是,您也可以同时选择使用 M. Bercot 的execline工具集。

下面是一个 FTP 服务的例子,从Wayne Marshall 的原始稍微修改了一下,它使用了 execline、s6、s6-networking 和来自publicfile的 FTP 服务器程序:

#!/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}
Run Code Online (Sandbox Code Playgroud)

ipsvd

Gerrit Pape 的ipsvd是另一个与 ucspi-tcp 和 s6-networking 运行相同的工具集。工具是chpsttcpsvd这次,但它们做同样的事情,读取、处理和写入不受信任的客户端通过网络发送的东西的高风险代码仍然在一个单独的程序中。

这是M. Papefnordrun脚本中运行的示例

#!/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
Run Code Online (Sandbox Code Playgroud)

systemd

systemd,可以在某些 Linux 发行版中找到的新服务监督和初始化系统,旨在做inetd可以做的事情。但是,它不使用一套小型自包含程序。systemd不幸的是,必须全面审计。

systemdone 创建配置文件来定义一个systemd侦听的套接字和一个systemd启动的服务。服务“单元”文件具有允许对服务进程进行大量控制的设置,包括它以何种用户身份运行。

将该用户设置为非超级用户,systemd完成打开套接字、将其绑定到端口、以超级用户身份调用listen()(以及,如果需要,accept())进程 #1 以及它的服务进程的所有工作spawns 在没有超级用户权限的情况下运行。

  • 谢谢你的赞美。这是一个很好的具体建议集合。+1。 (3认同)

noo*_*oob 34

我有一个相当不同的方法。我想将端口 80 用于 node.js 服务器。因为 Node.js 是为非 sudo 用户安装的,所以我无法做到。我尝试使用符号链接,但它对我不起作用。

然后我知道我可以将连接从一个端口转发到另一个端口。所以我在端口 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

我使用了第二个命令,它对我有用。所以我认为这是一个中间立场,不允许用户进程直接访问较低的端口,而是使用端口转发让他们访问。

  • 请记住,对于外部流量,您可能需要绑定到“0.0.0.0”而不是“127.0.0.1”。 (2认同)

小智 14

最简单的解决方案:删除 linux 上的所有特权端口

适用于 ubuntu/debian :

#save configuration permanently
echo 'net.ipv4.ip_unprivileged_port_start=0' > /etc/sysctl.d/50-unprivileged-ports.conf
#apply conf
sysctl --system
Run Code Online (Sandbox Code Playgroud)

(适用于具有非 root 帐户的 VirtualBox)

现在,请注意安全性,因为所有用户都可以绑定所有端口!

  • 这很聪明。一个小问题:该配置打开 80 和 443,但它也打开所有其他端口。可能不需要放宽其他端口上的权限。 (2认同)

Dal*_*und 5

您的直觉是完全正确的:以 root 身份运行大型复杂程序是一个坏主意,因为它们的复杂性使它们难以信任。

但是,允许普通用户绑定到特权端口也是一个坏主意,因为这些端口通常代表重要的系统服务。

解决这一明显矛盾的标准方法是特权分离。基本思想是将您的程序分成两个(或更多)部分,每个部分执行整个应用程序的一个明确定义的部分,并通过简单的有限接口进行通信。

在您给出的示例中,您希望将程序分成两部分。一个以 root 身份运行并打开并绑定到特权套接字,然后以某种方式将其交给以普通用户身份运行的另一部分。

这两种主要方式实现了这种分离。

  1. 以 root 身份启动的单个程序。它所做的第一件事就是以尽可能简单和有限的方式创建必要的套接字。然后,它放弃特权,即将自身转换为常规用户模式进程,并执行所有其他工作。正确删除权限很棘手,所以请花时间研究正确的方法。

  2. 通过父进程创建的套接字对进行通信的一对程序。非特权驱动程序接收初始参数,并可能进行一些基本的参数验证。它通过创建一对连接的套接字socketpair(),然后派生并执行另外两个将完成实际工作的程序,并通过套接字对进行通信。其中一个是有特权的,将创建服务器套接字和任何其他特权操作,另一个将执行更复杂且因此可信度较低的应用程序执行。