在Python中即使经过seteuid也无法放弃root权限,是个bug吗?

4 投票
1 回答
2450 浏览
提问于 2025-04-16 18:49

在Python中,根权限无法被取消,即使使用了seteuid。这是个bug吗?

编辑 总结:我忘记取消gid(组ID)了。不过,接受的答案可能对你有帮助。

你好。我在我的Linux上使用Python 3.2时,无法取消根权限。实际上,即使在执行seteuid(1000)后,它仍然可以读取根用户拥有的400权限的文件。euid确实被设置为1000了!

我发现,在调用空的os.fork()后,特权访问确实被拒绝了。(但这只在父进程中。子进程仍然可以不合法地读取文件。)这是Python的bug,还是Linux的问题呢?

试试下面的代码。把底部三行中的一行注释掉,然后以根用户身份运行。

提前谢谢你。

#!/usr/bin/python3

# Python seteuid pitfall example.
# Run this __as__ the root.

# Here, access to root-owned files /etc/sudoers and /etc/group- are tried.
# Simple access to them *succeeds* even after seteuid(1000) which should fail.

# Three functions, stillRoot(), forkCase() and workAround() are defined.
# The first two seem wrong. In the last one, access fails, as desired.


# ***Comment out*** one of three lines at the bottom before execution.

# If your python is < 3.2, comment out the entire def of forkCase()

import os

def stillRoot():
    """Open succeeds, but it should fail."""
    os.seteuid(1000)
    open('/etc/sudoers').close()

def forkCase():
    """Child can still open it. Wow."""
    # setresuid needs python 3.2
    os.setresuid(1000, 1000, 0)
    pid = os.fork()
    if pid == 0:
        # They're surely 1000, not 0!
        print('uid: ', os.getuid(), 'euid: ', os.geteuid())
        open('/etc/sudoers').close()
        print('open succeeded in child.')
        exit()
    else:
        print('child pid: ', pid)
        open('/etc/group-').close()
        print('parent succeeded to open.')

def workAround():
    """So, a dummy fork after seteuid is necessary?"""
    os.seteuid(1000)
    pid = os.fork()
    if pid == 0:
        exit(0)
    else:
        os.wait()

    open('/etc/group-').close()

## Run one of them.

# stillRoot()
# forkCase()
# workAround()

1 个回答

8

在Unix系统上处理进程的凭证是个复杂的事情。我强烈建议你先搞清楚真实用户ID、有效用户ID和保存用户ID之间的关系。搞错了“降低权限”的操作是很容易出错的。

关于你提到的具体情况……我在想,可能有个简单的原因你没注意到。你的代码在进行不一致的测试,而且你没有明确指定/etc/sudoers/etc/group-文件的具体权限。如果/etc/sudoers的权限是440,用户是root,组也是root(这是我系统上的默认权限),而/etc/group-的权限是400,那么你的代码表现得就会和你描述的一样。

你并没有修改进程的组ID,所以如果/etc/sudoers是可被组读取的,那就能解释为什么它总是可读的。fork()并不会修改进程的凭证。不过,在你的示例代码中,它似乎会这样做,因为你在父进程和子进程中检查了不同的文件。如果/etc/group-没有组读取权限,而/etc/sudoers有,那就能解释你所看到的问题。

如果你只是想“降低权限”,可以使用以下代码:

os.setgid( NEW_GID )
os.setuid( NEW_UID )

一般来说,只有在你的进程需要在运行过程中切换根权限时,才想要修改有效用户ID。如果你只是需要用根权限做一些设置操作,而在这些操作完成后不再需要根权限,那就用上面的代码来永久性地降低权限。

哦,还有一个在Linux上处理进程凭证的有用调试工具,就是打印/proc/self/status的输出。这个文件中的Uid和Gid行显示了当前进程的真实、有效、保存的用户ID和文件ID(按这个顺序)。虽然可以用Python的API获取相同的信息,但你可以把这个文件的内容视为“真实数据”,这样可以避免使用Python跨平台API可能带来的复杂问题。

撰写回答