[英]Secure child to parent communication in python
My python program needs elevated privileges and is therefore started by root (with a setuid-binary-wrapper).我的 python 程序需要提升权限,因此由 root 启动(使用 setuid-binary-wrapper)。
To keep the attack surface (and the impact of coding errors) as minimal as possible, I decided to split my code into two parts: One part is to be executed as root
and the other with regular user
permissions.为了尽可能减少攻击面(以及编码错误的影响),我决定将代码分成两部分:一部分以
root
身份执行,另一部分以regular user
权限执行。 The problem is, that the code is interdependent and therefore needs secure two-way communication
.问题是,代码是相互依赖的,因此需要
secure two-way communication
。
I do not know if this is the right approach (other ideas welcome), but I decided to go with two processes
- one parent process with elevated privileges and one child process with the privileges of a regular user.我不知道这是否是正确的方法(欢迎其他想法),但我决定使用
two processes
-一个具有提升权限的父进程和一个具有普通用户权限的子进程。
The idea:想法:
The questions:问题:
Is subprocess
(.Popen) sufficient for this? subprocess
进程 (.Popen) 是否足够? Would multiprocessing
be better suited? multiprocessing
会更适合吗?
How can the child process and the parent communicate in an interactive and secure way (Is subprocess.PIPE
secure)?子进程和父进程如何以交互和安全的方式进行通信(
subprocess.PIPE
是否安全)?
Do you know of any simple code examples somewhere for this scenario?你知道这个场景的某个地方有什么简单的代码示例吗?
Based on Gil Hamilton's suggestions, I came up with below code根据吉尔汉密尔顿的建议,我想出了以下代码
Some questions remain:一些问题仍然存在:
os.setuid(<unprivileged UID>)
sufficient?os.setuid(<unprivileged UID>)
足够? privileged.py
: 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
: 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 program): 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
*/
This can be done with Popen
but it's a bit clumsy IMO because you have no control over the process transition with Popen
.这可以通过
Popen
完成,但 IMO 有点笨拙,因为您无法控制Popen
的流程转换。 If you're relying on the UID to attenuate privileges, you need to fork
and then in the child, adjust your UID, before invoking other child code.如果您依赖 UID 来削弱特权,则需要在调用其他子代码之前先在子代码中
fork
,然后调整您的 UID。
(No real reason you couldn't put your child code in a separate program that you invoke with Popen
and have it adjust its UID as the first step, it just seems to me a strange way to structure it.) (没有真正的理由你不能把你的子代码放在一个单独的程序中,你用
Popen
调用并让它作为第一步调整它的 UID,在我看来这是一种奇怪的结构方式。)
I would recommend that you look at using the multiprocessing
module.我建议您考虑使用
multiprocessing
模块。 That module makes it easy to create a new process (it will handle the fork for you).该模块使创建新进程变得容易(它将为您处理分叉)。 Then you can easily drop in code that adjusts the UID (see below) and then you can just run the child code within the same "code base".
然后,您可以轻松放入调整 UID 的代码(见下文),然后您可以在同一“代码库”中运行子代码。 That is, you don't necessarily need to invoke a separate program.
也就是说,您不一定需要调用单独的程序。
The multiprocessing
module also provides its own Pipe
object as well as a Queue
object, both of which are inter-process communication mechanisms. multiprocessing
模块还提供了自己的Pipe
对象和Queue
对象,两者都是进程间通信机制。 Both are secure -- in the sense that no external user can horn in on them (without already having root privilege).两者都是安全的——从某种意义上说,没有外部用户可以对它们进行攻击(没有 root 权限)。 But of course if your unprivileged child process is compromised, it can send whatever it wants to the parent so your privileged parent will still need to validate / audit its input.
但是当然,如果您的非特权子进程受到损害,它可以向父进程发送它想要的任何内容,因此您的特权父进程仍然需要验证/审核其输入。
The documentation for the multiprocessing
module provides several simple examples that should get you started. multiprocessing
模块的文档提供了几个可以帮助您入门的简单示例。 Once created, using a pipe is as easy as reading and writing a file.一旦创建,使用管道就像读写文件一样简单。
As for adjusting the UID, that's simply a single call to os.setuid
in the child before invoking the code you want to run as an unprivileged user.至于调整 UID,在调用您想要作为非特权用户运行的代码之前,
os.setuid
在子os.setuid
中调用os.setuid
即可。 Read the setuid(2)
and credentials(7)
manual pages for more information.阅读
setuid(2)
和credentials(7)
手册页了解更多信息。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.