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)…defparse (validator_string, validators=None): validators= validators或val.DefaultValidators try: tree = ast.parse(validator_string, mode='eval') #在有限的全局范围内进行评估,只返回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): #检查恶意shell元字符如果re.search(r"[; ' $<>()|]*", path): #尝试命令注入!#运行Yamale子进程cmdstr = f" Yamale -s safe_schema。yaml {path}" subprocess.run(cmdstr, shell=True)
虽然上面的代码可以成功阻止命令注入的尝试,但是空格和-字符是允许的,可以允许攻击者注入任意标志。例如,想象下面的路径:
- s evil_schema。yaml /路径/ / file_to_validate
完整的cmdstr是:
s safe_schema。-s evil_schema。yaml /路径/ / file_to_validate
注意,多重定义- s根据所使用的参数解析器(以及指定的解析选项),parameter的行为会有所不同,但在Yamale的情况下(argparse使用默认选项)后者选项,这意味着攻击者可以意外地控制模式文件并远程执行代码注入攻击。
还要注意,参数注入攻击甚至可以在不允许使用空格字符的情况下通过使用各种转义机制来实现
结论和致谢
总之,我们建议使用上述方法之一进行消毒eval,并尽可能避免使用eval完全通过将其替换为针对所需任务的更具体的API来实现。
我们要感谢Yamale的维护者,他们在创纪录的时间内验证并修复了这个问题,并在修复版本可用后负责地为这个问题创建了一个CVE。
问题吗?想法吗?联络我们:research@m.si-fil.com有关安全漏洞的任何查询。
除了研究和发布新软件安全漏洞,通过自动安全扫描,JFrog为开发人员和安全团队提供了获取最新相关漏洞信息的便捷途径。欲知详情-点击这里.
