按条件加载模块 Python
我写了一个主要的Python模块,它需要加载一个文件解析器才能工作。最开始我只有一个文本解析器模块,但现在我需要为不同的情况添加更多的解析器。
parser_class1.py
parser_class2.py
parser_class3.py
每次运行时只需要其中一个解析器,所以我在想通过命令行来加载它:
mmain.py -p parser_class1
为此,我写了这段代码,以便在调用主模块时选择要加载的解析器:
#!/usr/bin/env python
import argparse
aparser = argparse.ArgumentParser()
aparser.add_argument('-p',
action='store',
dest='module',
help='-p module to import')
results = aparser.parse_args()
if not results.module:
aparser.error('Error! no module')
try:
exec("import %s" %(results.module))
print '%s imported done!'%(results.module)
except ImportError, e:
print e
但是,我在阅读时发现这种方式可能不太安全,也许不是标准做法……
那么,这种方法可以吗?还是我应该找其他方式来实现?为什么?谢谢,欢迎任何评论。
3 个回答
你应该考虑所有可能需要导入的模块,以便进行解析功能,然后使用一个选择语句或者字典来加载正确的模块。例如:
import parser_class1, parser_class2, parser_class3
parser_map = {
'class1': parser_class1,
'class2': parser_class2,
'class3': parser_class3,
}
if not args.module:
#report error
parser = None
else:
parser = parser_map[args.module]
#perform work with parser
如果在这个例子中加载任何一个 parser_classN 模块的成本很高,你可以定义一些简单的函数(比如 def get_class1(): import parser_class1; return parser_class1
),然后把那一行改成 parser = parser_map[args.module]()
。
使用 exec
这个选项可能非常危险,因为你是在执行未经验证的用户输入。想象一下,如果你的用户做了这样的事情 -
mmain.py -p "parser_class1; some_function_or_code_that_is_malicious()"
其实你可以在一个条件语句里执行import
这个命令:
if x:
import module1a as module1
else:
import module1b as module1
这样做可以让你以不同的方式处理各种允许的模块导入,基本上就是提前设置好要导入的模块,然后用一种类似于跳转的方式来导入合适的模块……如果你想让用户随便导入任何模块,那么可以使用__import__
这个函数,而不是eval
。
更新:
正如@thedox在评论中提到的,as module1
这一部分是加载不同底层代码但功能相似的API的常用方式。如果你打算用完全不同的API做完全不同的事情,那就不应该这样做。在这种情况下,更合理的做法是把与特定导入相关的代码放在那个导入语句旁边:
if ...:
import module1
# do some stuff with module1 ...
else:
import module2
# do some stuff with module2 ...
至于安全性,如果你允许用户导入一些任意的代码集(比如他们自己的模块),这和对用户输入使用eval
其实没什么区别。基本上都是同样的漏洞:用户可以让你的程序执行他们自己的代码。
我认为没有一种真正安全的方法可以让用户导入任意模块。唯一的例外是如果他们没有访问文件系统的权限,因此无法创建新的代码被导入。在这种情况下,你基本上又回到了允许列表的情况,最好还是实现一个明确的允许列表,以防将来用户获得文件系统访问权限时出现新的漏洞。
下面是如何使用 __import__()
的介绍。
allowed_modules = ['os', 're', 'your_module', 'parser_class1.py', 'parser_class2.py']
if not results.module:
aparser.error('Error! no module')
try:
if results.module in allowed_modules:
module = __import__(results.module)
print '%s imported as "module"'%(results.module)
else:
print 'hey what are you trying to do?'
except ImportError, e:
print e
module.your_function(your_data)
EVAL
和 __IMPORT__()
的区别
使用 eval
允许用户在你的电脑上运行任何代码。这是非常危险的,千万不要这样做。而 __import__()
只允许用户加载模块,表面上看起来不允许用户随意运行代码。但实际上,它的安全性也只是表面上的。
如果没有 allowed_modules
的保护,提议的函数仍然存在风险,因为它可能允许加载一个任意的模块,而这个模块可能包含一些恶意代码。一旦加载,攻击者可能会在某个地方(比如共享文件夹、FTP 文件夹、你的网站服务器管理的上传文件夹等)放置一个文件,然后通过你的参数调用它。
白名单
使用 allowed_modules
可以减轻这个问题,但并不能完全解决:为了进一步增强安全性,你还需要检查攻击者是否在你的脚本文件夹中写了 "os.py"、"re.py"、"your_module.py"、"parser_class1.py" 等文件,因为 Python 首先会在这里搜索模块(文档)。
最终,你可以将 parser_class*.py 的代码与一个哈希列表进行比较,就像 sha1sum 所做的那样。
最后的提醒:如果用户对你的脚本文件夹有写入权限,那么你无法确保代码绝对安全。