在 python 中保护 child 到 parent 的通信

Secure child to parent communication in python

我的 python 程序需要提升权限,因此由 root 启动(使用 setuid-binary-wrapper)。

为了尽可能减少攻击面(以及编码错误的影响),我决定将我的代码分成两部分:一部分将作为 root 执行另一个具有 regular user 权限。问题是,代码是相互依赖的,因此需要 secure two-way communication.

我不知道这是否是正确的方法(欢迎其他想法),但我决定使用 two processes - 一个 parent 具有更高权限的进程和一个child 具有普通用户权限的进程。

思路:

题目:

-------------------------------------------- --------------------------

根据 Gil Hamilton 的建议,我想出了以下代码

还有一些问题:

privileged.py:

#!/bin/python
from multiprocessing import Process, Pipe
from unprivileged import Unprivileged

if __name__ == '__main__':
  privilegedProcessPipeEnd, unprivilegedProcessPipeEnd = Pipe()
  unprivilegedProcess = Process(target=Unprivileged(unprivilegedProcessPipeEnd).operate)
  unprivilegedProcess.start()

  print(privilegedProcessPipeEnd.recv())
  privilegedProcessPipeEnd.send("ok")
  print(privilegedProcessPipeEnd.recv())
  privilegedProcessPipeEnd.send("nok")

  privilegedProcessPipeEnd.close()
  unprivilegedProcessPipeEnd.close()
  unprivilegedProcess.join()

unprivileged.py:

import os

class Unprivileged:

  def __init__(self, unprivilegedProcessPipeEnd):
    self._unprivilegedProcessPipeEnd = unprivilegedProcessPipeEnd

  def operate(self):
    invokerUid = os.getuid()

    if invokerUid == 0:
      # started by root; TODO: drop to predefined standard user
      # os.setuid(standardUid)
      pass
    else:
      # started by a regular user through a setuid-binary 
      os.setuid(invokerUid) # TODO: drop to predefined standard user (save invokerUid for future stuff)

    # os.setuid(0) # not permitted anymore, cannot become root again

    print("os.getuid(): " + str(os.getuid()))

    self._unprivilegedProcessPipeEnd.send("invoke privilegedFunction1")
    print(self._unprivilegedProcessPipeEnd.recv())
    self._unprivilegedProcessPipeEnd.send("invoke privilegedFunction2")
    print(self._unprivilegedProcessPipeEnd.recv())

    return

main.c(setuid-wrapper 程序):

#include <unistd.h>
#define SCRIPT_PATH "/home/u1/project/src/privileged.py"

int
main(int argc,
     char **argv) {
  return execv(SCRIPT_PATH, argv);
}

/* compile and run like this:
$ gcc -std=c99 main.c -o main
# chown root:root main
# chmod 6771 main
$ chmod +x /home/u1/project/src/privileged.py
$ ./main
*/

这可以用 Popen 完成,但在我看来这有点笨拙,因为你无法控制 Popen 的过程转换。如果您依赖 UID 来降低权限,则需要 fork 然后在 child 中调整您的 UID,然后再调用其他 child 代码。

(没有真正的理由你不能把你的 child 代码放在你用 Popen 调用的单独的程序中并让它调整它的 UID 作为第一步,在我看来一种奇怪的结构方式。)

我建议您考虑使用 multiprocessing 模块。该模块使创建新进程变得容易(它将为您处理分叉)。然后你可以轻松地放入调整 UID 的代码(见下文),然后你可以 运行 相同 "code base" 中的 child 代码。也就是说,您不一定需要调用单独的程序。

multiprocessing模块也提供了自己的Pipeobject以及一个Queueobject,两者都是inter-process沟通机制。两者都是安全的——从某种意义上说,没有任何外部用户可以侵入它们(没有 root 权限)。但是当然,如​​果您的非特权 child 进程受到威胁,它可以将任何它想要的发送到 parent,因此您的特权 parent 仍然需要验证/审核其输入。

multiprocessing 模块的文档提供了几个简单的示例,可以帮助您入门。创建后,使用管道就像读写文件一样简单。

至于调整 UID,只需在 child 中对 os.setuid 进行一次调用,然后再以非特权用户的身份调用您想要 运行 的代码。阅读 setuid(2)credentials(7) 手册页以获取更多信息。