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

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

我知道标准的解决方法 ,但没有一个正是我想要的:

  1. authbind (Debiantesting版本1.0,仅支持IPv4)
  2. 使用iptables的REDIRECT目标将一个低端口redirect到一个高端口 (“nat”表尚未针对ip6tables,iptables的IPv6版本实现)
  3. sudo(以root身份运行是我试图避免的)
  4. SELinux(或类似)。 (这只是我的开发盒,我不想介绍很多额外的复杂性。)

有没有一些简单的sysctlvariables允许非root进程绑定到Linux上的“特权”端口(端口less于1024),或者我只是运气不好?

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

好的,感谢那些指出function系统和CAP_NET_BIND_SERVICEfunction的人。 如果你有一个最新的内核,确实有可能使用这个来启动一个非超级用户的服务,但绑定低端口。 简短的答案是你做:

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

然后随时执行的program将具有CAP_NET_BIND_SERVICEfunction。 setcap在debian软件包libcap2-bin

现在要注意的是:

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

资源:

  • function(7)手册页 。 如果您打算在生产环境中使用function,请仔细阅读此内容。 关于如何通过这里详述的exec()调用来inheritancefunction,有一些非常棘手的细节。
  • setcap手册页
  • “在GNU / Linux上无根1024下绑定端口” :首先指向了setcap

注意: RHEL首先在v6中添加了这个 。

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

另一个例子是Postfix,它使用多个通过pipe道进行通信的守护进程,并且只有一个或两个(除了接受或发送字节以外的任何其他操作)以root身份运行,其余的以较低的权限运行。

你可以做一个端口redirect。 这就是我为在Linux机器上运行的Silverlight策略服务器所做的

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

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

理想的解决scheme,恕我直言,应该是能够创build一个可inheritance的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软件包中find。 这是怎么回事:

  • sg将有效的组ID改为守护进程用户的ID。 这是必要的,因为capsh保持GID不变,我们绝对不希望它。
  • 设置位'保持UID变化的能力'。
  • 将UID更改为$DAEMONUSER
  • --keep=1全部大写(此时全部大写仍然存在,因为--keep=1 ),除了可inheritance的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" 

另外两个简单的可能性

有一个旧的(不合时宜的)解决scheme,“守护进程绑定在一个低端口上,把控制权交给你的守护进程”。 这就是所谓的inetd(或xinetd)。 缺点是:

  • 你的守护进程需要在stdin / stdout上进行讨论(如果你不控制守护进程 – 如果你没有源代码的话 – 那么这可能是一个好消息,尽pipe一些服务可能有一个inetd兼容标志)
  • 为每个连接分配一个新的守护进程
  • 这是链中的一个额外的环节

优点:

  • 可以在任何旧的UNIX上使用
  • 一旦你的系统pipe理员设置了configuration,你就可以开始自己的开发(当你重新构build你的守护进程,你可能会失去setcapfunction吗?那么你将不得不回到你的pipe理员。 “。)
  • 守护进程不必担心networking的东西,只需要在stdin / stdout上交谈
  • 可以configuration为按照要求以非root用户身份执行守护程序

另一种select:从特权端口到被任意编号为高的端口的黑客代理(netcat,甚至更强大的东西),您可以运行目标守护进程。 (Netcat显然不是一个生产解决scheme,但“只是我的开发盒”,对吧?)。 这样你就可以继续使用你的服务器的networking版本,只需要root / sudo来启动代理(在启动时),不会依赖于复杂的/潜在的脆弱的function。

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

 socat tcp6-listen:80,fork tcp6:8080 

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

你可以设置一个本地SSH隧道,例如,如果你想让80端口绑定到3000:

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

这具有与脚本服务器一起工作的优点,并且非常简单。

或者修补你的内核并取消检查。

(最后的select,不推荐)。

TLDR:对于“答案”(正如我所见),跳到这个答案中的>> TLDR <<部分。

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

再次,我在这里回顾了所有的答案。 我尝试了其中的一些(并select不尝试别人,因为我只是不喜欢的解决scheme)。 我认为解决scheme是使用systemdCapabilities=CapabilitiesBindingSet=设置。 经过一段时间的摔跤后,我发现这不是解决scheme, 因为:

function旨在限制根进程!

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

您不能在systemd单元文件中使用与User=Group=相关的function,因为当execev (或任何函数)被调用时,function总是被重置。 换句话说,当systemd分叉和丢弃时,这些能力就会被重置。 这是没有办法的,内核中的所有绑定逻辑都是基于uid = 0的,而不是function。 这意味着能力永远不可能是这个问题的正确答案(至less在任何时候)。 顺便setcap ,正如其他人所说, setcap不是一个解决scheme。 它并不适用于我,它不能很好地处理脚本,而且只要文件发生变化就会重置它们。

