我正在寻找一种在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()
。
以下是我的要求:
- 理想情况下,脚本的语法应该类似于Python(但也可以类似于JavaScript)。
- 标准机制,如字符串操作、if/else/while 以及自己的变量和函数的定义需要在脚本中可用
- 脚本应该只能执行某些函数(在示例中,只允许
public()
) - 我只想依赖基于 Python 的实现/库,因为我不想依赖 Python 外部软件
- 它不应引入安全风险
到目前为止,我发现的唯一方法是使用解析库,我必须自己定义所有内容(例如: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()
这几乎就是全部。