On 24/04/20 13:35 +0200, Robert Nagy wrote: > On 24/04/20 12:15 +0100, Raf Czlonka wrote: > > Hi all, > > > > Really bad timing but it looks like Salt master in OpenBSD will be > > vulnerable for at least half a year more, unless someone backports > > the fix, which is to be released next week, to version 2018.3. > > > > https://groups.google.com/forum/#!topic/salt-users/zjwt44a919U > > > > Regards, > > > > Raf
Please give this a throughout testing. Index: Makefile =================================================================== RCS file: /cvs/ports/sysutils/salt/Makefile,v retrieving revision 1.137 diff -u -p -u -r1.137 Makefile --- Makefile 18 Apr 2020 09:22:32 -0000 1.137 +++ Makefile 30 Apr 2020 05:17:01 -0000 @@ -19,7 +19,7 @@ COMMENT = remote execution and configur MODPY_EGG_VERSION = 2018.3.3 DISTNAME = salt-${MODPY_EGG_VERSION} -REVISION = 1 +REVISION = 2 CATEGORIES = sysutils net devel Index: patches/patch-salt_master_py =================================================================== RCS file: patches/patch-salt_master_py diff -N patches/patch-salt_master_py --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-salt_master_py 30 Apr 2020 05:17:01 -0000 @@ -0,0 +1,110 @@ +$OpenBSD$ + +commit f47e4856497231eb672da2ce0df3e641581d47e6 +Author: Daniel A. Wozniak <dwozn...@saltstack.com> +Date: Mon Apr 13 06:41:04 2020 +0000 + + Fix CVE-2020-11651 + + Resolve issue which allows access to un-intended methods in the + ClearFuncs class of the salt-master process + +Index: salt/master.py +--- salt/master.py.orig ++++ salt/master.py +@@ -1045,12 +1045,13 @@ class MWorker(salt.utils.process.SignalHandlingMultipr + ''' + log.trace('Clear payload received with command %s', load['cmd']) + cmd = load['cmd'] +- if cmd.startswith('__'): +- return False ++ method = self.clear_funcs.get_method(cmd) ++ if not method: ++ return {}, {'fun': 'send_clear'} + if self.opts['master_stats']: + start = time.time() + self.stats[cmd]['runs'] += 1 +- ret = getattr(self.clear_funcs, cmd)(load), {'fun': 'send_clear'} ++ ret = method(load), {'fun': 'send_clear'} + if self.opts['master_stats']: + self._post_stats(start, cmd) + return ret +@@ -1068,8 +1069,9 @@ class MWorker(salt.utils.process.SignalHandlingMultipr + return {} + cmd = data['cmd'] + log.trace('AES payload received with command %s', data['cmd']) +- if cmd.startswith('__'): +- return False ++ method = self.aes_funcs.get_method(cmd) ++ if not method: ++ return {}, {'fun': 'send'} + if self.opts['master_stats']: + start = time.time() + self.stats[cmd]['runs'] += 1 +@@ -1092,13 +1094,43 @@ class MWorker(salt.utils.process.SignalHandlingMultipr + self.__bind() + + ++class TransportMethods(object): ++ ''' ++ Expose methods to the transport layer, methods with their names found in ++ the class attribute 'expose_methods' will be exposed to the transport layer ++ via 'get_method'. ++ ''' ++ ++ expose_methods = () ++ ++ def get_method(self, name): ++ ''' ++ Get a method which should be exposed to the transport layer ++ ''' ++ if name in self.expose_methods: ++ try: ++ return getattr(self, name) ++ except AttributeError: ++ log.error("Expose method not found: %s", name) ++ else: ++ log.error("Requested method not exposed: %s", name) ++ + # TODO: rename? No longer tied to "AES", just "encrypted" or "private" requests +-class AESFuncs(object): ++class AESFuncs(TransportMethods): + ''' + Set up functions that are available when the load is encrypted with AES + ''' +- # The AES Functions: +- # ++ ++ expose_methods = ( ++ 'verify_minion', '_master_tops', '_ext_nodes', '_master_opts', ++ '_mine_get', '_mine', '_mine_delete', '_mine_flush', '_file_recv', ++ '_pillar', '_minion_event', '_handle_minion_event', '_return', ++ '_syndic_return', '_minion_runner', 'pub_ret', 'minion_pub', ++ 'minion_publish', 'revoke_auth', 'run_func', '_serve_file', ++ '_file_find', '_file_hash', '_file_find_and_stat', '_file_list', ++ '_file_list_emptydirs', '_dir_list', '_symlink_list', '_file_envs', ++ ) ++ + def __init__(self, opts): + ''' + Create a new AESFuncs +@@ -1812,11 +1844,18 @@ class AESFuncs(object): + return ret, {'fun': 'send'} + + +-class ClearFuncs(object): ++class ClearFuncs(TransportMethods): + ''' + Set up functions that are safe to execute when commands sent to the master + without encryption and authentication + ''' ++ ++ # These methods will be exposed to the transport layer by ++ # MWorker._handle_clear ++ expose_methods = ( ++ 'ping', 'publish', 'get_token', 'mk_token', 'wheel', 'runner', ++ ) ++ + # The ClearFuncs object encapsulates the functions that can be executed in + # the clear: + # publish (The publish from the LocalClient) Index: patches/patch-salt_tokens_localfs_py =================================================================== RCS file: patches/patch-salt_tokens_localfs_py diff -N patches/patch-salt_tokens_localfs_py --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-salt_tokens_localfs_py 30 Apr 2020 05:17:01 -0000 @@ -0,0 +1,31 @@ +$OpenBSD$ + +commit 7bd0ab195fbec4f34523dad11149f741c154e2b7 +Author: Daniel A. Wozniak <dwozn...@saltstack.com> +Date: Mon Apr 13 06:44:58 2020 +0000 + + Fix CVE-2020-11652 + + Sanitize paths in ClearFuncs methods provided by salt-master. This + ensures we do not allow access to un-intended files and directories. + +Index: salt/tokens/localfs.py +--- salt/tokens/localfs.py.orig ++++ salt/tokens/localfs.py +@@ -12,6 +12,7 @@ import logging + + import salt.utils.files + import salt.utils.path ++import salt.utils.verify + import salt.payload + + from salt.ext import six +@@ -34,6 +35,8 @@ def mk_token(opts, tdata): + hash_type = getattr(hashlib, opts.get('hash_type', 'md5')) + tok = six.text_type(hash_type(os.urandom(512)).hexdigest()) + t_path = os.path.join(opts['token_dir'], tok) ++ if not salt.utils.verify.clean_path(opts['token_dir'], t_path): ++ return {} + while os.path.isfile(t_path): + tok = six.text_type(hash_type(os.urandom(512)).hexdigest()) + t_path = os.path.join(opts['token_dir'], tok) Index: patches/patch-salt_utils_verify_py =================================================================== RCS file: patches/patch-salt_utils_verify_py diff -N patches/patch-salt_utils_verify_py --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-salt_utils_verify_py 30 Apr 2020 05:17:01 -0000 @@ -0,0 +1,97 @@ +$OpenBSD$ + +commit 7bd0ab195fbec4f34523dad11149f741c154e2b7 +Author: Daniel A. Wozniak <dwozn...@saltstack.com> +Date: Mon Apr 13 06:44:58 2020 +0000 + + Fix CVE-2020-11652 + + Sanitize paths in ClearFuncs methods provided by salt-master. This + ensures we do not allow access to un-intended files and directories. + +Index: salt/utils/verify.py +--- salt/utils/verify.py.orig ++++ salt/utils/verify.py +@@ -31,6 +31,7 @@ import salt.utils.files + import salt.utils.path + import salt.utils.platform + import salt.utils.user ++import salt.ext.six + + log = logging.getLogger(__name__) + +@@ -472,23 +473,69 @@ def check_max_open_files(opts): + log.log(level=level, msg=msg) + + ++def _realpath_darwin(path): ++ base = '' ++ for part in path.split(os.path.sep)[1:]: ++ if base != '': ++ if os.path.islink(os.path.sep.join([base, part])): ++ base = os.readlink(os.path.sep.join([base, part])) ++ else: ++ base = os.path.abspath(os.path.sep.join([base, part])) ++ else: ++ base = os.path.abspath(os.path.sep.join([base, part])) ++ return base ++ ++ ++def _realpath_windows(path): ++ base = '' ++ for part in path.split(os.path.sep): ++ if base != '': ++ try: ++ part = os.readlink(os.path.sep.join([base, part])) ++ base = os.path.abspath(part) ++ except OSError: ++ base = os.path.abspath(os.path.sep.join([base, part])) ++ else: ++ base = part ++ return base ++ ++ ++def _realpath(path): ++ ''' ++ Cross platform realpath method. On Windows when python 3, this method ++ uses the os.readlink method to resolve any filesystem links. On Windows ++ when python 2, this method is a no-op. All other platforms and version use ++ os.realpath ++ ''' ++ if salt.utils.platform.is_darwin(): ++ return _realpath_darwin(path) ++ elif salt.utils.platform.is_windows(): ++ if salt.ext.six.PY3: ++ return _realpath_windows(path) ++ else: ++ return path ++ return os.path.realpath(path) ++ ++ + def clean_path(root, path, subdir=False): + ''' + Accepts the root the path needs to be under and verifies that the path is + under said root. Pass in subdir=True if the path can result in a + subdirectory of the root instead of having to reside directly in the root + ''' +- if not os.path.isabs(root): ++ real_root = _realpath(root) ++ if not os.path.isabs(real_root): + return '' + if not os.path.isabs(path): + path = os.path.join(root, path) + path = os.path.normpath(path) ++ real_path = _realpath(path) + if subdir: +- if path.startswith(root): +- return path ++ if real_path.startswith(real_root): ++ return real_path + else: +- if os.path.dirname(path) == os.path.normpath(root): +- return path ++ if os.path.dirname(real_path) == os.path.normpath(real_root): ++ return real_path + return '' + + Index: patches/patch-salt_wheel_config_py =================================================================== RCS file: patches/patch-salt_wheel_config_py diff -N patches/patch-salt_wheel_config_py --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-salt_wheel_config_py 30 Apr 2020 05:17:01 -0000 @@ -0,0 +1,35 @@ +$OpenBSD$ + +commit 7bd0ab195fbec4f34523dad11149f741c154e2b7 +Author: Daniel A. Wozniak <dwozn...@saltstack.com> +Date: Mon Apr 13 06:44:58 2020 +0000 + + Fix CVE-2020-11652 + + Sanitize paths in ClearFuncs methods provided by salt-master. This + ensures we do not allow access to un-intended files and directories. + +Index: salt/wheel/config.py +--- salt/wheel/config.py.orig ++++ salt/wheel/config.py +@@ -75,13 +75,19 @@ def update_config(file_name, yaml_contents): + dir_path = os.path.join(__opts__['config_dir'], + os.path.dirname(__opts__['default_include'])) + try: +- yaml_out = salt.utils.yaml.safe_dump(yaml_contents, default_flow_style=False) ++ yaml_out = salt.utils.yaml.safe_dump( ++ yaml_contents, ++ default_flow_style=False, ++ ) + + if not os.path.exists(dir_path): + log.debug('Creating directory %s', dir_path) + os.makedirs(dir_path, 0o755) + + file_path = os.path.join(dir_path, file_name) ++ if not salt.utils.verify.clean_path(dir_path, file_path): ++ return 'Invalid path' ++ + with salt.utils.files.fopen(file_path, 'w') as fp_: + fp_.write(yaml_out) + Index: patches/patch-salt_wheel_file_roots_py =================================================================== RCS file: patches/patch-salt_wheel_file_roots_py diff -N patches/patch-salt_wheel_file_roots_py --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-salt_wheel_file_roots_py 30 Apr 2020 05:17:01 -0000 @@ -0,0 +1,35 @@ +$OpenBSD$ + +commit 7bd0ab195fbec4f34523dad11149f741c154e2b7 +Author: Daniel A. Wozniak <dwozn...@saltstack.com> +Date: Mon Apr 13 06:44:58 2020 +0000 + + Fix CVE-2020-11652 + + Sanitize paths in ClearFuncs methods provided by salt-master. This + ensures we do not allow access to un-intended files and directories. + +Index: salt/wheel/file_roots.py +--- salt/wheel/file_roots.py.orig ++++ salt/wheel/file_roots.py +@@ -25,6 +25,8 @@ def find(path, saltenv='base'): + return ret + for root in __opts__['file_roots'][saltenv]: + full = os.path.join(root, path) ++ if not salt.utils.verify.clean_path(root, full): ++ continue + if os.path.isfile(full): + # Add it to the dict + with salt.utils.files.fopen(full, 'rb') as fp_: +@@ -107,7 +109,10 @@ def write(data, path, saltenv='base', index=0): + if os.path.isabs(path): + return ('The path passed in {0} is not relative to the environment ' + '{1}').format(path, saltenv) +- dest = os.path.join(__opts__['file_roots'][saltenv][index], path) ++ root = __opts__['file_roots'][saltenv][index] ++ dest = os.path.join(root, path) ++ if not salt.utils.verify.clean_path(root, dest, subdir=True): ++ return 'Invalid path: {}'.format(path) + dest_dir = os.path.dirname(dest) + if not os.path.isdir(dest_dir): + os.makedirs(dest_dir)