This patch fixes CVE-2016-5404. Versions for master, ipa-4-3 and ipa-4-2 branches are attached.
Thanks, Fraser
From 61590c223aa51668b3f661fc91bc35f2dfae8ae6 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 30 Jun 2016 10:21:01 +1000 Subject: [PATCH] cert-revoke: fix permission check bypass (CVE-2016-5404) The 'cert_revoke' command checks the 'revoke certificate' permission, however, if an ACIError is raised, it then invokes the 'cert_show' command. The rational was to re-use a "host manages certificate" check that is part of the 'cert_show' command, however, it is sufficient that 'cert_show' executes successfully for 'cert_revoke' to recover from the ACIError continue. Therefore, anyone with 'retrieve certificate' permission can revoke *any* certificate and cause various kinds of DoS. Fix the problem by extracting the "host manages certificate" check to its own method and explicitly calling it from 'cert_revoke'. Fixes: https://fedorahosted.org/freeipa/ticket/6232 --- ipaserver/plugins/cert.py | 60 +++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py index b8df074a186ca91daa8e8f5e725724ea7bc5a663..6dd9f6ffcdcd9d051d50d912996fea2104d71dff 100644 --- a/ipaserver/plugins/cert.py +++ b/ipaserver/plugins/cert.py @@ -242,6 +242,24 @@ def validate_certificate(value): return x509.validate_certificate(value, x509.DER) +def bind_principal_can_manage_cert(cert): + """Check that the bind principal can manage the given cert. + + ``cert`` + An NSS certificate object. + + """ + bind_principal = kerberos.Principal(getattr(context, 'principal')) + if not bind_principal.is_host: + return False + + hostname = bind_principal.hostname + + # If we have a hostname we want to verify that the subject + # of the certificate matches it. + return hostname == cert.subject.common_name #pylint: disable=E1101 + + class BaseCertObject(Object): takes_params = ( Bytes( @@ -744,18 +762,6 @@ class cert_show(Retrieve, CertMethod, VirtualCommand): def execute(self, serial_number, all=False, raw=False, no_members=False, **options): ca_enabled_check() - hostname = None - try: - self.check_access() - except errors.ACIError as acierr: - self.debug("Not granted by ACI to retrieve certificate, looking at principal") - bind_principal = kerberos.Principal(getattr(context, 'principal')) - if not bind_principal.is_host: - raise acierr - hostname = bind_principal.hostname - - ca_obj = api.Command.ca_show(options['cacn'])['result'] - issuer_dn = ca_obj['ipacasubjectdn'][0] # Dogtag lightweight CAs have shared serial number domain, so # we don't tell Dogtag the issuer (but we check the cert after). @@ -763,7 +769,15 @@ class cert_show(Retrieve, CertMethod, VirtualCommand): result = self.Backend.ra.get_certificate(str(serial_number)) cert = x509.load_certificate(result['certificate']) - if DN(unicode(cert.issuer)) != DN(issuer_dn): + try: + self.check_access() + except errors.ACIError as acierr: + self.debug("Not granted by ACI to retrieve certificate, looking at principal") + if not bind_principal_can_manage_cert(cert): + raise acierr # pylint: disable=E0702 + + ca_obj = api.Command.ca_show(options['cacn'])['result'] + if DN(unicode(cert.issuer)) != DN(ca_obj['ipacasubjectdn'][0]): # DN of cert differs from what we requested raise errors.NotFound( reason=_("Certificate with serial number %(serial)s " @@ -789,12 +803,6 @@ class cert_show(Retrieve, CertMethod, VirtualCommand): result['revoked'] = ('revocation_reason' in result) self.obj._fill_owners(result) - if hostname: - # If we have a hostname we want to verify that the subject - # of the certificate matches it, otherwise raise an error - if hostname != cert.subject.common_name: #pylint: disable=E1101 - raise acierr - return dict(result=result, value=pkey_to_value(serial_number, options)) @@ -819,22 +827,18 @@ class cert_revoke(PKQuery, CertMethod, VirtualCommand): # Make sure that the cert specified by issuer+serial exists. # Will raise NotFound if it does not. - cert_show_options = dict(cacn=kw['cacn']) - api.Command.cert_show(unicode(serial_number), **cert_show_options) + resp = api.Command.cert_show(unicode(serial_number), cacn=kw['cacn']) - hostname = None try: self.check_access() except errors.ACIError as acierr: self.debug("Not granted by ACI to revoke certificate, looking at principal") try: - # Let cert_show() handle verifying that the subject of the - # cert we're dealing with matches the hostname in the principal - result = api.Command['cert_show']( - unicode(serial_number), **cert_show_options - )['result'] + cert = x509.load_certificate(resp['result']['certificate']) + if not bind_principal_can_manage_cert(cert): + raise acierr except errors.NotImplementedError: - pass + raise acierr revocation_reason = kw['revocation_reason'] if revocation_reason == 7: raise errors.CertificateOperationError(error=_('7 is not a valid revocation reason')) -- 2.5.5
From d68f99203c5bab33e8bc4af6becea57e0736bbc5 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 30 Jun 2016 10:21:01 +1000 Subject: [PATCH] cert-revoke: fix permission check bypass (CVE-2016-5404) The 'cert_revoke' command checks the 'revoke certificate' permission, however, if an ACIError is raised, it then invokes the 'cert_show' command. The rational was to re-use a "host manages certificate" check that is part of the 'cert_show' command, however, it is sufficient that 'cert_show' executes successfully for 'cert_revoke' to recover from the ACIError continue. Therefore, anyone with 'retrieve certificate' permission can revoke *any* certificate and cause various kinds of DoS. Fix the problem by extracting the "host manages certificate" check to its own method and explicitly calling it from 'cert_revoke'. Fixes: https://fedorahosted.org/freeipa/ticket/6232 --- ipalib/plugins/cert.py | 49 +++++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py index b4ea2feae5de9ffc020709092f79791d99472ffc..f257088e2d45a0c991cce68222577dbe212415d9 100644 --- a/ipalib/plugins/cert.py +++ b/ipalib/plugins/cert.py @@ -243,6 +243,25 @@ def caacl_check(principal_type, principal_string, ca, profile_id): ) ) + +def bind_principal_can_manage_cert(cert): + """Check that the bind principal can manage the given cert. + + ``cert`` + An NSS certificate object. + + """ + bind_principal = getattr(context, 'principal') + if not bind_principal.startswith('host/'): + return False + + hostname = get_host_from_principal(bind_principal) + + # If we have a hostname we want to verify that the subject + # of the certificate matches it. + return hostname == cert.subject.common_name #pylint: disable=E1101 + + @register() class cert_request(VirtualCommand): __doc__ = _('Submit a certificate signing request.') @@ -608,29 +627,23 @@ class cert_show(VirtualCommand): def execute(self, serial_number, **options): ca_enabled_check() - hostname = None - try: - self.check_access() - except errors.ACIError as acierr: - self.debug("Not granted by ACI to retrieve certificate, looking at principal") - bind_principal = getattr(context, 'principal') - if not bind_principal.startswith('host/'): - raise acierr - hostname = get_host_from_principal(bind_principal) result=self.Backend.ra.get_certificate(serial_number) cert = x509.load_certificate(result['certificate']) + + try: + self.check_access() + except errors.ACIError as acierr: + self.debug("Not granted by ACI to retrieve certificate, looking at principal") + if not bind_principal_can_manage_cert(cert): + raise acierr # pylint: disable=E0702 + result['subject'] = unicode(cert.subject) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) - if hostname: - # If we have a hostname we want to verify that the subject - # of the certificate matches it, otherwise raise an error - if hostname != cert.subject.common_name: #pylint: disable=E1101 - raise acierr return dict(result=result) @@ -676,17 +689,17 @@ class cert_revoke(VirtualCommand): def execute(self, serial_number, **kw): ca_enabled_check() - hostname = None try: self.check_access() except errors.ACIError as acierr: self.debug("Not granted by ACI to revoke certificate, looking at principal") try: - # Let cert_show() handle verifying that the subject of the - # cert we're dealing with matches the hostname in the principal result = api.Command['cert_show'](unicode(serial_number))['result'] + cert = x509.load_certificate(result['certificate']) + if not bind_principal_can_manage_cert(cert): + raise acierr except errors.NotImplementedError: - pass + raise acierr revocation_reason = kw['revocation_reason'] if revocation_reason == 7: raise errors.CertificateOperationError(error=_('7 is not a valid revocation reason')) -- 2.5.5
From aa114a658b1f30e45f905bb6e19b04e9504da8a7 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 30 Jun 2016 10:21:01 +1000 Subject: [PATCH] cert-revoke: fix permission check bypass (CVE-2016-5404) The 'cert_revoke' command checks the 'revoke certificate' permission, however, if an ACIError is raised, it then invokes the 'cert_show' command. The rational was to re-use a "host manages certificate" check that is part of the 'cert_show' command, however, it is sufficient that 'cert_show' executes successfully for 'cert_revoke' to recover from the ACIError continue. Therefore, anyone with 'retrieve certificate' permission can revoke *any* certificate and cause various kinds of DoS. Fix the problem by extracting the "host manages certificate" check to its own method and explicitly calling it from 'cert_revoke'. Fixes: https://fedorahosted.org/freeipa/ticket/6232 --- ipalib/plugins/cert.py | 49 +++++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py index 7a07039a8488cc11d9bf05ef23642b8059d5921e..42dc4f571b9274f45bd6c20910362cf676764f3a 100644 --- a/ipalib/plugins/cert.py +++ b/ipalib/plugins/cert.py @@ -236,6 +236,25 @@ def caacl_check(principal_type, principal_string, ca, profile_id): ) ) + +def bind_principal_can_manage_cert(cert): + """Check that the bind principal can manage the given cert. + + ``cert`` + An NSS certificate object. + + """ + bind_principal = getattr(context, 'principal') + if not bind_principal.startswith('host/'): + return False + + hostname = get_host_from_principal(bind_principal) + + # If we have a hostname we want to verify that the subject + # of the certificate matches it. + return hostname == cert.subject.common_name #pylint: disable=E1101 + + @register() class cert_request(VirtualCommand): __doc__ = _('Submit a certificate signing request.') @@ -601,29 +620,23 @@ class cert_show(VirtualCommand): def execute(self, serial_number, **options): ca_enabled_check() - hostname = None - try: - self.check_access() - except errors.ACIError, acierr: - self.debug("Not granted by ACI to retrieve certificate, looking at principal") - bind_principal = getattr(context, 'principal') - if not bind_principal.startswith('host/'): - raise acierr - hostname = get_host_from_principal(bind_principal) result=self.Backend.ra.get_certificate(serial_number) cert = x509.load_certificate(result['certificate']) + + try: + self.check_access() + except errors.ACIError as acierr: + self.debug("Not granted by ACI to retrieve certificate, looking at principal") + if not bind_principal_can_manage_cert(cert): + raise acierr # pylint: disable=E0702 + result['subject'] = unicode(cert.subject) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) - if hostname: - # If we have a hostname we want to verify that the subject - # of the certificate matches it, otherwise raise an error - if hostname != cert.subject.common_name: #pylint: disable=E1101 - raise acierr return dict(result=result) @@ -669,17 +682,17 @@ class cert_revoke(VirtualCommand): def execute(self, serial_number, **kw): ca_enabled_check() - hostname = None try: self.check_access() except errors.ACIError, acierr: self.debug("Not granted by ACI to revoke certificate, looking at principal") try: - # Let cert_show() handle verifying that the subject of the - # cert we're dealing with matches the hostname in the principal result = api.Command['cert_show'](unicode(serial_number))['result'] + cert = x509.load_certificate(result['certificate']) + if not bind_principal_can_manage_cert(cert): + raise acierr except errors.NotImplementedError: - pass + raise acierr revocation_reason = kw['revocation_reason'] if revocation_reason == 7: raise errors.CertificateOperationError(error=_('7 is not a valid revocation reason')) -- 2.5.5
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code