安全地执行来自 Python 中用户输入的脚本



我正在寻找一种在Python中执行来自用户的潜在不安全脚本代码的安全机制。

代码示例:

def public(v):
print('Allowed to trigger this with '+v)

def secret():
print('Not allowed to trigger this')

unsafe_user_code = '''
def user_function(a):
if 'o' in a:
public('parameter')
a = 'hello'
user_function(a)
'''
run_code(unsafe_user_code, allowed=['public'])

这可以通过exec()轻松实现,但据我所知,在 Python 中没有办法以安全的方式使用exec()

以下是我的要求:

  1. 理想情况下,脚本的语法应该类似于Python(但也可以类似于JavaScript)。
  2. 标准机制,如字符串操作、if/else/while 以及自己的变量和函数的定义需要在脚本中可用
  3. 脚本应该只能执行某些函数(在示例中,只允许public())
  4. 我只想依赖基于 Python 的实现/库,因为我不想依赖 Python 外部软件
  5. 它不应引入安全风险

到目前为止,我发现的唯一方法是使用解析库,我必须自己定义所有内容(例如:https://github.com/lark-parser/lark)。

有没有更好的方法来实现这样的事情?

谢谢!

答案

如果你想复制,这是完整的python文件:

from copy import copy
def public(v):
print('Allowed to trigger this with '+v)

def secret():
print('Not allowed to trigger this')

unsafe_user_code = '''
def user_function(a):
if 'o' in a:
public('parameter')
# secret()
# If you uncomment it, it'll throw an error saying 'secret' does not exist
a = 'hello'
user_function(a)
'''
def run_code(code_to_run,allowed:list[str]):
g = globals()
allowed_dict = {f"{name}": g[name] for name in allowed}
code = compile(code_to_run,'','exec')

def useless():
pass

useless.__code__ = code

g = copy(useless.__globals__)
for x in g:
del useless.__globals__[x]
for x in allowed_dict:
useless.__globals__[x] = allowed_dict[x]

useless()

run_code(unsafe_user_code, allowed=['public'])

解释

我认为你不需要解析库。 您可以使用 python 的内置函数之一,称为 compile()。

您可以像这样编译代码:

text_to_compile = "print('hello world')"
code = compile(
text_to_compile, # text to compile
'file_name', # file name
'exec' # compile mode
)

有关编译模式的更多信息

然后,您可以简单地通过以下方式运行它:

exec(code) # prints 'hello world' in the terminal

或者,您可以将该代码放在函数中并运行该函数:

def useless():
pass

useless.__code__ = code
useless() # prints 'hello world' in the terminal

现在要更改函数的范围,我们可以访问其__global__字典并从中删除所有内容。 然后我们向其添加所需的函数/变量。

# from copy import copy
def run_code(code_to_run,allowed:list[str]):
g = globals()
allowed_dict = {f"{name}": g[name] for name in allowed}
code = compile(code_to_run,'','exec')

def useless():
pass

useless.__code__ = code

g = copy(useless.__globals__)
for x in g:
del useless.__globals__[x]
for x in allowed_dict:
useless.__globals__[x] = allowed_dict[x]

useless()

好吧,让我们谈谈正在发生的事情:

def run_code(code_to_run,allowed:list[str]):

:list[str]只是定义变量的类型allowed.

g = globals()

使用globals()方法,我们能够获取函数run_code有权访问的所有变量。 这包括公共函数和秘密函数。

allowed_dict = {f"{name}": g[name] for name in allowed}

并使用您传递给run_code函数的变量名称,并从此函数的全局变量中获取变量。

这几乎和说

allowed_dict = {}
for name in allowed:
allowed_dict[name] = g[name]

好的,然后我们编译代码。 我们还制作了一个useless函数,并将编译好的代码放入函数中:

code = compile(code_to_run,'','exec')

def useless():
pass

useless.__code__ = code

之后,我们复制useless函数的全局变量:

g = copy(useless.__globals__)

我们复制函数中的全局变量,以便稍后在代码中我们可以循环并删除它。 我们这样做是因为我们不能在 for 循环迭代字典时从字典中删除某些内容。

for x in g:
del useless.__globals__[x]

我们删除useless函数全局中的所有内容。 无用函数是一个空函数,但它的全局充满了各种东西,比如公共函数和秘密函数。

然后我们只是将所有允许的变量放入函数的全局变量中,以便函数内部的代码可以使用变量/函数。

for x in allowed_dict:
useless.__globals__[x] = allowed_dict[x]

然后我们可以运行无用的函数:

useless()

这几乎就是全部。

相关内容

  • 没有找到相关文章