diff --git a/.versioneer-lookup b/.versioneer-lookup index 0e9ab0a09..dd26fce50 100644 --- a/.versioneer-lookup +++ b/.versioneer-lookup @@ -10,10 +10,10 @@ # master shall not use the lookup table, only tags master -# maintenance is currently the branch for preparation of maintenance release 1.2.1 +# maintenance is currently the branch for preparation of maintenance release 1.2.2 # so are any fix/... branches -maintenance 1.2.1-dev cfa4cb2a7c5f1af10dc8 -fix/.* 1.2.1-dev cfa4cb2a7c5f1af10dc8 +maintenance 1.2.2-dev 9f8d30a66c2fcc5cd0e8984c72dc36f7e84fde10 +fix/.* 1.2.2-dev 9f8d30a66c2fcc5cd0e8984c72dc36f7e84fde10 # every other branch is a development branch and thus gets resolved to 1.3.0-dev for now .* 1.3.0-dev 198d3450d94be1a2 diff --git a/CHANGELOG.md b/CHANGELOG.md index b565dcb17..f0c30fd64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # OctoPrint Changelog +## 1.2.2 (2015-06-30) + +### Bug Fixes + +* Fixed an admin-only security issue introduced in 1.2.0, updating is strongly advised. + +([Commits](https://github.com/foosel/OctoPrint/compare/1.2.1...1.2.2)) + ## 1.2.1 (2015-06-30) ### Improvements @@ -168,7 +176,7 @@ changed under "Temperatures" in the Settings ([#343](https://github.com/foosel/OctoPrint/issues/343)). * High-DPI support for the GCode viewer ([#837](https://github.com/foosel/OctoPrint/issues/837)). * Stop websocket connections from multiplying ([#888](https://github.com/foosel/OctoPrint/pull/888)). -* New setting to rotate webcam by 90° counter clockwise ([#895](https://github.com/foosel/OctoPrint/issues/895) and +* New setting to rotate webcam by 90° counter clockwise ([#895](https://github.com/foosel/OctoPrint/issues/895) and [#906](https://github.com/foosel/OctoPrint/pull/906)) * System commands now be set to a) run asynchronized by setting their `async` property to `true` and b) to ignore their result by setting their `ignore` property to `true`. diff --git a/src/octoprint/events.py b/src/octoprint/events.py index a26a48e8f..828862336 100644 --- a/src/octoprint/events.py +++ b/src/octoprint/events.py @@ -298,6 +298,9 @@ def _executeSystemCommand(self, command, debug=False): def commandExecutioner(command): if debug: self._logger.info("Executing system command: %s" % command) + # we run this with shell=True since we have to trust whatever + # our admin configured as command and since we want to allow + # shell-alike handling here... subprocess.Popen(command, shell=True) try: diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py index 6c4e039b0..58b662bb9 100644 --- a/src/octoprint/plugins/pluginmanager/__init__.py +++ b/src/octoprint/plugins/pluginmanager/__init__.py @@ -121,11 +121,17 @@ def upload_archive(self): upload_path = flask.request.values[input_upload_path] upload_name = flask.request.values[input_upload_name] + exts = filter(lambda x: upload_name.lower().endswith(x), (".zip", ".tar.gz", ".tgz", ".tar")) + if not len(exts): + return flask.make_response("File doesn't have a valid extension for a plugin archive", 400) + + ext = exts[0] + import tempfile import shutil import os - archive = tempfile.NamedTemporaryFile(delete=False, suffix="-{upload_name}".format(**locals())) + archive = tempfile.NamedTemporaryFile(delete=False, suffix="{ext}".format(**locals())) try: archive.close() shutil.copy(upload_path, archive.name) @@ -197,7 +203,7 @@ def command_install(self, url=None, path=None, force=False, reinstall=None): if url is not None: pip_args = ["install", sarge.shell_quote(url)] elif path is not None: - pip_args = ["install", path] + pip_args = ["install", sarge.shell_quote(path)] else: raise ValueError("Either url or path must be provided") diff --git a/src/octoprint/server/api/__init__.py b/src/octoprint/server/api/__init__.py index a979053a4..735155da0 100644 --- a/src/octoprint/server/api/__init__.py +++ b/src/octoprint/server/api/__init__.py @@ -156,12 +156,10 @@ def performSystemAction(): ignore = availableAction["ignore"] if "ignore" in availableAction else False logger.info("Performing command: %s" % availableAction["command"]) try: - # Note: we put the command in brackets since sarge (up to the most recently released version) has - # a bug concerning shell=True commands. Once sarge 0.1.4 we can upgrade to that and remove this - # workaround again - # - # See https://bitbucket.org/vinay.sajip/sarge/issue/21/behavior-is-not-like-popen-using-shell - p = sarge.run([availableAction["command"]], stderr=sarge.Capture(), shell=True, async=async) + # we run this with shell=True since we have to trust whatever + # our admin configured as command and since we want to allow + # shell-alike handling here... + p = sarge.run(availableAction["command"], stderr=sarge.Capture(), shell=True, async=async) if not async: if not ignore and p.returncode != 0: returncode = p.returncode diff --git a/src/octoprint/server/api/languages.py b/src/octoprint/server/api/languages.py index 820089e47..1d9c7e9c3 100644 --- a/src/octoprint/server/api/languages.py +++ b/src/octoprint/server/api/languages.py @@ -90,11 +90,17 @@ def load_meta(path, locale): def uploadLanguagePack(): input_name = "file" input_upload_path = input_name + "." + settings().get(["server", "uploads", "pathSuffix"]) - if not input_upload_path in request.values: + input_upload_name = input_name + "." + settings().get(["server", "uploads", "nameSuffix"]) + if not input_upload_path in request.values or not input_upload_name in request.values: return make_response("No file included", 400) + upload_name = request.values[input_upload_name] upload_path = request.values[input_upload_path] + exts = filter(lambda x: upload_name.lower().endswith(x), (".zip", ".tar.gz", ".tgz", ".tar")) + if not len(exts): + return make_response("File doesn't have a valid extension for a plugin archive", 400) + target_path = settings().getBaseFolder("translations") if tarfile.is_tarfile(upload_path): diff --git a/src/octoprint/util/pip.py b/src/octoprint/util/pip.py index f670edf71..7d3d1da19 100644 --- a/src/octoprint/util/pip.py +++ b/src/octoprint/util/pip.py @@ -65,11 +65,10 @@ def execute(self, *args): command = [self._command] + list(args) joined_command = " ".join(command) - self._logger.debug(u"Calling: {}".format(joined_command)) self.on_log_call(joined_command) - p = sarge.run(joined_command, shell=True, async=True, stdout=sarge.Capture(), stderr=sarge.Capture()) + p = sarge.run(command, async=True, stdout=sarge.Capture(), stderr=sarge.Capture()) p.wait_events() all_stdout = [] @@ -140,8 +139,7 @@ def _find_pip(self): if pip_command is not None: self._logger.debug("Found pip at {}, going to figure out its version".format(pip_command)) - command = [pip_command, "--version"] - p = sarge.run(" ".join(command), shell=True, stdout=sarge.Capture(), stderr=sarge.Capture()) + p = sarge.run([pip_command, "--version"], stdout=sarge.Capture(), stderr=sarge.Capture()) if p.returncode != 0: self._logger.warn("Error while trying to run pip --version: {}".format(p.stderr.text))