80f874
From a35c267ec03a56d02c5f86294d6ed16f0ee9ae57 Mon Sep 17 00:00:00 2001
80f874
From: Fraser Tweedale <ftweedal@redhat.com>
80f874
Date: Thu, 30 Jun 2016 10:21:01 +1000
80f874
Subject: [PATCH] cert-revoke: fix permission check bypass (CVE-2016-5404)
80f874
80f874
The 'cert_revoke' command checks the 'revoke certificate'
80f874
permission, however, if an ACIError is raised, it then invokes the
80f874
'cert_show' command.  The rational was to re-use a "host manages
80f874
certificate" check that is part of the 'cert_show' command, however,
80f874
it is sufficient that 'cert_show' executes successfully for
80f874
'cert_revoke' to recover from the ACIError continue.  Therefore,
80f874
anyone with 'retrieve certificate' permission can revoke *any*
80f874
certificate and cause various kinds of DoS.
80f874
80f874
Fix the problem by extracting the "host manages certificate" check
80f874
to its own method and explicitly calling it from 'cert_revoke'.
80f874
80f874
Fixes: https://fedorahosted.org/freeipa/ticket/6232
80f874
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
80f874
---
80f874
 ipalib/plugins/cert.py | 47 ++++++++++++++++++++++++++++++-----------------
80f874
 1 file changed, 30 insertions(+), 17 deletions(-)
80f874
80f874
diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py
80f874
index 7a07039a8488cc11d9bf05ef23642b8059d5921e..42dc4f571b9274f45bd6c20910362cf676764f3a 100644
80f874
--- a/ipalib/plugins/cert.py
80f874
+++ b/ipalib/plugins/cert.py
80f874
@@ -236,6 +236,25 @@ def caacl_check(principal_type, principal_string, ca, profile_id):
80f874
             )
80f874
         )
80f874
 
80f874
+
80f874
+def bind_principal_can_manage_cert(cert):
80f874
+    """Check that the bind principal can manage the given cert.
80f874
+
80f874
+    ``cert``
80f874
+        An NSS certificate object.
80f874
+
80f874
+    """
80f874
+    bind_principal = getattr(context, 'principal')
80f874
+    if not bind_principal.startswith('host/'):
80f874
+        return False
80f874
+
80f874
+    hostname = get_host_from_principal(bind_principal)
80f874
+
80f874
+    # If we have a hostname we want to verify that the subject
80f874
+    # of the certificate matches it.
80f874
+    return hostname == cert.subject.common_name  #pylint: disable=E1101
80f874
+
80f874
+
80f874
 @register()
80f874
 class cert_request(VirtualCommand):
80f874
     __doc__ = _('Submit a certificate signing request.')
80f874
@@ -601,29 +620,23 @@ class cert_show(VirtualCommand):
80f874
 
80f874
     def execute(self, serial_number, **options):
80f874
         ca_enabled_check()
80f874
-        hostname = None
80f874
+
80f874
+        result=self.Backend.ra.get_certificate(serial_number)
80f874
+        cert = x509.load_certificate(result['certificate'])
80f874
+
80f874
         try:
80f874
             self.check_access()
80f874
-        except errors.ACIError, acierr:
80f874
+        except errors.ACIError as acierr:
80f874
             self.debug("Not granted by ACI to retrieve certificate, looking at principal")
80f874
-            bind_principal = getattr(context, 'principal')
80f874
-            if not bind_principal.startswith('host/'):
80f874
-                raise acierr
80f874
-            hostname = get_host_from_principal(bind_principal)
80f874
+            if not bind_principal_can_manage_cert(cert):
80f874
+                raise acierr  # pylint: disable=E0702
80f874
 
80f874
-        result=self.Backend.ra.get_certificate(serial_number)
80f874
-        cert = x509.load_certificate(result['certificate'])
80f874
         result['subject'] = unicode(cert.subject)
80f874
         result['issuer'] = unicode(cert.issuer)
80f874
         result['valid_not_before'] = unicode(cert.valid_not_before_str)
80f874
         result['valid_not_after'] = unicode(cert.valid_not_after_str)
80f874
         result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0])
80f874
         result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0])
80f874
-        if hostname:
80f874
-            # If we have a hostname we want to verify that the subject
80f874
-            # of the certificate matches it, otherwise raise an error
80f874
-            if hostname != cert.subject.common_name:    #pylint: disable=E1101
80f874
-                raise acierr
80f874
 
80f874
         return dict(result=result)
80f874
 
80f874
@@ -669,17 +682,17 @@ class cert_revoke(VirtualCommand):
80f874
 
80f874
     def execute(self, serial_number, **kw):
80f874
         ca_enabled_check()
80f874
-        hostname = None
80f874
         try:
80f874
             self.check_access()
80f874
         except errors.ACIError, acierr:
80f874
             self.debug("Not granted by ACI to revoke certificate, looking at principal")
80f874
             try:
80f874
-                # Let cert_show() handle verifying that the subject of the
80f874
-                # cert we're dealing with matches the hostname in the principal
80f874
                 result = api.Command['cert_show'](unicode(serial_number))['result']
80f874
+                cert = x509.load_certificate(result['certificate'])
80f874
+                if not bind_principal_can_manage_cert(cert):
80f874
+                    raise acierr
80f874
             except errors.NotImplementedError:
80f874
-                pass
80f874
+                raise acierr
80f874
         revocation_reason = kw['revocation_reason']
80f874
         if revocation_reason == 7:
80f874
             raise errors.CertificateOperationError(error=_('7 is not a valid revocation reason'))
80f874
-- 
80f874
2.9.3
80f874