![本地主机/环回](https://linux22.com/image/1396165/%E6%9C%AC%E5%9C%B0%E4%B8%BB%E6%9C%BA%2F%E7%8E%AF%E5%9B%9E.png)
是否可以调整内核参数以允许用户程序绑定到端口 80 和 443?
我之所以问这个问题,是因为我认为允许特权进程打开套接字并进行监听是愚蠢的。任何打开套接字并进行监听的行为都是高风险的,高风险应用程序不应以 root 身份运行。
我宁愿尝试找出哪个非特权进程正在监听端口 80,而不是尝试删除以 root 权限潜伏的恶意软件。
答案1
我不确定这里的其他答案和评论指的是什么。这很容易实现。有两个选项,都允许访问低编号端口,而无需将进程提升为 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
(手册页) 工具正是为了这个目的而存在的。
使用您最喜欢的包管理器进行安装
authbind
。将其配置为授予对相关端口的访问权限,例如允许所有用户和组访问 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
authbind
现在通过(可选指定--deep
或其他参数,请参阅手册页)执行您的命令:authbind --deep /path/to/binary command line args
例如
authbind --deep java -jar SomeServer.jar
上述两种方法各有利弊。第一种选择是信任二进制但不提供对每个端口访问的控制。选项 2 授予对用户/组并提供对每个端口访问的控制,但旧版本仅支持 IPv4(自从我最初编写此代码以来,已经发布了支持 IPv6 的新版本)。
答案2
我有一个相当不同的方法。我想将端口 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
我使用了第二个命令,它对我有用。所以我认为这是不允许用户进程直接访问较低端口,而是允许它们使用端口转发进行访问的折中方案。
答案3
Dale Hagglund 说得对。所以我只是用不同的方式说同样的话,并给出一些具体细节和例子。☺
在 Unix 和 Linux 世界中正确的做法是:
- 拥有一个小的、简单的、易于审计的程序,以超级用户身份运行并绑定监听套接字;
- 拥有另一个由第一个程序产生的小型、简单、易于审计的、可放弃特权的程序;
- 把服务的核心放在一个单独的第三程序,在非超级用户帐户下运行并由第二个程序链接加载,期望简单地继承套接字的打开文件描述符。
你对高风险所在的看法是错误的。高风险在于从网络上读取信息并采取行动不涉及打开套接字、将其绑定到端口并调用 的简单操作listen()
。服务中执行实际通信的部分才是高风险部分。打开、bind()
和的部分listen()
,甚至(在一定程度上) 的部分accepts()
,都不是高风险部分,可以在超级用户的支持下运行。它们不使用和处理(本例中源 IP 地址除外accept()
)网络上不受信任的陌生人控制的数据。
有很多方法可以做到这一点。
inetd
正如 Dale Hagglund 所说,旧的“网络超级服务器”inetd
就是这样做的。运行服务进程的帐户是 中的一列inetd.conf
。它没有将监听部分和放弃特权部分分成两个独立的程序,小巧且易于审计,但它确实将主服务代码分成一个单独的程序,exec()
并在它生成的服务进程中为套接字生成一个打开的文件描述符。
审计的难度不是什么大问题,因为只需要审计一个程序。 inetd
与更新的工具相比,的主要问题不是审计太多,而是它没有提供简单的细粒度的运行时服务控制。
UCSPI-TCP 和 daemontools
丹尼尔·伯恩斯坦UCSPI协议和守护进程工具软件包的设计就是为了实现这一点。你也可以使用 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
-style“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 \
'
在超级用户的支持下运行的程序是小型服务无关的链式加载工具,,,,,fdmove
和。在启动时,套接字已打开并绑定到端口,并且该进程不再具有超级用户权限。clearenv
envdir
softlimit
tcp-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 的执行程序工具集。
以下是 FTP 服务的示例,略作修改韦恩·马歇尔的原作,使用 execline、s6、s6-networking 和来自的 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 的奇异值分解是另一个与 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
将以超级用户身份完成打开套接字、将其绑定到端口以及在进程 #1 中调用listen()
(以及,如果需要accept()
)的所有工作,并且它生成的服务进程在没有超级用户权限的情况下运行。
答案4
你的直觉是完全正确的:让大型复杂程序以 root 身份运行是个坏主意,因为它们的复杂性使它们难以被信任。
但是,允许普通用户绑定到特权端口也是一个坏主意,因为这样的端口通常代表重要的系统服务。
解决这一明显矛盾的标准方法是特权分离。基本思想是将程序分成两个(或更多)部分,每个部分完成整个应用程序明确定义的部分,并通过简单有限的接口进行通信。
在您给出的示例中,您希望将程序分成两部分。一部分以 root 身份运行,打开并绑定到特权套接字,然后以某种方式将其交给另一部分,以普通用户身份运行。
这两种主要方式实现了这种分离。
以 root 身份启动的单个程序。它所做的第一件事是以尽可能简单和有限的方式创建必要的套接字。然后,它会放弃特权,也就是说,它会将自己转换为常规用户模式进程,并执行所有其他工作。正确放弃特权很棘手,因此请花时间研究正确的方法。
通过父进程创建的套接字对进行通信的一对程序。非特权驱动程序接收初始参数,并可能进行一些基本的参数验证。它通过 创建一对连接的套接字
socketpair()
,然后分叉并执行两个其他程序,这些程序将执行实际工作并通过套接字对进行通信。其中一个是特权程序,将创建服务器套接字和任何其他特权操作,另一个将执行更复杂且因此不太可信的应用程序执行。