是否有一种方法可以让非根进程绑定到“特权”;Linux上的端口?

在我的开发盒上有这种限制是非常令人讨厌的,因为除了我之外再也没有其他用户了。

我知道标准的变通办法,但它们都不是我想要的:

  1. authbind (Debian测试中的版本,1.0,只支持IPv4)
  2. 使用iptables REDIRECT目标将低端口重定向到高端口(“nat”表尚未实现ip6tables, iptables的IPv6版本)
  3. sudo(作为根是我试图避免的)
  4. SELinux(或类似的)。(这只是我的开发框,我不想引入很多额外的复杂性。)

是否有一些简单的sysctl变量允许非根进程绑定到Linux上的“特权”端口(端口小于1024),或者我只是运气不好?

编辑:在某些情况下,你可以使用功能来做到这一点。

364935 次浏览

标准方法是将它们设置为“setuid”,以便它们以根用户身份启动,然后在它们绑定到端口但开始接受到该端口的连接之前丢弃根用户特权。您可以在Apache和INN的源代码中看到这样的好例子。我听说莱特特警局是另一个很好的例子。

另一个例子是Postfix,它使用多个通过管道通信的守护进程,其中只有一两个守护进程(除了接受或发出字节外几乎不做任何事情)以根身份运行,其余的守护进程以较低的权限运行。

或者修补内核并删除检查。

(最后的选择,不推荐)。

net/ipv4/af_inet.c中,删除读取的两行

      if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
goto out;

内核将不再检查特权端口。

Linux支持功能以支持更细粒度的权限,而不仅仅是“此应用程序以root身份运行”。其中一个功能是CAP_NET_BIND_SERVICE,它是关于绑定到特权端口(<1024)。

不幸的是,我不知道如何利用它来运行一个应用程序作为非根,同时仍然给它CAP_NET_BIND_SERVICE(可能使用setcap,但肯定有一个现有的解决方案)。

好的,感谢那些指出能力系统和CAP_NET_BIND_SERVICE能力的人。如果您有一个最新的内核,确实可以使用它来以非root身份启动服务,但绑定低端口。简单的回答是:

setcap 'cap_net_bind_service=+ep' /path/to/program

然后,在此后任何时候执行program时,它都将具有CAP_NET_BIND_SERVICE功能。setcap在debian包libcap2-bin中。

