From 1095d2466d80ec00737cb69beb49eda777697e88 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sun, 3 Dec 2023 00:17:36 +0100 Subject: [PATCH] Fix path traversal on mod upload Fix possibility of a maliciously crafted mod being written outside of the mods directory and thus possibily overwriting unrelated files. --- packages/controller/src/routes.ts | 1 + packages/lib/build_mod.js | 4 ++-- test/file/path_traversing_mod/info.json | 9 +++++++++ test/integration/clusterio.js | 15 +++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 test/file/path_traversing_mod/info.json diff --git a/packages/controller/src/routes.ts b/packages/controller/src/routes.ts index 8abc77ec..8875bcd3 100644 --- a/packages/controller/src/routes.ts +++ b/packages/controller/src/routes.ts @@ -559,6 +559,7 @@ async function uploadMod(req: Request, res: Response) { await finished(writeStream); const modInfo = await lib.ModInfo.fromModFile(path.join(modsDirectory, tempFilename)); + checkModName(modInfo.filename); await fs.rename(path.join(modsDirectory, tempFilename), path.join(modsDirectory, modInfo.filename)); req.app.locals.controller.mods.set(modInfo.filename, modInfo); req.app.locals.controller.modUpdated(modInfo); diff --git a/packages/lib/build_mod.js b/packages/lib/build_mod.js index 29dc39cf..eee00d8d 100644 --- a/packages/lib/build_mod.js +++ b/packages/lib/build_mod.js @@ -12,10 +12,10 @@ const yargs = require("yargs"); const finished = util.promisify(stream.finished); -async function buildMod(args, info) { +async function buildMod(args, info, modName) { if (args.build) { await fs.ensureDir(args.outputDir); - let modName = `${info.name}_${info.version}`; + modName = args.modName ?? `${info.name}_${info.version}`; if (args.pack) { let zip = new JSZip(); diff --git a/test/file/path_traversing_mod/info.json b/test/file/path_traversing_mod/info.json new file mode 100644 index 00000000..941e2ef7 --- /dev/null +++ b/test/file/path_traversing_mod/info.json @@ -0,0 +1,9 @@ +{ + "name": "../bad_path_mod", + "version": "1.0.0", + "title": "Path Traversing Mod", + "author": "Me", + "description": "A mod who's name traverses the filesystem", + "factorio_version": "1.1", + "dependencies": [] +} diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index af982cea..b076b439 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -934,6 +934,21 @@ describe("Integration of Clusterio", function() { "mod not present in mods directory" ); }); + it("rejects path traversing mods", async function() { + await libBuildMod.build({ + build: true, + pack: true, + sourceDir: path.join("test", "file", "path_traversing_mod"), + outputDir: path.join("temp", "test"), + modName: "path_traversing_mod_1.0.0", + }); + await fs.remove(path.join("temp", "test", "bad_path_mod_1.0.0.zip")); + await assert.rejects(execCtl("mod upload path_traversing_mod_1.0.0.zip")); + assert( + !await fs.pathExists(path.join("temp", "test", "bad_path_mod_1.0.0.zip")), + "mod executed a path traversal attack", + ); + }); }); describe("mod show", function() {