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)

Reply via email to