
CVE-2023-29478 - Exploit de Manipulación de Archivos/Ejecución Remota de Código en BiblioCraft que afecta a versiones anteriores a la v2.4.6
¡Este método solo requiere BiblioCraft! No demasiado trucos y no se necesitan otros mods para lograr la ejecución de código.
El informe original centrado en CoreTweaks se puede encontrar aquí. Recomiendo leer esto primero, ya que puedo omitir algunos detalles que serán importantes a continuación.
La ejecución de código es posible mediante múltiples métodos. Esto afecta a BiblioCraft 1.7.10 v1.11.7 y BiblioCraft 1.12.2 v2.4.5 (confirmado) y probablemente a todas las versiones de BiblioCraft anteriores a v2.4.6 (no confirmado, no probado).
Los parches existentes que corrigen el error de path traversal aún evitarán esta nueva ruta de ejecución de código.
Esta vez nos centramos en el guardado de libros escritos de Vanilla.
Los libros escritos se guardan en world/books/<author-name>, <book-title>.
El formato de estos libros es, por línea:
Continuando desde el inicio, las páginas del libro comienzan con un marcador #pgx<page-number> y desde la siguiente línea hasta el siguiente marcador es parte de esa página. BiblioCraft siempre agregará una nueva línea al final de las líneas, incluso si no hay nada después.
Como antes, podemos controlar tanto el título del libro como el nombre del autor a través de la etiqueta NBT del libro, así como realizar path traversal.
El lugar donde vamos a escribir es la carpeta mods/. Los mods en este directorio se almacenan con mayor frecuencia como archivos JAR. Una propiedad interesante de los archivos JAR es que en realidad son solo archivos ZIP disfrazados.
Entonces, ¿por qué nos ayuda eso?
Los archivos ZIP tienen una propiedad interesante: pueden seguir siendo válidos incluso si se les antepone o añade datos basura.
El registro EOCD (End of Central Directory) dentro de un archivo ZIP se coloca al final.
Consiste (aproximadamente) en una mágica/firma 0x06054b50, así como el número, tamaño y desplazamiento de los registros del directorio central dentro del archivo.
El último campo en un registro EOCD es un comentario con prefijo de longitud, que puede ser casi cualquier secuencia de bytes que no sea la mágica (no probado).
(Creo que los analizadores de archivos ZIP comienzan desde el final y buscan la firma EOCD para analizar el archivo, pero no estoy completamente seguro).
Si todo lo que el analizador ZIP necesita para encontrar los archivos dentro del archivo es el registro EOCD, y si cualquier dato (con limitaciones) puede seguir al registro EOCD, entonces podemos hacer un ZIP válido incluso si hay otros datos dentro del archivo.
(¡Mucho crédito a https://github.com/c0ny1/ascii-jar! ¡Usé estas herramientas intensamente!)
Primero, necesitamos estructurar nuestro payload. Forge cargará las clases como mods si tienen la anotación especial @Mod, así que la usaremos aquí.
Nuestro payload aproximado:
@Mod(modid = "payload-mod")
public class Payload {
// ...
static {
System.out.println("Hello World!");
}
// ...
}
Para evitar problemas de codificación, usamos un script para crear un archivo JAR solo ASCII que contenga solo nuestra clase payload.
Rellenamos el principio con PK\3\4.jar\n../../mods/\nprivate\n#pgx0\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
La razón por la que anteponemos PK\3\4 (la firma del encabezado de archivo local de ZIP) al relleno se debe a una extraña peculiaridad en Forge que no pude reproducir fuera de donde se verifica que los archivos JAR comiencen con esta firma, a pesar de que esto no es un requisito de ZIP. ¿Puede ser un error en Forge?
Usamos la larga cadena de 'A's y 'a's para asegurarnos de que el desplazamiento sea mayor que 255, garantizando que el entero de 2 bytes que codifica el desplazamiento no tenga bytes fuera del rango ASCII.
Rellenamos el final con \n para evitar problemas con las nuevas líneas añadidas por BiblioCraft.
Con nuestro nuevo JAR rellenado, tomamos todos los bytes que siguen a la última nueva línea de los datos que rellenamos al principio y creamos una nueva cadena (la llamaremos <jar-data>) y creamos un nuevo libro escrito con datos NBT:
TAG_Compound(''): 2 entries
{
TAG_String("author"): "../../mods/"
TAG_String("title"): "PK\3\4.jar"
TAG_List("pages"): 1 entry
{
TAG_String(None): "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + <jar-data>
}
}
Cuando guardamos este libro en el disco a través de BiblioCraft, todos los datos de relleno serán "regenerados" por su formato, haciendo que nuestro JAR sea válido nuevamente.
Y ahora, la próxima vez que el servidor se reinicie (incluso un reinicio normal, o si provocas un crash) nuestro JAR de mod se cargará y nuestro código se ejecutará.
En BiblioPOC/tools/ hay un script de Python3 para ayudar a generar un payload válido.
Los argumentos son:
python3 create_payload.py [java_file] [forge_jar_location] [class_name] [output_dir]
Luego, en el juego mientras sostienes un atlas de BiblioCraft, ejecuta /jarpoccommand [completed_jar_path] donde [completed_jar_path] es la ruta a [output_dir]/completed.jar.