From 19491bf00146b7b8513f9109f0a94419b353167d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Mar=C3=A9chal?= Date: Wed, 11 Mar 2020 18:40:46 -0400 Subject: [PATCH] plugin-ext: validate path when unpacking archives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix zip-slip by validating where a given file will be unpacked. If the expected path is outside of the destination folder: log a warning and ignore the file. Fixes https://github.com/eclipse-theia/theia/issues/7319 Signed-off-by: Paul Maréchal --- packages/plugin-ext/package.json | 4 +- ...deployer-file-handler-context-impl.spec.ts | 69 ++++++++++++++++++ ...ugin-deployer-file-handler-context-impl.ts | 28 ++++++- .../src/main/node/test-data/slip.tar.gz | Bin 0 -> 210 bytes 4 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 packages/plugin-ext/src/main/node/plugin-deployer-file-handler-context-impl.spec.ts create mode 100644 packages/plugin-ext/src/main/node/test-data/slip.tar.gz diff --git a/packages/plugin-ext/package.json b/packages/plugin-ext/package.json index 688aa324f796a..6e4c0645b27dc 100644 --- a/packages/plugin-ext/package.json +++ b/packages/plugin-ext/package.json @@ -81,7 +81,9 @@ "@types/escape-html": "^0.0.20", "@types/lodash.clonedeep": "^4.5.3", "@types/ps-tree": "^1.1.0", - "@types/request": "^2.0.3" + "@types/request": "^2.0.3", + "chai": "^4.2.0", + "rimraf": "^2.6.1" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/plugin-ext/src/main/node/plugin-deployer-file-handler-context-impl.spec.ts b/packages/plugin-ext/src/main/node/plugin-deployer-file-handler-context-impl.spec.ts new file mode 100644 index 0000000000000..02281fd2947be --- /dev/null +++ b/packages/plugin-ext/src/main/node/plugin-deployer-file-handler-context-impl.spec.ts @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (C) 2020 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* eslint-disable no-unused-expressions */ + +import * as fs from 'fs'; +import * as path from 'path'; +import rimraf = require('rimraf'); +import { expect } from 'chai'; +import { PluginDeployerFileHandlerContextImpl } from './plugin-deployer-file-handler-context-impl'; + +const testDataPath = path.join(__dirname, '../../../src/main/node/test-data'); + +describe('PluginDeployerFileHandlerContextImpl', () => { + + /** + * Clean resources after a test. + */ + const finalizers: Array<() => void> = []; + + beforeEach(() => { + finalizers.length = 0; + }); + + afterEach(() => { + for (const finalize of finalizers) { + try { + finalize(); + } catch (error) { + console.error(error); + } + } + }); + + it('should prevent zip-slip', async function (): Promise { + if (process.platform === 'win32') { + this.skip(); // Test will not work on Windows (because of the /tmp path) + } + + const dest = fs.mkdtempSync('/tmp/plugin-ext-test'); + finalizers.push(() => rimraf.sync(dest)); + + const zipSlipArchivePath = path.join(testDataPath, 'slip.tar.gz'); + const slippedFilePath = '/tmp/slipped.txt'; + + finalizers.push(() => rimraf.sync(slippedFilePath)); + rimraf.sync(slippedFilePath); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const pluginDeployerFileHandlerContext = new PluginDeployerFileHandlerContextImpl(undefined as any); + await pluginDeployerFileHandlerContext.unzip(zipSlipArchivePath, dest); + + expect(fs.existsSync(slippedFilePath)).false; + }); + +}); diff --git a/packages/plugin-ext/src/main/node/plugin-deployer-file-handler-context-impl.ts b/packages/plugin-ext/src/main/node/plugin-deployer-file-handler-context-impl.ts index d8b469f44c676..0ca46f0438c19 100644 --- a/packages/plugin-ext/src/main/node/plugin-deployer-file-handler-context-impl.ts +++ b/packages/plugin-ext/src/main/node/plugin-deployer-file-handler-context-impl.ts @@ -14,6 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import * as path from 'path'; +import { promises as fs } from 'fs'; import { PluginDeployerEntry, PluginDeployerFileHandlerContext } from '../../common/plugin-protocol'; import * as decompress from 'decompress'; @@ -24,8 +26,30 @@ export class PluginDeployerFileHandlerContextImpl implements PluginDeployerFileH } async unzip(sourcePath: string, destPath: string): Promise { - await decompress(sourcePath, destPath); - return Promise.resolve(); + const zipSlipFiles = new Set<[string, string]>(); + const absoluteDestPath = await fs.realpath(destPath); + await decompress(sourcePath, absoluteDestPath, { + /** + * Prevent zip-slip: https://snyk.io/research/zip-slip-vulnerability + */ + filter(file: decompress.File): boolean { + const expectedFilePath = path.join(absoluteDestPath, file.path); + // If dest is not found in the expected path, it means file will be unpacked somewhere else. + if (!expectedFilePath.startsWith(path.join(absoluteDestPath, path.sep))) { + zipSlipFiles.add([file.path, expectedFilePath]); + return false; // only skip the exploit files, maybe the rest is fine. + } else { + return true; + } + } + }); + if (zipSlipFiles.size > 0) { + console.error(`Detected a zip-slip exploit in archive: "${sourcePath}"`); + for (const [relativePath, expectedPath] of zipSlipFiles) { + console.error(` - File "${relativePath}" was going to write to: "${expectedPath}"`); + } + console.error('See: https://snyk.io/research/zip-slip-vulnerability'); + } } pluginEntry(): PluginDeployerEntry { diff --git a/packages/plugin-ext/src/main/node/test-data/slip.tar.gz b/packages/plugin-ext/src/main/node/test-data/slip.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..68369b16bb4fed513050fdd2a46252281c79308d GIT binary patch literal 210 zcmb2|=HQs{Xj&}O|J+L5;+)I^y^_QthPRjZavd@dX?u7k&UKxF@1-TNq9Qsa8aGb* z2Dtpu-q!Tx)lsn{N9Q|oPyPPzfaRPN&j-PcTx~BuZtyd2U;BgiZq>D|-FIvAz8#Cm zZuN0xKbN}I!t>`>_irshj9(K&e=BK}&t4Rv8(O;Zi2aL4&+{rf7O6c}`}S+&_BDcP zU(Qv_OUZNPt}Tx9xSRT~zjSZ(-W}iH?OFbFzWU5F%NNI-yQFej?QtUm5^$Iw!#H8x K^3Mz!3=9CfYh?WZ literal 0 HcmV?d00001