允许非root进程绑定到80和443端口?
是否可以调整内核参数,允许用户国程序绑定到80和443端口?
我问这个问题的原因是,我认为允许一个有特权的进程打开一个套接字并监听是愚蠢的。任何打开套接字并监听的程序都是高风险的,高风险的程序不应该以root身份运行。
我更想弄清楚是什么无权限的进程在80端口监听,而不是试图清除用root权限钻进来的恶意软件。
是否可以调整内核参数,允许用户国程序绑定到80和443端口?
我问这个问题的原因是,我认为允许一个有特权的进程打开一个套接字并监听是愚蠢的。任何打开套接字并监听的程序都是高风险的,高风险的程序不应该以root身份运行。
我更想弄清楚是什么无权限的进程在80端口监听,而不是试图清除用root权限钻进来的恶意软件。
我不知道这里的其他答案和评论指的是什么。这是很容易实现的。有两个选项, 它们都允许访问低编号的端口, 而无需将进程提升为 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
man page )工具正是为此而存在的。
1.使用你喜欢的软件包管理器安装authbind
。
配置它以允许访问相关的端口,例如,允许所有用户和组访问80和443:
现在通过authbind
执行你的命令 (可选择指定--deep
或其他参数,参见手册页面):
以上两种方法都有优点和缺点。选项 1 授予 binary信任,但不提供对每个端口访问的控制。选项 2 授予 _用户/组信任,并提供对每个端口访问的控制,但旧版本只支持 IPv4(自从我最初写下这篇文章后,支持 IPv6 的新版本已经发布)。
Dale Hagglund说得很到位。所以我只是想说同样的话,但用不同的方式,用一些具体的方法和例子。☺
在Unix和Linux世界中,正确的做法是。
-有一个小的,简单的,容易审计的,以超级用户身份运行的程序,并绑定监听套接字; -有另一个小的,简单的,容易审计的,放弃权限的程序,由第一个程序产生; -有服务的内容,在一个单独的第三程序中,在一个非超级用户账户下运行,并由第二个程序连锁加载,期望简单地继承套接字的开放文件描述符。
你对高风险的概念是错误的。高风险在于从网络中读取并对读取的内容进行操作而不是打开一个套接字,将其绑定到一个端口,然后调用listen()
这样简单的行为。服务中进行实际通信的部分才是高风险。打开、bind()
和listen()
的部分,甚至(在某种程度上)accepts()
的部分,都不是高风险,可以在超级用户的主持下运行。它们不会使用和作用于(除了accept()
情况下的源IP地址)网络上不信任的陌生人所控制的数据。
有很多方法可以做到这一点。
inetd
正如Dale Hagglund所说,老式的 “网络超级服务器 ”inetd
就是这样做的。服务进程运行的账户是inetd.conf
中的列之一。它并没有把监听部分和丢弃权限部分分离成两个独立的程序,体积小,容易审计,但它把主服务代码分离出来,变成一个独立的程序,exec()
在一个服务进程中,它为socket生成了一个开放的文件描述符。
审计的难度不是很大,因为只需要审计一个程序。inetd
的主要问题不在于审计,而是相比于最近的工具,它没有提供简单的细粒度的运行时服务控制。
Daniel J. Bernstein的 UCSPI-TCP 和 daemontools 包就是为了结合起来做这件事。也可以使用Bruce Guenter的 daemontools-encore 工具集。
打开套接字文件描述符并绑定到本地特权端口的程序是 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
的脚本,确实做到了这一点。
#!/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 \
'
在超级用户支持下运行的程序是小型服务无关的链式加载工具qmail-smtpd
、fdmove
、clearenv
、envdir
、softlimit
和tcp-socket-listen
。到setuidgid
启动时,套接字已经打开并绑定到sh
端口,进程不再具有超级用户权限。
Laurent Bercot的 s6 和 s6-networking 包就是为了配合使用而设计的。这些命令在结构上与smtp
和UCSPI-TCP的命令非常相似。
daemontools
的脚本会大同小异,除了用 run
代替s6-tcpserver
和 tcpserver
代替s6-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}
Gerrit Pape的 ipsvd 是另一个工具集,它与ucspi-tcp和s6-networking的运行路线相同。这次的工具是setuidgid
和chpst
,但它们做的事情是一样的,高危代码对网络上发送的东西进行读取、处理和写入,由 不受信任的客户端仍然在一个单独的程序中。
这里是 M. Pape的例子 在tcpsvd
脚本中运行 fnord
。
#!/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
systemd
,新的服务监督和初始化系统,可以在一些Linux发行版中找到, 其目的是做systemd
能做的事 。然而,它并没有使用一套独立的小程序。不幸的是,人们必须审核inetd
的全部内容。
有了systemd
,人们会创建配置文件来定义一个systemd
监听的socket,以及一个systemd
启动的服务。服务 "单元 "文件的设置允许人们对服务进程进行大量的控制,包括它以什么用户身份运行。
在该用户设置为非超级用户的情况下,systemd
做的所有工作就是打开socket,将其绑定到一个端口,并以超级用户的身份在进程#1中调用systemd
(如果需要,还可以调用listen()
),它所生成的服务进程运行时没有超级用户权限。
我有一个相当不同的方法。我想为node.js服务器使用80端口。我无法做到这一点,因为Node.js是为非sudo用户安装的。我试着使用符号链接,但没有成功。
然后我知道了可以将一个端口的连接转发到另一个端口。所以我在3000端口上启动服务器,并设置了一个从80端口转发到3000端口的端口。 这个链接提供了可以用来做这件事的实际命令。这里是命令 -
localhost/loopback
sudo iptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 80 -j REDIRECT --to-ports 3000
external
sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000
我使用了第二个命令,它对我来说是有效的。所以我认为这是一个中间地带,不允许用户进程直接访问下层端口,但允许他们使用端口转发来访问。
你的直觉是完全正确的:让一个大型复杂的程序以root身份运行是个坏主意,因为它们的复杂性使它们很难被信任。
但是,允许普通用户绑定到特权端口也是个坏主意,因为这些端口通常代表重要的系统服务。
解决这个明显矛盾的标准方法是权限分离。它的基本思想是把你的程序分成两个(或更多)部分,每一个部分都做一个明确定义的整体应用,并通过简单的有限接口进行通信。
在你举的例子中,你想把你的程序分成两部分。一部分以root身份运行,打开并绑定到特权socket,然后以某种方式将其交给另一部分,以普通用户的身份运行。
实现这种分离的方法主要有这两种。
1.以root身份启动的单个程序。它做的第一件事就是创建必要的套接字,用尽可能简单和有限的方式。然后,它放弃权限,也就是说,它把自己转换为一个普通的用户模式进程,并做其他所有工作。正确地丢弃权限是很棘手的,请大家花时间研究一下正确的方法。
2.一对程序,通过父进程创建的套接字对进行通信。一个非特权驱动程序接收初始参数,也许会做一些基本的参数验证。它通过socketpair()创建一对连接的套接字,然后分叉并执行另外两个程序,这两个程序将完成真正的工作,并通过套接字对进行通信。其中一个是特权程序,它将创建服务器套接字,并进行其他特权操作,而另一个将进行更复杂的,因此不那么值得信任的应用程序执行。