gpt4 book ai didi

python - 如何在我的网络应用程序上安全地接受和运行用户的代码?

转载 作者:行者123 更新时间:2023-12-02 17:14:25 41 4
gpt4 key购买 nike

我正在开发一个基于 django 的 web 应用程序,它以 python 文件作为输入,其中包含一些函数,然后在后端我有一些列表作为参数通过用户函数传递,这将生成一个单值输出。生成的结果将是用于一些进一步的计算。
以下是用户文件中的函数的样子:

def somefunctionname(list):

''' some computation performed on list'''

return float value
目前我使用的方法是将用户的文件作为普通文件输入。然后在我的 views.py 中,我将文件作为模块执行并使用 eval 函数传递参数。下面给出了片段。
这里 modulename 是我从用户那里获取并作为模块导入的 python 文件名
exec("import "+modulename)

result = eval(f"{modulename}.{somefunctionname}(arguments)")
哪个工作得很好。但我知道这不是安全的方法。
我的问题,有没有其他方法可以安全地运行用户文件,因为我使用的方法不安全?我知道提议的解决方案不能完全证明,但是我可以运行它的其他方式是什么(例如,如果它可以通过 dockerization 解决,那么我可以使用 API 的方法或一些外部工具是什么)?
或者如果可能的话,有人可以告诉我如何简单地沙盒这个或任何可以帮助我的教程..?
任何引用或资源都会有所帮助。

最佳答案

这是一个重要的问题。在 python 中沙箱不是微不足道的。
这是您使用哪个版本的python解释器的问题的少数情况之一。例如,Jyton 生成 Java 字节码,JVM 有自己的机制来安全地运行代码。
对于默认解释器 CPython,最初有一些尝试制作 restricted execution mode ,但很久以前就放弃了。
目前,有一个非官方项目 RestrictedPython 可能会给你你需要的东西。它是 不是一个完整的沙箱 ,即不会给你限制文件系统访问或其他东西,但对于你的需要它可能就足够了。
基本上,那里的人只是以更受限制的方式重写了 python 编译。
它允许做的是编译一段代码然后执行,所有这些都在受限模式下。例如:

from RestrictedPython import safe_builtins, compile_restricted

source_code = """
print('Hello world, but secure')
"""

byte_code = compile_restricted(
source_code,
filename='<string>',
mode='exec'
)
exec(byte_code, {__builtins__ = safe_builtins})

>>> Hello world, but secure
使用 内置函数运行 = safe_builtins 禁用打开文件、导入等危险功能。还有 内置 和其他选项的其他变体,花一些时间阅读文档,它们非常好。
编辑:
这是您的用例示例
from RestrictedPython import safe_builtins, compile_restricted
from RestrictedPython.Eval import default_guarded_getitem


def execute_user_code(user_code, user_func, *args, **kwargs):
""" Executed user code in restricted env
Args:
user_code(str) - String containing the unsafe code
user_func(str) - Function inside user_code to execute and return value
*args, **kwargs - arguments passed to the user function
Return:
Return value of the user_func
"""

def _apply(f, *a, **kw):
return f(*a, **kw)

try:
# This is the variables we allow user code to see. @result will contain return value.
restricted_locals = {
"result": None,
"args": args,
"kwargs": kwargs,
}

# If you want the user to be able to use some of your functions inside his code,
# you should add this function to this dictionary.
# By default many standard actions are disabled. Here I add _apply_ to be able to access
# args and kwargs and _getitem_ to be able to use arrays. Just think before you add
# something else. I am not saying you shouldn't do it. You should understand what you
# are doing thats all.
restricted_globals = {
"__builtins__": safe_builtins,
"_getitem_": default_guarded_getitem,
"_apply_": _apply,
}

# Add another line to user code that executes @user_func
user_code += "\nresult = {0}(*args, **kwargs)".format(user_func)

# Compile the user code
byte_code = compile_restricted(user_code, filename="<user_code>", mode="exec")

# Run it
exec(byte_code, restricted_globals, restricted_locals)

# User code has modified result inside restricted_locals. Return it.
return restricted_locals["result"]

