在我的开发盒上有这个限制是非常烦人的,因为除了我之外不会有任何用户.
我知道标准的解决方法,但它们都没有完全符合我的要求:
authbind(Debian测试中的版本,1.0,仅支持IPv4)
使用iptables REDIRECT目标将低端口重定向到高端口(对于ip6tables,iptables的IPv6版本尚未实现"nat"表)
sudo(以root身份运行是我想避免的)
SELinux(或类似).(这只是我的开发盒,我不想引入很多额外的复杂性.)
是否有一些简单的sysctl
变量允许非root进程绑定到Linux上的"特权"端口(端口小于1024),或者我只是运气不好?
编辑:在某些情况下,您可以使用功能来执行此操作.
好的,感谢那些指出功能系统和CAP_NET_BIND_SERVICE
功能的人.如果您有最新的内核,确实可以使用它来启动非root用户服务但绑定低端口.简短的回答是你这样做:
setcap 'cap_net_bind_service=+ep' /path/to/program
然后随时program
执行它将具有该CAP_NET_BIND_SERVICE
功能.setcap
在debian包中libcap2-bin
.
现在注意事项:
您至少需要2.6.24内核
如果您的文件是脚本,这将不起作用.(即,使用#!行启动解释器).在这种情况下,据我所知,您必须将该功能应用于解释器可执行文件本身,这当然是一个安全噩梦,因为使用该解释器的任何程序都具有该功能.我无法找到任何干净,简单的方法来解决这个问题.
Linux将禁用任何program
具有提升权限的LD_LIBRARY_PATH,例如setcap
或suid
.因此,如果您program
使用自己的.../lib/
,您可能需要查看另一个选项,如端口转发.
资源:
能力(7)手册页.如果要在生产环境中使用功能,请仔细阅读这篇文章.关于如何通过exec()调用继承功能有一些非常棘手的细节.
setcap手册页
"在GNU/Linux上没有root的情况下绑定低于1024的端口":首先指向我的文档setcap
.
注意:RHEL首先在v6中添加了这个.
您可以执行端口重定向.这就是我在Linux机器上运行的Silverlight策略服务器所做的
iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 943 -j REDIRECT --to-port 1300
该标准的办法就是让他们"setuid的",使他们开始了为根,然后他们马上扔掉root特权,因为他们已经绑定的端口,但他们开始接受它连接之前.您可以在Apache和INN的源代码中看到这方面的好例子.我被告知Lighttpd是另一个很好的例子.
另一个例子是后缀,它使用通过管道进行通信,以及多个守护进程只有一个或它们中的两(其中做很少除了接收或发射字节)作为根,其余运行在较低的特权运行.
或者修补内核并删除检查.
(最后的选择,不推荐).
在net/ipv4/af_inet.c
,删除读取的两行
if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE)) goto out;
并且内核将不再检查特权端口.
您可以设置本地SSH隧道,例如,如果您希望端口80命中您的应用程序绑定到3000:
sudo ssh $USERNAME@localhost -L 80:localhost:3000 -N
这具有使用脚本服务器的优点,并且非常简单.
文件功能并不理想,因为它们可能会在程序包更新后中断.
理想的解决方案,恕我直言,应该是一个创建具有可继承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"
2017年更新:
CAP_NET_BIND_SERVICE授予对二进制文件的信任,但不提供对每个端口访问的控制.
Authbind向用户/组授予信任并提供对每端口访问的控制,并支持IPv4和IPv6(最近添加了IPv6支持).
安装: apt-get install 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
作为约书亚神话般的后续行动(=不推荐,除非你知道你做了什么)建议破解内核:
我先在这里发布.
简单.使用普通或旧内核,您不需要.
正如其他人所指出的,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为mono/.NET.
我也很确定xinetd不是最好的想法.
但由于这两种方法都是黑客攻击,为什么不通过解除限制来解除限制呢?
没有人说你必须运行一个普通的内核,所以你可以运行自己的内核.
您只需下载最新内核的源代码(或您目前拥有的内核).然后,你去:
/usr/src/linux-/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-.tar.bz2
cd linux-source-
# 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 - 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,如果你想确保这个限制再也不会困扰你,请编译内核.
另外两个简单的可能性
有一个旧的(不合时宜的)解决方案是"一个守护程序,它绑定在一个低端口并将控制权交给你的守护进程".它被称为inetd(或xinetd).缺点是:
您的守护进程需要在stdin/stdout上进行通信(如果您不控制守护进程 - 如果您没有源代码 - 那么这可能是一个showstopper,尽管某些服务可能具有inetd兼容性标志)
为每个连接分叉一个新的守护进程
它是链条中的一个额外链接
优点:
在任何旧UNIX上都可用
一旦你的系统管理员设置了配置,你就可以开始进行开发(当你重新构建你的守护进程时,你可能会失去setcap功能吗?然后你将不得不回到你的管理员那里"请先生...... ".)
守护进程不必担心网络的东西,只需要谈谈stdin/stdout
可以根据请求配置为以非root用户身份执行守护程序
另一个替代方案:从特权端口到一些任意高编号端口的hacked-up代理(netcat甚至更强大的代理),您可以在其中运行目标守护程序.(Netcat显然不是一个生产解决方案,但"只是我的开发盒",对吧?).这样,您可以继续使用支持网络的服务器版本,只需要root/sudo启动代理(启动时),不会依赖复杂/可能脆弱的功能.
我的"标准解决方法"使用socat作为用户空间重定向器:
socat tcp6-listen:80,fork tcp6:8080
请注意,这不会扩展,分叉是昂贵的,但这是socat的工作方式.
Linux支持的功能支持更细粒度的权限,而不仅仅是"此应用程序以root身份运行".其中一项功能是CAP_NET_BIND_SERVICE
绑定到特权端口(<1024).
不幸的是,我不知道如何利用它来运行非root用户同时仍然给它CAP_NET_BIND_SERVICE
(可能使用setcap
,但必然会有一个现有的解决方案).
我知道这是一个老问题,但现在最近(> = 4.3)内核终于有了一个很好的答案 - 环境功能.
快速回答是从git中获取最新(尚未发布)的libcap版本并进行编译.将生成的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'
按顺序,我们是
声明当我们切换用户时,我们希望保留当前的功能集
将用户和群组切换为"您的服务用户名"
将cap_net_bind_service
功能添加到继承和环境集
分叉bash -c 'your-command'
(因为capsh
后面的参数自动启动bash --
)
引擎盖下有很多内容.
首先,我们以root身份运行,因此默认情况下,我们会获得一整套功能.其中包括使用setuid
和setgid
系统调用切换uid和gid的功能.但是,通常当一个程序执行此操作时,它会丢失其功能集 - 这样就可以setuid
保留原有的root方法.该--keep=1
标志告诉capsh
发出prctl(PR_SET_KEEPCAPS)
syscall,它在更改用户时禁用功能丢弃.用户的实际变化通过capsh
与发生的--user
标志,它运行setuid
和setgid
.
我们需要解决的下一个问题是如何以我们exec
的孩子之后的方式设置能力.功能系统始终具有"继承"的一组功能,这是"在execve(2)中保留的一组功能"[ capabilities(7) ].虽然这听起来像是解决了我们的问题(只是设置了cap_net_bind_service
继承的能力,对吗?),但这实际上只适用于特权进程 - 而且我们的进程不再具有特权,因为我们已经更改了用户(带有--user
标志).
新的环境功能集解决了这个问题 - 它是"一组在非特权程序的execve(2)中保留的功能." 通过放入cap_net_bind_service
环境集,当capsh
exec是我们的服务器程序时,我们的程序将继承此功能并能够将侦听器绑定到低端口.
如果您有兴趣了解更多信息,功能手册页将详细解释这一点.运行capsh
经过strace
也非常丰富!
出于某种原因,没有人提到将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.使用systemd-socket方法排除此问题.使用authbind方法仅允许特殊用户绑定.
每次部署新版本的应用程序时都不需要设置CAP/suid.
不需要应用程序支持/修改,如systemd-socket方法.
不需要内核重建(如果运行版本支持此sysctl设置)
没有像authbind/privbind方法那样执行LD_PRELOAD,这可能会影响性能,安全性和行为(是吗?还没有测试过).在其余的authbind中是非常灵活和安全的方法.
过度执行iptables REDIRECT/DNAT方法,因为它不需要地址转换,连接状态跟踪等.这在高负载系统上才会显着.
根据具体情况,我会在sysctl,CAP,authbind和iptables-redirect之间进行选择.我们有很多选择,这很棒.
TLDR:对于"答案"(如我所见),请跳到本答案的>> TLDR <<部分.
好吧,我已经弄明白了(这次是真的),这个问题的答案,我的这个答案也是一种道歉的方式,以促进另一个答案(在这里和在推特上)我认为是"最好的"但是在尝试之后,发现我错了.从我的错误中学习孩子:在你真正尝试过之前不要宣传一些东西!
我再次回顾了这里的所有答案.我尝试了其中一些(并选择不尝试其他人,因为我根本不喜欢这些解决方案).我认为解决方案是使用systemd
它Capabilities=
和CapabilitiesBindingSet=
设置.经过一段时间的摔跤后,我发现这不是解决方案,因为:
功能旨在限制根进程!
由于OP明智地指出,这是总是最好避免(对所有的守护程序如果可能的话!).
您不能使用选项与相关的功能User=
,并Group=
在systemd
单元文件,因为能力总是当复位execev
(或任何功能)被调用.换句话说,当systemd
分叉并丢弃其权限时,功能将被重置.没有办法解决这个问题,内核中的所有绑定逻辑都是基于uid = 0的基础,而不是功能.这意味着Capabilities不太可能成为这个问题的正确答案(至少在很短的时间内).顺便提一下,setcap
正如其他人所提到的,不是解决方案.它对我不起作用,它对脚本不起作用,并且无论何时文件发生变化都会重置.
在我微薄的辩护中,我确实陈述了(在我现在删除的评论中),詹姆斯的iptables建议(OP也提到了),是"第二好的解决方案".:-P
>> TLDR <<
解决方案是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,但是由于53,成功接受了连接 iptables
我们可以将命令包含在单元文件本身中,从而节省人们的头痛.systemd
清除我们的防火墙规则,确保在守护程序未运行时删除它们.
我们从未以root身份运行,并且我们无法进行权限提升(至少systemd
声称),即使守护程序被泄露并设置也是如此uid=0
.
iptables
不幸的是,它仍然是一个非常丑陋且难以使用的实用程序.例如,如果守护程序正在侦听,eth0:0
而不是eth0
命令略有不同.
systemd是一个sysvinit替代品,可以选择启动具有特定功能的守护程序.选项Capabilities =,CapabilityBoundingSet =在systemd.exec(5)联机帮助页中.
端口重定向对我们来说最有意义,但我们遇到了一个问题,我们的应用程序将在本地解析一个也需要重新路由的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
在启动时:
iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
然后,您可以绑定到前进的端口.
使用systemd,您只需稍微修改您的服务即可接受预先激活的套接字.
您可以稍后使用systemd socket activate.
不需要功能,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
现代Linux支持/sbin/sysctl -w net.ipv4.ip_unprivileged_port_start=0
。