现在要注意的是:

  1. 您至少需要2.6.24内核
  2. 如果文件是脚本,这将不起作用。(即使用#!行启动解释器)。在这种情况下,据我所知,您必须将该功能应用到解释器可执行文件本身,这当然是一个安全噩梦,因为任何使用该解释器的程序都将具有该功能。我找不到任何干净、简单的方法来解决这个问题。
  3. Linux将禁用任何具有提升权限的program上的LD_LIBRARY_PATH,如setcapsuid。因此,如果你的program使用它自己的.../lib/,你可能不得不考虑另一个选择,比如端口转发。

资源:

注意:RHEL首次在v6中添加了这个功能

另外两个简单的可能性:守护进程代理

守护进程

“在低端口绑定并将控制权交给您的守护进程的守护进程”;有一个旧的(不流行的)解决方案。它被称为inetd(或xinetd)。

缺点是:

  • 你的守护进程需要在stdin/stdout上对话(如果你不控制守护进程——如果你没有源代码——那么这可能是一个showstopper,尽管一些服务可能有inetd兼容性标志)
  • 每个连接都会生成一个新的守护进程
  • 这是链条上的一环

优点:

  • 可以在任何旧的UNIX上使用
  • 一旦系统管理员设置了配置,就可以开始开发了(当重新构建守护进程时,可能会失去setcap功能吗?然后你就得回到你的管理部门了“请,先生……”;)
  • Daemon不需要担心网络的问题,只需要在stdin/stdout上进行对话
  • 是否可以按照请求配置为以非根用户执行守护进程

代理

另一种替代方案:从特权端口到任意高编号端口(可以在其中运行目标守护进程)的经过修改的代理(netcat甚至更健壮的)。(Netcat显然不是生产解决方案,而是“只是我的开发箱”;,对吧?)通过这种方式,您可以继续使用服务器的网络支持版本,只需要root/sudo启动代理(在引导时),而不依赖复杂/潜在的脆弱功能。

我的“标准解决方案”使用socat作为用户空间重定向器:

socat tcp6-listen:80,fork tcp6:8080

注意,这不会扩展,分叉是昂贵的,但这是socat工作的方式。

您可以进行端口重定向。这就是我为运行在Linux机器上的Silverlight策略服务器所做的工作

iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 943 -j REDIRECT --to-port 1300

文件功能并不理想,因为它们可能在包更新后失效。

恕我直言,理想的解决方案应该是能够创建一个具有可继承CAP_NET_BIND_SERVICE集的shell。

这里有一个有点复杂的方法:

sg $DAEMONUSER "capsh --keep=1 --uid=`id -u $DAEMONUSER` \
--caps='cap_net_bind_service+pei' -- \
YOUR_COMMAND_GOES_HERE"

capsh实用程序可以在Debian/Ubuntu发行版的libcap2-bin包中找到。事情是这样的:

  • sg将有效的组ID更改为守护用户的组ID。这是必要的,因为capsh保持GID不变,我们肯定不想要它。
  • 设置位“保持UID更改的能力”。
  • 将UID更改为$DAEMONUSER
  • 删除所有的大写字母(此时所有的大写字母仍然存在,因为--keep=1),除了可继承的cap_net_bind_service
  • 执行命令(“——”是分隔符)

结果是一个具有指定用户和组以及cap_net_bind_service特权的进程。

例如,ejabberd启动脚本中的一行:

sg $EJABBERDUSER "capsh --keep=1 --uid=`id -u $EJABBERDUSER` --caps='cap_net_bind_service+pei' -- $EJABBERD --noshell -detached"

systemd是一个sysvinit的替代品,它有一个选项来启动具有特定功能的守护进程。systemd.exec (5) manpage中的Options Capabilities=, capacityboundingset =。

在启动时:

iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080

然后可以绑定到您转发的端口。

你可以设置一个本地SSH隧道,例如,如果你想要端口80击中你的应用绑定到3000:

sudo ssh $USERNAME@localhost -L 80:localhost:3000 -N

这样做的优点是可以使用脚本服务器,而且非常简单。

还有一种“djb方式”。您可以使用此方法以运行在tcpserver下任何端口上的根用户身份启动您的进程,然后在进程启动后,它将把进程的控制权立即交给您指定的用户。

#!/bin/sh


UID=$(id -u username)
GID=$(id -g username)
exec tcpserver -u "${UID}" -g "${GID}" -RHl0 0 port /path/to/binary &

更多信息,参见:http://thedjbway.b0llix.net/daemontools/uidgid.html

TLDR:对于“答案”(如我所见),跳到>>TLDR<< . .

好吧,我已经弄清楚了(这次是真的),这个问题的答案,我的这个答案也是为推广另一个答案(在这里和twitter上)道歉的一种方式,我认为是“最好的”,但在尝试之后,发现我错了。孩子们,从我的错误中吸取教训吧:在你自己真正尝试过之前,不要推销任何东西!

我在这里复习了所有的答案。我已经尝试了一些的他们(并选择不尝试其他人,因为我根本不喜欢的解决方案)。我认为解决方案是使用systemd及其Capabilities=CapabilitiesBindingSet=设置。经过一段时间的思考,我发现这不是解决方案< em >因为:< / em >

功能旨在限制根进程!

正如OP所明智地指出的,总是最好避免这种情况(如果可能的话,对于所有的守护进程!)。

你不能在systemd单元文件中的User=Group=中使用与Capabilities相关的选项,因为当调用execev(或任何函数)时,Capabilities将被总是重置。换句话说,当systemd分叉并丢弃其烫发时,功能将被重置。这是无法避免的,内核中的所有绑定逻辑都是围绕uid=0进行的,而不是能力。这意味着能力不太可能是这个问题的正确答案(至少在短时间内)。顺便说一句,setcap,正如其他人提到的,不是一个解决方案。它对我不起作用,它不能很好地与脚本一起工作,而且无论如何,只要文件更改,这些脚本就会重置。

在我微薄的辩护中,我确实说过(在我现在删除的评论中),詹姆斯的iptables建议(OP也提到了)是“第二优解决方案”。: - p

< em > > > TLDR< & lt; < / em >

解决方案是将systemd与动态的iptables命令结合起来,就像这样(从DNSChain取的):

[Unit]
Description=dnschain
After=network.target
Wants=namecoin.service


[Service]
ExecStart=/usr/local/bin/dnschain
Environment=DNSCHAIN_SYSD_VER=0.0.1
PermissionsStartOnly=true
ExecStartPre=/sbin/sysctl -w net.ipv4.ip_forward=1
ExecStartPre=-/sbin/iptables -D INPUT -p udp --dport 5333 -j ACCEPT
ExecStartPre=-/sbin/iptables -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 5333
ExecStartPre=/sbin/iptables -A INPUT -p udp --dport 5333 -j ACCEPT
ExecStartPre=/sbin/iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 5333
ExecStopPost=/sbin/iptables -D INPUT -p udp --dport 5333 -j ACCEPT
ExecStopPost=/sbin/iptables -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 5333
User=dns
Group=dns
Restart=always
RestartSec=5
WorkingDirectory=/home/dns
PrivateTmp=true
NoNewPrivileges=true
ReadOnlyDirectories=/etc


# Unfortunately, capabilities are basically worthless because they're designed to restrict root daemons. Instead, we use iptables to listen on privileged ports.
# Capabilities=cap_net_bind_service+pei
# SecureBits=keep-caps


[Install]
WantedBy=multi-user.target

在这里,我们完成了以下工作:

  • 守护进程监听5333,但是由于iptables,连接在53上被成功接受
  • 我们可以将命令包含在单元文件本身中,从而为人们省去了麻烦。systemd为我们清理防火墙规则,确保当守护进程不运行时删除它们。
  • 我们从不以根用户身份运行,并且我们使特权升级不可能(至少systemd声称如此),即使守护进程被破坏并设置了uid=0

iptables仍然是,不幸的是,相当丑陋和难以使用的实用程序。如果守护进程正在监听eth0:0而不是eth0,例如,命令是稍微不同的

由于OP只是开发/测试,不那么圆滑的解决方案可能会有帮助:

setcap可以在脚本的解释器上使用,为脚本授予功能。如果全局解释器二进制文件上的setcaps是不可接受的,那么就对二进制文件做一个本地副本(任何用户都可以),并在这个副本上获取root到setcap。Python2(至少)使用脚本开发树中的解释器的本地副本正常工作。不需要suid,因此根用户可以控制用户可以访问哪些功能。

如果你需要跟踪解释器的系统范围的更新,可以使用下面这样的shell脚本来运行你的脚本:

#!/bin/sh
#
#  Watch for updates to the Python2 interpreter


PRG=python_net_raw
PRG_ORIG=/usr/bin/python2.7


cmp $PRG_ORIG $PRG || {
echo ""
echo "***** $PRG_ORIG has been updated *****"
echo "Run the following commands to refresh $PRG:"
echo ""
echo "    $ cp $PRG_ORIG $PRG"
echo "    # setcap cap_net_raw+ep $PRG"
echo ""
exit
}


./$PRG $*

使用privbind实用程序:它允许非特权应用程序绑定到预留端口。

2017年更新:

使用authbind

注意,authbind通过LD_PRELOAD工作,这只在你的程序使用libc时使用,如果你的程序是用GO编译的,或任何其他避免使用c的编译器,这是(或可能)不是这样的情况。如果你使用GO,为受保护的端口范围设置内核参数,见post底部。& lt; / EndUpdate>

AuthbindCAP_NET_BIND_SERVICE或自定义内核好得多。

  • CAP_NET_BIND_SERVICE授予二进制文件信任,但提供no 控制每个端口的访问。李
    < / >
  • Authbind授予对象信任 用户/组,并提供对每个端口访问的控制 支持IPv4和IPv6 (IPv6支持已添加到最近)
  1. < p >安装:apt-get install authbind

  2. 为所有用户和组配置访问相关端口,例如80和443:

    sudo touch /etc/authbind/byport/80
    触摸/etc/authbind/byport/443
    Sudo chmod 777 /etc/authbind/byport/80
    . sh Sudo chmod 777 /etc/authbind/byport/443

    . sh
  3. 通过authbind
    执行命令 (可选指定--deep或其他参数,参见man authbind):

         authbind --deep /path/to/binary command line args
    
    
    e.g.
    
    
    authbind --deep java -jar SomeServer.jar
    

作为约书亚的令人难以置信的(=不推荐,除非你知道你在做什么)的后续建议来破解内核:

我第一次发布它在这里

< p >简单。对于普通内核或旧内核,则不需要。
正如其他人指出的那样,iptables可以转发端口。
正如其他人指出的那样,CAP_NET_BIND_SERVICE也可以做这项工作。
当然,如果你从脚本启动你的程序,CAP_NET_BIND_SERVICE将会失败,除非你在shell解释器上设置了上限,这是毫无意义的,你也可以像root一样运行你的服务…
例如,对于Java,你必须将它应用到Java JVM

sudo /sbin/setcap 'cap_net_bind_service=ep' /usr/lib/jvm/java-8-openjdk/jre/bin/java
显然,这意味着任何Java程序都可以绑定系统端口。

. net 我也很确定xinetd不是最好的主意。
但既然这两种方法都是黑客,为什么不通过解除限制来解除限制呢?
没有人说你必须运行一个正常的内核,所以你可以运行你自己的内核 您只需下载最新内核的源代码(或与您当前拥有的相同)。 之后,你转到:

/usr/src/linux-<version_number>/include/net/sock.h:

看这条线

/* Sockets 0-1023 can't be bound to unless you are superuser */
#define PROT_SOCK       1024

把它改成

#define PROT_SOCK 0

如果你不想有一个不安全的SSH情况,你可以这样修改:

#define PROT_SOCK 24

通常,我会使用您需要的最低设置,例如http设置为79,在端口25上使用SMTP时设置为24。

这就是全部。
编译内核,并安装它。
重新引导。
完成——这个愚蠢的限制已经消失了,这也适用于脚本。
< / p >

下面是编译内核的方法:

https://help.ubuntu.com/community/Kernel/Compile

# You can get the kernel-source via package `linux-source`, no manual download required
apt-get install linux-source fakeroot


mkdir ~/src
cd ~/src
tar xjvf /usr/src/linux-source-<version>.tar.bz2
cd linux-source-<version>


# Apply the changes to PROT_SOCK define in /include/net/sock.h


# Copy the kernel config file you are currently using
cp -vi /boot/config-`uname -r` .config


# Install ncurses libary, if you want to run menuconfig
apt-get install libncurses5 libncurses5-dev


# Run menuconfig (optional)
make menuconfig


# Define the number of threads you wanna use when compiling (should be <number CPU cores> - 1), e.g. for quad-core
export CONCURRENCY_LEVEL=3
# Now compile the custom kernel
fakeroot make-kpkg --initrd --append-to-version=custom kernel-image kernel-headers


# And wait a long long time


cd ..

简而言之,

  • 如果你想保持安全,使用iptables
  • 如果您想确保这个限制不再困扰您,请编译内核。

sysctl方法

< p > 注意:
到目前为止,不再需要更新内核。
您现在可以设置

sysctl net.ipv4.ip_unprivileged_port_start=80

还是坚持

sysctl -w net.ipv4.ip_unprivileged_port_start=80.

如果产生错误,只需用nano编辑/etc/sysctl.conf,并在那里设置参数,以便在重新启动时保持持久性。

或通过procfs

echo 80 | sudo tee /proc/sys/net/ipv4/ip_unprivileged_port_start

端口重定向对我们来说是最有意义的,但我们遇到了一个问题,我们的应用程序将在本地解决一个url,也需要重新路由;(这意味着你聚会)。

这也将允许您在访问本地机器上的url时被重定向。

iptables -A PREROUTING -t nat -p tcp --dport 80 -j REDIRECT --to-port 8080
iptables -A OUTPUT -t nat -p tcp --dport 80 -j REDIRECT --to-port 8080

我尝试了iptables PREROUTING REDIRECT方法。在旧的内核中,似乎这种类型的规则IPv6不支持。但显然,现在ip6tables v1.4.18和Linux内核v3.8支持它。

我还发现PREROUTING REDIRECT对机器内启动的连接不起作用。要处理来自本地机器的连接,还需要添加OUTPUT规则-参见Iptables端口重定向不工作的本地主机。例如:

iptables -t nat -I OUTPUT -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080

我还发现PREROUTING REDIRECT 也会影响转发的数据包。也就是说,如果机器也在接口之间转发数据包(例如,如果它作为连接到以太网的Wi-Fi接入点),那么iptables规则也将捕获连接的客户端到Internet目的地的连接,并将它们重定向到机器。这不是我想要的——我只想重定向到机器本身的连接。我发现,通过添加-m addrtype --dst-type LOCAL,我可以使它只影响地址到盒子的数据包。例如:

iptables -A PREROUTING -t nat -p tcp --dport 80 -m addrtype --dst-type LOCAL -j REDIRECT --to-port 8080

另一种可能是使用TCP端口转发。例如,使用socat:

socat TCP4-LISTEN:www,reuseaddr,fork TCP4:localhost:8080

然而,这种方法的一个缺点是,在端口8080上侦听的应用程序不知道传入连接的源地址(例如用于日志记录或其他识别目的)。

2015年9月:

ip6tables现在支持IPV6 NAT: http://www.netfilter.org/projects/iptables/files/changes-iptables-1.4.17.txt

您将需要内核3.7+

证明:

[09:09:23] root@X:~ ip6tables -t nat -vnL
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target     prot opt in     out     source               destination
0     0 REDIRECT   tcp      eth0   *       ::/0                 ::/0                 tcp dpt:80 redir ports 8080
0     0 REDIRECT   tcp      eth0   *       ::/0                 ::/0                 tcp dpt:443 redir ports 1443


Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target     prot opt in     out     source               destination


Chain OUTPUT (policy ACCEPT 6148 packets, 534K bytes)
pkts bytes target     prot opt in     out     source               destination


Chain POSTROUTING (policy ACCEPT 6148 packets, 534K bytes)
pkts bytes target     prot opt in     out     source               destination

我知道这是一个老问题,但是现在有了最新的(>= 4.3)内核,这个问题终于有了一个很好的答案——环境能力。

快速的答案是获取libcap 从git的最新(尚未发布)版本的副本并编译它。将生成的progs/capsh二进制文件复制到某个地方(/usr/local/bin是一个不错的选择)。然后,作为根用户,使用

/usr/local/bin/capsh --keep=1 --user='your-service-user-name' \
--inh='cap_net_bind_service' --addamb='cap_net_bind_service' \
-- -c 'your-program'

按顺序,我们是

  • 声明当切换用户时,我们希望保持当前的功能集
  • 切换用户&分组到“your-service-user-name”
  • cap_net_bind_service功能添加到继承的&环境设置
  • 派生bash -c 'your-command'(因为capsh自动使用--后面的参数启动bash)

这里隐藏着很多秘密。

首先,我们以根用户身份运行,因此默认情况下,我们获得了一整套功能。其中包括切换uid &gid的setuidsetgid系统调用。然而,通常当一个程序这样做时,它会失去它的一组功能——这是为了让用setuid删除根的旧方法仍然有效。--keep=1标志告诉capsh发出prctl(PR_SET_KEEPCAPS)系统调用,它禁止在更改用户时删除功能。通过capsh实际更改用户发生在--user标志,该标志运行setuidsetgid

我们需要解决的下一个问题是如何以一种在exec我们的子函数之后继续的方式设置能力。能力系统总是有一组“继承的”能力,这是“一组跨执行(2)保存的能力”。[功能(7)]。虽然这听起来像是解决了我们的问题(只需要将cap_net_bind_service功能设置为继承,对吗?),但这实际上只适用于有特权的进程——我们的进程不再有特权了,因为我们已经更改了user(带有--user标志)。

新的环境能力集解决了这个问题——它是“在一个没有特权的程序的执行中保存的一组能力。”通过将cap_net_bind_service放在环境集中,当capsh执行我们的服务器程序时,我们的程序将继承此功能,并能够将侦听器绑定到低端口。

如果你有兴趣了解更多,手册页功能详细解释了这一点。运行capshstrace也是非常有用的!

使用systemd,您只需要稍微修改您的服务以接受预激活的套接字。

以后可以使用Systemd套接字激活

不需要任何功能、iptables或其他技巧。

这是简单Python HTTP服务器示例中相关systemd文件的内容

文件httpd-true.service

[Unit]
Description=Httpd true


[Service]
ExecStart=/usr/local/bin/httpd-true
User=subsonic


PrivateTmp=yes

文件httpd-true.socket

[Unit]
Description=HTTPD true


[Socket]
ListenStream=80


[Install]
WantedBy=default.target
由于某些原因,没有人提到降低sysctl net.ipv4。Ip_unprivileged_port_start到您需要的值。 示例:我们需要将应用绑定到443端口
sysctl net.ipv4.ip_unprivileged_port_start=443
有人可能会说,存在潜在的安全问题:非特权用户现在可能绑定到其他特权端口(444-1024)。 但是你可以用iptables通过阻塞其他端口轻松解决这个问题
iptables -I INPUT -p tcp --dport 444:1024 -j DROP
iptables -I INPUT -p udp --dport 444:1024 -j DROP

与其他方法的比较。这个方法:

    在某种程度上,
  • (IMO)甚至比设置CAP_NET_BIND_SERVICE/setuid更安全,因为应用程序根本不需要setuid,甚至部分不需要(功能实际上是)。 例如,要捕获启用了功能的应用程序的coredump,您将需要更改sysctl fs。Suid_dumpable(这会导致另一个潜在的安全问题) 此外,当CAP/suid被设置时,/proc/PID目录由root拥有,因此你的非root用户将无法拥有运行进程的全部信息/控制权,例如,用户将无法(在一般情况下)通过/proc/PID/fd/ (netstat -aptn | grep PID)确定哪些连接属于应用程序
  • 具有安全性缺点:当您的应用程序(或任何使用端口443-1024的应用程序)由于某种原因关闭时,另一个应用程序可能会占用该端口。但是这个问题也可以应用于CAP/suid(如果你在解释器上设置它,例如java/nodejs)和iptables-redirect。使用system -socket方法可以排除此问题。使用authbind方法只允许特殊用户绑定。
  • 不需要每次部署新版本的应用程序时都设置CAP/suid。
  • 不需要应用程序支持/修改,如system -socket方法。
  • 不需要内核重建(如果运行版本支持此sysctl设置)
  • 不做LD_PRELOAD像authbind/privbind方法,这可能会潜在地影响性能,安全性,行为(它吗?没有测试)。在其余的authbind是真正灵活和安全的方法。
  • 优于iptables REDIRECT/DNAT方法,因为它不需要地址转换,连接状态跟踪等。这只有在高负载系统上才明显。

根据具体情况,我将在sysctl、CAP、authbind和iptables-redirect之间进行选择。我们有这么多选择真是太好了。

现代Linux支持/sbin/sysctl -w net.ipv4.ip_unprivileged_port_start=0

将8080端口绑定为80端口,开放80端口:

sudo iptables -t nat -A OUTPUT -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT

然后以普通用户身份在8080端口上运行程序。

你将能够访问端口80上的http://127.0.0.1

libcap网站上有一个将支持文件的共享库链接到非特权应用程序的工作示例。它最近在关于向共享库添加功能的问题的回答中被提到。