except SyntaxError as e:
# Do whaever you want if the user has code that does not compile
raise
except Exception as e:
# The code did something that is not allowed. Add some nasty punishment to the user here.
raise
现在您有一个函数 execute_user_code ,它接收一些不安全代码作为字符串、来自该代码的函数名称、参数,并返回具有给定参数的函数的返回值。
这是一些用户代码的一个非常愚蠢的例子:
example = """
def test(x, name="Johny"):
return name + " likes " + str(x*x)
"""
# Lets see how this works
print(execute_user_code(example, "test", 5))
# Result: Johny likes 25
但是当用户代码试图做一些不安全的事情时会发生以下情况:
malicious_example = """
import sys
print("Now I have the access to your system, muhahahaha")
"""
# Lets see how this works
print(execute_user_code(malicious_example, "test", 5))
# Result - evil plan failed:
# Traceback (most recent call last):
# File "restr.py", line 69, in <module>
# print(execute_user_code(malitious_example, "test", 5))
# File "restr.py", line 45, in execute_user_code
# exec(byte_code, restricted_globals, restricted_locals)
# File "<user_code>", line 2, in <module>
#ImportError: __import__ not found
可能的扩展名:
请注意,每次调用函数时都会编译用户代码。但是,您可能希望编译一次用户代码,然后使用不同的参数执行它。因此,您所要做的就是将 byte_code 保存在某处,然后每次使用一组不同的 restricted_locals 调用 exec。
EDIT2:
如果您想使用导入,您可以编写自己的导入函数,只允许使用您认为安全的模块。例子:
def _import(name, globals=None, locals=None, fromlist=(), level=0):
safe_modules = ["math"]
if name in safe_modules:
globals[name] = __import__(name, globals, locals, fromlist, level)
else:
raise Exception("Don't you even think about it {0}".format(name))

safe_builtins['__import__'] = _import # Must be a part of builtins
restricted_globals = {
"__builtins__": safe_builtins,
"_getitem_": default_guarded_getitem,
"_apply_": _apply,
}

....
i_example = """
import math
def myceil(x):
return math.ceil(x)
"""
print(execute_user_code(i_example, "myceil", 1.5))
请注意,此示例导入​​函数非常原始,它不适用于 from x import y 之类的东西。您可以查看 here 以获得更复杂的实现。
编辑3
请注意,在 RestrictedPython 中,许多 Python 内置功能不可用 开箱即用 ,这并不意味着它根本不可用。您可能需要实现一些功能才能使其可用。
甚至一些明显的东西,比如 sum+= 运算符,在受限环境中也不明显。
例如, for 循环使用 _getiter_ 函数,您必须自己实现并提供该函数(在全局变量中)。由于您想避免无限循环,您可能需要对允许的迭代次数设置一些限制。这是将迭代次数限制为 100 的示例实现:
MAX_ITER_LEN = 100

class MaxCountIter:
def __init__(self, dataset, max_count):
self.i = iter(dataset)
self.left = max_count

def __iter__(self):
return self

def __next__(self):
if self.left > 0:
self.left -= 1
return next(self.i)
else:
raise StopIteration()

def _getiter(ob):
return MaxCountIter(ob, MAX_ITER_LEN)

....

restricted_globals = {
"_getiter_": _getiter,

....

for_ex = """
def sum(x):
y = 0
for i in range(x):
y = y + i
return y
"""

print(execute_user_code(for_ex, "sum", 6))
如果您不想限制循环次数,只需将标识函数用作 _getiter_ :
restricted_globals = {
"_getiter_": labmda x: x,
请注意,简单地限制循环计数并不能保证安全性。首先,循环可以嵌套。其次,您不能限制 while 循环的执行次数。为了使其安全,您必须在一定的超时时间内执行不安全的代码。
请花点时间阅读 docs
请注意,并非所有内容都被记录在案(尽管很多事情都有)。你必须学会​​阅读项目的 source code 以获得更高级的东西。最好的学习方法是尝试运行一些代码,看看缺少什么样的功能,然后查看项目的源代码以了解如何实现它。
EDIT4
还有另一个问题——受限代码可能有无限循环。为了避免这种情况,代码需要某种超时。
不幸的是,由于您使用的是 django,除非您明确指定,否则它是多线程的,使用信号超时的简单技巧在这里不起作用,您必须使用多处理。
我认为最简单的方法 - 使用 this library 。简单地向 execute_user_code 添加一个装饰器,它看起来像这样:
@timeout_decorator.timeout(5, use_signals=False)
def execute_user_code(user_code, user_func, *args, **kwargs):
你已经完成了。代码永远不会运行超过 5 秒。
注意use_signals=False,没有这个它可能在django中出现一些意想不到的行为。
还要注意,这对资源来说相对较重(我真的看不到克服这个问题的方法)。我的意思是不是真的很重,但它是一个额外的进程产生。您应该在 Web 服务器配置中牢记这一点 - 允许执行任意用户代码的 api 更容易受到 ddos​​ 的攻击。

关于python - 如何在我的网络应用程序上安全地接受和运行用户的代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63160370/

41 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com