在 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 具有普通用户权限的进程。
思路:
- parent 进程由 root 启动并保留其特权
- parent 进程生成了一个 child 进程,该进程掉落给普通用户(child 进程无法重新获得 root 权限)
- child 进程完成大部分工作,但如果它需要以 root 权限执行某些操作,它会告诉 parent 进程为它做这件事
题目:
subprocess
(.Popen) 是否足够? multiprocessing
会更适合吗?
child 进程和 parent 如何以交互和安全的方式进行通信(subprocess.PIPE
安全吗)?
你知道这个场景的任何简单代码示例吗?
-------------------------------------------- --------------------------
根据 Gil Hamilton 的建议,我想出了以下代码
还有一些问题:
- 这安全吗?我是否需要删除文件描述符等其他内容,或者
os.setuid(<unprivileged UID>)
是否足够?
- 一般来说,如果一个进程像这样掉落到特定用户,这个用户是否能够干扰掉落进程的内存?
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
模块也提供了自己的Pipe
object以及一个Queue
object,两者都是inter-process沟通机制。两者都是安全的——从某种意义上说,没有任何外部用户可以侵入它们(没有 root 权限)。但是当然,如果您的非特权 child 进程受到威胁,它可以将任何它想要的发送到 parent,因此您的特权 parent 仍然需要验证/审核其输入。
multiprocessing
模块的文档提供了几个简单的示例,可以帮助您入门。创建后,使用管道就像读写文件一样简单。
至于调整 UID,只需在 child 中对 os.setuid
进行一次调用,然后再以非特权用户的身份调用您想要 运行 的代码。阅读 setuid(2)
和 credentials(7)
手册页以获取更多信息。
我的 python 程序需要提升权限,因此由 root 启动(使用 setuid-binary-wrapper)。
为了尽可能减少攻击面(以及编码错误的影响),我决定将我的代码分成两部分:一部分将作为 root
执行另一个具有 regular user
权限。问题是,代码是相互依赖的,因此需要 secure two-way communication
.
我不知道这是否是正确的方法(欢迎其他想法),但我决定使用 two processes
- 一个 parent 具有更高权限的进程和一个child 具有普通用户权限的进程。
思路:
- parent 进程由 root 启动并保留其特权
- parent 进程生成了一个 child 进程,该进程掉落给普通用户(child 进程无法重新获得 root 权限)
- child 进程完成大部分工作,但如果它需要以 root 权限执行某些操作,它会告诉 parent 进程为它做这件事
题目:
subprocess
(.Popen) 是否足够?multiprocessing
会更适合吗?child 进程和 parent 如何以交互和安全的方式进行通信(
subprocess.PIPE
安全吗)?你知道这个场景的任何简单代码示例吗?
-------------------------------------------- --------------------------
根据 Gil Hamilton 的建议,我想出了以下代码
还有一些问题:
- 这安全吗?我是否需要删除文件描述符等其他内容,或者
os.setuid(<unprivileged UID>)
是否足够? - 一般来说,如果一个进程像这样掉落到特定用户,这个用户是否能够干扰掉落进程的内存?
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
模块也提供了自己的Pipe
object以及一个Queue
object,两者都是inter-process沟通机制。两者都是安全的——从某种意义上说,没有任何外部用户可以侵入它们(没有 root 权限)。但是当然,如果您的非特权 child 进程受到威胁,它可以将任何它想要的发送到 parent,因此您的特权 parent 仍然需要验证/审核其输入。
multiprocessing
模块的文档提供了几个简单的示例,可以帮助您入门。创建后,使用管道就像读写文件一样简单。
至于调整 UID,只需在 child 中对 os.setuid
进行一次调用,然后再以非特权用户的身份调用您想要 运行 的代码。阅读 setuid(2)
和 credentials(7)
手册页以获取更多信息。