在我微弱的防守中,我做了状态(在我现在删除的评论中),詹姆斯的iptablesbuild议(OP也提到)是“第二好的解决scheme”。 😛

>> TLDR <<

解决的方法是将systemd与dynamiciptables命令结合起来,就像这样( 从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身份运行,并且我们使权限升级成为不可能(至lesssystemd声称),即使守护进程被攻破并设置uid=0

不幸的是, iptables仍然是一个相当丑陋,难以使用的工具。 例如,如果守护程序正在侦听eth0:0而不是eth0 ,则命令稍有不同 。

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

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

我知道这是一个古老的问题,但现在最近(> = 4.3)内核最终有一个很好的答案 – 环境function。

最简单的答案是从git中获取libcap的最新版本(尚未发布的版本)并进行编译。 复制生成的progs/capsh二进制文件( /usr/local/bin是个不错的select)。 然后,以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' 

为了,我们是

  • 声明,当我们切换用户时,我们想保留我们当前的能力集
  • 将用户和组切换到“您的服务用户名”
  • cap_net_bind_servicefunction添加到inheritance的环境集
  • 分叉bash -c 'your-command' (因为capsh自动启动bash之后的参数)

这里有很多事情要做。

首先,我们以root身份运行,所以默认情况下,我们获得了一整套function。 包括在这里是能够切换与setuidsetgid系统调用uid&gid。 然而,通常当一个程序做到这一点时,就会失去它的一系列能力 – 这就是使用setuid root的旧方法仍然有效。 --keep=1标志告诉capsh发出prctl(PR_SET_KEEPCAPS)系统调用,它在更改用户时禁止丢弃function。 通过capsh实际更改用户时会发生--user标志,它运行setuidsetgid

我们需要解决的下一个问题是如何设置function,以便在我们exec我们的孩子后进行。 能力系统总是有一套“inheritance的”能力,即“一个能力(2)”[ 能力(7) ]中保存的一组能力。 虽然这听起来像解决了我们的问题(只需将cap_net_bind_servicefunction设置为inheritance,对吗?),但实际上这仅适用于特权进程 – 而且我们的进程不再具有特权,因为我们已经更改了用户(带有--user标志) 。

新的环境能力集合解决了这个问题 – 它是“一组保留在没有特权的程序的execve(2)中的能力”。 通过将cap_net_bind_service放在环境集合中,当capsh exec是我们的服务器程序时,我们的程序将inheritance这个function,并且能够将侦听器绑定到低端口。

如果您有兴趣了解更多信息,function手册页详细解释了这一点。 通过strace运行capsh也是非常翔实的!

systemd是一个sysvinitreplace,它可以select启动一个具有特定function的守护进程。 选项Capabilities =,CapabilityBoundingSet =在systemd.exec(5)手册页中。

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

这也将允许您访问本地机器上的URL时被redirect。

 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 -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080 

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

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

我第一次在这里发布。

简单。 使用正常的或旧的内核,你不需要。
正如其他人指出的,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单声道/ .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。

这已经是全部了。
编译内核,并安装它。
重启。
完成 – 那个愚蠢的限制是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), eg 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,如果您想确保这个限制再也不会让您感到困扰,请编译内核。

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

你以后可以使用systemd socket激活 。

没有能力,iptables或其他技巧是必要的。

这是来自这个简单的python http服务器的相关系统文件的内容

文件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 

还有'djb的方式'。 你可以使用这个方法来启动你的进程作为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

由于OP只是开发/testing,所以不太时尚的解决scheme可能会有帮助:

可以在脚本的解释器上使用setcap来赋予脚本function。 如果全局解释器二进制文件的setcaps是不可接受的,则创build二进制文件的本地副本(任何用户都可以),并在此副本上获得root的setcap。 在脚本开发树中,Python2(至less)可以与解释器的本地副本一起正常工作。 不需要suid,因此root用户可以控制用户有权访问哪些function。

如果您需要跟踪解释器的系统范围更新,请使用类似以下的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实用程序:它允许非特权应用程序绑定到保留端口。

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

我还发现PREROUTING REDIRECT不适用于在机器内启动的连接。 要从本地计算机上工作,还要添加一个OUTPUT规则 – 请参阅iptables port redirect not for localhost 。 例如:

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

我还发现PREROUTING REDIRECT 也影响转发的数据包 。 也就是说,如果机器也在接口之间转发数据包(例如,如果它充当连接到以太网networking的Wi-Fi接入点),则iptables规则还将捕获连接的客户端到因特网目的地的连接,并将它们redirect到机器。 这不是我想要的 – 我只想redirect指向机器本身的连接。 我发现我可以通过添加-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的应用程序不知道传入连接的源地址(例如,用于日志logging或其他识别目的)。

2015/9月回答:

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

你将需要内核3.7+

certificate:

 [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