2010-04-23 52 views
40

我想让一个Python程序开始在端口80上侦听,但之后在没有root权限的情况下执行。有没有办法删除根目录或没有它的端口80?在Python中删除Root权限

+3

http://stackoverflow.com/questions/413807/is-there-a-way-for-non-root-processes-to-bind-to-privileged -ports-1024-on-li – 2010-04-23 18:15:19

+4

在现代Linux上,您只需要功能CAP_NET_ BIND_SERVICE绑定到端口80,您不需要是root用户,即使在应用程序启动时也是如此。 功能是POSIX标准1003.1e,它将所有强大的根特权划分为一组不同的特权。 参见:蟒蛇-CAP-纳克 和/ sbin目录/ setcap,/ sbin目录/ getcap(这些都是等价于chmod的setuid和ls -l) – 2013-09-16 20:30:32

+0

对于Python2,也许还有其他解释,获得的能力是你的一部分要小心 - libcap-ng可以放置大写字母,但不会授予它们。 Ian引用的这个问题的答案是一个相对安全的方法,可以为特定项目每次提供一个上限:http://stackoverflow.com/a/21895123/1724577 – duanev 2014-02-20 21:34:04

回答

50

如果没有root权限,您将无法在端口80上打开服务器,这是对操作系统级别的限制。所以唯一的解决方法是在打开端口后删除root权限。

以下是在Python中删除root权限的可能解决方案:Dropping privileges in Python。这通常是一个很好的解决方案,但您还必须将os.setgroups([])添加到该函数中,以确保不保留根用户的组成员资格。

我复制并清理了一些代码,并删除了日志记录和异常处理程序,因此它将由您来正确处理OSError(当进程不允许切换其有效的UID时它将被抛出或GID):

import os, pwd, grp 

def drop_privileges(uid_name='nobody', gid_name='nogroup'): 
    if os.getuid() != 0: 
     # We're not root so, like, whatever dude 
     return 

    # Get the uid/gid from the name 
    running_uid = pwd.getpwnam(uid_name).pw_uid 
    running_gid = grp.getgrnam(gid_name).gr_gid 

    # Remove group privileges 
    os.setgroups([]) 

    # Try setting the new uid/gid 
    os.setgid(running_gid) 
    os.setuid(running_uid) 

    # Ensure a very conservative umask 
    old_umask = os.umask(077) 
+1

请记住,HOME目录仍然是'/ root“而不是'/ home/uid_name',并且uid_name将无法对'〜/'进行任何操作,然后会扩展到'/ root /'。这可能会影响模块,如matplotlib将配置数据存储在HOME目录中。 'authbind'似乎是解决这个问题的正确方法。 – 2014-05-01 20:29:58

+1

而HOME变量不会是唯一一个仍然认为当前用户是root的人。 – 2014-05-01 20:37:45

+1

*“您将无法在没有root权限的情况下在端口80上打开服务器...” - - 这不一定是真的(也许不再是?)。另请参阅SuperUser上的[允许非root进程绑定到端口80和443?](https://superuser.com/q/710253/173513),以及[是否有非root进程绑定到“特权“Linux上的端口?”(https://stackoverflow.com/q/413807/608639) – jww 2017-07-23 18:51:32

12

我建议使用authbind开始你的Python程序,所以它没有一个具有以root身份运行。

https://en.wikipedia.org/wiki/Authbind

+0

如何使用'authbind'的例子 - http://goo.gl/fxFde6 - 用你喜欢的任何东西替换NodeJS :python) – starlocke 2015-04-22 03:42:26

3
  1. systemd可以为你做,如果通过systemd启动程序,systemd可以将已经打开监听套接字移交给它,它也可以激活在第一次连接程序。而且你甚至不需要守护它。

  2. 如果您打算采用独立方法,则需要CAP_NET_BIND_SERVICE(检查功能手册页)功能。这可以通过使用正确的命令行工具逐个程序地完成,或者通过使应用程序(1)成为suid root(2)启动(3)立即侦听端口(4)删除特权/功能。

记住的suid root程序来有很多的安全考虑(清洁和安全的环境,umask的,特权,rlimits,所有这些事情都是东西,你的程序将不得不正确设置)。如果你可以使用像systemd这样的东西,那就更好了。

2

大多数情况下,除非你需要在做一些其他你不想成为超级用户的东西之后需要套接字,否则这些工作大部分都可以。

前段时间我做了一个叫tradesocket的项目。它允许您在进程之间的posix系统上来回传递套接字。我所做的是在开始时分离一个进程,保持超级用户,并且进程的其余部分在权限中下降,然后从另一个请求套接字。

7

当我需要删除权限时,要求用户输入他/她的用户名和组不是一个好主意。这是一个稍微修改过的Tamás代码版本,它将删除权限并切换到启动sudo命令的用户。我假设你正在使用sudo(如果没有,请使用Tamás的代码)。

#!/usr/bin/env python3 

import os, pwd, grp 

#Throws OSError exception (it will be thrown when the process is not allowed 
#to switch its effective UID or GID): 
def drop_privileges(): 
    if os.getuid() != 0: 
     # We're not root so, like, whatever dude 
     return 

    # Get the uid/gid from the name 
    user_name = os.getenv("SUDO_USER") 
    pwnam = pwd.getpwnam(user_name) 

    # Remove group privileges 
    os.setgroups([]) 

    # Try setting the new uid/gid 
    os.setgid(pwnam.pw_gid) 
    os.setuid(pwnam.pw_uid) 

    #Ensure a reasonable umask 
    old_umask = os.umask(0o22) 


#Test by running... 
#./drop_privileges 
#sudo ./drop_privileges 
if __name__ == '__main__': 
    print(os.getresuid()) 
    drop_privileges() 
    print(os.getresuid()) 
1

以下是Tamás's answer进一步适应,有以下变化:

  • 使用python-prctl module下降Linux的功能的能力,以保持一个指定的列表。
  • 用户可以选择作为参数传递(它默认查找运行sudo的用户)。
  • 它设置所有用户的组和HOME
  • 它可以选择更改目录。

(我是比较新的但是,使用这种功能,所以我可能错过了一些东西,它可能无法正常工作在老版本的内核(< 3.8)或内核禁用文件系统的能力。)

def drop_privileges(user=None, rundir=None, caps=None): 
    import os 
    import pwd 

    if caps: 
     import prctl 

    if os.getuid() != 0: 
     # We're not root 
     raise PermissionError('Run with sudo or as root user') 

    if user is None: 
     user = os.getenv('SUDO_USER') 
     if user is None: 
      raise ValueError('Username not specified') 
    if rundir is None: 
     rundir = os.getcwd() 

    # Get the uid/gid from the name 
    pwnam = pwd.getpwnam(user) 

    if caps: 
     prctl.securebits.keep_caps=True 
     prctl.securebits.no_setuid_fixup=True 

    # Set user's group privileges 
    os.setgroups(os.getgrouplist(pwnam.pw_name, pwnam.pw_gid)) 

    # Try setting the new uid/gid 
    os.setgid(pwnam.pw_gid) 
    os.setuid(pwnam.pw_uid) 

    os.environ['HOME'] = pwnam.pw_dir 

    os.chdir(os.path.expanduser(rundir)) 

    if caps: 
     prctl.capbset.limit(*caps) 
     try: 
      prctl.cap_permitted.limit(*caps) 
     except PermissionError: 
      pass 
     prctl.cap_effective.limit(*caps) 

    #Ensure a reasonable umask 
    old_umask = os.umask(0o22) 

它可以用作如下:

drop_privileges(user='www', rundir='~', caps=[prctl.CAP_NET_BIND_SERVICE])