23andMe的Yamale Python代码注入,并正确地消毒eval()

背景
JFrog安全研究团队(前Vdoo)最近披露了一个代码注入问题Yamale,一个流行的模式验证器YAML它被超过200个存储库使用。这个问题已经解决了cve - 2021 - 38305.
注射问题
攻击者可以控制提供给Yamale的模式文件的内容(- s /——模式命令行参数),可以提供一个看似有效的模式文件,该文件将导致任意Python代码运行。请注意,模式文件是Yamale的两个强制参数之一(另一个是要验证的YAML文件)。
问题在于parser.parse功能:
safe_globals = ('True', 'False', 'None') safe_builtins = dict((f, __builtins__[f]) for f in safe_globals)…def parse(validator_string, validators=None): validators= validators或val.DefaultValidators try: tree = ast.parse(validator_string, mode='eval') # evaluate with access to a limited global scope only return eval(compile(tree, ", 'eval'), {'__builtins__': safe_builtins}, validators)…
在我们的例子中,validator_string来自模式文件的用户输入。
我们可以看到任意输入流向eval,通常可以对其进行操作以进行代码注入。
在这种情况下,eval被削弱了全局变量参数已删除,保存为真正的,假而且没有一个内置命令。这意味着攻击者不能轻易地运行恶意代码,因为像__import__ (os)系统(“evil_command”)会失败,因为__import__内置将不可用。然而,正如我们下面所解释的,漏洞仍然存在。
绕过eval保护
清空内置代码是否可以防止攻击者运行任意代码?
答案是没有,实际上,即使完全空的内置也无济于事。
潜在的问题是,通过Python反射,攻击者可以“取回”任何所需的内置程序并运行任意代码。
例如,下面的字符串将运行Python HTTP服务器,即使是空的内置命令:
[x x (1) .__class__.__base__.__subclasses__()如果x.__name__ = = ' catch_warnings '] [0] () ._module。__builtins__['打印']([x x (1) .__class__.__base__.__subclasses__()如果x.__name__ = = ' catch_warnings '] [0] () ._module.__builtins__(“__import__”)(“操作系统”)。系统(cd /;Python3 -m http.server'))
有很多那样关于这个主题,但简短的回答是-如果您传递完全未经处理的输入到eval(无论内置命令),那么你就容易受到任意代码注入的影响。
让我们看看它是如何使用的eval而不允许代码注入。
Yamale的修复和消毒eval()
- Yamale的维护者选择了这样做清除输入字符串然后再传给
eval通过一个whiltelist.
如果eval的字符串包含任何不在白名单上的子字符串,则操作失败。
这是一个完全可以接受的解决方案,只要白名单有足够的限制。
注意,我们不建议使用黑名单,因为攻击者通常可以找到一些值的组合来逃避黑名单同时仍然在执行一些恶意的操作。 - 如果可能,我们强烈建议使用
ast.literal_eval而不是eval.literal_eval只能处理简单的表达式,但对于许多简单的用例应该足够了,而不会暴露代码的任何漏洞。
我们能远程利用它吗?
如前所述,攻击者需要能够指定模式文件的内容,以便注入Python代码。
这可以被远程利用,如果某些供应商代码允许攻击者这样做,例如:
Subprocess.run (["yamale", "-s", remote_userinput, "/path/to/file_to_validate"])
但是,这种情况有点做作,可能不会出现在远程/网络上下文中的产品代码中。
更有可能的情况是利用这些问题(通过命令行参数触发的漏洞)通过单独的参数注入问题。
想象一下下面的供应商代码-
def run_yamale_fixed_schema(path: str): # Check for malicious shell metacharacters if re.search(r"[; ' $<>()|]*", path): # failed COMMAND INJECTION!运行Yamale子进程cmdstr = f" Yamale -s safe_schema. return #subprocess.run(cmdstr, shell=True)
虽然上面的代码将成功停止命令注入尝试,但事实是空格和-字符是允许的,可以允许攻击者注入任意标志。例如,想象下面的路径:
- s evil_schema。yaml /路径/ / file_to_validate
完整的cmdstr是:
Yamale -s safe_schema。Yaml -s evil_schema。yaml /路径/ / file_to_validate
注意,乘法定义- s参数将根据所使用的参数解析器(以及指定的解析选项)表现不同,但在Yamale的情况下(argparse使用默认选项)后者选项,这意味着攻击者可以意外地控制模式文件并远程执行代码注入攻击。
还要注意,在不允许使用空格字符的某些情况下,通过使用各种转义机制,甚至可以实现参数注入攻击
结论与鸣谢
综上所述,我们建议使用上述方法中的一种进行消毒eval,并尽可能避免使用eval完全通过将其替换为更特定的API来完成所需的任务。
我们要感谢Yamale的维护者,他们在记录时间内验证并修复了问题,并在修复版本可用后负责地创建了一个CVE。
问题吗?想法吗?与我们联络research@www.si-fil.com有关安全漏洞的任何查询。
除了研究和发布新软件之外安全漏洞,通过自动安全扫描,JFrog为开发人员和安全团队提供了方便地访问最新的相关漏洞信息。欲知详情-点击这里.