b58328
From 84317f86f9c96805cf365784794142e65cfbb310 Mon Sep 17 00:00:00 2001
b58328
From: Rob Crittenden <rcritten@redhat.com>
b58328
Date: Tue, 2 Jul 2019 13:44:48 -0400
b58328
Subject: [PATCH] CVE-2019-10195: Don't log passwords embedded in commands in
b58328
 calls using batch
b58328
b58328
A raw batch request was fully logged which could expose parameters
b58328
we don't want logged, like passwords.
b58328
b58328
Override _repr_iter to use the individual commands to log the
b58328
values so that values are properly obscured.
b58328
b58328
In case of errors log the full value on when the server is in
b58328
debug mode.
b58328
b58328
Reported by Jamison Bennett from Cloudera
b58328
b58328
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
b58328
Reviewed-by:  Florence Blanc-Renaud <frenaud@redhat.com>
b58328
---
b58328
 ipaserver/plugins/batch.py | 96 ++++++++++++++++++++++++++++----------
b58328
 1 file changed, 72 insertions(+), 24 deletions(-)
b58328
b58328
diff --git a/ipaserver/plugins/batch.py b/ipaserver/plugins/batch.py
b58328
index 2794db895a014a6129654889289815d4286cf7f4..9df367d16234d1840a2e5297cdd5c3c59fa4828f 100644
b58328
--- a/ipaserver/plugins/batch.py
b58328
+++ b/ipaserver/plugins/batch.py
b58328
@@ -92,35 +92,82 @@ class batch(Command):
b58328
         Output('results', (list, tuple), doc='')
b58328
     )
b58328
 
b58328
+    def _validate_request(self, request):
b58328
+        """
b58328
+        Check that an individual request in a batch is parseable and the
b58328
+        commands exists.
b58328
+        """
b58328
+        if 'method' not in request:
b58328
+            raise errors.RequirementError(name='method')
b58328
+        if 'params' not in request:
b58328
+            raise errors.RequirementError(name='params')
b58328
+        name = request['method']
b58328
+        if (name not in self.api.Command or
b58328
+                isinstance(self.api.Command[name], Local)):
b58328
+            raise errors.CommandError(name=name)
b58328
+
b58328
+        # If params are not formated as a tuple(list, dict)
b58328
+        # the following lines will raise an exception
b58328
+        # that triggers an internal server error
b58328
+        # Raise a ConversionError instead to report the issue
b58328
+        # to the client
b58328
+        try:
b58328
+            a, kw = request['params']
b58328
+            newkw = dict((str(k), v) for k, v in kw.items())
b58328
+            api.Command[name].args_options_2_params(*a, **newkw)
b58328
+        except (AttributeError, ValueError, TypeError):
b58328
+            raise errors.ConversionError(
b58328
+                name='params',
b58328
+                error=_(u'must contain a tuple (list, dict)'))
b58328
+        except Exception as e:
b58328
+            raise errors.ConversionError(
b58328
+                name='params',
b58328
+                error=str(e))
b58328
+
b58328
+    def _repr_iter(self, **params):
b58328
+        """
b58328
+        Iterate through the request and use the Command _repr_intr so
b58328
+        that sensitive information (passwords) is not exposed.
b58328
+
b58328
+        In case of a malformatted request redact the entire thing.
b58328
+        """
b58328
+        exceptions = False
b58328
+        for arg in (params.get('methods', [])):
b58328
+            try:
b58328
+                self._validate_request(arg)
b58328
+            except Exception:
b58328
+                # redact the whole request since we don't know what's in it
b58328
+                exceptions = True
b58328
+                yield u'********'
b58328
+                continue
b58328
+
b58328
+            name = arg['method']
b58328
+            a, kw = arg['params']
b58328
+            newkw = dict((str(k), v) for k, v in kw.items())
b58328
+            param = api.Command[name].args_options_2_params(
b58328
+                *a, **newkw)
b58328
+
b58328
+            yield '{}({})'.format(
b58328
+                api.Command[name].name,
b58328
+                ', '.join(api.Command[name]._repr_iter(**param))
b58328
+            )
b58328
+
b58328
+        if exceptions:
b58328
+            logger.debug('batch: %s',
b58328
+                         ', '.join(super(batch, self)._repr_iter(**params)))
b58328
+
b58328
     def execute(self, methods=None, **options):
b58328
         results = []
b58328
         for arg in (methods or []):
b58328
             params = dict()
b58328
             name = None
b58328
             try:
b58328
-                if 'method' not in arg:
b58328
-                    raise errors.RequirementError(name='method')
b58328
-                if 'params' not in arg:
b58328
-                    raise errors.RequirementError(name='params')
b58328
+                self._validate_request(arg)
b58328
                 name = arg['method']
b58328
-                if (name not in self.api.Command or
b58328
-                        isinstance(self.api.Command[name], Local)):
b58328
-                    raise errors.CommandError(name=name)
b58328
-
b58328
-                # If params are not formated as a tuple(list, dict)
b58328
-                # the following lines will raise an exception
b58328
-                # that triggers an internal server error
b58328
-                # Raise a ConversionError instead to report the issue
b58328
-                # to the client
b58328
-                try:
b58328
-                    a, kw = arg['params']
b58328
-                    newkw = dict((str(k), v) for k, v in kw.items())
b58328
-                    params = api.Command[name].args_options_2_params(
b58328
-                        *a, **newkw)
b58328
-                except (AttributeError, ValueError, TypeError):
b58328
-                    raise errors.ConversionError(
b58328
-                        name='params',
b58328
-                        error=_(u'must contain a tuple (list, dict)'))
b58328
+                a, kw = arg['params']
b58328
+                newkw = dict((str(k), v) for k, v in kw.items())
b58328
+                params = api.Command[name].args_options_2_params(
b58328
+                    *a, **newkw)
b58328
                 newkw.setdefault('version', options['version'])
b58328
 
b58328
                 result = api.Command[name](*a, **newkw)
b58328
@@ -132,8 +179,9 @@ class batch(Command):
b58328
                 )
b58328
                 result['error']=None
b58328
             except Exception as e:
b58328
-                if isinstance(e, errors.RequirementError) or \
b58328
-                    isinstance(e, errors.CommandError):
b58328
+                if (isinstance(e, errors.RequirementError) or
b58328
+                        isinstance(e, errors.CommandError) or
b58328
+                        isinstance(e, errors.ConversionError)):
b58328
                     logger.info(
b58328
                         '%s: batch: %s',
b58328
                         context.principal,  # pylint: disable=no-member
b58328
-- 
b58328
2.23.0
b58328