|
|
403b09 |
From 18d4af260f777920dc2b474a588c3dcda20d8801 Mon Sep 17 00:00:00 2001
|
|
|
403b09 |
From: Fraser Tweedale <ftweedal@redhat.com>
|
|
|
403b09 |
Date: Thu, 30 Jun 2016 10:21:01 +1000
|
|
|
403b09 |
Subject: [PATCH] cert-revoke: fix permission check bypass (CVE-2016-5404)
|
|
|
403b09 |
|
|
|
403b09 |
The 'cert_revoke' command checks the 'revoke certificate'
|
|
|
403b09 |
permission, however, if an ACIError is raised, it then invokes the
|
|
|
403b09 |
'cert_show' command. The rational was to re-use a "host manages
|
|
|
403b09 |
certificate" check that is part of the 'cert_show' command, however,
|
|
|
403b09 |
it is sufficient that 'cert_show' executes successfully for
|
|
|
403b09 |
'cert_revoke' to recover from the ACIError continue. Therefore,
|
|
|
403b09 |
anyone with 'retrieve certificate' permission can revoke *any*
|
|
|
403b09 |
certificate and cause various kinds of DoS.
|
|
|
403b09 |
|
|
|
403b09 |
Fix the problem by extracting the "host manages certificate" check
|
|
|
403b09 |
to its own method and explicitly calling it from 'cert_revoke'.
|
|
|
403b09 |
|
|
|
403b09 |
Fixes: https://fedorahosted.org/freeipa/ticket/6232
|
|
|
403b09 |
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
|
|
|
403b09 |
---
|
|
|
403b09 |
ipaserver/plugins/cert.py | 60 +++++++++++++++++++++++++----------------------
|
|
|
403b09 |
1 file changed, 32 insertions(+), 28 deletions(-)
|
|
|
403b09 |
|
|
|
403b09 |
diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
|
|
|
403b09 |
index b8df074a186ca91daa8e8f5e725724ea7bc5a663..6dd9f6ffcdcd9d051d50d912996fea2104d71dff 100644
|
|
|
403b09 |
--- a/ipaserver/plugins/cert.py
|
|
|
403b09 |
+++ b/ipaserver/plugins/cert.py
|
|
|
403b09 |
@@ -242,6 +242,24 @@ def validate_certificate(value):
|
|
|
403b09 |
return x509.validate_certificate(value, x509.DER)
|
|
|
403b09 |
|
|
|
403b09 |
|
|
|
403b09 |
+def bind_principal_can_manage_cert(cert):
|
|
|
403b09 |
+ """Check that the bind principal can manage the given cert.
|
|
|
403b09 |
+
|
|
|
403b09 |
+ ``cert``
|
|
|
403b09 |
+ An NSS certificate object.
|
|
|
403b09 |
+
|
|
|
403b09 |
+ """
|
|
|
403b09 |
+ bind_principal = kerberos.Principal(getattr(context, 'principal'))
|
|
|
403b09 |
+ if not bind_principal.is_host:
|
|
|
403b09 |
+ return False
|
|
|
403b09 |
+
|
|
|
403b09 |
+ hostname = bind_principal.hostname
|
|
|
403b09 |
+
|
|
|
403b09 |
+ # If we have a hostname we want to verify that the subject
|
|
|
403b09 |
+ # of the certificate matches it.
|
|
|
403b09 |
+ return hostname == cert.subject.common_name #pylint: disable=E1101
|
|
|
403b09 |
+
|
|
|
403b09 |
+
|
|
|
403b09 |
class BaseCertObject(Object):
|
|
|
403b09 |
takes_params = (
|
|
|
403b09 |
Bytes(
|
|
|
403b09 |
@@ -744,18 +762,6 @@ class cert_show(Retrieve, CertMethod, VirtualCommand):
|
|
|
403b09 |
def execute(self, serial_number, all=False, raw=False, no_members=False,
|
|
|
403b09 |
**options):
|
|
|
403b09 |
ca_enabled_check()
|
|
|
403b09 |
- hostname = None
|
|
|
403b09 |
- try:
|
|
|
403b09 |
- self.check_access()
|
|
|
403b09 |
- except errors.ACIError as acierr:
|
|
|
403b09 |
- self.debug("Not granted by ACI to retrieve certificate, looking at principal")
|
|
|
403b09 |
- bind_principal = kerberos.Principal(getattr(context, 'principal'))
|
|
|
403b09 |
- if not bind_principal.is_host:
|
|
|
403b09 |
- raise acierr
|
|
|
403b09 |
- hostname = bind_principal.hostname
|
|
|
403b09 |
-
|
|
|
403b09 |
- ca_obj = api.Command.ca_show(options['cacn'])['result']
|
|
|
403b09 |
- issuer_dn = ca_obj['ipacasubjectdn'][0]
|
|
|
403b09 |
|
|
|
403b09 |
# Dogtag lightweight CAs have shared serial number domain, so
|
|
|
403b09 |
# we don't tell Dogtag the issuer (but we check the cert after).
|
|
|
403b09 |
@@ -763,7 +769,15 @@ class cert_show(Retrieve, CertMethod, VirtualCommand):
|
|
|
403b09 |
result = self.Backend.ra.get_certificate(str(serial_number))
|
|
|
403b09 |
cert = x509.load_certificate(result['certificate'])
|
|
|
403b09 |
|
|
|
403b09 |
- if DN(unicode(cert.issuer)) != DN(issuer_dn):
|
|
|
403b09 |
+ try:
|
|
|
403b09 |
+ self.check_access()
|
|
|
403b09 |
+ except errors.ACIError as acierr:
|
|
|
403b09 |
+ self.debug("Not granted by ACI to retrieve certificate, looking at principal")
|
|
|
403b09 |
+ if not bind_principal_can_manage_cert(cert):
|
|
|
403b09 |
+ raise acierr # pylint: disable=E0702
|
|
|
403b09 |
+
|
|
|
403b09 |
+ ca_obj = api.Command.ca_show(options['cacn'])['result']
|
|
|
403b09 |
+ if DN(unicode(cert.issuer)) != DN(ca_obj['ipacasubjectdn'][0]):
|
|
|
403b09 |
# DN of cert differs from what we requested
|
|
|
403b09 |
raise errors.NotFound(
|
|
|
403b09 |
reason=_("Certificate with serial number %(serial)s "
|
|
|
403b09 |
@@ -789,12 +803,6 @@ class cert_show(Retrieve, CertMethod, VirtualCommand):
|
|
|
403b09 |
result['revoked'] = ('revocation_reason' in result)
|
|
|
403b09 |
self.obj._fill_owners(result)
|
|
|
403b09 |
|
|
|
403b09 |
- if hostname:
|
|
|
403b09 |
- # If we have a hostname we want to verify that the subject
|
|
|
403b09 |
- # of the certificate matches it, otherwise raise an error
|
|
|
403b09 |
- if hostname != cert.subject.common_name: #pylint: disable=E1101
|
|
|
403b09 |
- raise acierr
|
|
|
403b09 |
-
|
|
|
403b09 |
return dict(result=result, value=pkey_to_value(serial_number, options))
|
|
|
403b09 |
|
|
|
403b09 |
|
|
|
403b09 |
@@ -819,22 +827,18 @@ class cert_revoke(PKQuery, CertMethod, VirtualCommand):
|
|
|
403b09 |
|
|
|
403b09 |
# Make sure that the cert specified by issuer+serial exists.
|
|
|
403b09 |
# Will raise NotFound if it does not.
|
|
|
403b09 |
- cert_show_options = dict(cacn=kw['cacn'])
|
|
|
403b09 |
- api.Command.cert_show(unicode(serial_number), **cert_show_options)
|
|
|
403b09 |
+ resp = api.Command.cert_show(unicode(serial_number), cacn=kw['cacn'])
|
|
|
403b09 |
|
|
|
403b09 |
- hostname = None
|
|
|
403b09 |
try:
|
|
|
403b09 |
self.check_access()
|
|
|
403b09 |
except errors.ACIError as acierr:
|
|
|
403b09 |
self.debug("Not granted by ACI to revoke certificate, looking at principal")
|
|
|
403b09 |
try:
|
|
|
403b09 |
- # Let cert_show() handle verifying that the subject of the
|
|
|
403b09 |
- # cert we're dealing with matches the hostname in the principal
|
|
|
403b09 |
- result = api.Command['cert_show'](
|
|
|
403b09 |
- unicode(serial_number), **cert_show_options
|
|
|
403b09 |
- )['result']
|
|
|
403b09 |
+ cert = x509.load_certificate(resp['result']['certificate'])
|
|
|
403b09 |
+ if not bind_principal_can_manage_cert(cert):
|
|
|
403b09 |
+ raise acierr
|
|
|
403b09 |
except errors.NotImplementedError:
|
|
|
403b09 |
- pass
|
|
|
403b09 |
+ raise acierr
|
|
|
403b09 |
revocation_reason = kw['revocation_reason']
|
|
|
403b09 |
if revocation_reason == 7:
|
|
|
403b09 |
raise errors.CertificateOperationError(error=_('7 is not a valid revocation reason'))
|
|
|
403b09 |
--
|
|
|
403b09 |
2.9.3
|
|
|
403b09 |
|