有没有办法让非root进程绑定到Linux上的“特权”端口?

时间:2009-01-05 17:09:37

标签: linux root ipv6 iptables linux-capabilities

在我的开发盒上有这个限制是非常烦人的,因为除了我之外不会有任何用户。

我知道the standard workarounds,但没有一个完全符合我的要求:

  1. authbind(Debian测试中的版本,1.0,仅支持IPv4)
  2. Using the iptables REDIRECT target to redirect a low port to a high port(“nat”表尚未实现ip6tables,iptables的IPv6版本)
  3. sudo(以root身份运行是我想避免的)
  4. SELinux(或类似)。 (这只是我的开发盒,我不想引入很多额外的复杂性。)
  5. 是否有一些简单的sysctl变量允许非root进程绑定到Linux上的“特权”端口(端口小于1024),或者我只是运气不好?

    编辑:在某些情况下,您可以use capabilities执行此操作。

24 个答案:

答案 0 :(得分:360)

好的,感谢那些指出功能系统和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将在具有programsetcap等权限的suid上禁用LD_LIBRARY_PATH。因此,如果您的program使用自己的.../lib/,则可能需要查看其他选项,例如端口转发。
  4. 资源:

    注意:RHEL first added this in v6

答案 1 :(得分:31)

标准方法是将它们设置为“setuid”,以便它们以root身份启动,然后一旦它们绑定到端口但在它们开始接受与它的连接之前,它们就会丢弃该root权限。您可以在Apache和INN的源代码中看到这方面的好例子。我被告知Lighttpd是另一个很好的例子。

另一个例子是Postfix,它使用通过管道进行通信的多个守护进程,并且只有一个或两个(除了接受或发出字节之外几乎没有)以root身份运行,其余的以较低权限运行。

答案 2 :(得分:28)

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

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

答案 3 :(得分:19)

或修补你的内核并删除支票。

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

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

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

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

答案 4 :(得分:19)

您可以设置本地SSH隧道,例如,如果您希望端口80命中您的应用程序绑定到3000:

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

这具有使用脚本服务器的优点,并且非常简单。

答案 5 :(得分:18)

文件功能并不理想,因为它们可能会在程序包更新后中断。

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

这是一种有点复杂的方法:

sg $DAEMONUSER "capsh --keep=1 --uid=`id -u $DAEMONUSER` \
     --caps='cap_net_bind_service+pei' -- \
     YOUR_COMMAND_GOES_HERE"
可以在Debian / Ubuntu发行版中的libcap2-bin包中找到

capsh实用程序。这是继续发生的事情:

  • 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"

答案 6 :(得分:15)

另外两个简单的可能性:

有一个旧的(不合时宜的)解决方案是“一个守护程序,它绑定在一个低端口并将控制权交给你的守护进程”。它被称为inetd(或xinetd)。缺点是:

  • 您的守护进程需要在stdin / stdout上进行通信(如果您不控制守护进程 - 如果您没有源代码 - 那么这可能是一个showstopper,尽管某些服务可能有一个inetd兼容性标志)
  • 为每个连接分叉一个新的守护程序进程
  • 这是链中的一个额外链接

优点:

  • 可在任何旧UNIX上使用
  • 一旦你的系统管理员设置了配置,你就可以开始进行开发了(当你重新构建你的守护进程时,你可能会失去setcap功能吗?然后你将不得不回到你的管理员那里“请”先生...“)
  • 守护进程不必担心网络内容,只需要谈谈stdin / stdout
  • 可以配置为按要求以非root用户身份执行您的守护程序

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

答案 7 :(得分:14)

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

socat tcp6-listen:80,fork tcp6:8080

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

答案 8 :(得分:14)

2017年更新:

使用authbind


比CAP_NET_BIND_SERVICE或自定义内核好多了。

  • CAP_NET_BIND_SERVICE授予对二进制文件的信任,但不提供 控制每端口访问。
  • Authbind授予信任 用户/组,并提供对每个端口访问的控制,以及 支持IPv4和IPv6(最近添加了IPv6支持)。

    1. 安装:apt-get install 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
      

      e.g。

      authbind --deep java -jar SomeServer.jar
      

作为约书亚神话般的后续行动(=不推荐,除非你知道你做了什么)建议破解内核:

