首页 文章

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

提问于
浏览
338

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

我知道the standard workarounds,但它们都没有完全符合我的要求:

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

编辑:在某些情况下,你可以use capabilities这样做 .

22 回答

  • 12

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

    setcap可以在脚本的解释器上使用,以授予脚本功能 . 如果全局解释器二进制文件上的setcaps不可接受,则制作二进制文件的本地副本(任何用户都可以)并在此副本上获取rootto to 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 $*
    
  • 12

    好的,感谢那些指出功能系统和 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内核

    • 这赢得了't work if your file is a script. (ie, uses a #! line to launch an interpreter). In this case, as far I as understand, you' d必须将该功能应用于解释器可执行文件本身,这当然是一个安全噩梦,因为使用该解释器的任何程序都具有该功能 . 我无法找到任何干净,简单的方法来解决这个问题 .

    • Linux将在具有提升权限的任何 program 上禁用LD_LIBRARY_PATH,例如 setcapsuid . 因此,如果您的 program 使用自己的 .../lib/ ,您可能需要查看另一个选项,如端口转发 .

    资源:

    注意:RHEL first added this in v6 .

  • 14

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

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

  • 28

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

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

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

    理想的解决方案,恕我直言,应该是一个创建具有可继承 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不变,我们绝对不希望它 .

    • 设置位'keep capabilities on UID change' .

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

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

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

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

  • 13

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

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

  • 8

    另外两个简单的可能性

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

    • 你的守护进程需要在stdin / stdout上讨论(如果你没有't control the daemon -- if you don't有源 - 那么这可能是一个showstopper,虽然有些服务可能有一个inetd兼容性标志)

    • 为每个连接分叉一个新的守护进程

    • 这是链中的一个额外链接

    优点:

    • 可在任何旧UNIX上使用

    • 一旦你的系统管理员设置了配置,你're good to go about your development (when you re-build your daemon, might you lose setcap capabilities? And then you'将不得不回到你的管理员"please sir...")

    • 守护进程不必担心网络内容,只需要在stdin / stdout上进行讨论

    • 可以根据请求配置为以非root用户身份执行守护程序

    另一种选择:从特权端口到一些任意高编号端口的黑客代理(netcat甚至更强大的代理),你可以运行目标守护程序 . (Netcat显然不是 生产环境 解决方案,但是"just my dev box",对吧?) . 通过这种方式,您可以继续使用支持网络的服务器版本,只需要root / sudo启动代理(启动时),不会依赖复杂/可能脆弱的功能 .

  • 15

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

    socat tcp6-listen:80,fork tcp6:8080
    

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

  • 2

    Linux支持capabilities以支持比"this application is run as root"更细粒度的权限 . 其中一个功能是 CAP_NET_BIND_SERVICE ,它涉及绑定到特权端口(<1024) .

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

  • 1

    我知道这是一个老问题,但现在最近(> = 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'
    

    按顺序,我们是

    • 声明当我们切换用户时,我们希望保留当前的功能集

    • 将用户和组切换为'your-service-user-name'

    • cap_net_bind_service 功能添加到继承和环境集

    • 分叉 bash -c 'your-command' (因为 capsh 自动在 -- 之后用参数启动bash)

    这里有很多内容 .

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

    我们需要解决的下一个问题是如何在我们的孩子们之后继续设置能力 . 功能系统始终具有'inherited'功能集,即" a set of capabilities preserved across an execve(2)" [capabilities(7)] . 虽然这听起来像是解决了我们的问题(只是将 cap_net_bind_service 功能设置为继承,对吗?),这实际上只适用于特权进程 - 而且我们的进程不再具有特权,因为我们已经更改了用户(使用 --user 标志) .

    新的环境功能集解决了这个问题 - 它是"a set of capabilities that are preserved across an execve(2) of a program that is not privileged."通过将 cap_net_bind_service 放在环境集中,当 capsh exec是我们的服务器程序时,我们的程序将继承此功能并能够将侦听器绑定到低端口 .

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

  • 13

    TLDR: For "the answer" (as I see it), jump down to the >>TLDR<< part in this answer.

    好吧,我已经弄明白了(这次真的是这个),这个问题的答案,我的这个答案也是一种道歉的方式,促使another answer(在这里和在推特上),我认为是"the best",但是之后尝试它,发现我错了 . 从错误的孩子那里学习:不要自己尝试过!

    我再次回顾了这里的所有答案 . 我尝试了其中一些(并选择不尝试其他人,因为我根本不喜欢这些解决方案) . 我认为解决方案是使用 systemd 及其 Capabilities=CapabilitiesBindingSet= 设置 . 在与此摔跤一段时间后,我发现这不是解决方案 because:

    Capabilities are intended to restrict root processes!

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

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

    在我微薄的辩护中,我做了状态(在评论中我提到了(OP也提到了),是"2nd best solution" . :-P

    >>TLDR<<

    解决方案是将 systemd 与on-the-fly 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 .

  • 17

    Update 2017:

    使用authbind

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

    • 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
    

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

    我第一次发布它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为mono / .NET .

    我是最好的想法 .
    但由于这两种方法都是黑客攻击,为什么不通过解除限制来解除限制呢?
    没有人说你必须运行一个普通的内核,所以你可以运行自己的内核 .

    您只需下载最新内核的源代码(或您目前拥有的内核) . 然后,你去:

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

  • 7

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

  • 2

    端口重定向对我们来说最有意义,但是我们遇到了一个问题,我们的应用程序将在本地解析一个也需要重新路由的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
    
  • 15

    在启动时:

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

    然后,您可以绑定到前进的端口 .

  • 10

    使用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
    
  • 31

    出于某种原因,没有人提到将sysctl net.ipv4.ip_unprivileged_port_start降低到所需的值 . 示例:我们需要将应用程序绑定到443端口 .

    sysctl net.ipv4.ip_unprivileged_port_start=443
    

    有人可能会说,存在潜在的安全问题:非特权用户现在可能绑定到其他特权端口(444-1024) . 但是你可以通过阻止其他端口轻松解决这个问题:

    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设置)

    • 没有't do LD_PRELOAD like authbind/privbind method, this could potentially affect performance, security, behavior (does it? haven'经过测试) . 在其余的authbind中是非常灵活和安全的方法 .

    • 过度执行iptables REDIRECT / DNAT方法,因为它不需要地址转换,连接状态跟踪等 . 这在高负载系统上才会显着 .

    根据具体情况,我会选择sysctl,CAP,authbind和iptables-redirect . 我们有很多选择,这很棒 .

  • 1

    还有'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

  • 18

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

  • 5

    我尝试了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 . 也就是说,如果机器也在接口之间转发数据包(例如,如果它连接到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的应用程序然后不知道传入连接的源地址(例如,用于记录或其他识别目的) .

  • 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
    

相关问题