我首先发布了here

简单。使用普通或旧内核,您不需要。
正如其他人所指出的,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程序都可以绑定系统端口 Dito for mono / .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

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

这已经是全部了。
编译内核,然后安装它。
重新启动。
完成 - 这个愚蠢的限制是GONE,这也适用于脚本。

以下是编译内核的方法:

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,如果你想确保这个限制再也不会困扰你,请编译内核。

答案 9 :(得分:13)

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

不幸的是,我不知道如何利用它来运行非root用户同时仍然给它CAP_NET_BIND_SERVICE(可能使用setcap,但是必然会有一个现有的解决方案)

答案 10 :(得分:13)

我知道这是一个老问题,但是现在有了最新的(&gt; = 4.3)内核,最终会有一个很好的答案 - 环境功能。

快速回答是获取最新(尚未发布)的libcap from git版本的副本并进行编译。将生成的progs/capsh二进制文件复制到某处(/usr/local/bin是一个不错的选择)。然后,以root身份,使用

启动您的程序
/usr/local/bin/capsh --keep=1 --user='your-service-user-name' \
    --inh='cap_net_bind_service' --addamb='cap_net_bind_service' \ 
    -- -c 'your-program'

按顺序,我们

  • 声明当我们切换用户时,我们希望保留当前的功能集
  • 切换用户&amp;分组到&#39; your-service-user-name&#39;
  • cap_net_bind_service功能添加到继承的&amp;环境集
  • 分叉bash -c 'your-command'(因为capsh自动使用--之后的参数启动bash

这里有很多内容。

首先,我们以root身份运行,因此默认情况下,我们会获得一整套功能。其中包括切换uid和amp;的能力。带有setuidsetgid系统调用的gid。但是,通常当程序执行此操作时,它会丢失其功能集 - 这样就可以使用setuid删除root的旧方法仍然有效。 --keep=1标志告诉capsh发出prctl(PR_SET_KEEPCAPS)系统调用,这会在更改用户时禁用功能丢弃。使用capsh标记--user运行setuidsetgid时,exec实际更改了用户。

我们需要解决的下一个问题是如何在我们cap_net_bind_service我们的孩子之后继续设置能力。能力系统一直有一个继承的&#39;一组能力,这是&#34;在execve(2)&#34;中保留的一组功能。 [capabilities(7)]。虽然这听起来像是解决了我们的问题(只是将--user功能设置为继承,对吧?),这实际上只适用于特权进程 - 而且我们的进程不再具有特权,因为我们已经更改了用户(使用{ {1}}旗帜)。

新的环境功能集解决了这个问题 - 它是一组在非特权程序的execve(2)中保留的功能。&#34;通过将cap_net_bind_service置于环境集中,当capsh执行我们的服务器程序时,我们的程序将继承此功能并能够将侦听器绑定到低端口。

如果您有兴趣了解更多信息,则manual page功能会详细解释这一点。通过capsh运行strace也非常有用!

答案 11 :(得分:12)

TLDR:对于“答案”(如我所见),跳到&gt;&gt; TLDR&lt;&lt;参与本答案。

好的,我已经弄明白了(这次是真的),这个问题的答案,我的这个答案也是一种道歉的方式,以促进another answer(在这里和在推特上)我认为它是“最好的”,但在尝试之后,发现我错了。从错误的孩子那里学习:在你真正尝试过之前不要宣传一些东西!

再次,我在这里回顾了所有的答案。我已经尝试了一些(并选择不尝试其他人,因为我根本不喜欢这些解决方案)。我认为解决方案是将systemdCapabilities=CapabilitiesBindingSet=设置一起使用。经过一段时间的摔跤后,我发现这不是 的解决方案,因为:

功能旨在限制根进程!

正如OP明智地指出的那样,始终最好避免这种情况(如果可能的话,为所有守护进程!)。

您无法在User=单元文件中使用与Group=systemd相关的功能相关选项,因为execev时功能始终重置(或者无论函数是什么)被调用。换句话说,当systemd分叉并丢弃其权限时,功能将被重置。没有办法解决这个问题,内核中的所有绑定逻辑都是基于uid = 0的基础,而不是功能。这意味着Capabilities不太可能成为这个问题的正确答案(至少在很短的时间内)。顺便提一下,正如其他人所提到的,setcap不是解决方案。它不适用于我,它不能很好地处理脚本,并且无论何时文件发生变化都会重置它们。

在我微薄的防守中,我确实陈述了(在我现在删除的评论中),詹姆斯的iptables建议(OP也提到了),是“第二好的解决方案”。 :-P

<强> &GT;&GT; TLDR&LT;&LT;

解决方案是将systemd与即时iptables命令结合使用,如下所示(taken from 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为我们清除防火墙规则,确保在守护程序未运行时删除它们。
  • 我们从不以root身份运行,并且我们无法进行权限提升(至少systemd声称),据称即使守护程序被泄露并设置uid=0
遗憾的是,

iptables仍然是一个非常丑陋且难以使用的实用程序。例如,如果守护程序正在侦听eth0:0而不是eth0,则命令为slightly different

答案 12 :(得分:10)

systemd是一个sysvinit替代品,可以选择启动具有特定功能的守护程序。选项Capabilities =,CapabilityBoundingSet = systemd.exec(5)联机帮助页。

答案 13 :(得分:8)

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

这也允许您在访问本地计算机上的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

答案 14 :(得分:8)

由于某种原因,没有人提及将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,甚至部分不设置setuid(实际上是功能)。 例如,要捕获已启用功能的应用程序的核心转储,您将需要更改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。使用systemd-socket方法可以排除此问题。使用authbind方法仅允许特殊的用户绑定。
  • 每次部署新版本的应用程序时都不需要设置CAP / suid。
  • 不需要像systemd-socket方法那样的应用程序支持/修改。
  • 不需要内核重建(如果正在运行的版本支持此sysctl设置)
  • 不会像authbind / privbind方法那样执行LD_PRELOAD,这可能会影响性能,安全性和行为(是吗?尚未测试)。其余的authbind确实是灵活且安全的方法。
  • 优于iptables REDIRECT / DNAT方法,因为它不需要地址转换,连接状态跟踪等。这仅在高负载系统上才可见。

根据情况,我将在sysctl,CAP,authbind和iptables-redirect之间进行选择。太好了,我们有很多选择。

答案 15 :(得分:7)

启动时:

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

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

答案 16 :(得分:7)

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

您稍后可以使用systemd socket activate

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

这是此简单python http server

示例中相关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

答案 17 :(得分:2)

还有&#39; djb方式&#39;。您可以使用此方法以root身份在tcpserver下的任何端口上运行启动流程,然后它将在流程启动后立即将流程控制权交给您指定的用户。

#!/bin/sh

UID=`id -u yourusername`
GID=`id -g yourusername`
exec tcpserver -u $UID -g $GID -RHl0 0 portnumber   /path/to/your/process &

有关详细信息,请参阅:http://thedjbway.b0llix.net/daemontools/uidgid.html

答案 18 :(得分:2)

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

答案 19 :(得分:2)

将端口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上运行程序。

然后您将能够在端口http://127.0.0.1上访问80

答案 20 :(得分:1)

由于OP只是开发/测试,不太流行的解决方案可能会有所帮助:

setcap可以在脚本的解释器上使用,以向脚本授予功能。如果全局解释器二进制文件上的setcaps不可接受,则制作二进制文件的本地副本(任何用户都可以)并在此副本上获取root用户的setcap。 Python2(至少)与脚本开发树中的解释器的本地副本一起正常工作。不需要suid,因此root用户可以控制用户可以访问的功能。

如果您需要跟踪解释器的系统范围更新,请使用以下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 $*

答案 21 :(得分:1)

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

我还发现PREROUTING REDIRECT不适用于在机器内启动的连接。要使用本地计算机中的连接,请同时添加OUTPUT规则 - 请参阅iptables port redirect not working for localhost。例如。类似的东西:

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

我还发现PREROUTING REDIRECT also affects forwarded packets。也就是说,如果机器也在接口之间转发数据包(例如,如果它充当连接到以太网网络的Wi-Fi接入点),那么iptables规则也将捕获连接的客户端&#39;连接到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的应用程序然后不知道传入连接的源地址(例如,用于记录或其他识别目的)。

答案 22 :(得分:0)

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

答案 23 :(得分:0)

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